はじめに
今まで使ってきた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]
お試し環境
つぎの環境でこのライブラリの動作確認をおこないました。
- ソフト
- VSCode
- PlatformIO(VSCode拡張機能)
- arduino-esp32ライブラリ(#1.0.6)
- ハード
KP-ESP32Cと中華DAC(I2Sインターフェイス付き)
動作の様子
iPhoneからApple Musicでダウンロードした曲をAirPlayで再生しています。
ESP32基板と中華DACをタカチのケースに収めました。
電子回路的な配線の説明はここでは省略します。詳細はESP-IDFのGitHubリポジトリなどを参照してください。
サンプルコード
こちらに私がモディファイしたサンプルがあります。よろしければご参照ください。
#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;
};
#include <Application.h>
static Application app;
void setup() {
app.setup();
}
void loop() {
app.handle();
}
[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
- ESP-IDF A2DP-SINK demo
- ES9038Q2M datasheet
- ESP32でBluetoothスピーカーを作ってみた (I2S編)
- ESP32でI2S+DACを使う
- [ESP32でサウンド出力時のクリックノイズ対策(I2S+内蔵DAC)]
(http://blog-yama.a-quest.com/?eid=970190)
(了)
-
sink:吸収源≒受信 source:供給源≒送信 ↩