M5StickS3にしっかりオーディオコーデックがついているので、MP3を再生します。
M5StickS3には、ES8311が積んであって、I2Cで制御するそうです。
でも、サンプルコードを見てみたら、M5Unifiedがセットアップしてくれているので、なんと全く意識することなく使えそうです。
ESP32上で動作するJavascript環境でMP3を鳴らしてみます。
以下のESP32での例を示します。I2Sを使う方法とDACを使う方法があり、ESP32のモジュールに搭載している状況に合わせます。
I2S方式
- M5StickS3
- M5Stack Core2
- M5Atom Echo
DAC方式
- M5StickC+Spearker Hat
- M5Stack Fire
ちなみに、DAC方式は結構CPUを使うため、音が途切れやすく音質が悪くなります。
Javascriptソースコード
以下、I2S方式の場合です。ポート番号は自動的に判別してくれるので、指定はなくても大丈夫でした。
import * as audio from "Audio";
import * as input from "Input";
function setup(){
audio.begin();
var ret = audio.playUrl("http://192.168.1.16:20080/g_07.mp3");
if( !ret )
console.log("playUrl error");
input.onButtonWasPressed(input.BUTTON_A, (e) =>{
console.log("pressed");
audio.playUrl("http://192.168.1.16:20080/g_07.mp3");
});
console.log("setup finished");
}
function loop(){
esp32.update();
audio.update();
}
もし、自動で判別されないモジュール種の場合は、以下のようにポート番号等を指定します。
第一引数はサンプリングレート、第二引数は内部で確保する内部バッファサイズです。第四引数にESP32のモジュールに合わせてポート番号を指定しています。
// M5StickS3の場合
audio.begin( 48000, 1536, audio.OUTPUT_EXTERNAL_I2S, { bck: 17, lrck: 15, dout: 14, mck: 18 } );
// M5Stack Core2の場合
audio.begin( 48000, 1536, audio.OUTPUT_EXTERNAL_I2S, { bck: 12, lrck: 0, dout: 2 } );
// M5Atom Echoの場合
audio.begin( 48000, 1536, audio.OUTPUT_EXTERNAL_I2S, { bck: 19, lrck: 33, dout: 22 } );
DACの場合は以下の通りです。ポート番号は、26固定です。ですので、M5StickCもM5Stack Fireも同じです。
import * as audio from "Audio";
import * as input from "Input";
function setup(){
audio.begin( 48000, 1536, audio.OUTPUT_INTERNAL_DAC, { dout: 26 } );
audio.setVolume(100);
var ret = audio.playUrl("http://192.168.1.16:20080/g_07.mp3");
if( !ret )
console.log("playUrl error");
input.onButtonWasPressed(input.BUTTON_A, (e) =>{
console.log("pressed");
audio.playUrl("http://192.168.1.16:20080/g_07.mp3");
});
console.log("setup finished");
}
function loop(){
esp32.update();
audio.update();
}
内部実装
内部的にはJavascsriptではなく通常のC言語で実現していますが、Javascriptを使うのみであれば、意識する必要はありません。
M5Unifiedには、MP3を解釈する機能はありません。そこで、ESP8266Audioの力を借りています。
抜き出すとこんな感じです。
まずは準備部分(audio.begin())です。
auto spk_cfg = M5.Speaker.config();
spk_cfg.sample_rate = sample_rate;
if( output_mode == AUDIO_OUTPUT_INTERNAL_DAC ){
spk_cfg.use_dac = true;
}else if( output_mode == AUDIO_OUTPUT_EXTERNAL_I2S ){
spk_cfg.use_dac = false;
}else if( output_mode == AUDIO_OUTPUT_AUTO ){
}else{
return JS_EXCEPTION;
}
// ポート番号は必須ではなく、必要に応じて
spk_cfg.pin_bck = pin_bck;
spk_cfg.pin_ws = pin_lrck;
spk_cfg.pin_data_out = pin_dout;
spk_cfg.pin_mck = pin_mck;
if( M5.Speaker.isRunning() )
M5.Speaker.end();
M5.Speaker.config(spk_cfg);
M5.Speaker.begin();
out = new AudioOutputM5Speaker(&M5.Speaker);
最後のAudioOutputM5Speakerは、M5.SpeakerとESP8266Audioとの間の音声データの橋渡しをしています。
M5UnifiedのSpeakerサンプルからほぼそのまま持ってきています。
class AudioOutputM5Speaker : public AudioOutput
{
public:
AudioOutputM5Speaker(m5::Speaker_Class* m5sound, uint8_t virtual_sound_channel = 0)
: _m5sound(m5sound), _virtual_ch(virtual_sound_channel){}
bool setBufferSize(uint32_t buf_size){
for (int i = 0; i < 3; i++) {
if( _tri_buffer[i] != NULL ){
free(_tri_buffer[i]);
_tri_buffer[i] = NULL;
}
}
for (int i = 0; i < 3; i++) {
_tri_buffer[i] = (int16_t*)malloc(buf_size * sizeof(int16_t));
if (_tri_buffer[i] == NULL) {
for (int j = 0; j < i; j++) {
free(_tri_buffer[j]);
_tri_buffer[j] = NULL;
}
return false;
}
}
tri_buf_size = buf_size;
return true;
}
virtual ~AudioOutputM5Speaker(void) {
for (int i = 0; i < 3; i++) {
if( _tri_buffer[i] != NULL )
free(_tri_buffer[i]);
}
};
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]; }
virtual bool SetRate(int hz) override
{
// Serial.printf("SetRate called: %d\n", hz);
hertz = hz;
return true;
}
protected:
m5::Speaker_Class* _m5sound;
uint8_t _virtual_ch;
size_t tri_buf_size = 0;
int16_t *_tri_buffer[3] = { NULL, NULL, NULL };
size_t _tri_buffer_index = 0;
size_t _tri_index = 0;
};
次が、MP3再生部分です。インターネット上からMP3ファイルを取得する例です。
file_http = new AudioFileSourceHTTPStream(url);
buff = new AudioFileSourceBuffer(file_http, bufsize);
mp3 = new AudioGeneratorMP3();
bool ret = mp3->begin(buff, out);
SDカードからMP3ファイルを取得する場合は以下になります。
file_sd = new AudioFileSourceSD(path);
mp3 = new AudioGeneratorMP3();
bool ret = mp3->begin(file_sd, out);
あとは、以下を繰り返すだけです。
if( mp3 != NULL ){
if (mp3->isRunning()) {
if( !audio_paused ){
if (!mp3->loop()){
// Serial.println("mp3 loop stop");
mp3->stop();
}
}
}
M5UnifiedのSpeakerのおかげで、これだけで再生できちゃいました。
(参考) ESP32で動作するJavascript実行環境
ESP32で動作するJavascript実行環境を公開しています。
この中で実装しています。
Javascriptのコードを書き換えるのに、いちいち再コンパイル不要なので、らくちんです。
「電子書籍:M5StackとJavascriptではじめるIoTデバイス制御」
サポートサイト
ぜひ、手に取ってみてください。
以上