LoginSignup
1
0

M5Stack での Raw Streaming PCM 再生方法(Core2 & CoreS3対応)

Last updated at Posted at 2023-07-22

はじめに

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 バイトなので、bufbufSize は 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_0I2S_NUM_1 になっている点を除き)Core2と同じです。

size_t wrote;
i2s_write(I2S_NUM_1, buf, bufSize, &wrote, portMAX_DELAY);
vTaskDelay(2);

実装例

本書解説処理の実装クラス

前項クラス(Audio class)の利用例

以下の audio.beginaudio.write の呼び出し箇所を参照

MSX1 のエミュレータが出力した波形データを書き込み続ける形になってます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0