(4)Radiko以外も聴く

Radiko以外のラジオ局を聴くために下記の変更を行います。

  • ロータリースイッチを使って16個の局を選択出来る。
  • この16個に対応するラジオ局データを持った構造体を作成。
  • 構造体は以下の要素を保つ
    • 再生先がRadikoかそれ以外かを判断するデータ
    • デーコードの方式
    • 局のデータ
    • 局のURL
  • 現在、このプログラムはAAC方式のデコーダーしか持っていないのでMP3のデコーダーを追加する。

プログラムの説明

今回は、mainフォルダーの play_radiko.c のみの変更です。

play_radiko.c

/* Play M3U HTTP Living stream


   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_common.h"
#include "http_stream.h"
#include "i2s_stream.h"
#include "aac_decoder.h"
#include "mp3_decoder.h"
#include "esp_tls.h"
#include "esp_http_client.h"
#include "radiko.h"
#include "esp_peripherals.h"
#include "periph_wifi.h"
#include "board.h"
#include "AQM1248A.h"
#include "audio_alc.h"

#if __has_include("esp_idf_version.h")
#include "esp_idf_version.h"
#else
#define ESP_IDF_VERSION_VAL(major, minor, patch) 1
#endif

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
#include "esp_netif.h"
#else
#include "tcpip_adapter.h"
#endif

#include "driver/gpio.h"
#include <stdio.h>
#include <stdlib.h>

#define CONFIG_WIFI_SSID 		"aterm-193b5c-g"
#define CONFIG_WIFI_PASSWORD 	"9c182d72c9385"

static const char *TAG = "HTTP_LIVINGSTREAM_EXAMPLE";

//#define AAC_STREAM_URI "http://open.ls.qingting.fm/live/274/64k.m3u8?format=aac"
#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_content 8192

audio_pipeline_handle_t pipeline;
audio_element_handle_t http_stream_reader, i2s_stream_writer, aac_decoder, mp3_decoder;

#define st_no1          35
#define st_no2          39
#define st_no4          34
#define st_no8          36

#define _radiko	        1
#define _shtmp3			2
#define _shtaac			3

struct st_info {         //Station info
    int _deco;
    int _st_no;
    char _st_ID[20];
    char _st_URL[200];
};

struct st_info station_info[] = {
	{_radiko, 0, "0", "0"},
	{_radiko, 1, "0", "0"},
	{_radiko, 2, "0", "0"},
	{_shtmp3, 3, "JAZZ1", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1528122"},
	{_shtmp3, 4, "Classic", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=22146"},
	{_radiko, 5, "5", "0"},
	{_radiko, 6, "6", "0"},
	{_radiko, 7, "0", "0"},
	{_radiko, 8, "0", "0"},
	{_radiko, 9, "0", "0"},
	{_radiko, 10, "0", "0"},
	{_radiko, 11, "0", "0"},
	{_shtmp3, 12, "TOP40", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1914279"},
	{_shtmp3, 13, "Piano", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=99513978"},
	{_shtmp3, 14, "Dance", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1817772"},
	{_shtaac, 15, "Antena", "http://yp.shoutcast.com/sbin/tunein-station.pls?id=1796249"}
};

void init_port(void)
{
    gpio_config_t io_conf = {};
    io_conf.mode = GPIO_MODE_INPUT;	    			//set as input mode
    io_conf.pin_bit_mask = 0x27ULL << 34;			//bit mask of the pins GPIO34,35,36,39	
    io_conf.pull_up_en = 0;						    //enable pull-up mode
    gpio_config(&io_conf);
}

int get_staion_no()
{
  int a;

    a = 0;
    if(gpio_get_level(st_no1)) a += 1;
    if(gpio_get_level(st_no2)) a += 2;
    if(gpio_get_level(st_no4)) a += 4;
    if(gpio_get_level(st_no8)) a += 8;

    return a;
}

int _http_stream_event_handle(http_stream_event_msg_t *msg)
{
    if (msg->event_id == HTTP_STREAM_RESOLVE_ALL_TRACKS) {
        return ESP_OK;
    }

    if (msg->event_id == HTTP_STREAM_FINISH_TRACK) {
        return http_stream_next_track(msg->el);
    }
    if (msg->event_id == HTTP_STREAM_FINISH_PLAYLIST) {
        return http_stream_fetch_again(msg->el);
    }
    return ESP_OK;
}

void print_station_info(int st_no)
{
	char c_buf[30];
	
	LCD_CLS(0);
	sprintf(c_buf, "%d:", st_no);
		switch(station_info[st_no]._deco)
    {
		case _radiko: 	strcat(c_buf, (stations + st_no)->id);
			    		break;
		case _shtmp3: 	
		case _shtaac: 	strcat(c_buf, station_info[st_no]._st_ID);
						break;
	}			
			    
	st_no = strlen(c_buf);
	if(st_no > 10) {  c_buf[10] = 0; st_no = 10; }
	st_no = 10 - st_no;
	st_no *= 6;  
	LCD_Print_Str(st_no, 14, c_buf, 1, 2);
	
}

void app_main(void)
{
	int st_now;
	int flg_no;

    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES) 
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
    ESP_ERROR_CHECK(esp_netif_init());
#else
    tcpip_adapter_init();
#endif

	init_port();
	Init_LCD();

	LCD_CLS(0);
	LCD_Print_Str(30, 16, "Wait", 1, 2);

    esp_log_level_set("*", ESP_LOG_INFO);
    esp_log_level_set(TAG, ESP_LOG_DEBUG);

    ESP_LOGI(TAG, "[ 3 ] Start and wait for Wi-Fi network");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);

    ESP_LOGI(TAG, "[3.1] Initialize keys on board");
    audio_board_key_init(set);

    periph_wifi_cfg_t wifi_cfg = 
    {
        .ssid = CONFIG_WIFI_SSID,
        .password = CONFIG_WIFI_PASSWORD,
    };

    esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
    esp_periph_start(set, wifi_handle);
    periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);

//------------------------------------------------------------------------------------
    ESP_LOGI(TAG, "[2.0] Create audio pipeline for playback");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    pipeline = audio_pipeline_init(&pipeline_cfg);

    ESP_LOGI(TAG, "[2.1] Create http stream to read data");
    http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
    http_cfg.event_handle = _http_stream_event_handle;
    http_cfg.type = AUDIO_STREAM_READER;
    http_cfg.enable_playlist_parser = true;
    http_stream_reader = http_stream_init(&http_cfg);

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);

    ESP_LOGI(TAG, "[2.3] Create aac decoder to decode aac file");
    aac_decoder_cfg_t aac_cfg = DEFAULT_AAC_DECODER_CONFIG();
    aac_decoder = aac_decoder_init(&aac_cfg);

    ESP_LOGI(TAG, "[2.3M] Create mp3 decoder to decode mp3 file");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

    const char *m_link_tag[3] = {"http", "mp3", "i2s"};
   	const char *link_tag[3] = {"http", "aac", "i2s"};

   	ESP_LOGI(TAG, "[2.4] Register all elements to audio pipeline");
   	audio_pipeline_register(pipeline, http_stream_reader, "http");
   	audio_pipeline_register(pipeline, aac_decoder,        "aac");
   	audio_pipeline_register(pipeline, i2s_stream_writer,  "i2s");
   	audio_pipeline_register(pipeline, mp3_decoder,        "mp3");

	st_now = get_staion_no();
    switch(station_info[st_now]._deco)
    {
    	case _radiko:	
    	case _shtaac:	ESP_LOGI(TAG, "[2.5] Link it together http_stream-->aac_decoder-->i2s_stream-->[codec_chip]");
    					audio_pipeline_link(pipeline, &link_tag[0], 3);
    					break;
    					
    	case _shtmp3: 	ESP_LOGI(TAG, "[2.5M] Link it together http_stream-->mp3_decoder-->i2s_stream-->[codec_chip]");
     					audio_pipeline_link(pipeline, &m_link_tag[0], 3);
     					break;
	}
	
    ESP_LOGI(TAG, "[ 4 ] Set up  event listener");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

    ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

//------------------------------------------------------------------------------------
  	auth();
    get_station_list();

	if(station_info[st_now]._deco == _radiko)
	{
		ESP_LOGI(TAG, "[2.6] Set up radiko uri)");							
		generate_playlist_url(stations + st_now);
		audio_element_set_uri(http_stream_reader, _playlist_url);
	}	
	else 
	{
		ESP_LOGI(TAG, "[2.6M] Set up shoutcast uri");
		audio_element_set_uri(http_stream_reader, station_info[st_now]._st_URL);
	}

	ESP_LOGI(TAG, "[ 5 ] Start audio_pipeline");
	audio_pipeline_run(pipeline);
	print_station_info(st_now);

    while (1) 
    {
        audio_event_iface_msg_t msg;
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) 
        {
            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
            continue;
        }

	    switch(station_info[st_now]._deco)
    	{
	        case _radiko:	 
	        case _shtaac:	if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) aac_decoder 
	        					&& msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) 
			        		{
				            	audio_element_info_t music_info = {0};
        				    	audio_element_getinfo(aac_decoder, &music_info);

        				    	ESP_LOGI(TAG, "[ * ] Receive music info from aac decoder, sample_rates=%d, bits=%d, ch=%d",
        				            music_info.sample_rates, music_info.bits, music_info.channels);
	
        				    	audio_element_setinfo(i2s_stream_writer, &music_info);
        				    	i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
        				    	continue;
        				    }
 				    	    break;
		    	    
			case _shtmp3: if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) mp3_decoder 
								&& msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) 
			        		{
				 	            audio_element_info_t music_info = {0};
	    				        audio_element_getinfo(mp3_decoder, &music_info);
	
	    				        ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
	    				                 music_info.sample_rates, music_info.bits, music_info.channels);
			
	    				        audio_element_setinfo(i2s_stream_writer, &music_info);
	    				        i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
	    				        continue;
	    				    }
	    				    break;
		}
        
   		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) http_stream_reader
		            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_ERROR_OPEN) 		
        {
	        ESP_LOGW(TAG, "[ * ] Restart stream");
	        audio_pipeline_stop(pipeline);
	        audio_pipeline_wait_for_stop(pipeline);

	        if(station_info[st_now]._deco == _shtmp3)
		        audio_element_reset_state(mp3_decoder);
			else
    	        audio_element_reset_state(aac_decoder);
			
			audio_element_reset_state(i2s_stream_writer);
	        audio_pipeline_reset_ringbuffer(pipeline);
	        audio_pipeline_reset_items_state(pipeline);
	        audio_pipeline_run(pipeline);
	        continue;
	    }
	    
        if (msg.source_type == PERIPH_ID_BUTTON)
        {
        	vTaskDelay(1000 / portTICK_PERIOD_MS);
	        flg_no = get_staion_no();
    	    if( flg_no != st_now)
    	    {
    	   		st_now = flg_no;
  	        	ESP_LOGI(TAG, "Station No:%d",flg_no);
	        	audio_pipeline_stop(pipeline);
   		        audio_pipeline_wait_for_stop(pipeline);
				audio_pipeline_terminate(pipeline);
   				audio_pipeline_unlink(pipeline);
    			audio_pipeline_remove_listener(pipeline);

			    switch(station_info[st_now]._deco)
		    	{
		    		case _shtmp3:	ESP_LOGI(TAG, "[2.5M] Link it together http_stream-->mp3_decoder-->i2s_stream-->[codec_chip]");
						    		audio_pipeline_link(pipeline, &m_link_tag[0], 3);
    						        audio_element_set_uri(http_stream_reader, station_info[st_now]._st_URL);
						            break;
				            
					case _shtaac: 	
					case _radiko: 	ESP_LOGI(TAG, "[2.5] Link it together http_stream-->aac_decoder-->i2s_stream-->[codec_chip]");
						    		audio_pipeline_link(pipeline, &link_tag[0], 3);
			
									if(station_info[st_now]._deco == _radiko)
									{
										generate_playlist_url(stations + st_now);
		    							audio_element_set_uri(http_stream_reader, _playlist_url);
		    						}	
									else audio_element_set_uri(http_stream_reader, station_info[st_now]._st_URL);
						            break;
		       	}
    			ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    			audio_pipeline_set_listener(pipeline, evt);
				audio_pipeline_run(pipeline);
			   	print_station_info(st_now);
        	}
   		}
    }
    
    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_terminate(pipeline);

    audio_pipeline_unregister(pipeline, http_stream_reader);
    audio_pipeline_unregister(pipeline, i2s_stream_writer);
    audio_pipeline_unregister(pipeline, aac_decoder);

    /* Terminate the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline);

    /* Stop all peripherals before removing the listener */
    esp_periph_set_stop_all(set);
    audio_event_iface_remove_listener(esp_periph_set_get_event_iface(set), evt);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(http_stream_reader);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(aac_decoder);
    esp_periph_set_destroy(set);
}

  • 72行:struct st_info {
    • 局データ保存用構造体の宣言
    • int _deco; デコード方式の指定
      • 1:Radiko
      • 2:MP3
      • 3:AAC
    • int _st_no; 構造体の番号。ロータリースイッチの値に対応
      • Radikoの場合は ロータリースイッチの値=ラジオ局になります。
      • Radikoの場合は、0:TBS 15:NHK-FM
    • char _st_ID[20];  ディスプレイに表示される局データを保管。10文字以内
      • Radikoの場合は get_station_list(); 関数で作成されるデータを表示します。
      • Radikoの場合は、”0″を指定
    • char _st_URL[200]; ラジオのURLを保管。
      • Radikoの場合は generate_playlist_url(); で作成したURLを使用。
      • Radikoの場合は、”0″を指定
  • 79行:struct st_info station_info[] = {
    • ここに局データを保存。
    • 今回は、3,4。12から15番にShoutcastのラジオ局を保存しています。
  • 222から256行:MP3用のデコーダも追加で作成。
    • 従来のAACデコーダーに追加してMP3デコーダーを追加
    • 選択されている局のデータから再生方式を判断し(AACかMP3かを判断)パイプラインを作成
  • 278から380行:再生ループ
    • 281行:esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
      • この部分でロータリースイッチの変更を確認
    • 341行:if (msg.source_type == PERIPH_ID_BUTTON)
      • ロータリースイッチが変更されるとこのコードが実行される。
      • 局が変更された事を確認。下記を実行してStreamingを停止する。
        • audio_pipeline_stop(pipeline);
        • audio_pipeline_wait_for_stop(pipeline);
        • audio_pipeline_terminate(pipeline);
        • audio_pipeline_unlink(pipeline);
        • audio_pipeline_remove_listener(pipeline);
      • 355行:switch(station_info[st_now]._deco)
        • ここでデコードに合わせてパイプラインを再編成
      • 376行:audio_pipeline_run(pipeline); 再生開始
      • 377行:print_station_info(st_now); 局データをディスプレイに表示

プロジェクトの構成

プロジェクトの構成は前回と同じです。


- radiko-esp32-main_01/
      - components/
                   - radiko-esp32/
                              - CMakeLists.txt
                              - Kconfig.projbuild
                              - radiko.c
                              - radiko.h
                              - radiko_jp_rootcert.pem
                   - my_board/
                              - my_board_v1_0/
                                        - board.c
                                        - board.h
                                        - board_def.h
                                        - board_pins_config.c
                              - AQM1248/
                                        - AQM1248A.c
                                        - AQM1248A.h
                              - CMakeLists.txt
      - main/
                   - CMakeLists.txt
                   - Kconfig.projbuild
                   - play_radiko.c
      - CMakeLists.txt
      - partitions.csv
      - sdkconfig.defaults

コンパイルと実行

  • ESP32を使用することを前提としています。
  • 先ずは、idf.py menuconfig を実行して初期設定を行います。
    • Topー>Audio HALー>Audio board custom audio board を選択
    • Example ConfigurationでWiFi関係の設定を行います。mainフォルダー下のKconfig.projbuildでWiFi関係の設定を行っていれば入力不要。
    • Radiko Configurationで認証キーを入力します。今回は、radiko-esp32フォルダー下のKconfig.projbuildで指定しているので入力不要。
  • この後、idf.py -p [USB Port] flash monitor を実行して下さい。コンパイルして実行出来ます。
  • 実行後しばらくしてラジオの再生が始まります。今回追加したディスプレイに局データが表示されます。
  • ロータリースイッチを回して下さい。ラジオ局が選択され局表示も更新されます。
  • 設定したRadiko以外のラジオ局も聞けます。

次は

Radiko 以外のラジオ局も聞ける様になり便利になりました。次回は以前ネットで買ったスピーカーに今回製作して回路全てを収納したいと思います。