はじめに
M5Stack で PCM 生データ(Raw PCM)のストリーミング再生をする方法を調べてみました。
ターゲットデバイス(Core2 / CoreS3)
ターゲットデバイスは当初、普及している Core2 にしようと思ったのですが、調査の過程で手持ちのCore2が壊れてしまったので、買い直すことにしたのですが、せっかくだから新しく発売された CoreS3 の方を書い直しました。
Core2 と CoreS3 では、オーディオを再生する仕組みが全く異なります。
Core2のざっくりとしたオーディオ再生の仕組み
I2S で ポート0 (I2S_NUM_0) に PCM データを DMA 転送することで DAC 変換が走り、本体スピーカーで音声が鳴ります。(とてもシンプル)
CoreS3のざっくりとしたオーディオ再生の仕組み
AW88298 というオーディオアンプのチップが新たに搭載され、従来の DAC の仕組みは廃止されたようです。
一番簡単な方法(M5Unified)
M5Unified の Speaker を用いる方式が最も簡単だと思われます。
以下のように初期化し、
auto config = M5.Speaker.config();
config.sample_rate = 44100;
M5.Speaker.config(config);
M5.Speaker.setVolume(255);
M5.Speaker.begin();
以下のように Raw PCM を書き込むことで Core2 / CoreS3 のどちらでもオーディオ再生ができるようです。
while (M5.Speaker.isPlaying()) { vTaskDelay(1); }
M5.Speaker.playRaw(buf, bufSize, 44100, false);
ただし、M5Unified は内部的に PCM を再生するタスクへバッファを渡す形で処理しており、その処理方式だとオーディオストリーミングの用途(短いPCMデータを連続して永久に書き込み続ける用途)には向いていません。
恐らく、固定のPCMバッファを再生する用途を想定した API だと考えられます。
Core2の処理方式(44100Hz, 16bits, monoral)
Core2 の場合、I2Sのポート番号0(I2S_NUM_0
)にデータを書き込むだけで DAC 変換されるので、単純に以下のように I2S を初期化して、
i2s_config_t audioConfig = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = I2S_COMM_FORMAT_STAND_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true};
i2s_driver_install(I2S_NUM_0, &audioConfig, 0, nullptr);
i2s_set_clk(I2S_NUM_0, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
i2s_zero_dma_buffer(I2S_NUM_0);
以下のように書き込み続けることでストリーミング再生ができました。
size_t wrote;
i2s_write(I2S_NUM_0, buf, bufSize, &wrote, portMAX_DELAY);
vTaskDelay(2);
I2S の DMA バッファサイズの上限は 1,024 バイトなので、buf
と bufSize
は 1,024 バイトづつで渡し続けるのがベストです。
なお、上記では 16bits (I2S_BITS_PER_SAMPLE_16BIT) の PCM を書き込むようにしていますが、Core2のDACは内部的に 8bits しか処理できないらしいので、可能であれば波形データのバッファリングを 8bits にした方が良いと考えられます。
ただし、CoreS3の AW88298 は 8bits PCM をサポートしていない(16bits 以降のみサポートしている)ので、Core2 と CoreS3 の両対応をしたい場合、16bitsでバッファリングにせざるを得ないかもしれません。
CoreS3の処理方式(44100Hz, 16bits, monoral)
AW88298 も I2S インタフェースのオーディオチップですが、個別に AW88298 のレジスタ設定 → IS2 config設定を行う必要があり、ついでに AW88298 の GPIO へのピンアサインも必要です。
具体的には、次のように初期化します。
// setup AW88298 regisgter
M5.In_I2C.bitOn(0x36, 0x02, 0b00000100, 400000);
this->writeRegister(0x61, 0x0673);
this->writeRegister(0x04, 0x4040);
this->writeRegister(0x05, 0x0008);
this->writeRegister(0x06, 0b0001110000000111); // I2SCTL: 44.1kHz, 16bits, monoral (他は全部デフォルト値)
this->writeRegister(0x0C, 0x0064);
// I2S config
i2s_config_t config;
memset(&config, 0, sizeof(i2s_config_t));
config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
config.sample_rate = 48000; // dummy setting
config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
config.dma_buf_count = 4;
config.dma_buf_len = 1024;
config.tx_desc_auto_clear = true;
// I2S pin config
i2s_pin_config_t pinConfig;
memset(&pinConfig, ~0u, sizeof(i2s_pin_config_t));
pinConfig.bck_io_num = GPIO_NUM_34;
pinConfig.ws_io_num = GPIO_NUM_33;
pinConfig.data_out_num = GPIO_NUM_13;
// Setup I2S
i2s_driver_install(I2S_NUM_1, &config, 0, nullptr);
i2s_set_pin(I2S_NUM_1, &pinConfig);
i2s_zero_dma_buffer(I2S_NUM_1);
i2s_start(I2S_NUM_1);
ストリーミングデータの書き込み処理は、(I2Sポート番号が I2S_NUM_0
→ I2S_NUM_1
になっている点を除き)Core2と同じです。
size_t wrote;
i2s_write(I2S_NUM_1, buf, bufSize, &wrote, portMAX_DELAY);
vTaskDelay(2);
実装例
本書解説処理の実装クラス
前項クラス(Audio class)の利用例
以下の audio.begin
と audio.write
の呼び出し箇所を参照
MSX1 のエミュレータが出力した波形データを書き込み続ける形になってます。