Radikoを聞く(Arduino編)ーRadiko Playerの製作ー

今回はESP32にHTTPサーバを上げてRadiko Playerを作って見ました。こんな感じです。

機能の説明

  1.  サーバのIPアドレスを固定
    • スマホでアクセスする時にDNSが使えないのでサーバのIPアドレスを固定しています。
  2.  ボリューム調整用のスライドバー
    • 最小:0  最大:100
  3.  ラジオ局選択用プルダウンメニュー
    • ここでラジオ局を選択します。
    • 右の図の様にラジオ局が表示され選択出来ます。
    • 選択と同時に開始します。
  4.  開始ボタン
  5.  消音(Mute)ボタン
  6.  停止ボタン
  7.  状態保存ボタン
    • このボタンで状態を保存すれば次回ESP32が起動状態が再現されます。
    • 保存される状態は
      • ボリューム
      • ラジオ局
      • 自動開始ボタン 
        •  オンの場合、ESP32の起動と同時にラジオの再生が開始。
        •  サーバにアクセスしなくてもラジオが聞ける。

プログラムの説明

今回製作したプログラムは下記の5つ

  • radiko_02.ino  本体
  • radiko.css    サーバ用HTMLファイル
  • radiko.js    サーバ用HTMLファイル
  • state.txt    状態保存ファイル
  • favicon.ico   ファビコンファイル

radiko_02.ino(本体)の説明

これまで説明してきた通りRadikoの再生は

  1.  第一認証
  2.  第二認証
  3.  エリアコードとラジオ局一覧
  4.  Play List取得
  5.  Chunk List 取得
  6.  AACファイルの再生

を実行する事になります。このプログラムは

  • ESP32が起動時(電源がオン)
    • 上記の1,2,3を実行してラジオ局リストを取得
    • それを元にプルダウンリストを作成します。
  • ラジオ局の再生
    • 起動時に1,2,3を既に行っているので上記4からの実行で良さそうですが
    • 起動してから時間が経つと認証が切れる様なので、毎回1,2,4,5,6の順で実行
    • 3は飛ばしても問題無い様です)

サーバ本体のHTMLは ”radiko_02.ino” に組み込まれています。サーバの状態に合わせてクライアントにデータを送信しています。

radiko_02.ino

#include <Arduino.h>
#include <WiFi.h>
#include <string.h>
#include "mbedtls/base64.h"
#include <HTTPClient.h>

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

//For VS1053b PIN
#define XCS           17
#define XDCS          22
#define DREQ          39
#define XRESET        21
#define Buf_busy      32
#define Dummy         25

#define CLK           18
#define MISO          19
#define MOSI          23

//VS1053 register
#define VLSI_MODE     0x00
#define VLSI_VOL      0x0B

#define Run_LED       2

#define AAC_HEADER    0x49
#define TIME_OFFSET   40
#define BUFF_MAX      73728

// Enter your WiFi setup here:
const char *SSID = "XXXXXXXX";
const char *PASSWORD = "YYYYYYYYY";

IPAddress ip(192, 168, 3, 250);           // IP Address
IPAddress gateway(192,168, 3, 1);         // Gateway Address
IPAddress dns(192,168, 3, 1);             // Gateway Address
IPAddress subnet(255, 255, 255, 0);       // Subnet Address

WebServer _server(80);
String _index_top =
                  "<!DOCTYPE html>\n<html>\n<head>\n<meta charset='utf-8'>\n<meta name='viewport' content='width=device-width,initial-scale=1'>\n"
                  "<link rel='stylesheet' type='text/css' href='/radiko.css' >\n<title>Radiko</title>\n</head>\n"
                  "<body>\n<center>\n<div class='b_frame'>\n<br>\n<div class='t_font'><u>Radiko 1.0</u></div>\n<br>\n"
                  "<form method='get' id='abc'>\n<input name='1' id='123' style='display:none'>\n</form>\n"
                  "<form oninput='op.value=ab.value'>\n<span class='v_font'>Volume</span><br>\n"
                  "<input style='width:220px;' type='range' value='";
                  
int _buff_pt, _play_pt, _aac_no;
byte _st_selected, _station_MAX, _flg_mute, _flg_auto, _flg_play, _volume;
byte _sound_buff[BUFF_MAX];
String _auth_token, _chunk_url, _base_aac_url;
String _station_ID[25][2];

void setup()
{
  String str;

   //-----------------  part0 ------------------------------------------------------------------------------
    Serial.begin(115200);
    delay(500);

    pinMode(Run_LED,OUTPUT);
    digitalWrite(Run_LED, LOW);

    WiFi.disconnect();
    WiFi.softAPdisconnect(true);
    delay(500);
    
    WiFi.mode(WIFI_STA);
    WiFi.config(ip, gateway, subnet, dns);
    WiFi.begin(SSID, PASSWORD);
    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());

    //-----------------  HTTP Server & SPIFFS ----------------------------------------------------------------------
    _server.on("/", handleRoot);
    _server.onNotFound(handleWebRequests);
    _server.begin();
    Serial.println("HTTP server started");

    SPIFFS.begin();
    Serial.println("SPIFFS started");
    Init_vs1053();
    Serial.print("Init vs1053: ");
    Serial.println(VLSIReadReg(VLSI_MODE),HEX);
    
    Read_state();
    _flg_mute = 0;
    VLSI_SetVolume(_volume); 

    Radiko(1, " ");
    if(_st_selected > (_station_MAX - 1)) _st_selected = 0; 
    
    digitalWrite(Run_LED, HIGH);

    if(_flg_auto) Radiko_play(_st_selected);
}

void loop()
{
  int a;

    _server.handleClient();

    if(_flg_play)
    {
      if(_buff_pt > _play_pt) a = _buff_pt - _play_pt;
      else a = BUFF_MAX - _play_pt + _buff_pt;

      if( a < 4096 * 9) Ring_buffer();
    }
}

void handleRoot()
{
  String cmd,str;
  int a,flg;

    flg = 1;
    cmd = _server.argName(0);
    switch(cmd.toInt())
    {
      case 1: // Play, Mute, Auto, Save
                cmd = _server.arg("1");
                str = cmd.substring(2);
                a = str.toInt();
                flg = 0;
                switch(cmd.charAt(0))
                {
                  case '1': // volume data
                            _volume = a;
                            VLSI_SetVolume(_volume); 
                            break;
                            
                  case '2': // station No
                            _st_selected = a;
                            Radiko_stop();
                            delay(50);
                            Radiko_play(_st_selected);
                            break;
                            
                  case '3': // play
                            if(!_flg_play) Radiko_play(_st_selected);
                            break;

                  case '4': // mute
                            _flg_mute = !_flg_mute;
                            a = _volume;
                            if(_flg_mute) a = 5; 
                            VLSI_SetVolume(a);
                            break;

                  case '5': // auto flg
                            _flg_auto = !_flg_auto;
                            break;

                  case '6': // Save
                            Save_state();
                            break;

                  case '7': // Unlock 1st load
                            if(_flg_play) attachInterrupt(DREQ, Send_vs1053, RISING);
                            Send_vs1053();
                            break;
                }
                break;

      case 2: //  Stop
                Radiko_stop();
                break;
    }

    if(flg)
    {
      str = Make_index(str);
      if(_flg_play) detachInterrupt(DREQ);
      _server.send(200, "text/html",str);
    }
    else _server.send(204, "text/html","");
}

void Radiko_play(int ch_no)
{
    digitalWrite(Run_LED, LOW);
    _flg_play = 1;
    Radiko(0, _station_ID[_st_selected][0]);
    Get_aac_offset();
    _buff_pt = 0;
    Ring_buffer();
    _play_pt = 0;
    attachInterrupt(DREQ, Send_vs1053, RISING);
    Send_vs1053();
    digitalWrite(Run_LED, HIGH);
}

void Radiko_stop()
{
    if(_flg_play) detachInterrupt(DREQ);
    _flg_play = 0;
}

void Radiko(byte flg, String ID)
{
  char * auth_key = "bcd151073c03b352e1ef2fd66c32209da9ca0afa" ;
  HTTPClient http;
  int key_length, offset;
  char base64_encoded[32],token_p[32];
  size_t base64_len;
  const char *headerKeys[] = {"X-Radiko-AuthToken", "X-Radiko-KeyLength", "X-Radiko-KeyOffset"};
  String str;
  int a,b,c;
  WiFiClient * stream;

    //-----------------  part1 ------------------------------------------------------------------------------
    http.begin("https://radiko.jp/v2/api/auth1");
    http.addHeader("User-Agent", "esp32/4");
    http.addHeader("Accept", "*/*");
    http.addHeader("X-Radiko-App", "pc_html5");
    http.addHeader("X-Radiko-App-Version", "0.0.1");
    http.addHeader("X-Radiko-User", "dummy_user");
    http.addHeader("X-Radiko-Device", "pc");
    http.collectHeaders(headerKeys, 3);
    Serial.println("part1 = " + String(http.GET()));
    digitalWrite(Run_LED, HIGH);
    
    offset = String(http.header(headerKeys[2])).toInt();
    key_length = String(http.header(headerKeys[1])).toInt();
    _auth_token = http.header(headerKeys[0]);
    http.end();

    token_p[0] = '\0';
    strncpy(token_p, &auth_key[offset], key_length);
    token_p[key_length] = '\0';
    mbedtls_base64_encode((unsigned char *) base64_encoded, sizeof(base64_encoded),&base64_len, (unsigned char *) token_p, strlen(token_p));

    //-----------------  part2 ------------------------------------------------------------------------------
    http.begin("https://radiko.jp/v2/api/auth2");
    http.addHeader("X-Radiko-AuthToken", _auth_token);
    http.addHeader("X-Radiko-Partialkey", base64_encoded);
    http.addHeader("X-Radiko-User", "dummy_user");
    http.addHeader("X-Radiko-Device", "pc");
    Serial.println("part2 = " + String(http.GET()));
    digitalWrite(Run_LED, LOW);
    
    str = http.getString();
    str = str.substring(0,str.indexOf(","));
    http.end();
  
    if(flg)
    {
      //-----------------  part3 ------------------------------------------------------------------------------
      //    http.begin("https://radiko.jp/v2/station/list/[ID].xml");
      str = "https://radiko.jp/v2/station/list/" + str + ".xml";
      http.begin(str);
      http.addHeader("X-Radiko-AuthToken", _auth_token);
      Serial.println("part3 = " + String(http.GET()));
      digitalWrite(Run_LED, HIGH);
      
      b = http.getSize();
      c = 0;
      stream = http.getStreamPtr();
      while(b)
      {
        while(!(a = stream->available())) NOP();
        stream->readBytes(&_sound_buff[c], a);
        b -= a;
        c += a;
      }
      _sound_buff[c] = 0;
      str = String((char *)_sound_buff);    

      b = 0;
      while((a = str.indexOf("<id>")) != -1)
      {
        a +=  4;
        str = &str[a];
        a = str.indexOf("</id>");
        _station_ID[b][0] = str.substring(0,a);
        a = a + 16;
        str = &str[a];
        a = str.indexOf("</name>");
        _station_ID[b][1] = str.substring(0,a);
        b ++;
      }
      _station_MAX = b;
      for(a = 0; a < b; a ++)
        Serial.println("No." + String(a) +" ID: " + _station_ID[a][0] + " , name: " + _station_ID[a][1]);
      Serial.println("station_MAX = " + String(b));
    }    
    else
    {
      //-----------------  part4 ------------------------------------------------------------------------------
      str = "https://f-radiko.smartstream.ne.jp/" + ID + "/_definst_/simul-stream.stream/playlist.m3u8";
      http.begin(str);
      http.addHeader("X-Radiko-AuthToken", _auth_token);
      Serial.println("part4 = " + String(http.GET()));
      digitalWrite(Run_LED, HIGH);
      
      str = http.getString();
      _chunk_url = &str[str.indexOf("https")];
    }
    http.end();
}

void Get_aac_offset()
{
  int a,b;
  HTTPClient http;
  String str;
  
    http.begin(_chunk_url);
    http.addHeader("X-Radiko-AuthToken", _auth_token);
    Serial.println("part5 = " + String(http.GET()));
    digitalWrite(Run_LED, LOW);
    
    str = http.getString();
    a = str.indexOf("https");
    str = &str[a];
    a = str.indexOf(".aac");
    _base_aac_url =str.substring(0,a);
    a =_base_aac_url.length();
    while(_base_aac_url[a] != '_') a --;
    str = &_base_aac_url[a + 1];
    _base_aac_url =_base_aac_url.substring(0,a + 1);
    _aac_no = str.toInt();
    http.end();

    a = TIME_OFFSET;
    b = 1;
    while(b)
    {
      a --;
      str =_base_aac_url + String(_aac_no + a) + ".aac";
      http.begin(str);
      http.addHeader("X-Radiko-AuthToken", _auth_token);
      if(http.GET() == 200) b = 0;
      http.end();
      if(a == 1) b = 0;
    }
    a --;
    Serial.println("TIME OFFSET: " + String(a));
    _aac_no += a;
}

void Ring_buffer()
{
  int a, aac_size, avail;
  byte d_buff[0x50];
  String str;
  HTTPClient http;
  WiFiClient * stream;

    str =_base_aac_url + String(_aac_no) + ".aac";
    http.begin(str);
    http.addHeader("X-Radiko-AuthToken", _auth_token);
    if(http.GET() != 200)
    {
        http.end();
        Get_aac_offset();
        str =_base_aac_url + String(_aac_no) + ".aac";
        http.begin(str);
        http.addHeader("X-Radiko-AuthToken", _auth_token);
        http.GET();
    }
    Serial.println(str);
    aac_size =  http.getSize();
    stream = http.getStreamPtr();
    avail = stream->available();
    aac_size -= AAC_HEADER;
    avail -= AAC_HEADER;
    stream->readBytes(d_buff, AAC_HEADER);

    while(aac_size)
    {
      aac_size -= avail;
      a = _buff_pt + avail;
      if(a > BUFF_MAX - 1)
      {
        stream->readBytes(&_sound_buff[_buff_pt], BUFF_MAX - _buff_pt);
        avail = a - BUFF_MAX;
        _buff_pt = 0;
      }
      stream->readBytes(&_sound_buff[_buff_pt], avail);
      _buff_pt += avail;
      if(aac_size)
        while(!(avail = stream->available())) NOP();
    }
    _aac_no ++;

    http.end();
}

String Make_index(String str)
{
  int a;
  
    str = String(_volume);
    str += "' id='vol' name='ab' onchange='set_data(1)'>\n<output name='op' class='v_font'>";
    str += String(_volume);
    str += "</output>\n<br>\n<form>\n<select id='_title' class='m_font' onchange='set_data(2)'>\n";
    
    for(a = 0; a < _station_MAX; a ++)
    {
      str += "<option value='";
      str += (String(a) + "'");
      if(a == _st_selected) str += " selected>";
      else str += ">";
      str += _station_ID[a][1];
      str += "</option>\n";
    }

    str += "</select>\n</form>\n<br>\n<form method='get'>\n<button type='button' id='_play' onclick='set_data(3)' ";
    if(_flg_play) str += "style ='background: red;'";
    str += ">Play</button>\n<button type='button' id='_mute' onclick='set_data(4)' ";
    if(_flg_mute) str += "style ='background: red;'";
    
    str += ">Mute</button>\n<button type='submit' name='2' >Stop</button>\n<br>\n<span class='v_font' style ='margin-left:5%;'>Auto Start :</span>\n";
    str += "<input type='radio' id='_auto' style ='margin-left:5%;' onclick='set_data(5)'";
    if(_flg_auto) str += " checked ";
    str += "/>\n<button type='button' style ='margin-left:16%;' onclick='set_data(6)'";
    str += ">Save</button>\n</form>\n</div>\n</center>\n<script src='./radiko.js'></script>\n</body>\n</html>";

    str = _index_top + str;
    
    return str;
}

void handleWebRequests()
{
  String dataType = "text/plain";
  String path;
  File dataFile;

    path = _server.uri();
    if(path.endsWith(".txt")) dataType = "text/plain";
    else if(path.endsWith(".html")) dataType = "text/html";
    else if(path.endsWith(".css")) dataType = "text/css";
    else if(path.endsWith(".js")) dataType = "application/javascript";
    else if(path.endsWith(".mp3")) dataType = "audio/mp3";
    else if(path.endsWith(".aac")) dataType = "audio/aac";
    else if(path.endsWith(".ico")) dataType = "image/html";
    
    dataFile = SPIFFS.open(path.c_str(), "r");
    _server.streamFile(dataFile, dataType);
    dataFile.close();
    delay(5);
}

void Send_vs1053()
{
  digitalWrite(XDCS,LOW);
  while(digitalRead(DREQ))
  {
    SPI.write(_sound_buff[_play_pt]);
    _play_pt ++;
    if(_play_pt == BUFF_MAX) _play_pt = 0;
  }
  digitalWrite(XDCS,HIGH);
}

void VLSI_SetVolume(byte v_data)
{
  float a;
  uint16_t vol_data;

    a = v_data * 2.5;
    vol_data = a;
    vol_data = 250 - vol_data;
    vol_data |= (vol_data << 8);
    VLSIWriteReg(VLSI_VOL, vol_data);
}

uint16_t VLSIReadReg(byte vAddress)
{
  uint16_t wValue;

    while(!digitalRead(DREQ)) NOP();
    digitalWrite(XCS,LOW);
    SPI.write(0x03);                         // Read
    SPI.write(vAddress);                     // Read
    ((byte*)&wValue)[1] = SPI.transfer(0xFF);   // 16 bit high byte
    ((byte*)&wValue)[0] = SPI.transfer(0xFF);   // 16 bit low byte
    digitalWrite(XCS,HIGH);
    return wValue;
}

void VLSIWriteReg(byte vAddress, uint16_t wValue)
{
    while(!digitalRead(DREQ)) NOP();
    digitalWrite(XCS,LOW);
    SPI.write(0x02);                     // Write
    SPI.write(vAddress);                 // Register address
    SPI.write(((byte*)&wValue)[1]);      // 16 bit write high byte
    SPI.write(((byte*)&wValue)[0]);      // 16 bit write low byte
    digitalWrite(XCS,HIGH);
}

void Init_vs1053()
{
    pinMode(XCS, OUTPUT);
    digitalWrite(XCS,HIGH);

    pinMode(XDCS, OUTPUT);
    digitalWrite(XDCS,HIGH);

    pinMode(Buf_busy, OUTPUT);
    digitalWrite(Buf_busy,HIGH);

    pinMode(XRESET, OUTPUT);
    digitalWrite(XRESET,HIGH);

    pinMode(DREQ,INPUT);

    // Reset VS1053
    digitalWrite(XRESET,LOW);
    delay(500);
    digitalWrite(XRESET,HIGH);
    delay(500);

    // SPI
    SPI.begin(CLK, MISO, MOSI, Dummy);
    VLSIWriteReg(VLSI_MODE, 0x800);
}

void Save_state()
{
  String str;
  File dataFile;

    str = String(_volume) + "\n" + String(_st_selected) +"\n" + String(_flg_auto) + "\n";
    dataFile = SPIFFS.open("/state.txt", "w");
    dataFile.print(str);
    dataFile.close();
}

void Read_state()
{
  String str;
  File dataFile;

    dataFile = SPIFFS.open("/state.txt", "r");
  
    str = dataFile.readStringUntil('\n');
    _volume = str.toInt();

    str = dataFile.readStringUntil('\n');
    _st_selected = str.toInt();
    
    str = dataFile.readStringUntil('\n');
    _flg_auto = str.toInt();

    dataFile.close();
}
  • 34,35行: 自宅ルーターの情報を入力下さい。
  • 37,40行: サーバのIPアドレスを固定する為に必要です。
  • 73行: ここでサーバのIPアドレスを固定
  • 97行: ここで前回の状態を読み込んでいます。ボリュームとラジオ局、自動開始のパラメータが読み込まれます
  • 101行: ここでラジオ局のリストを作成しています。
  • 106行: 自動再生がオンならここで再生を開始します。
  • 124行: HP画面の操作はここで行います。
  • 185行: Make_index(str)関数でクライアントに送るデータを作成しています。
    • 403行: Make_index(str)関数本体
      • ここで、状態に合わせてHPのHTMLを作成しています
      • 最後に43行のString _index_topを付け足してサーバHTML作成完了
  • 192行: HPのPlayボタンをクリックするとここが実行されます。
    • 196行: Radiko(0, _station_ID[_st_selected][0]); ここで1,2,4まで実行されます。
    • 197行: Get_aac_offset(); ここでAACファイルのURLを取得します。
    • 199行: AACファイルのデータをバッファーにダウンロード
    • 201,202行: そのデータをVS1053に転送して再生開始

以下は、radiko.js、radiko.cssです。 

radiko.js

//    var url = "http://raspberrypi.local:8080/?80=0";
function set_data(a)
{
    var str;
    var _color;

    str = a + ",";
    switch(Number(a))
    {
        case 1: // Volume
                str += document.getElementsByName("ab")[0].value;
                break;

        case 2: // Station no
        case 3: // play
                document.getElementById('_play').style.background = "#ff3034";
                if(a == '2') str += document.getElementById('_title').value;
                else str += "0";
                break;

        case 4: // mute
                _color = "red";
                if(document.getElementById('_mute').style.background == "red") _color = "#228b22";
                document.getElementById('_mute').style.background = _color;
                str += "0";
                break;

        case 5: // Auto flg
                _auto = !_auto;
                if(_auto) document.getElementById('_auto').checked = true;
                else document.getElementById('_auto').checked = false;

        case 6: // Save Parameter
        case 7: // Send Comp
                str += "0";
                break;
    }
    document.getElementById('123').value = str;
    document.getElementById('abc').submit();
}

var _auto = document.getElementById('_auto').checked;

set_data(7);
history.pushState(null,null,'/');
radiko.css

@charset "UTF-8";

.t_font {
        font-size: 40px;
        font-weight: bold;
        font-style: italic;
        color: #0ff;
}

.m_font {
        width: 290px;
        background: khaki;
        border-radius: 10px;
        font-size: 26px;
        font-weight: bold;
        font-style: italic;
        color: mediumblue;
        text-align: center;
}

.v_font {
        font-size: 20px;
        font-weight: bold;
        font-style: italic;
        color: #0ff;
}

.e_font {
        font-size: 18px;
        font-weight: bold;
        color: #0ff;
}

.b_frame {
        width: 320px;
        background: #363636;
        border-radius: 50px;
        border-style: ridge;
        border-width: 5px 30px;
        border-color: sienna;
}

button {
        display: inline;
        margin: 10px;
        line-height: 30px;
        cursor: pointer;
        color: #fff;
        background: #228b22;
        border-radius: 20px;
        font-size: 20px;
	    width: 80px;
        font-weight: bold;
        font-style: italic;
}

button:active{
        background: red;
}

button:disabled{
        filter:brightness(0.5);
        cursor:not-allowed;
}

option {
        text-align: center;
        margin: 10px;
        color: #fff;
        margin-left:16%;
        background: #228b22;
        border-radius: 20px;
        font-size: 20px;
        font-weight: bold;
        font-style: italic;
}

select {
        text-align: center;
        margin: 10px;
        background-color: red;
        color: #fff;
        background: #228b22;
        border-radius: 20px;
        font-size: 40px;
}

state.txtは以下の様になっています。

state.txt

70
7
0

上から 

  • 70
    • 音量の値
    • ラジオ局。東京エリアでは、J-WAVEになります。
    • 地域が違う場合その地域の7の局になります
    • その地域に7が無い場合、一番最初の局になります
    • 自動再生の有無。
    • 1:自動再生有り  0:自動再生無し

となります。radiko.js、radiko.css、state.txt、favicon.icoの4つファイルはSPIFFSで予めESP32にUploadして下さい。

プログラム実行時シリアルモニタには以下の様に表示されます。起動直後

起動直後

IP address: 192.168.3.250
HTTP server started
SPIFFS started
Init vs1053: 800
part1 = 200
part2 = 200
part3 = 200
No.0 ID: TBS , name: TBSラジオ
No.1 ID: QRR , name: 文化放送
No.2 ID: LFR , name: ニッポン放送
No.3 ID: RN1 , name: ラジオNIKKEI第1
No.4 ID: RN2 , name: ラジオNIKKEI第2
No.5 ID: INT , name: interfm
No.6 ID: FMT , name: TOKYO FM
No.7 ID: FMJ , name: J-WAVE
No.8 ID: JORF , name: ラジオ日本
No.9 ID: BAYFM78 , name: BAYFM78
No.10 ID: NACK5 , name: NACK5
No.11 ID: YFM , name: FMヨコハマ
No.12 ID: IBS , name: LuckyFM 茨城放送
No.13 ID: HOUSOU-DAIGAKU , name: 放送大学
No.14 ID: JOAK , name: NHKラジオ第1(東京)
No.15 ID: JOAK-FM , name: NHK-FM(東京)
station_MAX = 16
  • 1行: サーバのIPアドレスの表示
  • 4行: VS1053の初期化
  • 5行: Part1正常完了
  • 6行: Part2正常完了
  • 7行: Part3正常完了
  • 8行以降: 取得したラジオ局一覧

PCのブラウザを上げてURL欄に”192.168.3.250”と入力すると以下の画面が表示されます。

ここで、”Play”ボタンをクリックすると

Play後

part1 = 200
part2 = 200
part4 = 200
part5 = 200
TIME OFFSET: 37
https://f-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream/media-uq19ty0ur_w1215646049_708269.aac
https://f-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream/media-uq19ty0ur_w1215646049_708270.aac
https://f-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream/media-uq19ty0ur_w1215646049_708271.aac
https://f-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream/media-uq19ty0ur_w1215646049_708272.aac

  • 1から4行: Part1,2,4,5と実行
  • 5行: ここでAACファイルのURLを時間遅れを考慮して取得しています。
  • 6行以降: ダウンロードされたAACをファイルのURLを表示しています。

問題点

再生中時々音が飛びます。これは前回から起こっている現象ですが、以前原因が分かりません。

最後に

これで本来の目的である、”ArduinoでRadikoを聞く”が実現しました(若干の問題は有りますが)。かなり満足です。

ところでこのVS1053はSDカードを持っています。ダウンロードしたAACファイルを保存すればラジオの録音が可能になります。次はそのあたりを調べたいと思います。

今回製作したファイル