LoginSignup
4

More than 1 year has passed since last update.

【ESP Arduinoライブラリのご紹介】ESP32-A2DP

Last updated at Posted at 2021-10-02

はじめに

今まで使ってきたESPマイコン用Arduinoライブラリについてご紹介したいと思います。
これらの記事がどなたかの何らかに貢献できれば幸いです。

ESP32-A2DP Library

今回はESP32-A2DPライブラリをご紹介します。

Thanks to @pschatzmann, author of the ESP32-A2DP library.

概要

スマートフォンからESP32へ音声データをBluetooth(A2DP)でストリーミングして、ESP32から出力したI2S信号をDAC(Digital Analog Converter)へ入力できます。つまり、このライブラリを使ってBluetoothスピーカーを構成できます。
A2DP(Advanced Audio Distribution Profile)とは無線通信を利用して音声データをストリーミングするBluetoothのプロファイルです。iOSのAirPlayはこのプロファイルに準拠しています。

はじめ、ESP-IDF開発環境のGitHubリポジトリにC言語で書かれたa2dp_sink1ドライバが公開されていました。その後、@pschatzmannさんがESP-IDF A2DP-SINK demoを使いやすいArduinoクラスライブラリに変換?してくれました。Thanks👍

(かんたんな構成図)
[iPhone] ---(Bluetooth [Radio Frequency 2.4GHz])--->[ESP32]---(I2S)--->[DAC]---(RCA Cable)--->[AMP]---(Cu Cable)--->[Speaker]

お試し環境

つぎの環境でこのライブラリの動作確認をおこないました。

KP-ESP32Cと中華DAC(I2Sインターフェイス付き)
ESP-WROOM-32 変換基板 KP-ESP32C中華DAC

動作の様子

iPhoneからApple Musicでダウンロードした曲をAirPlayで再生しています。
ESP32基板と中華DACをタカチのケースに収めました。

電子回路的な配線の説明はここでは省略します。詳細はESP-IDFのGitHubリポジトリなどを参照してください。

サンプルコード

こちらに私がモディファイしたサンプルがあります。よろしければご参照ください。

Application.h

#include <Arduino.h>
#include <BluetoothA2DPSink.h>
#include <esp32-hal-log.h>

class Application {
   public:
    Application() {}

    ~Application() {}

    static void avrc_metadata_callback(uint8_t id, const uint8_t* text) {
        log_i("==> AVRC metadata rsp");
        String str((char*)text);

        switch (id) {
            case 0x01:
                log_i("==> 曲名:%s", str.c_str());
                break;
            case 0x02:
                log_i("==> アーティスト:%s", str.c_str());
                break;
            case 0x04:
                log_i("==> アルバム名:%s", str.c_str());
                break;
            case 0x20:
                log_i("==> ジャンル:%s", str.c_str());
                break;
            default:
                log_i("==> Unknown:%s", str.c_str());
                break;
        }
    }

    static void on_data_receive_callback(void) {
        static int count;
        if (++count % 100 == 0) {
            digitalWrite(_BLUE_LED_PORT, HIGH);
        } else {
            digitalWrite(_BLUE_LED_PORT, LOW);
        }
    }
#ifdef TEST
    BluetoothA2DPSink* getA2DPSink(void) {
        return &_a2dp_sink;
    }
#endif
    void setup(void) {
        pinMode(_BLUE_LED_PORT, OUTPUT);
        pinMode(_GREEN_LED_PORT, OUTPUT);

        i2s_pin_config_t pin_config = {
            .bck_io_num   = 26,
            .ws_io_num    = 22,
            .data_out_num = 25,
            .data_in_num  = I2S_PIN_NO_CHANGE  //Use in i2s_pin_config_t for pins which should not be changed
        };

        //Settings for ES9038Q2M VR1.07 DAC Board(eBay item number:263908779821)
        i2s_config_t i2s_config = {
            .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
            .sample_rate          = 44100,                       // corrected by info from bluetooth
            .bits_per_sample      = (i2s_bits_per_sample_t)16,   // set_bits_per_sample()
            .channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT,  // 2-channels
            .communication_format = I2S_COMM_FORMAT_I2S,         // I2S communication format I2S
            .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,        // default interrupt priority
            .dma_buf_count        = 8,                           // default
            .dma_buf_len          = 64,                          // default
            .use_apll             = false,                       // I2S using APLL as main I2S clock, enable it to get accurate clock
            .tx_desc_auto_clear   = true                         // I2S auto clear tx descriptor if there is underflow condition
        };

        _a2dp_sink.set_pin_config(pin_config);
        _a2dp_sink.set_i2s_config(i2s_config);
        _a2dp_sink.set_bits_per_sample(I2S_BITS_PER_SAMPLE_32BIT);  //for I2S : PCM 44.1K-384K 32BIT
        _a2dp_sink.set_on_data_received(on_data_receive_callback);
        _a2dp_sink.set_avrc_metadata_callback(avrc_metadata_callback);

        _a2dp_sink.start("Riraosan Player", true);
    }

    void handle(void) {
        if (_a2dp_sink.isConnected()) {
            digitalWrite(_GREEN_LED_PORT, HIGH);
        } else {
            digitalWrite(_GREEN_LED_PORT, LOW);
        }

        switch (_a2dp_sink.get_audio_state()) {
            case ESP_A2D_AUDIO_STATE_STARTED:
                break;
            case ESP_A2D_AUDIO_STATE_STOPPED:
            case ESP_A2D_AUDIO_STATE_REMOTE_SUSPEND:
                digitalWrite(_BLUE_LED_PORT, LOW);
                break;
            default:
                break;
        }
    }

   private:
    BluetoothA2DPSink _a2dp_sink;
    constexpr static int _BLUE_LED_PORT  = 33;
    constexpr static int _GREEN_LED_PORT = 32;
};

main.cpp
#include <Application.h>

static Application app;

void setup() {
    app.setup();
}

void loop() {
    app.handle();
}
platformio.ini
[platformio]
default_envs    = esp32_a2dp_debug

[env:esp32_a2dp_release]
build_type      = release
extends         = esp32, arduino-esp32, serial, Windows

build_flags =
        -D CORE_DEBUG_LEVEL=0

[env:esp32_a2dp_debug]
build_type      = debug
extends         = esp32, arduino-esp32, serial, Windows

monitor_filters = esp32_exception_decoder
build_flags =
        -D TEST
        -D CORE_DEBUG_LEVEL=4
        -D CONFIG_ARDUHAL_LOG_COLORS

test_port               = COM7
test_speed              = 115200
test_build_project_src  = no

[esp32]
board = esp32dev

;OTA or Serial
[ota]
;upload_protocol = espota
;upload_port    = url
;upload_port    = 192.168.xx.xx
;monitor_port   = /dev/tty.usbserial-1952FF03F3
;monitor_speed  = 115200
;targets        = monitor

[serial]
upload_protocol = esptool
upload_speed    = 115200
monitor_speed   = 115200
targets         = upload, monitor

;Serial Port(Mac or Linux or Windows)
[Mac]
upload_port     = /dev/tty.usbserial-1952FF03F3
monitor_port    = /dev/tty.usbserial-1952FF03F3

[Linux]
upload_port     = /dev/ttyUSB0
monitor_port    = /dev/ttyUSB0

[Windows]
upload_port     = COM7
monitor_port    = COM7

[arduino-esp32]
platform        = espressif32
framework       = arduino
platform_packages = framework-arduinoespressif32@https://github.com/espressif/arduino-esp32.git#1.0.6
build_flags =
        -std = gnu++14
        -D ARDUINO_ARCH_ESP32
        -D ESP32

build_unflags =
        ;-std = gnu++11

board_build.mcu         = esp32
board_build.f_cpu       = 240000000L
board_build.f_flash     = 80000000L
board_build.flash_mode  = qio
board_build.partitions  = default.csv

lib_deps =
        ;https://github.com/riraosan/ESP32-A2DP.git ;modified for ES9038Q2M VR1.07 DAC Board
        https://github.com/pschatzmann/ESP32-A2DP.git

気をつけること

  • ESP8266はペリフェラル(周辺機器)にBluetoothが内蔵されていないので、このライブラリは使えません。
  • このサンプルはarduino-esp32ライブラリ1.0.6で動作確認しています。最新のarduino-esp32ライブラリを使用するとI2SライブラリのI2S定数定義が変更されているので、コンパイルエラーとなります。
  • I2Sポートはi2s_pin_config_tで設定しています。
  • I2Sドライバはi2s_config_tで設定しています。お持ちのDACに応じて設定を変更してください。
  • コーデックはSBCのみです。AACと比べて音質の差は感じられませんでした。
  • ジョン・ウィリアムズ指揮のオーケストラ映像をiPhoneで視聴した際に、指揮振り(映像)と音楽のタイミング差を感じました。(厳密に測定してませんが、体感で約0.5秒未満ぐらい遅延を感じます)リップシンクができればよいのですが。どうすればリップシンクできるのか、どなたか教えて下さい。🙏
  • 一曲送る、一曲戻る、早送り、巻き戻し、一時停止、停止、再生、一曲の再生が終了して次の曲の再生が開始する時などに、「プツッ」というクリックノイズが聞こえることがあるかもしれません。その際は、ESP32-A2DPライブラリのDMAバッファを適切なタイミングでクリアしてください。

さいごに

私はESP-IDF版のa2dp_sinkサンプル(C言語版)をいろいろ調べながら、動作するところまでこぎつけました。
その時に、「このC言語ドライバをクラス化したらいいのでは?」とは思っていましたが、その思いつきを完全に忘れてしまいました。
その後2年ぐらい経って、何気なくGitHubを調べてみたら、pschatzmannさんがArduino環境向けにクラスを作成してくれていて驚きました。私は彼と同じことを考えていたけれども、私には実装できなかったと思います。
@pschatzmannさん、素敵なライブラリを作ってくれてありがとう。あなたの貢献に感謝します。👍

See Also

(了)


  1. sink:吸収源≒受信 source:供給源≒送信 

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
What you can do with signing up
4