ホームページH8をMacで>その6a・インターネットラジオを聴きたい(中編)

その6a・インターネットラジオを聴きたい(中編)

目次
 目的と経緯

 引き続き、インターネットラジオを試行します。

 当初予定と異なり、後編ではなく中編としたのは、まだきちんと音が鳴っていないからです。
 それも、(自分にとっては)かなり深刻で、現状解決のめどが立っていません。
 ただ、問題点を明確にする意味でも、判明したことは覚書として、まとめておきます。


 SHOUTcast

 インターネットラジオにはいくつかの形式があるようですが、今回はSHOUTcastを対象とすることにしました。

 SHOUTcastの詳細は専門のサイト等を参照して頂くとして、簡単にいうと、HTTPサーバにRequestを送り、返ってきた内容が妥当なものであれば、それに続く音楽データ(ストリーム)を受信する、という手順になります。
 データの形式には特に決まりはないようですが、MP3が最もポピュラーなので、MP3に限定して話を進めていきます。

 MP3データは「フレーム」と呼ばれるデータ群の集合体で、全体のヘッダ/フッタのようなものはなく、各フレームの先頭部分にヘッダがあって、そこにデータの諸元が記されています。
 ソフトウェアでデコードするような場合は、このフレームヘッダを解析してサンプリング周波数やビットレート等を抽出することになりますが、VS1011Eはチップ内でヘッダを解析して然るべき値をセットしているようです。

 また、SHOUTcastにおけるストリームデータは、必ずしもMP3のフレームヘッダから始まっていないため、自前で処理する場合は、まずヘッダの位置を特定することから始めなくてはなりませんが、VS1011Eは受け取ったデータをそのまま流し込めば、ヘッダを見つけて処理してくれるようです。
いずれも、データ処理後のレジスタ値からの推測。
なお、データの途中から送った場合は、ヘッダを見つけて解析するまで、音が変になるという話もあるようです。(ウチではまだきちんと音が鳴っていため、確認不可。)
 その他、SHOUTcastは「Icy-MetaData: 1」オプションを指定すれば、曲名やアーティスト名等を含んだメタデータをストリーム中に挟み込んで送ってくれますが、今回は表示することを考えていないのと、メタデータの除去がめんどくさい(笑)のとで、このオプションは使用しないこととしました。

 参考サイト(1):プログラミングSHOUTcastその1
 参考サイト(2):SHOUTcastについて調べてみる


 動作テスト(失敗)

 とりあえず、ソフトウェアの大枠を確定するため、まずは、シンプルな構成で書いてみました。
 TCPソケットを使ってHTTP接続し、接続が確立したらMP3データを受信してVS1011Eに転送する、というものです。

 あわよくば、そのまま実用になるかと、淡い期待も持ったのですが、そうは甘くありませんでした(当然か)。
 再生はしてくれるのですが、音がブチブチに切れてしまい、とても聴けたものではありません。
 また、受信バッファのサイズを大きくもしてみましたが、再生/無音のインターバルが長くなるだけで、本質的な解決にはなりません。

 加えて、不可解なことがいくつか起こっています。
  1. 音のテンポが遅い(その後進展あり>動作テスト・その後
    iTunesで同じ局を再生してみると、明らかにテンポが遅いです。それに、ジュルジュルというデジタルノイズ?のようなものがまとわりついています。
    H8ボードのクロック(20MHz)が遅いのか、VS1011Eのクリスタルが適合していないのか、それとも他に原因があるのか、良く分かりません。(ソフト上での、周波数やクロックダブラの設定はサンプル通り、の筈。)

  2. 受信バッファの使い方を変えると音が変わる
    TCPからの読み込みは小刻みに、VS1011Eへの流し込みはある程度まとまって、という方式の場合、例えばbuf[48000]としてTCP_Readすると、
    ・1,000byteずつ48回読み込んだところで転送すると、音が早回し&切れ切れになってしまう。
    ・1,500byteずつ32回読み込んだところで転送すると、(テンポは遅いままではあるものの)音としては聴けるものになる。
    当初は境目のアドレスがずれてしまったかと思ったのですが、dumpしたら正常でした。
    本来、転送されるデータは同じものになる筈なのですが、なぜ違いが出るのかが分かりません。
    (境界としては、1,470byte近辺になるのですが、何か意味はあるのだろうか…)

  3. 転送時間が再生時間とほぼ同じ
    計測が適切でない可能性も考えられますが、TCPアクセスLEDを見ると、再生中はほとんど読みにいっていないことから、そう大きなずれはないと思われます。
    ハードウェアの性能がこんなもの?という疑問も残りますが、少なくともOSはこの時間が過ぎるまで次のステップに進んでくれません。
    もし、転送時間を短縮化できないのであれば、どんなソフトの組み方をしても、途切れずに再生させることは不可能です。
    転送中の割り込みは抑止していますし、他に短縮化に寄与しそうな要因は(今のところ)見つかっていません。


 一応マルチタスク化(邪道?)

 前述の、特に3項が解決しない限り、何をやっても無駄な気もしますが、このまま何もしないのもあれ(笑)なので、やれることをやってみます。

 TCPアクセスとVS1011Eへの転送を同時進行的に行う方法として、マルチスレッド化が考えられます。
 しかし、MESはスレッドを発行できないようです。
 ただし、MESはマルチタスクOSなので、複数プロセスの同時進行(タイムシェアリング)は可能です。

 問題はプロセス間でのデータの共有化ですが、子プロセスを起動するときに親プロセスの変数のアドレスを渡すと、子側で親の変数の内容を参照したり、書き換えたりできるようになります。(以下は一例

 とりあえず実装し、実行してみると、シングルタスク版とあまり変わりません。
 まぁ、予想通りでしたが、やはり転送時間がネックになっています。


 一応マルチタスク化(正統)

 上述のようなアクロバチックな方法を使わずとも、OS側で共有化のための手段を用意してくれていました。(以下は一例)
 tasc.c
#include <string.h>
#include <mes2.h>

int main(int argc, char **argv)
{
	int vflg, buff, ret, i;
	char *vflg_a, *buff_a, *argv2[4];
	
	vflg = shm_get(1, 4);  // 共有メモリ識別子1を取得(vflg)
	if(vflg == -1) {
		printf("shm_get error!\r");
	}
	
	vflg_a = shm_at(vflg);  // 共有メモリ識別子1に対応したメモリを確保(vflg)
	if((int)vflg_a == -1) {
		printf("shm_at error!\r");
	}
	
	buff = shm_get(2, 256);  // 共有メモリ識別子2を取得(buff)
	if(buff == -1) {
		printf("shm_get error!\r");
	}
	
	buff_a = shm_at(buff);  // 共有メモリ識別子2に対応したメモリを確保(buff)
	if((int)buff_a == -1) {
		printf("shm_at error!\r");
	}
	
	*vflg_a = 16;
	strcpy(buff_a, "asdfghjkl");
	
	printf("task.vflg.0 = %d\r",*vflg_a);  // 現在の値(vflg)
	printf("task.buff.0 = %s\r",buff_a);  // 現在の値(buff)
	
	argv2[0] = "task2.elf";
	argv2[1] = "&";  // Background Job
	exec(2,argv2);  // 子プロセス(task2.elf)の起動
	
	sleep(3000);  // 子プロセスが終了する(であろう時間)まで待つ
	
	printf("task.vflg.1 = %d\r",*vflg_a);  // 子プロセスで変更された後の値(vflg)
	printf("task.buff.1 = %s\r",buff_a);  // 子プロセスで変更された後の値(buff)

	ret = shm_dt(vflg);  // 共有メモリ識別子1に対応したメモリを破棄(vflg)
	if(ret == -1) {
		printf("shm_dt error!\r");
	}
	
	ret = shm_dt(buff);  // 共有メモリ識別子2に対応したメモリを破棄(buff)
	if(ret == -1) {
		printf("shm_dt error!\r");
	}

}
 tasc2.c
#include <string.h>
#include <mes2.h>

int main(int argc, char **argv)
{
	int vflg, buff, ret;
	char *vflg_a, *buff_a;

	vflg = shm_get(1, 4);  // 共有メモリ識別子1を取得(vflg)
	if(vflg == -1) {
		printf("shm_get error!\r");
	}
	
	vflg_a = shm_at(vflg);  // 共有メモリ識別子1に対応したメモリを確保(vflg)
	if((int)vflg_a == -1) {
		printf("shm_at error!\r");
	}
	
	buff = shm_get(2, 256);  // 共有メモリ識別子2を取得(buff)
	if(buff == -1) {
		printf("shm_get error!\r");
	}
	
	buff_a = shm_at(buff);  // 共有メモリ識別子2に対応したメモリを確保(buff)
	if((int)buff_a == -1) {
		printf("shm_at error!\r");
	}
	
	printf("task2.vflg.0 = %d\r",*vflg_a);  // 親プロセスがセットした値(vflg)
	printf("task2.buff.0 = %s\r",buff_a);  // 親プロセスがセットした値(buff)
	
	*vflg_a = 123;
	strcpy(buff_a, "qwertyuio");
	
	printf("task2.vflg.1 = %d\r",*vflg_a);  // 変更後の値(vflg)
	printf("task2.buff.1 = %s\r",buff_a);  // 変更後の値(buff)

}

 今後の課題

 問題が多すぎて、どこから手をつけていいか分からない状態です。
 OSとの相性も考えられなくはないため、場合によってはTOPPERSあたりを導入して比較してみるとか、H8のクリスタルを25MHzに交換してみるとか、けっこう敷居の高い(というか、めんどくさい)作業になりそうで、躊躇しています。
(こうなると、EasyMP3 モジュールを流用した、スタンドアロンのMP3プレーヤを考えた方が実用的かも…。)

 手をつけ易いのは、例えば「ポート4(ポートA)ではなく、ポートB(SCI2)を使う」辺りだとは思いますが、シリアルポートの使い方が良く分かっていないので、まずは勉強からです。

 という訳で、後編はいつになることやら…。


 動作テスト・その後

 音のテンポが遅い問題については、その後進展がありました。
 iTunesで同じ局を再生して比較しても、耳でチェックした限りでは遅れを感じないレベルまで改善可能なことを確認しました。
 とはいえ、ちょっと油断すると、また元に戻るという、かなりきわどい状況にあることは確かなようです。

 どうも、データ転送ループ内を極力シンプル化するあたりにカギがありそうです。
(例えば、ループ内にif文を置いたりするとダメなようです。この辺はコンパイラの最適化とも絡むので、一筋縄ではいかない感じ。)
 あと、DREQのチェックを32バイトごとに行うように変更したのも効いている?感じです。

 また、ジュルジュルというデジタルノイズ?のようなものも、バッファ(の配列)サイズを変化させると、出たり出なかったり、とこちらも微妙だったのですが、あちこちいじっているうちに、ようやく落ち着いてきました。
(このパターンでありがちなのが、プログラムのバグでどこかの領域を破壊していて、配列サイズを変化させると、破壊領域が無害な場所に移動して、見かけ上、正常にみえる、というものですが、こういうのが最も厄介だったりして…。)

 あと、シリアルポート(SCI2)も試してみましたが、書き込んだバイト数が0、つまり書き込めない状態になってしまいます。
 config.sysとか、何か設定が足りないのかもしれません。
(mmc1を消さないといけないのかな?ROM書き込みはあまり何度もやりたくないので、どうするか悩ましいところ。)


 お世話になったサイト

 有用なソフトウェアおよび貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):プログラミングSHOUTcastその1
 参考サイト(2):SHOUTcastについて調べてみる


 更新履歴

 2008.12.22 動作テスト・その後を追加
 2008.12.16 一応マルチタスク化(正統)を追加、「一応マルチタスク化」に「(邪道?)」を付加し、ソースを別ページ化。
 2008.12.12 新規作成


[Home]  [MacSoft]  [Donation]  [History]