WebRadio w/I2S -06 . ラジオ局の選局

今回はラジオ局の選局機能の実装です。内容は、

  • プルダウンリストを追加してラジオ局を選択。
    • サーバにラジオ局名、リンク先等を記録した専用ファイルを作成
    • ファイル処理用に、SPIFFSを導入。
  • MP3とAACの2フォーマットに対応。
    • 記録データを元に、MP3、AACを切り替える。
    • MP3,AACどちらも使用出来るAudioGeneratorを使用

例によって、前回からの修正でスケッチを書いていますが、今回は修正部分が多いです。


#include <Arduino.h>
#include <WiFi.h>
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"

#include <WebServer.h>
#include <ESPmDNS.h>
#include "SPIFFS.h"

// Enter your WiFi setup here:
const char *SSID = "XXXXXX";
const char *PASSWORD = "YYYYYY";

AudioGenerator *decoder = NULL;
AudioFileSourceICYStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2S *out;

// Controll flg
static int8_t flg[10];
#define play    0
#define volume  1
#define total   2
#define now     3

WebServer server(80);

String index_Dt =
                  "<!DOCTYPE html>\n<html>\n<head>\n"
                  "<title>ESP32 WebRadio</title>\n</head>\n"
                  "<body>\n<center>\n<h2>ESP32 WebRadio</h2>\n"
                  
                  "<form id='cmd_vol' method='get'>\n"
                  "<input type='range' min='0' max='100' step='1' name='3' onchange='showValue()' id='s_value' value='50'>\n"
                  "<span id='p_value'>50</span><br><br>\n"
                  "</form>\n"

                  "<form id='pull_list' method='get'>\n"
                  "<select name='5' id='pull_item' onchange='getItem()'>\n";
                  
String index_Dt1 =
                  "</select><br><br>\n"
                  "</form>\n"

                  "<form method='get'>\n"
                  "<button type='submit' name='1' id='b_play' style='background:#90ee90'>PLAY</button>\n"
                  "<button type='submit' name='2' style='background:#90ee90'>STOP</button>\n"
                  "<button type='submit' name='4' id='b_mute' style='background:#90ee90'>MUTE</button>\n"
                  "</form>\n"
                  
                  "<script>\n"
                  "function showValue() {\n"
                  "target = document.getElementById('cmd_vol');\n"
                  "target.submit();\n}\n"
                  
                  "function getItem() {\n"
                  "target = document.getElementById('pull_list');\n"
                  "target.submit();\n}\n";
                  
void setup()
{
  Serial.begin(115200);
  delay(1000);
  Serial.println("Connecting to WiFi");

  WiFi.disconnect();
  WiFi.softAPdisconnect(true);
  WiFi.mode(WIFI_STA);
  
  WiFi.begin(SSID, PASSWORD);

  // Try forever
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("...Connecting to WiFi");
    delay(1000);
  }
  Serial.println("Connected");
  Serial.println(SSID);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp32")) {
    Serial.println("MDNS responder started");
  }
  
  server.on("/", handleRoot);
  
  server.begin();
  Serial.println("HTTP server started");

  out = new AudioOutputI2S();

  //Initialize File System
  SPIFFS.begin();
  Serial.println("File System Initialized");    //Initialize File System

  flg[play]=0;
  flg[volume]=30;
  flg[total]=6;
  flg[now]=1;
  out->SetGain((float)flg[volume]/100.0);
}

void loop()
{
  if(flg[play])
  {
    if (decoder->isRunning()) {
      if (!decoder->loop()) decoder->stop();
    } 
    else {
        Serial.print("decoder done\n");
        StopPlaying();    
        delay(500);
        radio_play(flg[now]);
    }
  }

  server.handleClient();
}

void handleRoot()
{
  String cmd;
  int a;
   
    cmd=server.argName(0);
    switch(cmd.toInt())
    {
      case 1:   // Play 
                if(flg[play] == 0)
                   if(flg[total] != flg[now])
                       radio_play(flg[now]);
                break;
                
      case 2:   // Stop 
                StopPlaying();
                break;
                
      case 3:   // Volume 
                cmd=server.arg("3");
                flg[volume]=cmd.toInt();
                out->SetGain((float)flg[volume]/100.0);
                break;
                
      case 4:   // Mute
                flg[volume] *= -1;
                a=flg[volume];
                if(flg[volume] < 0) a=0;
                out->SetGain(((float)a)/100.0);
                break;
                
      case 5:   // List
                cmd = server.arg("5");
                flg[now] = cmd.toInt();
                break;

    }

  load_index();
}

void load_index()
{
  int a;
  File dataFile;
  String line,line1,stbuf;
  
    //Pull Down List
    flg[total] = 0;
    a = flg[now];

    dataFile = SPIFFS.open("/title.txt", FILE_READ);
    stbuf="";
    while(dataFile.available())
     {
       line = dataFile.readStringUntil('\n');
       stbuf += "<option value='" + String(flg[total]+1) + "'";
       a -= 1; 
       if(a) stbuf += ">"; 
       else { stbuf += " selected>"; line1=line; }
        
       if(line != "<--- end --->") stbuf = stbuf + String(flg[total]+1) + ": ";
       stbuf = stbuf + line + "</option>\n";
       flg[total] += 1;
     }
    dataFile.close();
    
    stbuf = index_Dt + stbuf + index_Dt1;

    stbuf += ("document.getElementById('s_value').value='" + String(abs((int)flg[volume])) + "'\n");
    stbuf += ("document.getElementById('p_value').innerHTML='" + String(abs((int)flg[volume])) + "'\n");
 
    if(flg[play])
      stbuf += ("document.getElementById('b_play').style.backgroundColor='#ff6347'\n");
    if(flg[volume] < 0)
      stbuf += ("document.getElementById('b_mute').style.backgroundColor='#2020ff'\n");
    stbuf += "</script>\n</center>\n</body>\n</html>";
   
  server.send(200, "text/html",stbuf);    
}

void radio_play(int no)
{
  File l_file;
  int a;
  String link_data;

    l_file = SPIFFS.open("/link_list.txt", FILE_READ);  
    for(a=0; a<no; a ++) link_data=l_file.readStringUntil('\n');
    l_file.close();

    file = new AudioFileSourceICYStream(link_data.substring(2).c_str());
    buff = new AudioFileSourceBuffer(file, 2048*16);
    
    if(link_data.charAt(0) == '1') decoder = (AudioGenerator*) new AudioGeneratorAAC();
    else decoder = (AudioGenerator*) new AudioGeneratorMP3();

    decoder->begin(buff, out);
    
    flg[play]=1;
}
  
void StopPlaying()
{
  if (decoder) { decoder->stop(); delete decoder; decoder = NULL; }
  if (buff) { buff->close(); delete buff; buff = NULL; }
  if (file) { file->close(); delete file; file = NULL; }
  
  flg[play] = 0; 
}
  • 6行:#include “AudioGeneratorAAC.h” AAC対応のGenerator
  • 11行:#include “SPIFFS.h” SPIFFS用ヘッダー
  • 17行:AudioGenerator *decoder = NULL; MP3,AAC対応のGenerator宣言
  • 23行:static int8_t flg[10];
    • 今回はサーバーの状態を保持するパラメターが幾つか必要だったので、パラメターを配列にまとめました。
    • 例えば、再生中 の状態はflg[0]で保持します。再生=1。停止=0です。
    • 24行目以降でパラメータの格納場所を宣言しています。再生は、flg[0]->flg[play] (play=0)
  • 44行:String index_Dt1 =
    • 前回は、HTMLをひとまとまりで扱っていました。今回はボリュームとボタンの間にプルダウンリストを入れたかったので2つに分けました。
    • HTMLは index_Dt + プルダウンリスト + index_Dt2 となります。
  • 59から61行:Scriptでプルダウンリストが変更された時サーバーに変更内容を送信する様に設定しています。
  • 97行:SPIFFS.begin(); SPIFFSの開始
  • 100から104行:各パラメータの初期設定
    • flg[play]=0; 再生無し 停止
    • flg[volume]=30; 音量30
    • flg[total]=6; 登録曲数+1 今回は5局登録している。
    • flg[now]=1; 現在選択されているラジオ局の番号
    • out->SetGain((float)flg[volume]/100.0); ボリューム設定
  • 116から118行:止まったらもう一度再生する様にしました。
  • 134から136行:再生をradio_play(flg[now]);関数で実行
  • 140行:停止をStopPlaying();関数で実行。
  • 156から159行:プルダウンリストが変更されるとここに来ます。リストの値をflg[now](今選択されている局番を保持)に代入しています。
  • 166から204行:ここでHTMLのコードを作っています。
    • 176から190行:ここで、タイトル保存ファイル、”title.txt”から各局のタイトルを読み込み、リストを作成しています。
    • 192行:リスト作成後、index_Dt +リスト+index_Dt2 でHTMLを作成
    • 以降: ボリュームの表示とボタンの処理を上記にHTMLに足してクライアントに送信
  • 206から225行:ここが再生部分
    • URLが登録されたファイル、”link_list.txt”を開く。
    • 213行:現在選択されている局順番を元に局にURLを探す。
      • 例えば現在、3番目の局が選択されていれば、”link_list.txt”の3番目のURLが目的のURL
    • 216行:局のURLを指定
      • “link_list.txt”に登録してあるデータは、0,http://199.180.75.118:80/streamの様に先頭にMP3かAACかを区別する為に0または1を付けています。
      • よって、局のURLは3文字目から始まります。
    • 219から220行:MP3からAACか判断してどちらかのGeneratorを作成
      • AudioGenerator *decoder = NULL; と宣言すると、MP3型でもAAC型でも対応出来る様です。
    • 222行:再生開始
  • 227行以降: 停止処理。
    • 前回で、mp3と有った箇所が、decoderに変わっています。
    • これで、MP3 AAC両方い対応します。

 今回は、タイトルを保持した、”title.txt” と URLを保持した、”link_list.txt”の2つのファイルを使用します。

“title.txt”

title.txt

Jazz Radio
CLASSIC
J-POP
SomaFM
antena
<--- end --->

“link_list.txt”

link_list.txt

0,http://199.180.75.118:80/stream
0,http://144.217.49.251:80/stream1
0,http://158.69.38.195:20278/stream
0,http://ice1.somafm.com/secretagent-128-mp3
1,http://107.191.33.98:8232/stream/1
---  end  ---
  • タイトルとURLが一対一になっています。
  • URLの先頭には、フォーマット区別用に、MP3なら”0”をAACなら”1”を付けています。

スケッチのコンパイル。その後でデータのアップロードを行って実行して下さい。Webブラウザを上げて、”esp32.local”と入力すると、こんな感じの画面が表示されます。

ページのソースは

ラジオ局は5局登録して有ります。リストから選んで再生してみて下さい。

次回はラジオ局の編集(登録、削除等)について実装します。