0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ESP32のBluetoothA2DPSinkでI2Sを常時走らせる

Posted at

解決すべき問題

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();
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?