玩式草子─ソフトウェアとたわむれる日々

第29回Plamo Linuxで音楽三昧その3]

歴史的な災害に見舞われた2011年も残りわずかになりました。筆者が主宰しているPlamo Linuxも、残りわずかな年内に何とかx86_64版の公式リリースを出そうとラストスパートをかけています。

前回までにそのような作業中のBGMに使う音楽コレクションの話題を取りあげました。お気に入りのBGMは作業の進捗には役だつものの、それらばかりを聞いていると、ふと世の中から取り残されているような気分になることがあります。筆者の場合、そんな時に手が伸びるのがラジオです。

テレビと違って目を向けなくても済むラジオは、ディスプレイに向かい続ける作業中のよき友ではあるものの、PCの近くではノイズが乗りがちで聴きづらくなります。そのような問題を解決してくれるのが「インターネットラジオ」です。

インターネット上にラジオ番組を配信する「インターネットラジオ」は、海外ではずいぶん前から広く利用されており、日本に居ながら米国の地方FM局の番組を聴くようなことが可能です。

それに対し国内では、放送法や著作権などさまざまな理由からラジオ番組のインターネット上への配信はなかなか進みませんでしたが、ようやく最近になってradiko.jpや今回紹介する「らじる★らじる」などの取り組みが出てきました。

「らじる★らじる」とは

らじる★らじるとは、今年の10月からNHKが始めたインターネットラジオで、FMとラジオ第一、第二の3チャンネルを放送と同時にインターネットに流しています。

図1 NHKのインターネットラジオ「らじる★らじる」のページ
図1 NHKのインターネットラジオ「らじる★らじる」のページ

ホームページによると、電波の届きにくい山間部や海外の電波との混信といった従来からの問題に加え、高層ビルや電波の入りにくい鉄筋構造の家屋が増えた結果、ラジオ放送の受信が難しい世帯が増加したため、それらの対策として平成25年度まで試験的に実施することになったそうです。

最近では、ラジオなんて車の運転中くらいしか聞かない、という人がほとんどだと思いますが、筆者のようにエア・チェックを趣味にしていた世代の人間には、FMの音楽番組にはそれなりの思い入れがあります。また、ラジオ第二にはさまざまな語学番組が揃っており、無料で語学の勉強ができる魅力もあります。そこで、手元でもさっそく「らじる★らじる」を試してみました。

元々、筆者の住んでいる山あいの地区は放送局からの電波が届きにくく、テレビの難視聴解消のために共同アンテナを使っています。NHKのラジオ放送の場合、FMはテレビ用の共同アンテナで拾ってくれるので明瞭に聞くことができますが、AMのラジオ放送、特にラジオ第二は受信状態が悪く、夜間になると韓国や北朝鮮の電波も混信してきて、ほとんど聞くに耐えませんでした。

それに対し「らじる★らじる」ではインターネット経由でデータが届くため、混信が全くないクリアな音声を聞くことができます。従来からクリアに聞こえていたFMについては、音声圧縮の影響か、曲間のナレーションの際などにやや不自然な音の揺らぎを感じることもありますが、演奏が始まるとまったく気にはなりませんし、以前はノイズのためほとんど聞きとれなかったラジオ第二の英会話講座も発音の細部までクリアに聞きとれるようになりました。

しばらくはPCを使っている際のBGM的に聞いていましたが、NHKの番組表を見るとリアルタイムで聞くことの難しい早朝や深夜にも面白そうな番組がありますし、語学講座など繰替し聞いてみたい番組もあります。そう思うと、かっての「エア・チェック」マニアの虫が騒ぎだし、⁠らじる★らじる」を録音できないかと調べ始めることになりました。

「らじる★らじる」の構造

「らじる★らじる」自体はブラウザ経由で利用するようになっており、そのままでは送られてくるデータを保存することはできません。そこで、まずはブラウザ外で再生する方法を調べてみることにしました。

「らじる★らじる」はフラッシュビデオ(FLV)用とウィンドウズメデイアプレイヤー(WMP)用の2つが用意されています。ざっとページのソースコードを眺めてみると、FLV用のデータはJavaScriptのプレイヤーに処理を任せていて、データの送信元等はプレイヤーのコードを調べないとわからないのに対し、WMP用のデータはページ中で直接送信元のURLを指定しているので解読は簡単そうでした。そこで今回はWMPの形式を試してみました。

図2 ⁠らじる★らじる」のWMP用のソース
図2 「らじる★らじる」のWMP用のソース

WMP用のプレイヤーのソースコードにはコンテンツ配信サービスであるakamai.comを参照している部分があります。

<embed name="wmp" type="application/x-mplayer2" pluginspage="http://www.microsoft.com/Windows/MediaPlayer/" \
 src="http://mfile.akamai.com/129933/live/reflector:46051.asx?bkup=46055&prop=a" \
 width="215" height="26" autostart="1" showcontrols="1" showaudiocontrols="1"  \
 showpositioncontrols="0" showtracker="0"></embed></object>

どうやらこの部分がデータの送信元らしいと見当をつけ、このURLをwgetで取り寄せてみると、WMPが利用しているmmsプロトコルのURLでした。

<ASX VERSION="3.0">
 <ENTRY>
  <REF HREF="mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051" />
 </ENTRY>
</ASX>

mms(Microsoft Media Server)はMicrosoftがストリーミング用に開発した独自プロトコルです。最近ではMicrosoft自身もこのプロトコルのサポートを終了して、RTMPやHTTP経由のストリーミングに移行を進めていますが、mmsプロトコル自身はこの例のように現在でも広く利用されており、mplayerやvlcといったLinux用の主要なメディアプレイヤーもサポートしています。

このURLを引数にvlcを起動してみると、コンソールにいくつかエラーメッセージが表示されるものの、WMA2形式のストリームデータを受け取れ、NHK FMのインターネット中継が聞けるようになりました。

図3 vlcメディアプレイヤーで「らじる★らじる」の中継を再生
図3 vlcメディアプレイヤーで「らじる★らじる」の中継を再生

「らじる★らじる」の録音用スクリプト

vlcやmplayerといったメディアプレイヤーで再生が可能になれば、録音までは後一歩です。たとえば、vlcならば「変換/保存」という機能が用意されており、ストリーミングで受けとったデータをMP3等の形式に変換した上で保存することが可能です。

図4 vlcで「らじる★らじる」を変換して保存
図4 vlcで「らじる★らじる」を変換して保存

PCに向かっている時に流れている番組を録音するならこれだけでも間に合いますが、深夜や早朝の番組をタイマー録音するにはvlcの対話的な操作方法では不便です。そこで、CUI的に操作できるmplayerを試してみました。

mplayerでもmmsプロトコルのURLを引数に指定すれば「らじる★らじる」の再生が可能です。

$ mplayer mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051
MPlayer SVN-r34353-snapshot-4.5.3 (C) 2000-2011 MPlayer Team
mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051 を再生中
STREAM_ASF, URL: mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051
...
ASF ファイルフォーマットと判断.
[asfheader] Audio stream found, -aid 1
==========================================================================
音声コーデックを開いています: [ffmpeg] FFmpeg/libavcodec audio decoders
AUDIO: 44100 Hz, 2 ch, s16le, 48.0 kbit/3.40% (ratio: 6003->176400)
Selected audio codec: [ffwmav2] afm: ffmpeg (DivX audio v2 (FFmpeg))
==========================================================================
AO: [oss] 44100Hz 2ch s16le (2 bytes per sample)
Video: 映像がありません
再生開始...
A:285625.8 (79:20:25.8) of 0.0 (unknown)  0.2% 12% 

mplayerでは、-aoオプションを指定することで出力先を変更することができます。manページによると、mplayer -ao pcm:file=test.wav test.aviというオプション指定で、test.aviの音声データをPCM形式でtest.wavに保存することができるそうです。

ただし、mplayerにはvlcのような音声の圧縮形式を変換して保存する機能は見当たらないので、MP3等へ変換するためには別のコマンドが必要です。今回は、MP3形式に保存するためにLAMEエンコーダを使うことにしました。

$ mplayer -ao pcm:file=/dev/stdout -really-quiet  \
     mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051 \
     | lame -q4 - NHK-FM.mp3

このコマンドでは、mplayerで標準出力(/dev/stdout)に出力したPCM形式の音声データを、lameで標準入力(-)から受け、MP3形式に圧縮してNHK-FM.mp3に保存します。

なお、-really-quietはmplayerのメッセージ出力を抑制するオプションで、このオプションを指定しないとlame等を標準出力の先につなぐとエラーになるようです。

このコマンドをシェルスクリプトにして、atコマンドで指定した時刻に実行するようにすれば「らじる★らじる」のタイマー録音が実現できそうです。まずは録音開始時刻でデータを整理しようと、こんなスクリプトにしてみました。

#!/bin/sh
title="NHK-FM"
date=`date +"%F-%H-%M"`
mplayer -ao pcm:file=/dev/stdout -really-quiet  \
      mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051 \
      | lame -q4 - ${title}-${date}.mp3

手動でこのスクリプトを動かしてみたところ、NHK-FM-2011-12-22-13-00.mp3のようなファイルが作成できました。ただし、このスクリプトでは録音を止めるには ^C を打つしかありません。atコマンドを使ってタイマー録音する際には ^C を打って止めるような操作はできないので、別の方法を考える必要がありそうです。そのヘンは先送りすることにして、とりあえずは録音を止めたい時刻にkillall mplayerするようなスクリプトを実行することにしておきました。

録音用スクリプトの改良

しばらく上記スクリプトでタイマー録音を試してみたら、あれこれ不便な点が目に付きました。まず何よりも不便なのは、録音を止めるためにkillall mplayerしてしまうと、複数の番組を同時に録音していれば他の録音も止まってしまうことです。

サーバ系のソフトウェアでは自分のプロセスID(PID)を/var/run/あたりに記録しておいて、そのPIDで特定のプロセスを選択的に殺せるようにしている例がよくあります。しかし、mplayerはクライアント寄りのソフトウェアなので自らのPIDを記録するような機能は無さそうです。

それでは、とmplayerを起動するシェルスクリプトのPIDからmplayerのPIDを求めるような処理を考えてみました。たとえば、先に紹介したスクリプトがPID=100として実行されたとすると、内部で実行しているdateコマンドがPID=101で実行され、その次に実行されるmplayerのPIDは102になるでしょう。

こういうアイデアでスクリプトを書いてみたところ、指定時刻をすぎても録音が止まらない例が散見されました。あれれ…、と調べてみると、たとえば19:00に録音開始するスクリプトが2つあった場合、2つの録音用スクリプトが同時に起動されるためPIDの順番が乱れ、mplayerのPIDが起動したシェルスクリプトの2つ後になる、という仮定が成立しないことがわかりました。

さて、どうしたものか…、としばらく模索していたところ、mplayerにはslaveモードという特殊なモードがあることに気づきました。

-slave (also see -input)
   Switches  on  slave mode, in which MPlayer works as a backend for other pro-
   grams.  Instead of intercepting keyboard events, MPlayer will read  commands
   separated by a newline (\n) from stdin.
   NOTE:   See   -input   cmdlist   for   a   list   of   slave   commands  and
   DOCS/tech/slave.txt for their description.  Also, this is  not  intended  to
   disable  other inputs, e.g. via the video window, use some other method like
   -input nodefault-bindings:conf=/dev/null for that.

このモードでは、-input file=/tmp/inputのような形で、実行したいコマンドをキーボードではなくファイルから読み込ませることができるようです。このモードで利用可能なコマンドはmplayer -input cmdlistで一覧が表示され、mplayerを終了させる"quit"もあります。この機能を使えばPIDを使わなくても狙ったmplayerを終了できそうです。

$ mplayer -input cmdlist
radio_step_channel   Integer
radio_set_channel    String
radio_set_freq       Float
...
quit                 [Integer]
stop                
pause               
...

どういう形でslaveモードのmplayerにコマンドを送るかもしばらく試行錯誤しましたが、このような用途ならば専用のFIFOを使ってやりとりするのが簡単そうです。

FIFO経由でmplayerを操作するためには、mplayerを動かすプロセスとquitコマンドを送るプロセスの2つが必要になります。しかし、上述のスクリプトを動かした経験からすると、これら2つのプロセスを別々のシェルスクリプトにすると管理が面倒になるのが目に見えています。

そこでmplayerとlameを動かすプロセスはサブシェルで動かすような手を考えました。この方法を取る場合、指定した録音時間の間は親シェルをスリープさせておき、スリープが終了すればmplayerにquitコマンドを送ってやればいいでしょう。

#!/bin/sh
title="NHK-FM"
date=`date +"%F-%H-%M"`
mkfifo /tmp/fifo_$$
( mplayer -slave -input file=/tmp/fifo_$$ -ao pcm:file=/dev/stdout -really-quiet  \
      mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051 \
      | lame -q4 - ${title}-${date}.mp3 ) &
sleep 60m
echo 'quit' > /tmp/fifo_$$
rm -f /tmp/fifo_$$

このスクリプトでは、( .. )内に記載しているmplayerとlameは子のシェルで実行され、slaveモードのmplayerはスクリプトごとに固有の/tmp/fifo_$$でコマンドを待ち受けています。

親のシェルスクリプト側は、mplayerとlameを実行する子のシェルを起動した後、指定された時間(上記では60分)sleepしてから/tmp/fifo_$$に 'quit' コマンドを送ってmplayerを終了させます。

しばらくはこのスクリプトをテンプレートにして、番組表を見ながら録音時間を書き変えたスクリプトを用意してatコマンドに手動で登録していましたが、いろいろな番組を録り出すともっと簡単にタイマー登録したくなりました。そこで先の録音用シェルスクリプトを生成するようなシェルスクリプトを書いてみました。

#!/bin/sh
usage() {
    echo "Usage:"
    echo $0, "start_date, duration"
    echo "   start_date 録音を開始する時刻 HH:MM の書式"
    echo "   duration は録音時間を分単位で示した数字(60 = 1時間)"
    exit
}

if [ ! $# -eq 2 ]; then
   usage
fi

start=$1
duration=$2

cat <<EOF > radiru_scripts_$$
#!/bin/sh
title="NHK-FM"
date=\`date +"%F-%H-%M"\`
mkfifo /tmp/fifo_\$\$
( mplayer -slave -input file=/tmp/fifo_\$\$ -ao pcm:file=/dev/stdout -really-quiet  \
      mms://a52.l12993346051.c129933.g.lm.akamaistream.net/D/52/129933/v0001/reflector:46051 \
      | lame -q4 - \${title}-\${date}.mp3 ) &
sleep $duration"m"
echo 'quit' > /tmp/fifo_\$\$
rm -f /tmp/fifo_\$\$ radiru_scripts_\$\$
EOF

at $start -f radiru_scripts_$$

このスクリプトをradiru_rec.shのような名前で保存して、実行可能にしておけば、

$ ./radiru_rec.sh 19:30 110

のような指定で、radiru_scripts_XXX(XXXはPIDの数字)という録音用のスクリプトを生成し、atコマンドにそのスクリプトを登録するところまでまとめて行います。

かってFM番組をカセットテープに録音していたころは、カセットテープは片面30分か45分の記録時間だったので、長い番組を録る際には、テープレコーダーの近くに待機して、タイミングを見計らってテープを引っくり返したり、新しいテープに入れ替えたりする必要がありました。しかし、今回紹介したようなスクリプトを使ってHDD上に記録できるようになると、そういったわずらわしさもなく、録音したファイルの移動や削除も簡単です。

また、今回の圧縮設定(lameの-q4オプション)では、1分の録音データが1MB弱になるようなので、1GBあたりに1000分(16.6時間)ほど録音可能な計算になります。

昔は少ない小遣いのなかからカセットテープ代を捻出するのに苦労したものですが、最近の大容量HDDを使えば時間あたりのメディア代は微々たるものです。年末にかけてはラジオでもあれこれ特番も増えてくるので、久しぶりに「エア・チェック」熱が再燃してきそうです(苦笑⁠⁠。


今回紹介したスクリプトは必要最低限の機能だけを実装したサンプルで、実際に使ってみると「FMだけではなくラジオ第一や第二も録音したい」⁠今日だけでなく、明日以降の番組も指定したい」⁠録音時間の計算が面倒なので、終了時刻でも指定したい」⁠深夜放送を指定するには25:30みたいな(atコマンドでは使えない)時刻も使いたい」等々、さまざまなニーズが生じてきました。

そのようなニーズを検討した結果、シェルスクリプトで複雑な時間計算をするのは面倒そうなので、録音用スクリプトを生成するスクリプトはPythonで書き直してみました。Pythonで書いたコードは本稿で紹介するには大きめなので取りあげませんでしたが、plamo.linet.gr.jpのメンテナ日記のページで公開しているので、興味ある人はそちらをご覧ください。

今回の「らじる★らじる」録音用スクリプトでは、データのダウンロードにmplayer、エンコードにlameを使い、それらを組み合わせるためのシェルスクリプトをatコマンドで実行する、という形になっていて、筆者が実際に書いたのはシェルスクリプトの部分だけです。

この例のように、Linux/UNIXの世界では、0からソフトウェアを書き上げなくても、既存のソフトウェアをうまく組み合わせるだけで、さまざまな新しいニーズに対応できるようになっています。誰かが新しいソフトウェア書いてくれるのを待つのではなく、既存のツールを工夫して自分のニーズは自分で解決する、それがLinuxに代表されるOSSを使う面白さでしょう。

おすすめ記事

記事・ニュース一覧