2007年10月
Linux でテレビ録画を行なう方法は、 多くの Web ページで紹介されているが、 ビデオキャプチャ・カードによっては、 Linux カーネルのバージョンが変わると一筋縄にはいかなかったりするので、 現時点での Linux カーネル安定版の最新バージョン 2.6.22.10 で、 I-O DATA 製 ハードウェア MPEG-2 エンコーダ搭載TVキャプチャボード GV-MVP/RX2W (2006年7月5日生産終了) を使う方法をメモ (2.6.24.4 で使う方法)。
といっても多くの方々の努力により、 GV-MVP/RX2 は標準のカーネルのままで (多少の不具合はあるにせよ) テレビ視聴が可能になりつつあるので、 Linux 2.6.22.10 の時点で必要なパッチは、 以下の三ヵ所のみ (モノラル音声で構わなければ saa7115 に対するパッチのみ)。 なお、 IVTV プロジェクト で 最新ドライバが公開されているが、 少なくとも GV-MVP/RX2W を使う限りにおいては 既に Linux 2.6.22.10 カーネルに取り込まれているドライバだけで問題がないので、 標準のカーネルのドライバのみを使っている。
まず、SAA7115 Video Decoder ドライバ・モジュール saa7115.ko に対するパッチ:
diff -ru linux-2.6.22.10.org/drivers/media/video/saa7115.c linux-2.6.22.10/drivers/media/video/saa7115.c --- linux-2.6.22.10.org/drivers/media/video/saa7115.c 2007-08-23 08:23:54.000000000 +0900 +++ linux-2.6.22.10/drivers/media/video/saa7115.c 2007-10-14 06:37:09.000000000 +0900 @@ -1543,7 +1543,8 @@ saa711x_writeregs(client, saa7113_init); break; default: - state->crystal_freq = SAA7115_FREQ_32_11_MHZ; + state->ucgc = 1; + state->cgcdiv = 0x04; saa711x_writeregs(client, saa7115_init_auto_input); } saa711x_writeregs(client, saa7115_init_misc);
CGC (Clock Generation Circuit) の設定。 SAA7115 は 32.11MHz か 24.576MHz の水晶発振子を利用できて、 GV-MVP/RX2 では後者なのであるが、 このパッチをあてておかないと 32.11MHz の設定になってしまって、 録画したものを再生すると音声が早回しになってしまう。
次に、オーディオ・チップ・ドライバ・モジュール tvaudio.ko に対するパッチ:
diff -ru linux-2.6.22.10.org/drivers/media/video/tvaudio.c linux-2.6.22.10/drivers/media/video/tvaudio.c
--- linux-2.6.22.10.org/drivers/media/video/tvaudio.c 2007-08-23 08:23:54.000000000 +0900
+++ linux-2.6.22.10/drivers/media/video/tvaudio.c 2007-10-14 06:38:26.000000000 +0900
@@ -528,6 +528,31 @@
chip_write(chip,TDA985x_C6,c6);
}
+static int gvmvprx_getmode(struct CHIPSTATE *chip) {
+ return VIDEO_SOUND_MONO;
+}
+
+extern int tda9887_tuner_init(struct i2c_client *c);
+static void gvmvprx_setmode(struct CHIPSTATE *chip, int mode) {
+ u8 data[3] = { 0x00, 0x00, 0x04 };
+
+ switch (mode) {
+ case VIDEO_SOUND_MONO:
+ case VIDEO_SOUND_LANG1:
+ break;
+ case VIDEO_SOUND_STEREO:
+ data[1] = 0x01;
+ break;
+ case VIDEO_SOUND_LANG2:
+ data[1] = 0x02;
+ break;
+ }
+
+ if (i2c_master_send(&chip->c, data, sizeof(data)) != sizeof(data)) {
+ v4l_err(&chip->c, "%s: I/O error setting audmode\n", chip->c.name);
+ }
+}
+
/* ---------------------------------------------------------------------- */
/* audio chip descriptions - defines+functions for tda9873h */
@@ -1307,17 +1332,17 @@
.checkmode = generic_checkmode,
},
{
- .name = "tda9850",
+ .name = "tda9850 (GV-MVP/RX)",
.id = I2C_DRIVERID_TDA9850,
.insmodopt = &tda9850,
.addr_lo = I2C_ADDR_TDA985x_L >> 1,
.addr_hi = I2C_ADDR_TDA985x_H >> 1,
.registers = 11,
- .getmode = tda985x_getmode,
- .setmode = tda985x_setmode,
+ .getmode = gvmvprx_getmode,
+ .setmode = gvmvprx_setmode,
- .init = { 8, { TDA9850_C4, 0x08, 0x08, TDA985x_STEREO, 0x07, 0x10, 0x10, 0x03 } }
+ .init = { 3, { 0x00, 0x01, 0x04 } }
},
{
.name = "tda9855",
ステレオ/モノラル/音声多重(二ヶ国語放送) 音声モードの変更。 GV-MVP/RX2W は他のチューナと異なり、 設定変更の際 3 バイトのデータを送信する必要があるらしい。
tda9887 を deemphasis->off (このチップは deemphasis すると音声を強制でモノラルにしてしまう。 tda9887.c の NTSC-M-J の項では deemphasis を default で OFF にすべきなのではないでしょうか。 素人考えかな。)
ivtv-0.2.0-rc3j-paken.051002-bilingul.patch をベースに MPX 操作 (レジスタ0に3バイト書き込むほう) すればうちのカードでは音声多重に対応できるようです。 レジスタ 0 に 1 バイト書き込むほうのMPX操作では うちのカードでは動作しませんでした。 カードのリビジョンとかの関係かもしれません。ぱ研「LinuxでITVC16-STVLP」 の kazz 氏コメント (2007-03-10) から引用
DeEmphasis (DEM) モードを設定すると常にモノラル音声になってしまう。 kazz 氏が言及しているように default で DEM を off にしておけば 済むような気がするし、 そもそもなぜ DEM がデフォルトで ON になっているのか疑問だったので、 以下のようなパッチを tuner チップ・ドライバ・モジュール tuner.ko の tda9887.c にあてて、 NTSC-M-JP の場合は DeEmphasis を off にしてみた。
diff -ru linux-2.6.22.10.org/drivers/media/video/tda9887.c linux-2.6.22.10/drivers/media/video/tda9887.c --- linux-2.6.22.10.org/drivers/media/video/tda9887.c 2007-08-23 08:23:54.000000000 +0900 +++ linux-2.6.22.10/drivers/media/video/tda9887.c 2007-10-14 11:59:50.000000000 +0900 @@ -214,8 +214,7 @@ .name = "NTSC-M-JP", .b = ( cNegativeFmTV | cQSS ), - .c = ( cDeemphasisON | - cDeemphasis50 | + .c = ( cDeemphasisOFF | cTopDefault), .e = ( cGating_36 | cAudioIF_4_5 |
「cDeemphasisON | cDeemphasis50」の部分を「cDeemphasisOFF」に変更しただけの 安易なパッチ (^^;) であるが、 とりあえずこれでステレオ/音声多重での録画が可能になっている。
以上のように tvaudio と tuner (tda9887.c) の両ドライバ・モジュールに パッチをあてることにより、 ステレオ/音声多重で録画できる。 もちろん v4l2-ctl コマンドで音声モードを変更することもできるが、 録画は常に「ステレオ/音声多重」で行なっておいて、 再生時に音声モードを切替えたほうが便利。
私は録画 perl スクリプトを自作して使っているが、 この perl スクリプトで使用している Video::ivtv & Video::Frequencies モジュールには若干問題がある。 すなわち setFrequency する際にチューナ形式を指定し忘れているので、 正しく TV チャンネルの変更が行なえない。 私は以下のような修正パッチをあてて使用している。
diff -u Video-ivtv-0.13/ivtv.xs.org Video-ivtv-0.13/ivtv.xs
--- Video-ivtv-0.13/ivtv.xs.org 2004-06-14 06:07:40.000000000 +0900
+++ Video-ivtv-0.13/ivtv.xs 2007-10-08 10:03:57.000000000 +0900
@@ -132,6 +132,7 @@
}
CODE:
vf.tuner = tuner;
+ vf.type = V4L2_OUTPUT_TYPE_ANALOG;
if (ioctl(fd, VIDIOC_G_FREQUENCY, &vf) < 0)
{
RETVAL = -1;
@@ -158,6 +159,7 @@
}
CODE:
vf.tuner = tuner;
+ vf.type = V4L2_OUTPUT_TYPE_ANALOG;
vf.frequency = freq;
if (ioctl(fd, VIDIOC_S_FREQUENCY, &vf) < 0)
{
この録画スクリプトは
予約録画 (-t オプション) もサポートしている他、
tv -P 1234
などと実行すると、
TV サーバとして利用することもできる。
つまり LAN 内の任意のマシンで、
VLC media player を使って
http://senri:1234/?c=1
などとチャンネル指定付で tv サーバへ接続し、
TV を視聴できる。
(ビデオキャプチャ・カード GV-MVP/RX2W を使って Linux 2.6.24.4 でテレビ録画)
極めて稀とはいえ、Linux もハングすることはある。 ハードウェア自体には何ら異常はなく、 リセットスイッチを押したら正常に再起動してしまって、 何が問題だったか分からずじまい、という経験は誰にでもあるのではなかろうか。 原因不明のハングが全く無くなるのが理想ではあるのだが、 ハングして止ったままになるよりは、 自動的にリセットがかかって再起動してくれたほうがいい、という場合もあるだろう。
もちろんハードウェア障害が原因でハングしてしまった場合は、 リセットスイッチを押しても解決にはならない。 再起動を試みることにより、障害がより致命的になる可能性もあるので、 ウォッチドッグ・タイマーを設定する際は、 「止ったまま」と「自動再起動」とどちらがマシか天秤にかける必要がある。
そんなとき、 ウォッチドッグ・タイマー (watchdog timer) が便利。 一定時間 (例えば 30秒) 放置すると、 システムを自動的にリセットするタイマーである。 この自動リセットを回避するには、 定期的に (30秒以内に) タイマーを元に戻す (以下、番犬 (watchdog) に「蹴りを入れる」と略記) 必要があるわけで、 システムが正常に動作している時は 定期的に「蹴り」を入れ続けるようなプログラムを走らせておく。 で、 カーネルがハングしたなどの理由によって 「蹴り」を入れるプログラムが動かなくなると、 自動的にシステム・リセットがかかって ハング状態を脱出できる、という仕掛け。
ソフトウェアにどんなトラブルが起きても確実に再起動を行なわせるには、 ハードウェアで物理的にリセット スイッチを押す ハードウェアを用いるのが一番であるが、 まずはお手軽にソフトウェア版を利用してみることにした。仙石浩明の日記: ウォッチドッグ タイマ から引用
わざわざハードウェア・ウォッチドッグ・タイマーを買ってきて 組み込むのは大変と思ったので、 上に引用した日記 (2006年5月) で書いたように ソフトウェア版ウォッチドッグ (softdog.ko モジュール) を使っていたのだが、 実はインテル・チップセットであれば大抵の PC に標準で ハードウェア・ウォッチドッグ・タイマーがついていた (何たる不覚 orz)。
Intel TCO Timer/Watchdog
Hardware driver for the intel TCO timer based watchdog devices. These drivers are included in the Intel 82801 I/O Controller Hub family (from ICH0 up to ICH8) and in the Intel 6300ESB controller hub.linux/drivers/char/watchdog/Kconfig から引用
つまり Intel の ICH には最初から ハードウェア・ウォッチドッグ・タイマーがついていたようである。 最近の Linux カーネルには、 このウォッチドッグ・タイマーのドライバが含まれているので早速使ってみた。 というか、 Linux 2.6.22.9 を使っていたら、 このドライバ・モジュール iTCO_wdt が自動的に読み込まれていた (^^;) ので、 このウォッチドッグ・タイマーの存在に気づいた、という次第。 /dev/watchdog に何か書込んでみるだけで (例えば「echo @ > /dev/watchdog」を実行)、 タイマーがスタートした (/dev/watchdog が存在しない場合は、 「mknod /dev/watchdog c 10 130」を実行する)。
そして 30秒後、勝手にリセットがかかった (めでたしめでたし)。
ウォッチドッグ タイマというと、 普通は 60秒くらいに設定しておくものだとは思うが、 自宅サーバの場合、一時間くらいハング状態が続いてもそんなに困らない ;) のと、 あまりタイマの間隔が短すぎると、 不用意に再起動してしまう恐れもあるので、 3600秒 (一時間) に設定している。 つまり一時間以内にタイマをリセットしないと、 自動再起動が行なわれる。仙石浩明の日記: ウォッチドッグ タイマ から引用
じゃ、iTCO_wdt.ko モジュールでも同様に heartbeat=3600 を指定すればいいのかな と思っていたら、 heartbeat は最大 613 秒までしか設定できない (TCO v2 の場合) ようである。 わずか 10分足らずでは不用意に再起動してしまう恐れ大。 そこで、 監視プログラムが直接 /dev/watchdog に「蹴り」を入れる代わりに、 監視プログラムは /var/run/watchdog に「蹴り」を入れることにして、 /var/run/watchdog を監視して /dev/watchdog に「蹴り」を入れる 「蹴り代行」デーモンを走らせておくことにした。
つまり、監視プログラムは 20分に一度 /var/run/watchdog に「蹴り」を入れるだけで、 あとは「蹴り代行」デーモンが 5秒に一度、 /dev/watchdog に「蹴り」を入れ続けてくれる。 だからウォッチドッグ・タイマーのドライバの設定は、 デフォルト (30秒) のままで済むし、 また「蹴り代行」デーモンの設定次第で、 20分といわずもっと長い余裕を持たせることも可能。
/service/watchdog/run
#!/usr/bin/perl
use strict;
use warnings;
$| = 1;
my $watchdog_uid = getpwnam("adsl_check");
my $watchdog_gid = getgrnam("watchdog");
my $watchdog_file = "/var/run/watchdog";
my $watchdog_dev = "/dev/watchdog";
print "start\n";
if (! -f $watchdog_file) {
if (!open(WATCHDOG, ">$watchdog_file")) {
print "can't create $watchdog_file exiting...\n";
exit 1;
}
close(WATCHDOG);
chown $watchdog_uid, $watchdog_gid, $watchdog_file;
}
($(, $)) = ($watchdog_gid, $watchdog_gid);
($<, $>) = ($watchdog_uid, $watchdog_uid);
while (-z $watchdog_file) {
sleep 5;
}
print "confirmed $watchdog_file\n";
truncate($watchdog_file, 0);
if (!open(WATCHDOG, ">$watchdog_dev")) {
print "can't open $watchdog_dev exiting...\n";
exit 1;
}
select(WATCHDOG);
$| = 1;
select(STDOUT);
for (my $i=0; $i < 240; $i++) {
print WATCHDOG "\@\n";
sleep 5;
}
print WATCHDOG "\@\n";
close(WATCHDOG);
print "exiting...\n";
exit 0;
「/service/watchdog/run」というパス名からも分かる通り、 このスクリプトは daemontools 配下で動かしている。 このスクリプトは、 20分間 (5 秒 * 240) /dev/watchdog に蹴りを入れ続けると終了する。 そして daemontools がこのスクリプトを再起動すると、 /var/run/watchdog の存在を確認した上で再び蹴りを入れ続ける。 つまり、 20 分間以上 /var/run/watchdog に蹴りが入れられないと、 この「蹴り代行」スクリプトは止ってしまい、 /dev/watchdog への蹴りも止ってしまう。
ここでなぜ 20分毎にこのスクリプトを終了するようにしているかというと、 daemontools の動きも監視対象に含めたいから。 つまり、システムの負荷が高くなり過ぎて daemontools による再起動に時間がかかるような事態になっても、 /dev/watchdog への蹴りが止る。
まとめると、 /var/run/watchdog への蹴りが止ったり、 あるいは daemontools による再起動が滞ったりすると、 /dev/watchdog への蹴りも止ってしまって、 ウォッチドッグ・タイマーが時間切れになり、 ハードウェア的に自動リセットがかかる、という仕掛け。
私は他のマシンから
ssh server "echo '@' > /var/run/watchdog"
などと ssh でアクセスするよう cron に設定している。 ssh が成功すれば /var/run/watchdog へ書き込み、 すなわち蹴りが入れられるので、 蹴り代行スクリプトによってウォッチドッグ・タイマーに蹴りが入れられる。