170 lines
4.7 KiB
C++
170 lines
4.7 KiB
C++
#include "esp_log.h"
|
|
#include "esp_gap_bt_api.h"
|
|
#include "esp_a2dp_api.h"
|
|
#include "driver/i2s.h"
|
|
|
|
#include "a2dp.h"
|
|
#include "helper.h"
|
|
#include "wav.h"
|
|
#include "storage.h"
|
|
#include "i2s.h"
|
|
#include "bluetooth.h"
|
|
|
|
#define A2DP_TAG "APP_A2DP"
|
|
|
|
static void handle_connection_state(uint16_t event, esp_a2d_cb_param_t* a2d) {
|
|
ESP_LOGI(A2DP_TAG, "partner address: %s", addr_to_str(a2d->conn_stat.remote_bda));
|
|
|
|
ESP_LOGI(A2DP_TAG, "A2DP connection state: %s, [%s]", connection_state_to_str(a2d->conn_stat.state), addr_to_str(a2d->conn_stat.remote_bda));
|
|
|
|
static int retry_count = 0;
|
|
static bool was_connected = false;
|
|
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
|
ESP_LOGI(A2DP_TAG, "ESP_A2D_CONNECTION_STATE_DISCONNECTED");
|
|
|
|
if (a2d->conn_stat.disc_rsn == ESP_A2D_DISC_RSN_ABNORMAL && retry_count < 3) {
|
|
ESP_LOGI(A2DP_TAG,"Connection try number: %d", retry_count);
|
|
|
|
a2dp::connect_to_last();
|
|
} else {
|
|
bluetooth::set_scan_mode(true);
|
|
|
|
if (was_connected) {
|
|
vTaskDelay(300 / portTICK_PERIOD_MS);
|
|
WAV_PLAY(disconnect);
|
|
}
|
|
}
|
|
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
|
|
ESP_LOGI(A2DP_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTED");
|
|
|
|
bluetooth::set_scan_mode(false);
|
|
retry_count = 0;
|
|
was_connected = true;
|
|
|
|
WAV_PLAY(connect);
|
|
|
|
// record current connection
|
|
nvs::set_last_connection(a2d->conn_stat.remote_bda);
|
|
if (esp_bt_gap_read_remote_name(a2d->conn_stat.remote_bda) != ESP_OK) {
|
|
ESP_LOGE(A2DP_TAG, "esp_bt_gap_read_remote_name failed");
|
|
}
|
|
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTING){
|
|
ESP_LOGI(A2DP_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTING");
|
|
retry_count++;
|
|
}
|
|
}
|
|
|
|
static void handle_audio_cfg(uint16_t event, esp_a2d_cb_param_t* a2d) {
|
|
ESP_LOGI(A2DP_TAG, "a2dp audio_cfg_cb , codec type %d", a2d->audio_cfg.mcc.type);
|
|
|
|
char oct0 = a2d->audio_cfg.mcc.cie.sbc[0];
|
|
if (oct0 & (0x01 << 6)) {
|
|
i2s::set_sample_rate(32000);
|
|
} else if (oct0 & (0x01 << 5)) {
|
|
i2s::set_sample_rate(44100);
|
|
} else if (oct0 & (0x01 << 4)) {
|
|
i2s::set_sample_rate(48000);
|
|
} else {
|
|
i2s::set_sample_rate(16000);
|
|
}
|
|
|
|
ESP_LOGI(A2DP_TAG, "a2dp audio_cfg_cb , sample_rate %d", i2s::get_sample_rate());
|
|
}
|
|
|
|
static void a2d_callback(esp_a2d_cb_event_t event, esp_a2d_cb_param_t* a2d) {
|
|
switch (event) {
|
|
case ESP_A2D_CONNECTION_STATE_EVT:
|
|
ESP_LOGD(A2DP_TAG, "%s ESP_A2D_CONNECTION_STATE_EVT", __func__);
|
|
handle_connection_state(event, a2d);
|
|
break;
|
|
|
|
case ESP_A2D_AUDIO_STATE_EVT:
|
|
ESP_LOGD(A2DP_TAG, "%s ESP_A2D_AUDIO_STATE_EVT", __func__);
|
|
break;
|
|
|
|
case ESP_A2D_AUDIO_CFG_EVT:
|
|
ESP_LOGD(A2DP_TAG, "%s ESP_A2D_AUDIO_CFG_EVT", __func__);
|
|
handle_audio_cfg(event, a2d);
|
|
break;
|
|
|
|
case ESP_A2D_PROF_STATE_EVT:
|
|
if (ESP_A2D_INIT_SUCCESS == a2d->a2d_prof_stat.init_state) {
|
|
ESP_LOGI(A2DP_TAG,"A2DP PROF STATE: Init Compl\n");
|
|
} else {
|
|
ESP_LOGI(A2DP_TAG,"A2DP PROF STATE: Deinit Compl\n");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ESP_LOGE(A2DP_TAG, "%s unhandled evt %d", __func__, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Utility structure that can be used to split a int32_t up into 2 separate channels with int16_t data.
|
|
* @author Phil Schatzmann
|
|
* @copyright Apache License Version 2
|
|
*/
|
|
struct __attribute__((packed)) Frame {
|
|
int16_t channel1;
|
|
int16_t channel2;
|
|
|
|
Frame(int v=0){
|
|
channel1 = channel2 = v;
|
|
}
|
|
|
|
Frame(int ch1, int ch2){
|
|
channel1 = ch1;
|
|
channel2 = ch2;
|
|
}
|
|
|
|
};
|
|
|
|
static void audio_data_callback(const uint8_t* data, uint32_t len) {
|
|
i2s::write(data, len);
|
|
/* Frame* frame = (Frame*)data; */
|
|
/* for (int i = 0; i < len/4; i++) { */
|
|
/* int16_t temp = frame[i].channel1; */
|
|
/* frame[i].channel1 = frame[i].channel2; */
|
|
/* frame[i].channel2 = temp; */
|
|
/* } */
|
|
|
|
/* size_t bytes_written; */
|
|
/* if (i2s_write(I2S_PORT, data, len, &bytes_written, portMAX_DELAY) != ESP_OK) { */
|
|
/* ESP_LOGE(A2DP_TAG, "i2s_write has failed"); */
|
|
/* } */
|
|
|
|
/* if (bytes_written < len) { */
|
|
/* ESP_LOGE(A2DP_TAG, "Timeout: not all bytes were written to I2S"); */
|
|
/* } */
|
|
}
|
|
|
|
void a2dp::init() {
|
|
ESP_LOGI(A2DP_TAG, "Initializing A2DP");
|
|
|
|
// Initialize A2DP sink
|
|
if (esp_a2d_register_callback(a2d_callback) != ESP_OK){
|
|
ESP_LOGE(A2DP_TAG,"esp_a2d_register_callback failed");
|
|
}
|
|
if (esp_a2d_sink_register_data_callback(audio_data_callback) != ESP_OK){
|
|
ESP_LOGE(A2DP_TAG,"esp_a2d_sink_register_data_callback failed");
|
|
}
|
|
if (esp_a2d_sink_init() != ESP_OK){
|
|
ESP_LOGE(A2DP_TAG,"esp_a2d_sink_init failed");
|
|
}
|
|
}
|
|
|
|
void a2dp::connect_to_last() {
|
|
if (nvs::has_last_connection()) {
|
|
esp_bd_addr_t last_connection = {0,0,0,0,0,0};
|
|
nvs::get_last_connection(last_connection);
|
|
if (esp_a2d_sink_connect(last_connection) == ESP_FAIL) {
|
|
ESP_LOGE(A2DP_TAG, "Failed connecting to device!");
|
|
}
|
|
} else {
|
|
bluetooth::set_scan_mode(true);
|
|
}
|
|
}
|
|
|