ESP32-audioI2S-masterを使う

ESP32のAudioライブラリにESP32-audioI2S-masterが有ります。今回はこれの説明。このライブラリを使用するとWebラジオが非常に簡単に出来ます。

まずはハード

CPUとそれが再生出来るCodecの表をWebで見つけました。この表よりESP32−S3であれば表の全てのCodecに対応しているのでCPUはESP32-S3を選択。

その他下記のモジュールが必要です。

これらのモジュールを以下の様に配線しています。

音はPCM5102のジャックにイヤフォンをつないで聞いています。(アンプとスピーカ無し)

サンプルスケッチ

サンプルスケッチは、ファイルー>スケッチ例ー>ESP32-audioI2S-masterー>I2Saudio を使います。

このスケッチのピン配置を今回の回路に合わせて書き直し

I2Saudio.ino

//**********************************************************************************************************
//*    audioI2S-- I2S audiodecoder for ESP32,                                                              *
//**********************************************************************************************************
//
// first release on 11/2018
// Version 3  , Jul.02/2020
//
//
// THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT.
// FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR
// OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
//

#include "Arduino.h"
#include "WiFiMulti.h"
#include "Audio.h"
#include "SPI.h"
#include "SD.h"
#include "FS.h"

SPIClass sd_spi(HSPI);
// Digital I/O used
/*
#define SD_CS          5
#define SPI_MOSI      23
#define SPI_MISO      19
#define SPI_SCK       18
#define I2S_DOUT      25
#define I2S_BCLK      27
#define I2S_LRC       26
*/
#define SD_CS          7
#define SPI_MOSI      15
#define SPI_MISO      17
#define SPI_SCK       16
#define I2S_DOUT       5
#define I2S_BCLK       6
#define I2S_LRC        4

Audio audio;
WiFiMulti wifiMulti;
String ssid =     "xxxxx";
String password = "xxxxx";


void setup() {
    pinMode(SD_CS, OUTPUT);
    digitalWrite(SD_CS, HIGH);

//    SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
    sd_spi.begin(SPI_SCK, SPI_MISO, SPI_MOSI);

    SPI.setFrequency(1000000);
    Serial.begin(115200);

//  SD.begin(SD_CS);
    SD.begin(SD_CS, sd_spi);

    WiFi.mode(WIFI_STA);
    wifiMulti.addAP(ssid.c_str(), password.c_str());
    wifiMulti.run();
    if(WiFi.status() != WL_CONNECTED){
        WiFi.disconnect(true);
        wifiMulti.run();
    }
    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
    audio.setVolume(12); // 0...21

//    audio.connecttoFS(SD, "test.wav");
//    audio.connecttohost("http://www.wdr.de/wdrlive/media/einslive.m3u");
//    audio.connecttohost("http://somafm.com/wma128/missioncontrol.asx"); //  asx
//    audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.aac"); //  128k aac
      audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.mp3"); //  128k mp3
}

void loop()
{
    audio.loop();
    if(Serial.available()){ // put streamURL in serial monitor
        audio.stopSong();
        String r=Serial.readString(); r.trim();
        if(r.length()>5) audio.connecttohost(r.c_str());
        log_i("free heap=%i", ESP.getFreeHeap());
    }
}

// optional
void audio_info(const char *info){
    Serial.print("info        "); Serial.println(info);
}
void audio_id3data(const char *info){  //id3 metadata
    Serial.print("id3data     ");Serial.println(info);
}
void audio_eof_mp3(const char *info){  //end of file
    Serial.print("eof_mp3     ");Serial.println(info);
}
void audio_showstation(const char *info){
    Serial.print("station     ");Serial.println(info);
}
void audio_showstreamtitle(const char *info){
    Serial.print("streamtitle ");Serial.println(info);
}
void audio_bitrate(const char *info){
    Serial.print("bitrate     ");Serial.println(info);
}
void audio_commercial(const char *info){  //duration in sec
    Serial.print("commercial  ");Serial.println(info);
}
void audio_icyurl(const char *info){  //homepage
    Serial.print("icyurl      ");Serial.println(info);
}
void audio_lasthost(const char *info){  //stream URL played
    Serial.print("lasthost    ");Serial.println(info);
}
  • 23行: SPIClass sd_spi(HSPI);
    • 今回使用したESP32-S3はPSRAMインターフェイスとしてVSPIを使用していました。
    • そこで、uSDカード用にHSPIを使用しています。
  • 34から40行: ここでピンを合わせています。
  • 53行: HSPIの初期化
  • 59行: SDの初期化

Arduino IDEのツールでパラメータを下記の様に設定(Arduino 1.8.19)

  • ボード: ESP32S3 Dev Module
  • Partition Scheme:  ”Huge APP(3MB No OTA/1MB SPIFFS)”
  • PSRAM: ”OPI PSRAM”

    これでコンパイル実行出来ました。実行するとシリアルモニタに下記の様に表示されました。

    monitor
    
    ...Connecting to WiFi
    Connected
    info        PSRAM found, inputBufferSize: 638965 bytes
    info        buffers freed, free Heap: 237500 bytes
    info        connect to: "www.wdr.de" on port 80 path "/wdrlive/media/einslive.m3u"
    info        Connection has been established in 303 ms, free Heap: 236868 bytes
    info        buffers freed, free Heap: 237324 bytes
    info        connect to: "wdr-1live-live.icecast.wdr.de" on port 80 path "/wdr/1live/live/mp3/128/stream.mp3?ar-distributor=ffa1"
    info        Connection has been established in 299 ms, free Heap: 237076 bytes
    info        redirect to new host "http://d141.rndfnk.com/ard/wdr/1live/live/mp3/128/stream.mp3?aggregator=app&cid=01FBRZTS1K1TCD4KA2YZ1ND8X3&sid=2xi7X6nlERLtPQFuAOK1Y45j7z9&token=Il5Y_JNOQky-zVbAY4vVc0RqnV5XwkOmz_PC9EGl5Ps&tvf=KVvNtm-sQxhkMTQxLnJuZGZuay5jb20"
    info        buffers freed, free Heap: 237324 bytes
    info        connect to: "d141.rndfnk.com" on port 80 path "/ard/wdr/1live/live/mp3/128/stream.mp3?aggregator=app&cid=01FBRZTS1K1TCD4KA2YZ1ND8X3&sid=2xi7X6nlERLtPQFuAOK1Y45j7z9&token=Il5Y_JNOQky-zVbAY4vVc0RqnV5XwkOmz_PC9EGl5Ps&tvf=KVvNtm-sQxhkMTQxLnJuZGZuay5jb20"
    info        Connection has been established in 279 ms, free Heap: 237076 bytes
    bitrate     128000
    station     1Live, Westdeutscher Rundfunk Koeln
    icyurl      https://www1.wdr.de/radio/1live/
    info        MP3Decoder has been initialized, free Heap: 235624 bytes , free stack 5320 DWORDs
    lasthost    http://d141.rndfnk.com/ard/wdr/1live/live/mp3/128/stream.mp3?aggregator=app&cid=01FBRZTS1K1TCD4KA2YZ1ND8X3&sid=2xi7X6nlERLtPQFuAOK1Y45j7z9&token=Il5Y_JNOQky-zVbAY4vVc0RqnV5XwkOmz_PC9EGl5Ps&tvf=KVvNtm-sQxhkMTQxLnJuZGZuay5jb20
    info        stream ready
    info        syncword found at pos 0
    info        MPEG-2.5, Layer I
    info        Channels: 2
    info        SampleRate: 48000
    info        BitsPerSample: 16
    info        BitRate: 128000
    info        StreamTitle='1LIVE'
    streamtitle 1LIVE
    
    

    これらは、loop() 以下に有る関数の出力の様です。これらの関数はスケッチで意図的に呼び出していません。CALL_BACK関数なのでしょうか。ちなみに89行以下をコメントアウトすると”info xxxxxxx”は表示されませんでした。

    スケッチ内の以下のコメントを外して試してみました。全て繋がりました。これだけで立派なWebRadioです。

    //    audio.connecttoFS(SD, "test.wav");
    //    audio.connecttohost("http://www.wdr.de/wdrlive/media/einslive.m3u");
    //    audio.connecttohost("http://somafm.com/wma128/missioncontrol.asx"); //  asx
    //    audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.aac"); //  128k aac
        audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.mp3"); //  128k mp3
    

    audio.connecttohost();とaudio.loop();が組になっていて、audio.connecttohost();で対象に接続。audio.loop();で処理(Decodeとバッファー管理)を行う様です。audio.connecttohost();で相手に接続しただけでは音声は出ませんでした。

    関数の紹介

    スケッチで使用した関数は以下の通り。

    • audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
      • I2Sのピンを設定します。
    • audio.setVolume(12);
      • ボリュウムの設定です。値は0から21まで(21が最大)指定出来ます。
    • audio.connecttoFS(SD, “test.wav”);
      • ファイルシステムとファイル名を指定して再生する。
      • この例ではファイルシステムは、”SD”。ファイル名は、””test.wav”
    • audio.connecttohost(“http://www.wdr.de/wdrlive/media/einslive.m3u”);
      • サーバURLを指定してそのサーバに接続します。
      • m3u, asx, aac, mp3形式に対応しています。
    • audio.loop();
      • ここで実際の処理が行われています。
      • 一度この関数を呼び出すとある程度のデータをバッファーに読み込み、そのデータを処理する。
      • バッファーにデータが有る間はDecodeされるのでこの関数を呼び出す必要は有りません。
      • よってバッファーのデータが無くならない間隔でこの関数を繰り返し呼び出せば再生が続く事になります。
      • Web Radioを製作する場合、下記の様になります。
        1. audio.loop()を呼び出す。
        2. Web Radioで必要な処理を行う。
        3. audio.loop()を呼び出す。 
    • audio_info(const char *info); 現在のステータス
    • audio_id3data(const char *info); アーティスト、アルバム、バンドに関する情報
    • audio_eof_mp3(const char *info); ファイル名
    • audio_showstation(const char *info); 放送局名
    • audio_showstreamtitle(const char *info); アーティスト、音楽トラックなどの情報
    • audio_bitrate(const char *info); ビットレート
    • audio_commercial(const char *info); 予想されるコマーシャルの長さ
    • audio_icyurl(const char *info); 放送局にホームページ
    • audio_lasthost(const char *info); 別のURLにリダイレクトされる場合、そのURL

    その他にもこんな関数が有ります。

    • setVolumeSteps(uint8_t steps); 
      • ボリューム値の範囲を設定する。
      • setVolumeSteps(100); → 0から100で指定出来る。
    • uint8_t getVolume()
      • 現在のボリュームの値を取得
    • setBalance(int8_t bal)
      • 左または右のスピーカの減衰
      • 指定範囲は、−16から16。
      • −16で左のスピーカがMute。 16で右のスピーカがMute
    • bool pauseResume(); PAUSEとRESUME。
    • uint32_t Audio::stopSong(); 再生中止
    • uint32_t getAudioCurrentTime(); 開始からの再生時間を秒単位で返します。

    これらの関数を呼び出すだけで簡単にWebRadioが出来そうです。

    <<Before

    Next>>