解決すべき問題
ESP32でBluetoothA2DPSinkを使っているのだが、これはBluetooth接続していない時にはI2Sのクロックと無音データを生成してくれない。ESP32をI2Sのマスターとして他の機器をスレーブで使う時にこれは都合が悪いので、常に生成するようにする。
対処方法
A2SPSinkが繋がってない、動いていない時にダミーのデータを書き込み続けるようにする。幸いにも、BluetoothA2DPSinkクラスはほぼ全てのメンバ関数がvirtual宣言されているので、I2Sを止めているところを探し出して全て無効にする。止まっている時にダミーデータ(0で埋め尽くされたバッファ)を書き込むのは取り急ぎメインループで行うちょっと甘い対応をとる。
既知の問題
接続、解除時に一瞬クロックが途切れる。対処方法はほぼわかっているがめんどくさいのでまだ対応していない。
ソース
MyA2DPSink.h
# pragma once
# include "BluetoothA2DPSink.h"
class MyA2DPSink : public BluetoothA2DPSink
{
public:
MyA2DPSink()
{
my_bt_connected = false; // ring2 changed
memset(dummyData, 0, sizeof(dummyData)); // ring2 changed
set_bits_per_sample(32); // ring2 changed
}
// ring2 changed
bool is_my_bt_connected()
{
return my_bt_connected;
}
// ring2 changed
void dummyWrite()
{
uint32_t len = sizeof(dummyData);
size_t i2s_bytes_written;
if (i2s_config.bits_per_sample==I2S_BITS_PER_SAMPLE_16BIT){
// standard logic with 16 bits
if (i2s_write(i2s_port,(void*) dummyData, len, &i2s_bytes_written, portMAX_DELAY)!=ESP_OK){
ESP_LOGE(BT_AV_TAG, "i2s_write has failed");
}
//ESP_LOGI(BT_AV_TAG, "i2s_write: %u bytes with range %d - %d avg: %d", i2s_bytes_written, minV, maxV, avg);
} else {
if (i2s_config.bits_per_sample>16){
// expand e.g to 32 bit for dacs which do not support 16 bits
if (i2s_write_expand(i2s_port,(void*) dummyData, len, I2S_BITS_PER_SAMPLE_16BIT, i2s_config.bits_per_sample, &i2s_bytes_written, portMAX_DELAY) != ESP_OK){
ESP_LOGE(BT_AV_TAG, "i2s_write has failed");
}
} else {
ESP_LOGE(BT_AV_TAG, "invalid bits_per_sample: %d", i2s_config.bits_per_sample);
}
}
if (i2s_bytes_written<len){
ESP_LOGE(BT_AV_TAG, "Timeout: not all bytes were written to I2S");
}
}
void end(bool release_memory=false) override
{
// reconnect should not work after end
BluetoothA2DPCommon::end(release_memory);
// stop I2S
if (is_i2s_output){
// ring2 changed
/*
ESP_LOGI(BT_AV_TAG,"uninstall i2s");
if (i2s_driver_uninstall(i2s_port) != ESP_OK){
ESP_LOGE(BT_AV_TAG,"Failed to uninstall i2s");
}
else {
player_init = false;
}
*/
}
log_free_heap();
}
void handle_audio_state(uint16_t event, void *p_param)
{
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
esp_a2d_cb_param_t* a2d = (esp_a2d_cb_param_t *)(p_param);
ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", to_str(a2d->audio_stat.state));
// callback on state change
audio_state = a2d->audio_stat.state;
if (audio_state_callback!=nullptr && audio_state){
audio_state_callback(a2d->audio_stat.state, audio_state_obj);
}
if (is_i2s_output){
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
m_pkt_cnt = 0;
ESP_LOGI(BT_AV_TAG,"i2s_start");
if (i2s_start(i2s_port)!=ESP_OK){
ESP_LOGE(BT_AV_TAG, "i2s_start");
}
my_bt_connected = true; // ring2 changed
} else if ( ESP_A2D_AUDIO_STATE_REMOTE_SUSPEND == a2d->audio_stat.state || ESP_A2D_AUDIO_STATE_STOPPED == a2d->audio_stat.state ) {
//ESP_LOGW(BT_AV_TAG,"i2s_stop"); // ring2 changed
//i2s_stop(i2s_port); // ring2 changed
//i2s_zero_dma_buffer(i2s_port); // ring2 changed
my_bt_connected = false; // ring2 changed
}
}
}
void handle_connection_state(uint16_t event, void *p_param)
{
ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event);
esp_a2d_cb_param_t* a2d = (esp_a2d_cb_param_t *)(p_param);
// determine remote BDA
memcpy(peer_bd_addr, a2d->conn_stat.remote_bda, ESP_BD_ADDR_LEN);
ESP_LOGI(BT_AV_TAG, "partner address: %s", to_str(peer_bd_addr));
// callback
connection_state = a2d->conn_stat.state;
if (connection_state_callback!=nullptr){
connection_state_callback(connection_state, connection_state_obj);
}
ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%s]", to_str(a2d->conn_stat.state), to_str(a2d->conn_stat.remote_bda));
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
ESP_LOGI(BT_AV_TAG, "ESP_A2D_CONNECTION_STATE_DISCONNECTED");
// reset pin code
pin_code_int = 0;
pin_code_request = Undefined;
// call callback
if (bt_dis_connected!=nullptr){
(*bt_dis_connected)();
}
if (is_i2s_output) {
//ESP_LOGI(BT_AV_TAG, "i2s_stop"); // ring2 changed
//i2s_stop(i2s_port); // ring2 changed
//i2s_zero_dma_buffer(i2s_port); // ring2 changed
my_bt_connected = false;
}
if (is_reconnect(a2d->conn_stat.disc_rsn) && is_auto_reconnect && has_last_connection()) {
if ( has_last_connection() && connection_rety_count < try_reconnect_max_count ){
ESP_LOGI(BT_AV_TAG,"Connection try number: %d", connection_rety_count);
connect_to_last_device();
// when we lost the connection we do allow any others to connect after 2 trials
if (connection_rety_count==2) set_scan_mode_connectable(true);
} else {
if ( has_last_connection() && a2d->conn_stat.disc_rsn == ESP_A2D_DISC_RSN_NORMAL ){
clean_last_connection();
}
set_scan_mode_connectable(true);
}
} else {
set_scan_mode_connectable(true);
}
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){
ESP_LOGI(BT_AV_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTED");
// checks if the address is valid
bool is_valid = true;
if(address_validator!=nullptr){
uint8_t *bda = a2d->conn_stat.remote_bda;
if (!address_validator(bda)){
ESP_LOGI(BT_AV_TAG,"esp_a2d_sink_disconnect: %s", (char*)bda );
esp_a2d_sink_disconnect(bda);
is_valid = false;
}
}
if (bt_connected!=nullptr){
(*bt_connected)();
}
set_scan_mode_connectable(false);
connection_rety_count = 0;
if (is_i2s_output) {
ESP_LOGI(BT_AV_TAG,"i2s_start");
if (i2s_start(i2s_port)!=ESP_OK){
ESP_LOGE(BT_AV_TAG, "i2s_start");
}
my_bt_connected = true; // ring2 changed
}
// record current connection
if (is_auto_reconnect && is_valid) {
set_last_connection(a2d->conn_stat.remote_bda);
}
# ifdef CURRENT_ESP_IDF
// ask for the remote name
esp_err_t esp_err = esp_bt_gap_read_remote_name(a2d->conn_stat.remote_bda);
# endif
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTING){
ESP_LOGI(BT_AV_TAG, "ESP_A2D_CONNECTION_STATE_CONNECTING");
connection_rety_count++;
}
}
// ring2 changed
private:
uint8_t dummyData[512];
bool my_bt_connected;
};
MyBTAudio.ino
# include <driver/i2s.h>
# include "BluetoothA2DPSink32.h"
# include "MyA2DPSink.h"
MyA2DPSink a2dp_sink; // Subclass of BluetoothA2DPSink
void setup()
{
a2dp_sink.start("MyBTAudio");
}
void loop()
{
if (!a2dp_sink.is_my_bt_connected())
{
a2dp_sink.dummyWrite();
}
}