component/bt: implement A2DP source
1. Modified symbol names of A2DP sink APIs include: - esp_a2d_data_cb_t --> esp_a2d_sink_data_cb_t - esp_a2d_register_data_callback --> esp_a2d_sink_register_data_callback
This commit is contained in:
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := a2dp_source
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
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
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#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"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "bt_app_core.h"
|
||||
|
||||
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;
|
||||
|
||||
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, msg.param, p_params);
|
||||
}
|
||||
return bt_app_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xQueueSend(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(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)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (bt_app_task_handle) {
|
||||
vTaskDelete(bt_app_task_handle);
|
||||
bt_app_task_handle = NULL;
|
||||
}
|
||||
if (bt_app_task_queue) {
|
||||
vQueueDelete(bt_app_task_queue);
|
||||
bt_app_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef __BT_APP_CORE_H__
|
||||
#define __BT_APP_CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
|
||||
#define BT_APP_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*/
|
||||
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 */
|
||||
} bt_app_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*/
|
||||
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*/
|
||||
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);
|
||||
|
||||
void bt_app_task_start_up(void);
|
||||
|
||||
void bt_app_task_shut_down(void);
|
||||
|
||||
#endif /* __BT_APP_CORE_H__ */
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "bt_app_core.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "esp_avrc_api.h"
|
||||
|
||||
#define BT_AV_TAG "BT_AV"
|
||||
|
||||
/* event for handler "bt_av_hdl_stack_up */
|
||||
enum {
|
||||
BT_APP_EVT_STACK_UP = 0,
|
||||
};
|
||||
|
||||
/* A2DP global state */
|
||||
enum {
|
||||
APP_AV_STATE_IDLE,
|
||||
APP_AV_STATE_DISCOVERING,
|
||||
APP_AV_STATE_DISCOVERED,
|
||||
APP_AV_STATE_UNCONNECTED,
|
||||
APP_AV_STATE_CONNECTING,
|
||||
APP_AV_STATE_CONNECTED,
|
||||
APP_AV_STATE_DISCONNECTING,
|
||||
};
|
||||
|
||||
/* sub states of APP_AV_STATE_CONNECTED */
|
||||
enum {
|
||||
APP_AV_MEDIA_STATE_IDLE,
|
||||
APP_AV_MEDIA_STATE_STARTING,
|
||||
APP_AV_MEDIA_STATE_STARTED,
|
||||
APP_AV_MEDIA_STATE_STOPPING,
|
||||
};
|
||||
|
||||
#define BT_APP_HEART_BEAT_EVT (0xff00)
|
||||
|
||||
/// 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);
|
||||
|
||||
static void a2d_app_heart_beat(void *arg);
|
||||
|
||||
/// A2DP application state machine
|
||||
static void bt_app_av_sm_hdlr(uint16_t event, void *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);
|
||||
|
||||
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;
|
||||
|
||||
TimerHandle_t tmr;
|
||||
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *p = bda;
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
p[0], p[1], p[2], p[3], p[4], p[5]);
|
||||
return str;
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
// Initialize NVS.
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
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_BTDM) != 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);
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 */
|
||||
uint8_t *eir = NULL;
|
||||
esp_bt_gap_dev_prop_t *p;
|
||||
|
||||
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;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
cod = *(uint32_t *)(p->val);
|
||||
ESP_LOGI(BT_AV_TAG, "--Class of Device: 0x%x", cod);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
rssi = *(int8_t *)(p->val);
|
||||
ESP_LOGI(BT_AV_TAG, "--RSSI: %d", rssi);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_EIR:
|
||||
eir = (uint8_t *)(p->val);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_BDNAME:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* search for device with MAJOR service class as "rendering" in COD */
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
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, "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)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
filter_inquiry_scan_result(param);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
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);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Discovery started.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_RMT_SRVCS_EVT:
|
||||
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
|
||||
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);
|
||||
|
||||
/* register GAP callback function */
|
||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||
|
||||
/* initialize A2DP source */
|
||||
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_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
|
||||
|
||||
/* start device discovery */
|
||||
ESP_LOGI(BT_AV_TAG, "Starting device discovery...");
|
||||
m_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),
|
||||
pdTRUE, (void *)tmr_id, a2d_app_heart_beat);
|
||||
xTimerStart(tmr, portMAX_DELAY);
|
||||
} while (0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len)
|
||||
{
|
||||
if (len < 0 || data == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// generate random sequence
|
||||
int val = rand() % (1 << 16);
|
||||
for (int i = 0; i < (len >> 1); i++) {
|
||||
data[(i << 1)] = val & 0xff;
|
||||
data[(i << 1) + 1] = (val >> 8) & 0xff;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void a2d_app_heart_beat(void *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_LOGE(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, m_a2d_state, event);
|
||||
switch (m_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);
|
||||
break;
|
||||
case APP_AV_STATE_CONNECTING:
|
||||
bt_app_av_state_connecting(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_CONNECTED:
|
||||
bt_app_av_state_connected(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_DISCONNECTING:
|
||||
bt_app_av_state_disconnecting(event, param);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, m_a2d_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_unconnected(uint16_t event, void *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
uint8_t *p = 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;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_connecting(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
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;
|
||||
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
m_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
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;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_media_proc(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (m_media_state) {
|
||||
case APP_AV_MEDIA_STATE_IDLE: {
|
||||
if (event == BT_APP_HEART_BEAT_EVT) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY);
|
||||
} else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY &&
|
||||
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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STARTING: {
|
||||
if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STARTED: {
|
||||
if (event == BT_APP_HEART_BEAT_EVT) {
|
||||
if (++m_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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STOPPING: {
|
||||
if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(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;
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media stopping...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_connected(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
// 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_disconnecting(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Override some defaults so BT stack is enabled and
|
||||
# Classic BT is enabled
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BLUEDROID_ENABLED=y
|
||||
CONFIG_CLASSIC_BT_ENABLED=y
|
||||
CONFIG_A2DP_ENABLE=y
|
||||
CONFIG_A2DP_SINK_ENABLE=
|
||||
CONFIG_A2DP_SRC_ENABLE=y
|
||||
CONFIG_BT_SPP_ENABLED=
|
||||
CONFIG_GATTS_ENABLE=
|
||||
CONFIG_GATTC_ENABLE=
|
||||
CONFIG_BLE_SMP_ENABLE=
|
||||
Reference in New Issue
Block a user