optmize a2dp demo document

This commit is contained in:
jincheng 2021-12-23 21:05:18 +08:00 committed by bot
parent 5bad27d0d5
commit df62fbb6d0
14 changed files with 1253 additions and 711 deletions

View File

@ -1,12 +1,12 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP-IDF A2DP-SINK demo
A2DP-SINK EXAMPLE
======================
Demo of A2DP audio sink role
Example of A2DP audio sink role
This is the demo of API implementing Advanced Audio Distribution Profile to receive an audio stream.
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.
@ -34,9 +34,9 @@ If the internal DAC is selected, analog audio will be available on GPIO25 and GP
idf.py menuconfig
```
* Set the use of 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
* Enable Classic Bluetooth and A2DP under **Component config --> Bluetooth --> Bluedroid Enable**
### Build and Flash
@ -72,5 +72,5 @@ I (128697) BT_AV: Audio packet count 400
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
* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC data stream is transmitted to A2DP sink and then decoded into PCM samples as output. The PCM data format is normally of 44.1kHz sampling rate, two-channel 16-bit sample stream. Other decoder configurations in ESP32 A2DP sink is supported but need additional modifications of protocol stack settings.
* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC data stream is transmitted to A2DP sink and then decoded into PCM samples as output. The PCM data format is normally of 44.1kHz sampling rate, two-channel 16-bit sample stream. Other SBC configurations in ESP32 A2DP sink is supported but need additional modifications of protocol stack settings.
* As a usage limitation, ESP32 A2DP sink can support at most one connection with remote A2DP source devices. Also, A2DP sink cannot be used together with A2DP source at the same time, but can be used with other profiles such as SPP and HFP.

View File

@ -1,4 +1,3 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
@ -27,31 +26,360 @@
#include "sys/lock.h"
// AVRCP used transaction label
/* 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)
/*******************************
* 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);
/* 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 CT event handler */
/* avrc controller event handler */
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
/* avrc TG event handler */
/* avrc target event handler */
static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param);
static uint32_t s_pkt_cnt = 0;
static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED;
static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"};
static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"};
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
static _lock_t s_volume_lock;
static xTaskHandle s_vcs_task_hdl = NULL;
static uint8_t s_volume = 0;
static bool s_volume_notify;
/*******************************
* 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_STOPPED;
/* 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", "Stopped", "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 xTaskHandle 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 */
/********************************
* 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;
}
static void bt_av_new_track(void)
{
uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE |
ESP_AVRC_MD_ATTR_ARTIST |
ESP_AVRC_MD_ATTR_ALBUM |
ESP_AVRC_MD_ATTR_GENRE;
esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask);
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)
{
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)
{
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: %d-ms", event_parameter->play_pos);
bt_av_play_pos_changed();
break;
/* others */
default:
ESP_LOGI(BT_AV_TAG, "unhandled event: %d", event_id);
break;
}
}
static void volume_set_by_controller(uint8_t volume)
{
ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller to: %d%%", (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: %d%%", (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_RATE_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_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();
}
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_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", a2d->audio_cfg.mcc.type);
/* for now only SBC stream is supported */
if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
int sample_rate = 16000;
char oct0 = a2d->audio_cfg.mcc.cie.sbc[0];
if (oct0 & (0x01 << 6)) {
sample_rate = 32000;
} else if (oct0 & (0x01 << 5)) {
sample_rate = 44100;
} else if (oct0 & (0x01 << 4)) {
sample_rate = 48000;
}
i2s_set_clk(0, sample_rate, 16, 2);
ESP_LOGI(BT_AV_TAG, "Configure audio player: %x-%x-%x-%x",
a2d->audio_cfg.mcc.cie.sbc[0],
a2d->audio_cfg.mcc.cie.sbc[1],
a2d->audio_cfg.mcc.cie.sbc[2],
a2d->audio_cfg.mcc.cie.sbc[3]);
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;
}
/* 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) {
esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
} else {
s_avrc_peer_rn_cap.bits = 0;
}
break;
}
/* when passthrough responsed, 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", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
break;
}
/* when metadata responsed, 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);
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 %x, 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);
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;
}
/* 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%x", 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: %x, CT features: %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag);
break;
}
/* others */
default:
ESP_LOGE(BT_RC_TG_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
/********************************
* EXTERNAL FUNCTION DEFINITIONS
*******************************/
/* callback for A2DP sink */
void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
{
switch (event) {
@ -71,21 +399,13 @@ void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
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 %u", s_pkt_cnt);
ESP_LOGI(BT_AV_TAG, "Audio packet count: %u", s_pkt_cnt);
}
}
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;
}
void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
{
switch (event) {
@ -122,251 +442,3 @@ void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param
break;
}
}
static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
{
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
esp_a2d_cb_param_t *a2d = NULL;
switch (event) {
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_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();
}
break;
}
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;
}
case ESP_A2D_AUDIO_CFG_EVT: {
a2d = (esp_a2d_cb_param_t *)(p_param);
ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type);
// for now only SBC stream is supported
if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
int sample_rate = 16000;
char oct0 = a2d->audio_cfg.mcc.cie.sbc[0];
if (oct0 & (0x01 << 6)) {
sample_rate = 32000;
} else if (oct0 & (0x01 << 5)) {
sample_rate = 44100;
} else if (oct0 & (0x01 << 4)) {
sample_rate = 48000;
}
i2s_set_clk(0, sample_rate, 16, 2);
ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x",
a2d->audio_cfg.mcc.cie.sbc[0],
a2d->audio_cfg.mcc.cie.sbc[1],
a2d->audio_cfg.mcc.cie.sbc[2],
a2d->audio_cfg.mcc.cie.sbc[3]);
ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate);
}
break;
}
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 Compl\n");
} else {
ESP_LOGI(BT_AV_TAG,"A2DP PROF STATE: Deinit Compl\n");
}
break;
}
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
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;
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)
{
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)
{
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);
}
}
void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
{
switch (event_id) {
case ESP_AVRC_RN_TRACK_CHANGE:
bt_av_new_track();
break;
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;
case ESP_AVRC_RN_PLAY_POS_CHANGED:
ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos);
bt_av_play_pos_changed();
break;
}
}
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
{
ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event);
esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
switch (event) {
case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
uint8_t *bda = rc->conn_stat.remote_bda;
ESP_LOGI(BT_RC_CT_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) {
// 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;
}
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
break;
}
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);
free(rc->meta_rsp.attr_text);
break;
}
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;
}
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
break;
}
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;
}
default:
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}
static void volume_set_by_controller(uint8_t volume)
{
ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f);
_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: %d%%", (uint32_t)volume * 100 / 0x7f);
_lock_acquire(&s_volume_lock);
s_volume = volume;
_lock_release(&s_volume_lock);
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 (;;) {
vTaskDelay(10000 / portTICK_RATE_MS);
uint8_t volume = (s_volume + 5) & 0x7f;
volume_set_by_local_host(volume);
}
}
static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
{
ESP_LOGD(BT_RC_TG_TAG, "%s evt %d", __func__, event);
esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(p_param);
switch (event) {
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, "vcsT", 2048, NULL, 5, &s_vcs_task_hdl);
} else {
vTaskDelete(s_vcs_task_hdl);
ESP_LOGI(BT_RC_TG_TAG, "Stop volume change simulation");
}
break;
}
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;
}
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;
}
case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: {
ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%x", 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;
}
case ESP_AVRC_TG_REMOTE_FEATURES_EVT: {
ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag);
break;
}
default:
ESP_LOGE(BT_RC_TG_TAG, "%s unhandled evt %d", __func__, event);
break;
}
}

View File

@ -13,27 +13,40 @@
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#define BT_AV_TAG "BT_AV"
#define BT_RC_TG_TAG "RCTG"
#define BT_RC_CT_TAG "RCCT"
/* 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
* @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
* @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 AVRCP controller
* @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
* @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);

View File

@ -19,41 +19,31 @@
#include "driver/i2s.h"
#include "freertos/ringbuf.h"
/*******************************
* 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 xQueueHandle s_bt_app_task_queue = NULL;
static xTaskHandle s_bt_app_task_handle = NULL;
static xTaskHandle s_bt_i2s_task_handle = NULL;
static RingbufHandle_t s_ringbuf_i2s = NULL;;
/*******************************
* STATIC VARIABLE 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);
static xQueueHandle s_bt_app_task_queue = NULL; /* handle of work queue */
static xTaskHandle s_bt_app_task_handle = NULL; /* handle of application task */
static xTaskHandle s_bt_i2s_task_handle = NULL; /* handle of I2S task */
static RingbufHandle_t s_ringbuf_i2s = NULL; /* handle of ringbuffer for I2S */
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, msg.param, p_params);
}
return bt_app_send_msg(&msg);
}
}
return false;
}
/*******************************
* STATIC FUNCTION DEFINITIONS
******************************/
static bool bt_app_send_msg(bt_app_msg_t *msg)
{
@ -78,17 +68,20 @@ static void bt_app_work_dispatched(bt_app_msg_t *msg)
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, (portTickType)portMAX_DELAY)) {
ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
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 sig: %d", __func__, msg.sig);
ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled signal: %d", __func__, msg.sig);
break;
} // switch (msg.sig)
} /* switch (msg.sig) */
if (msg.param) {
free(msg.param);
@ -97,11 +90,57 @@ static void bt_app_task_handler(void *arg)
}
}
static void bt_i2s_task_handler(void *arg)
{
uint8_t *data = NULL;
size_t item_size = 0;
size_t bytes_written = 0;
for (;;) {
/* receive data from ringbuffer and write it to I2S DMA transmit buffer */
data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (portTickType)portMAX_DELAY);
if (item_size != 0){
i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY);
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, "BtAppT", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
return;
xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
}
void bt_app_task_shut_down(void)
@ -116,30 +155,12 @@ void bt_app_task_shut_down(void)
}
}
static void bt_i2s_task_handler(void *arg)
{
uint8_t *data = NULL;
size_t item_size = 0;
size_t bytes_written = 0;
for (;;) {
data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (portTickType)portMAX_DELAY);
if (item_size != 0){
i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY);
vRingbufferReturnItem(s_ringbuf_i2s,(void *)data);
}
}
}
void bt_i2s_task_start_up(void)
{
s_ringbuf_i2s = xRingbufferCreate(8 * 1024, RINGBUF_TYPE_BYTEBUF);
if(s_ringbuf_i2s == NULL){
if ((s_ringbuf_i2s = xRingbufferCreate(8 * 1024, RINGBUF_TYPE_BYTEBUF)) == NULL) {
return;
}
xTaskCreate(bt_i2s_task_handler, "BtI2ST", 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle);
return;
xTaskCreate(bt_i2s_task_handler, "BtI2STask", 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle);
}
void bt_i2s_task_shut_down(void)
@ -148,7 +169,6 @@ void bt_i2s_task_shut_down(void)
vTaskDelete(s_bt_i2s_task_handle);
s_bt_i2s_task_handle = NULL;
}
if (s_ringbuf_i2s) {
vRingbufferDelete(s_ringbuf_i2s);
s_ringbuf_i2s = NULL;
@ -158,9 +178,6 @@ void bt_i2s_task_shut_down(void)
size_t write_ringbuf(const uint8_t *data, size_t size)
{
BaseType_t done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (portTickType)portMAX_DELAY);
if(done){
return size;
} else {
return 0;
}
return done ? size : 0;
}

View File

@ -13,41 +13,78 @@
#include <stdbool.h>
#include <stdio.h>
#define BT_APP_CORE_TAG "BT_APP_CORE"
/* log tag */
#define BT_APP_CORE_TAG "BT_APP_CORE"
#define BT_APP_SIG_WORK_DISPATCH (0x01)
/* signal for `bt_app_work_dispatch` */
#define BT_APP_SIG_WORK_DISPATCH (0x01)
/**
* @brief handler for the dispatched work
* @brief handler for the dispatched work
*
* @param [in] event event id
* @param [in] param handler parameter
*/
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
/* message to be sent */
typedef struct {
uint16_t sig; /*!< signal to bt_app_task */
uint16_t event; /*!< message event id */
bt_app_cb_t cb; /*!< context switch callback */
void *param; /*!< parameter area needs to be last */
uint16_t sig; /*!< signal to bt_app_task */
uint16_t event; /*!< message event id */
bt_app_cb_t cb; /*!< context switch callback */
void *param; /*!< parameter area needs to be last */
} bt_app_msg_t;
/**
* @brief parameter deep-copy function to be customized
* @brief parameter deep-copy function to be customized
*
* @param [out] p_dest pointer to destination data
* @param [in] p_src pointer to source data
* @param [in] len data length in byte
*/
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
typedef void (* bt_app_copy_cb_t) (void *p_dest, void *p_src, int len);
/**
* @brief work dispatcher for the application task
* @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] param_len parameter length in byte
* @param [in] p_copy_cback parameter deep-copy function
*
* @return true if work dispatch successfully, false otherwise
*/
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);
/**
* @brief start up the application task
*/
void bt_app_task_start_up(void);
/**
* @brief shut down the application task
*/
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__ */

View File

@ -33,18 +33,110 @@
#include "esp_avrc_api.h"
#include "driver/i2s.h"
/* event for handler "bt_av_hdl_stack_up */
/* device name */
#define LOCAL_DEVICE_NAME "ESP_SPEAKER"
/* event for stack up */
enum {
BT_APP_EVT_STACK_UP = 0,
};
/********************************
* STATIC FUNCTION DECLARATIONS
*******************************/
/* 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 void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
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);
}
break;
}
#if (CONFIG_BT_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: %d", 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: %d", 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", param->mode_chg.mode);
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_dev_set_device_name(LOCAL_DEVICE_NAME);
esp_bt_gap_register_callback(bt_app_gap_cb);
assert(esp_avrc_ct_init() == ESP_OK);
esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
assert(esp_avrc_tg_init() == ESP_OK);
esp_avrc_tg_register_callback(bt_app_rc_tg_cb);
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);
assert(esp_a2d_sink_init() == ESP_OK);
esp_a2d_register_callback(&bt_app_a2d_cb);
esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb);
/* 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)
{
/* Initialize NVS — it is used to store PHY calibration data */
/* 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());
@ -52,23 +144,48 @@ void app_main(void)
}
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\n", __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\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
/* I2S configuration parameters */
i2s_config_t i2s_config = {
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
#else
.mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX
.mode = I2S_MODE_MASTER | I2S_MODE_TX, /* only TX */
#endif
.sample_rate = 44100,
.bits_per_sample = 16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.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
.intr_alloc_flags = 0, /* default interrupt priority */
.tx_desc_auto_clear = true /* auto clear tx descriptor on underflow */
};
/* enable I2S */
i2s_driver_install(0, &i2s_config, 0, NULL);
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
@ -78,53 +195,19 @@ void app_main(void)
.bck_io_num = CONFIG_EXAMPLE_I2S_BCK_PIN,
.ws_io_num = CONFIG_EXAMPLE_I2S_LRCK_PIN,
.data_out_num = CONFIG_EXAMPLE_I2S_DATA_PIN,
.data_in_num = -1 //Not used
.data_in_num = -1 /* not used */
};
i2s_set_pin(0, &pin_config);
#endif
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\n", __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\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
/* create application task */
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);
#if (CONFIG_BT_SSP_ENABLED == true)
/* Set default parameters for Secure Simple Pairing */
/* 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
*/
/* 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';
@ -133,78 +216,7 @@ void app_main(void)
pin_code[3] = '4';
esp_bt_gap_set_pin(pin_type, 4, pin_code);
}
void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
switch (event) {
case ESP_BT_GAP_AUTH_CMPL_EVT: {
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
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);
}
break;
}
#if (CONFIG_BT_SSP_ENABLED == true)
case ESP_BT_GAP_CFM_REQ_EVT:
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
break;
case ESP_BT_GAP_KEY_NOTIF_EVT:
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
break;
case ESP_BT_GAP_KEY_REQ_EVT:
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
break;
#endif
case ESP_BT_GAP_MODE_CHG_EVT:
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode:%d", param->mode_chg.mode);
break;
default: {
ESP_LOGI(BT_AV_TAG, "event: %d", event);
break;
}
}
return;
}
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
{
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
switch (event) {
case BT_APP_EVT_STACK_UP: {
/* set up device name */
char *dev_name = "ESP_SPEAKER";
esp_bt_dev_set_device_name(dev_name);
esp_bt_gap_register_callback(bt_app_gap_cb);
/* initialize AVRCP controller */
esp_avrc_ct_init();
esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
/* initialize AVRCP target */
assert (esp_avrc_tg_init() == ESP_OK);
esp_avrc_tg_register_callback(bt_app_rc_tg_cb);
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);
/* initialize A2DP sink */
esp_a2d_register_callback(&bt_app_a2d_cb);
esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb);
esp_a2d_sink_init();
/* set discoverable and connectable mode, wait to be connected */
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
break;
}
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
break;
}
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);
}

View File

@ -7,5 +7,3 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_SPP_ENABLED=n
CONFIG_BT_BLE_ENABLED=n

View File

@ -0,0 +1,163 @@
# A2DP Sink Example Walkthrough
## Introduction
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).
### NVS Initialization
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:
```c
/* initialize NVS — it is used to store PHY calibration */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
```
### 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.
```c
/* initialize Bluetooth Controller with default configuration */
esp_bt_controller_config_t bt_cfg = ONTROLLER_INIT_CONFIG_DEFAULT();
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize controller ed\n", __func__);
return;
}
/* enable Bluetooth Controller in Classic Bluetooth mode */
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != OK) {
ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__);
return;
}
```
After the initialization of the Bluetooth Controller, the Bluedroid Stack, which includes the common definitions and APIs for Classic Bluetooth is initialized and enabled by using:
```c
/* initialize Bluedroid Host */
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__);
return;
}
/* enable Bluedroid Host */
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__);
return;
}
```
The Classic Bluetooth uses an asynchronous programming paradigm. The entire Bluedroid stack sees the use of events, event handlers, callbacks and state machines. Most APIs are implemented by posting specific type of events to the work queue of Bluedroid Stack. Threads(FreeRTOS) tasks inside Bluedroid then process the events with specific handlers, according to the internal state. When the operations are completed, Bluedroid stack invokes the callback function which is registered by application, to report some other events and information.
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
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
/* I2S configuration parameters */
i2s_config_t i2s_config = {
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
#else
.mode = I2S_MODE_MASTER | I2S_MODE_TX, /* only TX */
#endif
.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 */
i2s_driver_install(0, &i2s_config, 0, NULL);
#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);
i2s_set_pin(0, NULL);
#else
i2s_pin_config_t pin_config = {
.bck_io_num = CONFIG_EXAMPLE_I2S_BCK_PIN,
.ws_io_num = CONFIG_EXAMPLE_I2S_LRCK_PIN,
.data_out_num = CONFIG_EXAMPLE_I2S_DATA_PIN,
.data_in_num = -1 /* not used */
};
i2s_set_pin(0, &pin_config);
#endif
```
### Paring Parameter Settings
The main function continues to set up paring parameters including Secure Simple Pairing and Legacy Pairing.
```c
#if (CONFIG_BT_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) */
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);
```
### Create Application Task
Finally, the main function starts up the application task. `bt_app_task_start_up()` creates a work queue and a FreeRTOS task to process the events(messages) posted into the work queue.
```c
/* create application task */
bt_app_task_start_up();
```
### Profile Set up
The main function ends up dispatching event "BT_APP_STACK_UP_EVT" to the application task.
```c
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_STACK_UP_EVT, NULL, 0, NULL);
```
The call of function bt_app_work_dispatch posts the event "BT_APP_STACK_UP_EVT" together with the handler function `bt_av_hdl_stack_evt`, to the work queue of application task. The application task is then triggered to invoke the handler function to process this event.

View File

@ -1,18 +1,18 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP-IDF A2DP-SOURCE demo
A2DP-SOURCE EXAMPLE
========================
Demo of A2DP audio source role
Example of A2DP audio source role
This is the demo of using Advanced Audio Distribution Profile APIs to transmit audio stream. Application can take advantage of this example to implement portable audio players or microphones to transmit audio stream to A2DP sink devices.
This is the example of using Advanced Audio Distribution Profile (A2DP) APIs to transmit audio stream. Application can take advantage of this example to implement portable audio players or microphones to transmit audio stream to A2DP sink devices.
## How to use this example
### Hardware Required
This example is able to run on any commonly available ESP32 development board. And is supposed to connect to A2DP sink example in ESP-IDF.
This example is able to run on any commonly available ESP32 development board, and is supposed to connect to [A2DP sink example](../a2dp_sink) in ESP-IDF.
### Configure the project
@ -30,11 +30,15 @@ Build the project and flash it to the board, then run monitor tool to view seria
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
For the first step, this example performs device discovery to search for a target device(A2DP sink) whose device name is "ESP_SPEAKER" and whose "Rendering" bit of its Service Class field is set in its Class of Device. If a candidate target is found, the local device will initiate connection with it.
For the first step, this example performs device discovery to search for a target device (A2DP sink) whose device name is "ESP_SPEAKER" and whose "Rendering" bit of its Service Class field is set in its Class of Device (COD). If a candidate target is found, the local device will initiate connection with it.
After connection with A2DP sink is established, the example performs the following running loop 1-2-3-4-1:
1. audio transmission starts and lasts for a while
@ -47,7 +51,7 @@ The example implements an event loop triggered by a periodic "heart beat" timer
After the local device discovers the target device and initiates connection, there will be logging message like this:
```
I (4090) BT_AV: Found a target device, address 24:0a:c4:02:0e:ee, name ESP_SPEAKER
I (4090) BT_AV: Found a target device, address xx:xx:xx:xx:xx:xx, name ESP_SPEAKER
I (4090) BT_AV: Cancel device discovery ...
I (4100) BT_AV: Device discovery stopped.
I (4100) BT_AV: a2dp connecting to peer: ESP_SPEAKER
@ -59,7 +63,7 @@ If connection is set up successfully, there will be such message:
I (5100) BT_AV: a2dp connected
```
Start of audio transmission has the following notification message:
Starting of audio transmission has the following notification message:
```
I (10880) BT_AV: a2dp media ready checking ...
@ -80,7 +84,8 @@ I (111040) BT_AV: a2dp disconnected
```
## Troubleshooting
* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC audio stream is encoded from PCM data normally formatted as 44.1kHz sampling rate, two-channel 16-bit sample data. Other SBC configurations can be supported but there is a need for additional modifications to the protocol stack.
* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC audio stream is encoded from PCM data normally formatted as 44.1kHz sampling rate, two-channel 16-bit sample data.
* The raw PCM media stream in the example is generated by a sequence of random number, so the sound played on the sink side will be piercing noise.
* As a usage limitation, ESP32 A2DP source can support at most one connection with remote A2DP sink devices. Also, A2DP source cannot be used together with A2DP sink at the same time, but can be used with other profiles such as SPP and HFP.
* Usually, the `ACL_CONN_NUM` is set to `2` but not `1` as one is used for a2dp connection and the other is used for avrcp connection.
*

View File

@ -9,7 +9,6 @@
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "freertos/xtensa_api.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
@ -17,16 +16,80 @@
#include "esp_log.h"
#include "bt_app_core.h"
/*********************************
* STATIC FUNCTION DECLARATIONS
********************************/
/* application task handler */
static void bt_app_task_handler(void *arg);
/* message sender for Work queue */
static bool bt_app_send_msg(bt_app_msg_t *msg);
/* handler for dispatched message */
static void bt_app_work_dispatched(bt_app_msg_t *msg);
/*********************************
* STATIC VARIABLES
********************************/
static xQueueHandle s_bt_app_task_queue = NULL;
static xTaskHandle s_bt_app_task_handle = NULL;
/*********************************
* STATIC FUNCTION DEFINITIONS
********************************/
static bool bt_app_send_msg(bt_app_msg_t *msg)
{
if (msg == NULL) {
return false;
}
if (pdTRUE != xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS)) {
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, (portTickType)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;
}
if (msg.param) {
free(msg.param);
}
}
}
}
/*********************************
* EXTERN 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);
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));
@ -42,7 +105,7 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i
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, msg.param, p_params);
p_copy_cback(msg.param, p_params, param_len);
}
return bt_app_send_msg(&msg);
}
@ -51,53 +114,10 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i
return false;
}
static bool bt_app_send_msg(bt_app_msg_t *msg)
{
if (msg == NULL) {
return false;
}
if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_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 (;;) {
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 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 sig: %d", __func__, msg.sig);
break;
} // switch (msg.sig)
if (msg.param) {
free(msg.param);
}
}
}
}
void bt_app_task_start_up(void)
{
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
return;
xTaskCreate(bt_app_task_handler, "BtAppTask", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle);
}
void bt_app_task_shut_down(void)

View File

@ -13,12 +13,17 @@
#include <stdbool.h>
#include <stdio.h>
#define BT_APP_CORE_TAG "BT_APP_CORE"
/* log tag */
#define BT_APP_CORE_TAG "BT_APP_CORE"
#define BT_APP_SIG_WORK_DISPATCH (0x01)
/* signal for dispatcher */
#define BT_APP_SIG_WORK_DISPATCH (0x01)
/**
* @brief handler for the dispatched work
* @brief handler for the dispatched work
*
* @param [in] event message event id
* @param [in] param pointer to the parameter
*/
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
@ -31,17 +36,35 @@ typedef struct {
} bt_app_msg_t;
/**
* @brief parameter deep-copy function to be customized
* @brief parameter deep-copy function to be customized
*
* @param [in] p_dest pointer to the destination
* @param [in] p_src pointer to the source
* @param [in] len data length in byte
*/
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
typedef void (* bt_app_copy_cb_t) (void *p_dest, void *p_src, int len);
/**
* @brief work dispatcher for the application task
* @brief work dispatcher for the application task
*
* @param [in] p_cback handler for the dispatched work (event handler)
* @param [in] event message event id
* @param [in] p_params pointer to the parameter
* @param [in] param_len length of the parameter
* @param [in] p_copy_cback parameter deep-copy function
*
* @return true if work dispatch successfully, false otherwise
*/
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);
/**
* @brief start up the application task
*/
void bt_app_task_start_up(void);
/**
* @brief shut down the application task
*/
void bt_app_task_shut_down(void);
#endif /* __BT_APP_CORE_H__ */

View File

@ -26,19 +26,24 @@
#include "esp_a2dp_api.h"
#include "esp_avrc_api.h"
#define BT_AV_TAG "BT_AV"
#define BT_RC_CT_TAG "RCCT"
/* log tags */
#define BT_AV_TAG "BT_AV"
#define BT_RC_CT_TAG "RC_CT"
// AVRCP used transaction label
/* device name */
#define TARGET_DEVICE_NAME "ESP_SPEAKER"
#define LOCAL_DEVICE_NAME "ESP_A2DP_SRC"
/* AVRCP used transaction label */
#define APP_RC_CT_TL_GET_CAPS (0)
#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1)
/* event for handler "bt_av_hdl_stack_up */
enum {
BT_APP_EVT_STACK_UP = 0,
BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */
BT_APP_HEART_BEAT_EVT = 0xff00, /* event for heart beat */
};
/* A2DP global state */
/* A2DP global states */
enum {
APP_AV_STATE_IDLE,
APP_AV_STATE_DISCOVERING,
@ -57,43 +62,60 @@ enum {
APP_AV_MEDIA_STATE_STOPPING,
};
#define BT_APP_HEART_BEAT_EVT (0xff00)
/*********************************
* STATIC FUNCTION DECLARATIONS
********************************/
/// handler for bluetooth stack enabled events
/* handler for bluetooth stack enabled events */
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param);
/// callback function for A2DP source
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
/// callback function for A2DP source audio data stream
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len);
/// 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);
static void a2d_app_heart_beat(void *arg);
/// A2DP application state machine
static void bt_app_av_sm_hdlr(uint16_t event, void *param);
/// avrc CT event handler
/* avrc controller event handler */
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
/* A2DP application state machine handler for each state */
static void bt_app_av_state_unconnected(uint16_t event, void *param);
static void bt_app_av_state_connecting(uint16_t event, void *param);
static void bt_app_av_state_connected(uint16_t event, void *param);
static void bt_app_av_state_disconnecting(uint16_t event, void *param);
/* GAP callback function */
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param);
static esp_bd_addr_t s_peer_bda = {0};
static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
static int s_a2d_state = APP_AV_STATE_IDLE;
static int s_media_state = APP_AV_MEDIA_STATE_IDLE;
static int s_intv_cnt = 0;
static int s_connecting_intv = 0;
static uint32_t s_pkt_cnt = 0;
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
static TimerHandle_t s_tmr;
/* callback function for A2DP source */
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
/* callback function for A2DP source audio data stream */
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len);
/* 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);
/* handler for heart beat timer */
static void bt_app_a2d_heart_beat(TimerHandle_t arg);
/* A2DP application state machine */
static void bt_app_av_sm_hdlr(uint16_t event, void *param);
/* utils for transfer BLuetooth Deveice Address into string form */
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size);
/* A2DP application state machine handler for each state */
static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param);
static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param);
static void bt_app_av_state_connected_hdlr(uint16_t event, void *param);
static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param);
/*********************************
* STATIC VARIABLE DEFINITIONS
********************************/
static esp_bd_addr_t s_peer_bda = {0}; /* Bluetooth Device Address of peer device*/
static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; /* Bluetooth Device Name of peer device*/
static int s_a2d_state = APP_AV_STATE_IDLE; /* A2DP global state */
static int s_media_state = APP_AV_MEDIA_STATE_IDLE; /* sub states of APP_AV_STATE_CONNECTED */
static int s_intv_cnt = 0; /* count of heart beat intervals */
static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */
static uint32_t s_pkt_cnt = 0; /* count of packets */
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */
static TimerHandle_t s_tmr; /* handle of heart beat timer */
/*********************************
* STATIC FUNCTION DEFINITIONS
********************************/
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
{
@ -101,68 +123,11 @@ static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
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]);
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
return str;
}
void app_main(void)
{
// Initialize NVS.
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize controller failed\n", __func__);
return;
}
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__);
return;
}
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__);
return;
}
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__);
return;
}
/* create application task */
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);
#if (CONFIG_BT_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 variable pin, input pin code when pairing
*/
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code;
esp_bt_gap_set_pin(pin_type, 0, pin_code);
}
static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
{
uint8_t *rmt_bdname = NULL;
@ -172,6 +137,7 @@ static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len
return false;
}
/* get complete or short local name from eir data */
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
if (!rmt_bdname) {
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
@ -198,11 +164,12 @@ static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len
static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
{
char bda_str[18];
uint32_t cod = 0;
int32_t rssi = -129; /* invalid value */
uint32_t cod = 0; /* class of device */
int32_t rssi = -129; /* invalid value */
uint8_t *eir = NULL;
esp_bt_gap_dev_prop_t *p;
/* handle the discovery results */
ESP_LOGI(BT_AV_TAG, "Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18));
for (int i = 0; i < param->disc_res.num_prop; i++) {
p = param->disc_res.prop + i;
@ -230,38 +197,39 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
return;
}
/* search for device named "ESP_SPEAKER" in its extended inqury response */
/* search for target device in its Extended Inqury Response */
if (eir) {
get_name_from_eir(eir, s_peer_bdname, NULL);
if (strcmp((char *)s_peer_bdname, "ESP_SPEAKER") != 0) {
return;
if (strcmp((char *)s_peer_bdname, TARGET_DEVICE_NAME) == 0) {
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname);
s_a2d_state = APP_AV_STATE_DISCOVERED;
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
esp_bt_gap_cancel_discovery();
}
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname);
s_a2d_state = APP_AV_STATE_DISCOVERED;
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
esp_bt_gap_cancel_discovery();
}
}
void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_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)
{
switch (event) {
/* when device discovered a result, this event comes */
case ESP_BT_GAP_DISC_RES_EVT: {
filter_inquiry_scan_result(param);
break;
}
/* when discovery state changed, this event comes */
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
s_a2d_state = APP_AV_STATE_CONNECTING;
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
/* connect source to peer device specificed by Bluetooth Device Address */
esp_a2d_source_connect(s_peer_bda);
} else {
// not discovered, continue to discover
/* not discovered, continue to discover */
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}
@ -270,20 +238,19 @@ void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
}
break;
}
case ESP_BT_GAP_RMT_SRVCS_EVT:
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
break;
/* 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_LOGE(BT_AV_TAG, "authentication failed, status: %d", param->auth_cmpl.stat);
}
break;
}
/* when Legacy Pairing pin code requested, this event comes */
case ESP_BT_GAP_PIN_REQ_EVT: {
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit: %d", param->pin_req.min_16_digit);
if (param->pin_req.min_16_digit) {
ESP_LOGI(BT_AV_TAG, "Input pin code: 0000 0000 0000 0000");
esp_bt_pin_code_t pin_code = {0};
@ -301,59 +268,60 @@ void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
}
#if (CONFIG_BT_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: %d", 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:%d", param->key_notif.passkey);
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey: %d", 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", param->mode_chg.mode);
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode: %d", param->mode_chg.mode);
break;
/* other */
default: {
ESP_LOGI(BT_AV_TAG, "event: %d", event);
break;
}
}
return;
}
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
{
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
switch (event) {
case BT_APP_EVT_STACK_UP: {
/* set up device name */
char *dev_name = "ESP_A2DP_SRC";
esp_bt_dev_set_device_name(dev_name);
ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event);
/* register GAP callback function */
switch (event) {
/* when stack up worked, this event comes */
case BT_APP_STACK_UP_EVT: {
char *dev_name = LOCAL_DEVICE_NAME;
esp_bt_dev_set_device_name(dev_name);
esp_bt_gap_register_callback(bt_app_gap_cb);
/* initialize AVRCP controller */
esp_avrc_ct_init();
esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
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_ERROR_CHECK(esp_avrc_tg_set_rn_evt_cap(&evt_set));
/* initialize A2DP source */
esp_a2d_source_init();
esp_a2d_register_callback(&bt_app_a2d_cb);
esp_a2d_source_register_data_callback(bt_app_a2d_data_cb);
esp_a2d_source_init();
/* set discoverable and connectable mode */
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
/* start device discovery */
ESP_LOGI(BT_AV_TAG, "Starting device discovery...");
s_a2d_state = APP_AV_STATE_DISCOVERING;
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
@ -362,15 +330,17 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
do {
int tmr_id = 0;
s_tmr = xTimerCreate("connTmr", (10000 / portTICK_RATE_MS),
pdTRUE, (void *) &tmr_id, a2d_app_heart_beat);
pdTRUE, (void *) &tmr_id, bt_app_a2d_heart_beat);
xTimerStart(s_tmr, portMAX_DELAY);
} while (0);
break;
}
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
/* other */
default: {
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
}
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
@ -378,54 +348,56 @@ static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL);
}
/* generate some random noise to simulate source audio */
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len)
{
if (len < 0 || data == NULL) {
if (data == NULL || len < 0) {
return 0;
}
// generate random sequence
int val = rand() % (1 << 16);
int *p_buf = (int *)data;
for (int i = 0; i < (len >> 1); i++) {
data[(i << 1)] = val & 0xff;
data[(i << 1) + 1] = (val >> 8) & 0xff;
p_buf[i] = rand() % (1 << 16);
}
return len;
}
static void a2d_app_heart_beat(void *arg)
static void bt_app_a2d_heart_beat(TimerHandle_t arg)
{
bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL);
}
static void bt_app_av_sm_hdlr(uint16_t event, void *param)
{
ESP_LOGI(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, s_a2d_state, event);
ESP_LOGI(BT_AV_TAG, "%s state: %d, event: 0x%x", __func__, s_a2d_state, event);
/* select handler according to different states. */
switch (s_a2d_state) {
case APP_AV_STATE_DISCOVERING:
case APP_AV_STATE_DISCOVERED:
break;
case APP_AV_STATE_UNCONNECTED:
bt_app_av_state_unconnected(event, param);
bt_app_av_state_unconnected_hdlr(event, param);
break;
case APP_AV_STATE_CONNECTING:
bt_app_av_state_connecting(event, param);
bt_app_av_state_connecting_hdlr(event, param);
break;
case APP_AV_STATE_CONNECTED:
bt_app_av_state_connected(event, param);
bt_app_av_state_connected_hdlr(event, param);
break;
case APP_AV_STATE_DISCONNECTING:
bt_app_av_state_disconnecting(event, param);
bt_app_av_state_disconnecting_hdlr(event, param);
break;
default:
ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, s_a2d_state);
ESP_LOGE(BT_AV_TAG, "%s invalid state: %d", __func__, s_a2d_state);
break;
}
}
static void bt_app_av_state_unconnected(uint16_t event, void *param)
static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param)
{
/* handle the events of intrest in unconnected state */
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT:
case ESP_A2D_AUDIO_STATE_EVT:
@ -433,23 +405,26 @@ static void bt_app_av_state_unconnected(uint16_t event, void *param)
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
break;
case BT_APP_HEART_BEAT_EVT: {
uint8_t *p = s_peer_bda;
uint8_t *bda = s_peer_bda;
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",
p[0], p[1], p[2], p[3], p[4], p[5]);
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
esp_a2d_source_connect(s_peer_bda);
s_a2d_state = APP_AV_STATE_CONNECTING;
s_connecting_intv = 0;
break;
}
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
default: {
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
}
static void bt_app_av_state_connecting(uint16_t event, void *param)
static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
{
esp_a2d_cb_param_t *a2d = NULL;
/* handle the events of intrest in connecting state */
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT: {
a2d = (esp_a2d_cb_param_t *)(param);
@ -468,13 +443,17 @@ static void bt_app_av_state_connecting(uint16_t event, void *param)
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
break;
case BT_APP_HEART_BEAT_EVT:
/**
* Switch state to APP_AV_STATE_UNCONNECTED
* when connecting lasts more than 2 heart beat intervals.
*/
if (++s_connecting_intv >= 2) {
s_a2d_state = APP_AV_STATE_UNCONNECTED;
s_connecting_intv = 0;
}
break;
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
@ -482,6 +461,7 @@ static void bt_app_av_state_connecting(uint16_t event, void *param)
static void bt_app_av_media_proc(uint16_t event, void *param)
{
esp_a2d_cb_param_t *a2d = NULL;
switch (s_media_state) {
case APP_AV_MEDIA_STATE_IDLE: {
if (event == BT_APP_HEART_BEAT_EVT) {
@ -507,7 +487,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
s_intv_cnt = 0;
s_media_state = APP_AV_MEDIA_STATE_STARTED;
} else {
// not started succesfully, transfer to idle state
/* not started succesfully, transfer to idle state */
ESP_LOGI(BT_AV_TAG, "a2dp media start failed.");
s_media_state = APP_AV_MEDIA_STATE_IDLE;
}
@ -516,6 +496,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
}
case APP_AV_MEDIA_STATE_STARTED: {
if (event == BT_APP_HEART_BEAT_EVT) {
/* stop media after 10 heart beat intervals */
if (++s_intv_cnt >= 10) {
ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
@ -541,12 +522,17 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
}
break;
}
default: {
break;
}
}
}
static void bt_app_av_state_connected(uint16_t event, void *param)
static void bt_app_av_state_connected_hdlr(uint16_t event, void *param)
{
esp_a2d_cb_param_t *a2d = NULL;
/* handle the events of intrest in connected state */
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT: {
a2d = (esp_a2d_cb_param_t *)(param);
@ -565,22 +551,25 @@ static void bt_app_av_state_connected(uint16_t event, void *param)
break;
}
case ESP_A2D_AUDIO_CFG_EVT:
// not suppposed to occur for A2DP source
/* not suppposed to occur for A2DP source */
break;
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
case BT_APP_HEART_BEAT_EVT: {
bt_app_av_media_proc(event, param);
break;
}
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
default: {
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
}
static void bt_app_av_state_disconnecting(uint16_t event, void *param)
static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param)
{
esp_a2d_cb_param_t *a2d = NULL;
/* handle the events of intrest in disconnecing state */
switch (event) {
case ESP_A2D_CONNECTION_STATE_EVT: {
a2d = (esp_a2d_cb_param_t *)(param);
@ -596,18 +585,20 @@ static void bt_app_av_state_disconnecting(uint16_t event, void *param)
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
case BT_APP_HEART_BEAT_EVT:
break;
default:
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
default: {
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
}
/* 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)
{
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_METADATA_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:
@ -615,10 +606,11 @@ static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
break;
}
default:
default: {
ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event);
break;
}
}
}
static void bt_av_volume_changed(void)
@ -632,52 +624,63 @@ static void bt_av_volume_changed(void)
void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
{
switch (event_id) {
case ESP_AVRC_RN_VOLUME_CHANGE:
/* when volume changed locally on target, this event comes */
case ESP_AVRC_RN_VOLUME_CHANGE: {
ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume);
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5);
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5);
bt_av_volume_changed();
break;
}
/* other */
default:
break;
}
}
/* AVRC controller event handler */
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
{
ESP_LOGD(BT_RC_CT_TAG, "%s evt %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 evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
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 responsed, 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", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough response: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
break;
}
/* when metadata responsed, 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);
ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata response: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
free(rc->meta_rsp.attr_text);
break;
}
/* when notification changed, 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 indicate feature of remote device, this event comes */
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
break;
}
/* when get supported notification events capability of peer device, 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);
@ -686,13 +689,73 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
bt_av_volume_changed();
break;
}
/* when set absolute volume responsed, this event comes */
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume rsp: volume %d", rc->set_volume_rsp.volume);
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume response: volume %d", rc->set_volume_rsp.volume);
break;
}
default:
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event);
/* other */
default: {
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event);
break;
}
}
}
/*********************************
* MAIN ENTRY POINT
********************************/
void app_main(void)
{
/* initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/*
* 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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize controller failed\n", __func__);
return;
}
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__);
return;
}
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__);
return;
}
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__);
return;
}
#if (CONFIG_BT_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 variable pin, input pin code when pairing
*/
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code;
esp_bt_gap_set_pin(pin_type, 0, pin_code);
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_STACK_UP_EVT, NULL, 0, NULL);
}

View File

@ -7,5 +7,3 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_SPP_ENABLED=n
CONFIG_BT_BLE_ENABLED=n

View File

@ -0,0 +1,121 @@
# A2DP Source Example Walkthrough
## Introduction
In this tutorial, the A2DP source example code is reviewed. The code implements an A2DP source role, which transmits audio stream to A2DP sink devices. This example performs device discovery to search for a target device and then initiates connection with it. After connection established, the source starts to transmit audio stream to peer device and change the volume locally based on heart beat.
## File Tree
The file tree of this example is shown below. The [main](../main) folder contains the main files of this example including program core files, main function file, etc. The [tutorial](../tutorial) folder contains this guidance document.
```c
└── a2dp_source
   ├── CMakeLists.txt
   ├── main
   │   ├── bt_app_core.c
   │   ├── bt_app_core.h
   │   ├── CMakeLists.txt
   │   └── main.c
   ├── README.md
   ├── sdkconfig.defaults
   └── tutorial
   └── Example_A2DP_Source.md
2 directories, 8 files
```
## Main Entry Point
This example is located in the examples folder of the ESP-IDF under the [bluetooth/bluedroid/classic_bt/a2dp_source](../). The entry point of this program is `app_main()` which contained in [main/main.c](../main/main.c).
### NVS Initialization
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:
```c
/* Initialize NVS — it is used to store PHY calibration */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
```
### 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.
```c
/* initialize Bluetooth Controller with default configuration */
esp_bt_controller_config_t bt_cfg = ONTROLLER_INIT_CONFIG_DEFAULT();
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize controller ed\n", __func__);
return;
}
/* Enable Bluetooth Controller in Classical Bluetooth mode */
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != OK) {
ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__);
return;
}
```
After the initialization of the Bluetooth Controller, the Bluedroid stack, which includes the common definitions and APIs for Classic Bluetooth is initialized and enabled by using:
```c
/* initialize Bluedroid Host */
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__);
return;
}
/* Enable Bluedroid Host */
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__);
return;
}
```
The Classic Bluetooth uses an asynchronous programming paradigm. The entire Bluedroid stack sees the use of events, event handlers, callbacks and state machines. Most APIs are implemented by posting specific type of events to the work queue of Bluedroid Stack. Threads(FreeRTOS) tasks inside Bluedroid then process the events with specific handlers, according to the internal state. When the operations are completed, Bluedroid stack invokes the callback function which is registered by application, to report some other events and information.
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.
### Paring Parameter Settings
The main function continues to set up paring parameters including Secure Simple Pairing and Legacy Pairing.
```c
#if (CONFIG_BT_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 variable pin, input pin code when pairing
*/
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code;
esp_bt_gap_set_pin(pin_type, 0, pin_code);
```
### Create Application Task
Finally, the main function starts up the application task. `bt_app_task_start_up()` creates a work queue and a FreeRTOS task to process the events(messages) posted into the work queue.
```c
/* create application task */
bt_app_task_start_up();
```
### Profile Set up
The main function ends up dispatching event "BT_APP_STACK_UP_EVT" to the application task.
```c
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch(bt_av_hdl_stack_evt,BT_APP_STACK_UP_EVT, NULL, 0, NULL);
```
The call of function bt_app_work_dispatch posts the event "BT_APP_STACK_UP_EVT" together with the handler function `bt_av_hdl_stack_evt`, to the work queue of application task. The application task is then triggered to invoke the handler function to process this event.