M5UnifiedのExampleに音声再生があったのでそれで試す。
File->Example->M5Unified->Advanced->Speaker_SD_wav_fileを選択。
自宅用通知マシン作成(2)
と、行こうとしたけど色々初めてなのでメモ残しながら確認していく。描画関係はおいておいて音声出力周りのみ予定。
試しながら書いているので丸っと書き換わるかも
M5Unified
最近のM5Stackは機種間をまたいでソースをある程度共有できるM5Unifiedを使う。M5Unifidには
auto cfg = M5.config();
cfg.external_speaker.atomic_spk = true;
M5.begin(cfg);
auto spk_cfg = M5.Speaker.config();
spk_cfg.sample_rate = 96000;
M5.Speaker.config(spk_cfg);
M5.Speaker.begin();
みたいに各階層(カテゴリ?)毎にそれぞれコンフィグがあって細かい設定を仕込めるみたい。
ダウンロードしたソースのM5Unified\src\utility\M5Unified.hppやM5Unified\src\utilityあたりに中身がいる。ドキュメントは探していないここにあった。ぱっとみ、cppのコメントパッと見た中だと日本語もある。
/// Increasing the sample_rate will improve the sound quality instead of increasing the CPU load.
spk_cfg.sample_rate = 96000;
音質とCPU負荷は引き換えと。手持ちのM5Stack Basicのスピーカーの音質だとどうせ頑張るところじゃないと思うので下げてよいかも。
SDカード
M5Unifiedへの移植ポイントにSD.begin()の追加ってのがあるけど前はなかったのかな?よくわからん。
bool begin(uint8_t ssPin=SS, SPIClass &spi=SPI, uint32_t frequency=4000000, const char * mountpoint=”/sd”, uint8_t max_files=5, bool format_if_empty=false);で
+ while (false == SD.begin(GPIO_NUM_4, SPI, 25000000))
+ {
+ delay(500);
+ }
とりあえず上のイメージでよいらしい。最後のクロックレートはSDカードの一番遅い規格だと思うのでものによってはもっとあげられるのかな。GPIO_NUM_4はESP32のPIN指定からきていると思うけど追っていない。
音声
AudioOutput(本体)、AudioFileSourceHOGE(読み込み元)、AudioGeneratorHUGA(音声化)の3点セットが基本みたい。
AudioFileSourceID3は音声ファイルのプロパティ的な奴を扱える。
AudioOutputM5Speakerはトリプルバッファリングをよろしくやってくれながら(よくわかっていない)、M5Unifidのスピーカーに音声を渡してくれている。ChatGPT API搭載AIスタックチャンのレポジトリでは別ファイルに切り出されていた。(おおよそ一緒ぐらいのふんわりした確認)何か困ったらこっちにも情報あるかも。
AudioOutputM5Speakerが依存しているのはAudioOutput.hとM5Unified.hとみたい。
static AudioFileSourceSD file;
static AudioOutputM5Speaker out(&M5.Speaker, m5spk_virtual_channel);
static AudioGeneratorMP3 mp3;
file.open(fname);
mp3.begin(&file, &out);
で開き
void loop() {
if (mp3.isRunning())
{
if (!mp3.loop()) { mp3.stop(); }
}
else
{
delay(1);
}
で継続
out.stop();
mp3.stop();
file.close();
で終了。
おおよそ最小ぐらいの動作ソース
イタリックになっているところは別ファイルに切り出してすっきりさせたらよさそう。
#include <SD.h>
#include <AudioOutput.h>
#include <AudioFileSourceSD.h>
#include <AudioGeneratorMP3.h>
#include <M5Unified.h>
class AudioOutputM5Speaker : public AudioOutput {
public:
AudioOutputM5Speaker(m5::Speaker_Class* m5sound, uint8_t virtual_sound_channel = 0) {
_m5sound = m5sound;
_virtual_ch = virtual_sound_channel;
}
virtual ~AudioOutputM5Speaker(void){};
virtual bool begin(void) override {
return true;
}
virtual bool ConsumeSample(int16_t sample[2]) override {
if (_tri_buffer_index < tri_buf_size) {
_tri_buffer[_tri_index][_tri_buffer_index] = sample[0];
_tri_buffer[_tri_index][_tri_buffer_index + 1] = sample[1];
_tri_buffer_index += 2;
return true;
}
flush();
return false;
}
virtual void flush(void) override {
if (_tri_buffer_index) {
_m5sound->playRaw(_tri_buffer[_tri_index], _tri_buffer_index, hertz, true, 1, _virtual_ch);
_tri_index = _tri_index < 2 ? _tri_index + 1 : 0;
_tri_buffer_index = 0;
}
}
virtual bool stop(void) override {
flush();
_m5sound->stop(_virtual_ch);
return true;
}
const int16_t* getBuffer(void) const {
return _tri_buffer[(_tri_index + 2) % 3];
}
protected:
m5::Speaker_Class* _m5sound;
uint8_t _virtual_ch;
static constexpr size_t tri_buf_size = 1536;
int16_t _tri_buffer[3][tri_buf_size];
size_t _tri_buffer_index = 0;
size_t _tri_index = 0;
};
AudioFileSourceSD file; // サンプルはstaticだったけど多分使い方でどちらも選べる
AudioOutputM5Speaker out(&M5.Speaker); // サンプルのAudioOutputM5Speakerだとvirtual_sound_channelのデフォルト0
AudioGeneratorMP3 mp3;
void setup() {
M5.begin(); // コンフィグ設定しなくてもデフォルトのスピーカーは動く
//M5.Speaker.begin(); // ボード次第かもしれないがデフォルト設定でbeginはされていそう
while (false == SD.begin(GPIO_NUM_4, SPI, 25000000)) {
delay(500);
}
file.open("/mp3/file02.mp3");
mp3.begin(&file, &out);
}
void loop() {
// ここ重要
if (mp3.isRunning()) {
if (!mp3.loop()) { mp3.stop(); }
} else {
delay(1);
}
// 音声終わるの待たない場合は割り込み必要?
M5.update();
if (M5.BtnA.wasClicked()) {
M5.Speaker.tone(1000, 100);
out.stop();
mp3.stop();
file.close();
}
}
こいつをベースにサンプル等をみたら何とかなりそう。
isRunningの周辺ここに大体まとまっていた。
ボリューム
ボリューム設定は以下。特定のバーチャルチャンネルに個別設定もできる(バーチャルチャンネルはわかっていない。文字から察するになにか仮想的な音声チャンネルを使う機能があるのだろう)
M5.Speaker.setVolume(volume);
M5.Speaker.setChannelVolume(m5spk_virtual_channel, volume);
コア指定?
ChatGPT API搭載AIスタックチャンのレポジトリでスピーカー再生タスクを割り当てるCPUコアを以下のように割り当てている。
spk_cfg.task_pinned_core = APP_CPU_NUM;
こちらを見ると無線関係を使っているときにはCore0(PRO_CPU_NUM)を無線関連でかなり使うので、比較的空いているCore1(APP_CPU_NUM)を指定しているのかな?
AudioGeneratorMP3#close
cppの内部でoutputとfile閉じて、running(起動確認フラグ)をfalseにしている。こちら一つでまとめてクローズする思想な気がする。
running = false;
output->stop();
return file->close();
この実装を信じるとisRunningとcloseで処理できる。