component/bt: fix the format and add more content to A2DP source example document and source code

This commit is contained in:
wangmengyang 2018-09-12 10:48:15 +08:00
parent 35785d08fd
commit 031ec7af93
3 changed files with 138 additions and 71 deletions

View file

@ -3,16 +3,83 @@ ESP-IDF A2DP-SOURCE demo
Demo of A2DP audio source role
This is the demo for user to use ESP_APIs to use Advanced Audio Distribution Profile in transmitting audio stream
This is the demo of using Advanced Audio Distribution Profile APIs to transmit audio stream. Applications 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.
### Configure the project
```
make menuconfig
```
* Set serial port under Serial Flasher Options.
* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output.
```
make -j4 flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
## 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.
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
2. audio transmission stops
3. disconnect with target device
4. reconnect to target device
The example implements an event loop triggered by a periodic "heart beat" timer and events from Bluetooth protocol stack callback functions.
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: Cancel device discovery ...
I (4100) BT_AV: Device discovery stopped.
I (4100) BT_AV: a2dp connecting to peer: ESP_SPEAKER
```
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:
```
I (10880) BT_AV: a2dp media ready checking ...
...
I (10880) BT_AV: a2dp media ready, starting ...
...
I (11400) BT_AV: a2dp media start successfully.
```
Stop of audio transmission, and disconnection with remote device generate the following notification message:
```
I (110880) BT_AV: a2dp media stopping...
...
I (110920) BT_AV: a2dp media stopped successfully, disconnecting...
...
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.
* 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.
Options choose step:
1. make menuconfig.
2. enter menuconfig "Component config", choose "Bluetooth"
3. enter menu Bluetooth, choose "Bluedroid Enable"
4. enter menu Bluedroid Enable, choose "Classic Bluetooth"
5. select "A2DP" and choose "SOURCE"
In this example, the bluetooth device implements A2DP source. The A2DP sink device to be connected to can be set up with the example "A2DP sink" in another folder in ESP-IDF example directory.
For the first step, the device performs device discovery to find a target device(A2DP sink) named "ESP_SPEAKER". Then it initiate connection with the target device.
After connection is established, the device then start media transmission. The raw PCM media stream to be encoded and transmited in this example is random sequence therefore continuous noise can be heard if the stream is decoded and played on the sink side.
After a period of time, media stream suspend, disconnection and reconnection procedure will be performed.

View file

@ -21,8 +21,8 @@ static void bt_app_task_handler(void *arg);
static bool bt_app_send_msg(bt_app_msg_t *msg);
static void bt_app_work_dispatched(bt_app_msg_t *msg);
static xQueueHandle bt_app_task_queue = NULL;
static xTaskHandle bt_app_task_handle = NULL;
static xQueueHandle s_bt_app_task_queue = NULL;
static xTaskHandle s_bt_app_task_handle = NULL;
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)
{
@ -57,7 +57,7 @@ static bool bt_app_send_msg(bt_app_msg_t *msg)
return false;
}
if (xQueueSend(bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) {
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;
}
@ -75,7 +75,7 @@ static void bt_app_task_handler(void *arg)
{
bt_app_msg_t msg;
for (;;) {
if (pdTRUE == xQueueReceive(bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
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:
@ -95,19 +95,19 @@ static void bt_app_task_handler(void *arg)
void bt_app_task_start_up(void)
{
bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &bt_app_task_handle);
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;
}
void bt_app_task_shut_down(void)
{
if (bt_app_task_handle) {
vTaskDelete(bt_app_task_handle);
bt_app_task_handle = NULL;
if (s_bt_app_task_handle) {
vTaskDelete(s_bt_app_task_handle);
s_bt_app_task_handle = NULL;
}
if (bt_app_task_queue) {
vQueueDelete(bt_app_task_queue);
bt_app_task_queue = NULL;
if (s_bt_app_task_queue) {
vQueueDelete(s_bt_app_task_queue);
s_bt_app_task_queue = NULL;
}
}

View file

@ -74,15 +74,15 @@ 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);
static esp_bd_addr_t peer_bda = {0};
static uint8_t peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
static int m_a2d_state = APP_AV_STATE_IDLE;
static int m_media_state = APP_AV_MEDIA_STATE_IDLE;
static int m_intv_cnt = 0;
static int m_connecting_intv = 0;
static uint32_t m_pkt_cnt = 0;
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;
TimerHandle_t tmr;
static TimerHandle_t s_tmr;
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
{
@ -212,14 +212,14 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
/* search for device named "ESP_SPEAKER" in its extended inqury response */
if (eir) {
get_name_from_eir(eir, peer_bdname, NULL);
if (strcmp((char *)peer_bdname, "ESP_SPEAKER") != 0) {
get_name_from_eir(eir, s_peer_bdname, NULL);
if (strcmp((char *)s_peer_bdname, "ESP_SPEAKER") != 0) {
return;
}
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, peer_bdname);
m_a2d_state = APP_AV_STATE_DISCOVERED;
memcpy(peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
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();
}
@ -235,11 +235,11 @@ void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
}
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
if (m_a2d_state == APP_AV_STATE_DISCOVERED) {
m_a2d_state = APP_AV_STATE_CONNECTING;
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", peer_bdname);
esp_a2d_source_connect(peer_bda);
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
esp_a2d_source_connect(s_peer_bda);
} else {
// not discovered, continue to discover
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
@ -309,15 +309,15 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
/* start device discovery */
ESP_LOGI(BT_AV_TAG, "Starting device discovery...");
m_a2d_state = APP_AV_STATE_DISCOVERING;
s_a2d_state = APP_AV_STATE_DISCOVERING;
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
/* create and start heart beat timer */
do {
int tmr_id = 0;
tmr = xTimerCreate("connTmr", (10000 / portTICK_RATE_MS),
s_tmr = xTimerCreate("connTmr", (10000 / portTICK_RATE_MS),
pdTRUE, (void *)tmr_id, a2d_app_heart_beat);
xTimerStart(tmr, portMAX_DELAY);
xTimerStart(s_tmr, portMAX_DELAY);
} while (0);
break;
}
@ -355,8 +355,8 @@ static void a2d_app_heart_beat(void *arg)
static void bt_app_av_sm_hdlr(uint16_t event, void *param)
{
ESP_LOGI(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, m_a2d_state, event);
switch (m_a2d_state) {
ESP_LOGI(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, s_a2d_state, event);
switch (s_a2d_state) {
case APP_AV_STATE_DISCOVERING:
case APP_AV_STATE_DISCOVERED:
break;
@ -373,7 +373,7 @@ static void bt_app_av_sm_hdlr(uint16_t event, void *param)
bt_app_av_state_disconnecting(event, param);
break;
default:
ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, m_a2d_state);
ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, s_a2d_state);
break;
}
}
@ -387,12 +387,12 @@ 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 = peer_bda;
uint8_t *p = 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]);
esp_a2d_source_connect(peer_bda);
m_a2d_state = APP_AV_STATE_CONNECTING;
m_connecting_intv = 0;
esp_a2d_source_connect(s_peer_bda);
s_a2d_state = APP_AV_STATE_CONNECTING;
s_connecting_intv = 0;
break;
}
default:
@ -409,11 +409,11 @@ static void bt_app_av_state_connecting(uint16_t event, void *param)
a2d = (esp_a2d_cb_param_t *)(param);
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
ESP_LOGI(BT_AV_TAG, "a2dp connected");
m_a2d_state = APP_AV_STATE_CONNECTED;
m_media_state = APP_AV_MEDIA_STATE_IDLE;
s_a2d_state = APP_AV_STATE_CONNECTED;
s_media_state = APP_AV_MEDIA_STATE_IDLE;
esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_NONE);
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
m_a2d_state = APP_AV_STATE_UNCONNECTED;
s_a2d_state = APP_AV_STATE_UNCONNECTED;
}
break;
}
@ -422,9 +422,9 @@ 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:
if (++m_connecting_intv >= 2) {
m_a2d_state = APP_AV_STATE_UNCONNECTED;
m_connecting_intv = 0;
if (++s_connecting_intv >= 2) {
s_a2d_state = APP_AV_STATE_UNCONNECTED;
s_connecting_intv = 0;
}
break;
default:
@ -436,7 +436,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 (m_media_state) {
switch (s_media_state) {
case APP_AV_MEDIA_STATE_IDLE: {
if (event == BT_APP_HEART_BEAT_EVT) {
ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ...");
@ -447,7 +447,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ...");
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
m_media_state = APP_AV_MEDIA_STATE_STARTING;
s_media_state = APP_AV_MEDIA_STATE_STARTING;
}
}
break;
@ -458,23 +458,23 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(BT_AV_TAG, "a2dp media start successfully.");
m_intv_cnt = 0;
m_media_state = APP_AV_MEDIA_STATE_STARTED;
s_intv_cnt = 0;
s_media_state = APP_AV_MEDIA_STATE_STARTED;
} else {
// not started succesfully, transfer to idle state
ESP_LOGI(BT_AV_TAG, "a2dp media start failed.");
m_media_state = APP_AV_MEDIA_STATE_IDLE;
s_media_state = APP_AV_MEDIA_STATE_IDLE;
}
}
break;
}
case APP_AV_MEDIA_STATE_STARTED: {
if (event == BT_APP_HEART_BEAT_EVT) {
if (++m_intv_cnt >= 10) {
if (++s_intv_cnt >= 10) {
ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
m_media_state = APP_AV_MEDIA_STATE_STOPPING;
m_intv_cnt = 0;
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
s_intv_cnt = 0;
}
}
break;
@ -485,9 +485,9 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP &&
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(BT_AV_TAG, "a2dp media stopped successfully, disconnecting...");
m_media_state = APP_AV_MEDIA_STATE_IDLE;
esp_a2d_source_disconnect(peer_bda);
m_a2d_state = APP_AV_STATE_DISCONNECTING;
s_media_state = APP_AV_MEDIA_STATE_IDLE;
esp_a2d_source_disconnect(s_peer_bda);
s_a2d_state = APP_AV_STATE_DISCONNECTING;
} else {
ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
@ -506,7 +506,7 @@ static void bt_app_av_state_connected(uint16_t event, void *param)
a2d = (esp_a2d_cb_param_t *)(param);
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
m_a2d_state = APP_AV_STATE_UNCONNECTED;
s_a2d_state = APP_AV_STATE_UNCONNECTED;
esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
break;
@ -514,7 +514,7 @@ static void bt_app_av_state_connected(uint16_t event, void *param)
case ESP_A2D_AUDIO_STATE_EVT: {
a2d = (esp_a2d_cb_param_t *)(param);
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
m_pkt_cnt = 0;
s_pkt_cnt = 0;
}
break;
}
@ -540,7 +540,7 @@ static void bt_app_av_state_disconnecting(uint16_t event, void *param)
a2d = (esp_a2d_cb_param_t *)(param);
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
m_a2d_state = APP_AV_STATE_UNCONNECTED;
s_a2d_state = APP_AV_STATE_UNCONNECTED;
esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
break;