概要
アナログスピーカーのM5Stack(BASIC, GRAY, FIRE)とスピーカーHATを接続したM5StickC、I2SスピーカーのM5Stack Core2とATOM Echoを同じコードでWaveファイルの再生をできるようにしてみました。
M5Liteとは?
M5Stack社の製品をM5StickCライクに、同一ソース、同一バイナリで動作させるためのライブラリです。

ライブラリマネージャーより、「ESP32LitePack」「M5Lite」などで検索すると出てきます。ESP32系のごった煮ライブラリの「ESP32LitePack」に含まれています。
使い方
#include "M5Lite.h" void setup() { M5Lite.begin(); M5Lite.Ex.Sound.play(wav, sizeof(wav)); } void loop() { }
最小限のコードになります。M5Lite.hを読み込み、M5Lite.begin()で初期化をします。M5Stack(BASIC, GRAY, FIRE)とM5Stack Core2はスピーカーが必ず内蔵されているので、デフォルトでスピーカーが有効になっています。スピーカーHATが必要なM5StickCや、ATOM Echoなどは自動判定できないので、明示的にM5.Ex.Sound.begin()を呼ぶ必要があります。
再生はM5Lite.Ex.Sound.play(wav, sizeof(wav))で、Waveファイルのデータ配列とサイズを渡すだけになります。アナログ接続であればADC経由、I2S接続であれば自動的にI2Sを初期化してスピーカーを鳴らします。
今後別タスクに分離して非同期再生に対応する可能性がありますが、現状は再生が終わるまで制御が戻ってこないブロック関数なので注意してください。
再生できるデータの作り方
- サンプリングレート16000、モノラル、8ビット
- サンプリングレート16000、モノラル、16ビット
とりあえず確認しているのは上記の2種類です。サンプリングレートはもっと高レートでも再生可能です。16000だと音声を再生する最低レベルの音質になります。この低レートだと8ビットと16ビットで音質に差がないので、通常は8ビットでいいと思います。
再生するサウンドを準備する
効果音ラボさんのデータを利用させていただきました。音声はどんなものでも構いませんが、埋め込む関係でファイルサイズに上限があります。
編集
編集自体は必須ではありません。素材集などからライセンスを確認して利用するのが楽なのですが、そのままだと前後に無音が多い場合があり、たまに加工が必要になります。ただ加工NGのライセンスもあるのでご注意ください。
効果音ラボさんでもソフト紹介がありますが、私はオープンソースのクロスプラットフォーム対応のオーディオソフトであるAudacityを使わせてもらっています。
変換
Audacityでエクスポートできる人であれば、サンプリングレートとビット数などを指定して出力すればOKです。ちょっと面倒なので、上記サイトを使うと楽ができます。

変換したいファイルを開き、保存形式を「WAV」、詳細設定を開き「サンプリングレート」を利用するもの(16000など)、チャンネル数を「1」にして変換します。複数のファイルを同時に変換することも可能でした。
これで元になるWaveファイルがかんたんに作れます。
Waveファイルから埋め込み形式に変換
ここもいろいろ面倒なので、ブラウザで変換できるツールを私が作りました。
このブログのメニューにある「TOOLS」の中に「音声ファイル変換」がありますので、このページになります。

先程変換したWaveファイルを選択し、データ名を変更し、音を小さくする場合にはボリュームを100より小さい値に変更。ビットレートは8か16を選択します。

送信ボタンを押すと、下に変換した出力データが表示されます。Waveファイルを解析して、不要なヘッダー部分を削除し、8ビットの場合には変換してから配列に出力しています。
元データは160ミリ秒(0.16秒)で変換後は2.5Kですね。サンプリングレートが16000なので、1秒分が8ビットだと16000バイトになります。メインメモリではなく、フラッシュ領域に保存されるのでデフォルトだと1M弱は保存できるはずです。1分ぐらいは保存できるかな?
実際のスケッチ例
ESP32LitePackの中に「M5LiteSound」というスケッチ例があります。ベタッと音声データが先頭にあります。通常は.hファイルなどに切り出して、.inoファイルから読み込んだほうがいいと思います。
注意事項
if (M5.Ex.board == lgfx::board_M5Stack) { // AutoStart } else if (M5.Ex.board == lgfx::board_M5StackCore2) { // AutoStart } else if (M5.Ex.board == lgfx::board_M5StickC) { // Speaker HAT M5.Ex.Sound.begin(); } else if (M5.Ex.board == lgfx::board_M5StickCPlus) { // Speaker HAT M5.Ex.Sound.begin(); } else if (M5.Ex.board == lgfx::board_unknown) { // ATOM Echo Only M5.Ex.Sound.begin(); }
最初の方に書きましたが、スピーカーがついているかわからないM5StickCとATOMは明示的にM5.Ex.Sound.begin()を呼び出す必要があります。M5Stack系はすべてスピーカーが内蔵されているので、初期状態で有効化されています。
データ形式の指定
if (M5Lite.BtnA.wasReleased()) { M5Lite.Ex.Sound.play(wav, sizeof(wav), 16000, 16); Serial.println("End A"); } if (M5Lite.BtnB.wasReleased()) { M5Lite.Ex.Sound.play(wav16, sizeof(wav16), 16000, 16); Serial.println("End B"); } if (M5Lite.BtnC.wasReleased()) { //M5Lite.Ex.Sound.play(wav8, sizeof(wav8), 16000, 8); M5Lite.Ex.Sound.play(wav8, sizeof(wav8)); Serial.println("End C"); }
無指定の場合サンプリングレート16000で8ビットのデータとして扱います。なるべくM5Lite.Ex.Sound.play(wav8, sizeof(wav8), 16000, 8)などのように省略しないほうが明示的で安全だと思います。
内部処理
アナログDACは8ビット、I2Sは16ビットでスピーカーに出力する必要があります。なので受け取ったデータを出力先に応じて変換してから送信する処理が内部に入っています。機種別にピン設定などは違うのですが、内部で機種の自動判定を行い出力の出し分けをしています。
まとめ
アナログとI2Sはデータ形式が違うので、結構両対応が面倒でした。mp3とかにも対応するとよさそうなのですが、別ライブラリが必須になるのでちょっと考え中です。マイクも同じような対応ができそうですが、アナログがM5Stack Fire、I2SがM5StickCとATOM Echo、M5Stack Core2と判定が面倒なFireとEchoがあるのでちょっと工夫が必要になりそうです。。。
コメント