かんたん Video4Linux プログラミング(建設中)

Access counter: since Mon Oct 5 15:44:18 JST 1998
かんたんなんだ!

このWWWページについて

この WWW ページはまだ未完成で、書き加えるべき部分が多くあります。 文章および内容に関する間違いなどの指摘を歓迎します。 メールで お知らせください。 質問、感想などもお待ちしております。

Video4Linux とは?

Video4Linux は、その名のとおり Linux でビデオキャプチャデバイスを使うための API 仕様の名前です。 ビデオキャプチャだけでなく、チューナ操作のための API も揃っており、 これを使ってテレビや AM/FM ラジオ、Teletext などの放送も楽しむことができます。

この API に準拠したビデオデバイスには、今のところ次のものがあります。 これらのドライバは、2.1 系列のカーネルソースコードの配布に 標準的に含まれています。

やること

この文章では、 Video4Linux に準拠した Bt848 用ドライバである bttv ドライバを用いて、 Video4Linux API を用いたプログラミングの方法を説明していきたいと思います。 この Bt848 は、 メインメモリやビデオカードの VRAM に CPU を介せず直接データを転送することができる すぐれものの PCI ビデオキャプチャチップです。

今回用いるカードは KOUWELL KW-606 という PCI カードです。 秋葉原で 10,000 円くらいで多くでまわっています。 私は、今は亡き A-Master で購入しました。 KW-606 にはビデオ入力に S 端子入力、さらにチューナがついています。 このチューナも Video4Linux を使って操作することができます。

さて、 この Video4Linux の機能を駆使して X Windows System でテレビを見たりするのは xawtv や xwintv といった優秀なアプリケーションにまかせておくことにして、 本稿ではとりあえずキャプチャした結果をメインメモリに 効率よく取りこむ方法について説明します。 メモリに取りこめてしまえば、もうこっちのものですからね。 煮るなり焼くなり好きなよーにして遊んでください。

今回用いる bttv ドライバは Linux カーネル 2.1.119 に付属のものですが、 Linux 2.0 系列をお使いの方も、 bttv ドライバの WWW ページ から最新のドライバをインストールすれば、 同じようにプログラミングできるはずです。

なお KOUWELL KW-606 でなくとも、 Bt848 を塔載した安価なビデオキャプチャカードは、 多くの種類が出回っています。それらのカードでは本稿のプログラムは 少々の変更で動作させることが可能と思われます。 また、Bt848 を使ったカードでなくても、API が Video4Linux に準拠した デバイスドライバがあれば、同じ流れが適用できるはずです。

ビデオキャプチャの手順

bttv ドライバを用いる場合のビデオキャプチャの手順は、だいたい以下に従います。 bttv 以外の場合、ドライバによっては mmap など一部の操作が その機能がないために実行できなかったりします。 その詳細は、最初の Video4Linux デバイス仕様の取得によって 知ることができます。

  1. Video4Linux デバイス仕様の取得
    キャプチャカードに関する情報を得ます。 キャプチャカードの素性を決めうちしているのなら、 やらなくてもかまいません。

  2. 使用可能なチャネル情報の取得
    キャプチャカードで使用可能なチャネル(映像ソース)の種類を取得します。 これも、キャプチャカードの素性を決めうちしているのなら やる必要はありません。

  3. キャプチャするチャネルの設定
    キャプチャするチャネルを選択します。 同時に、映像信号のフォーマット(NTSC, PAL, SECAM など)も 選択します。

  4. ピクチャの設定
    明るさ、コントラスト、ヒューなどのパラメータを設定します。

  5. mmap
    mmap システムコールによって、 キャプチャデータを取得するためのバッファ領域を確保します。

  6. キャプチャ開始の指示
    mmap したバッファに対して、 キャプチャを開始するように指示します。

  7. キャプチャ終了まで待つ
    他にすることがなければ、キャプチャ処理が終了するまで待ちます。

  8. 画像処理
    キャプチャ処理の結果を用いて、目的の画像処理を行います。

具体例による説明

さて、それでは上記の手順に従ったサンプルのソースコード v4ltest.cを見ていくことにします。 常に API 仕様 およびヘッダファイル videodev.hを参照しながら、このソースコードと以下の説明を読むようにしてください。

Video4Linux デバイス仕様の取得

デバイス仕様を取得するには、 オープンした Video4Linux デバイスに対して VIDIOCGCAP ioctlを発行します。 ioctl については各種 UNIX の解説書および マニュアルページを参考にしてください。 ほとんど ioctlの練習のようなコードです。

    27	  struct video_capability vd;
   ...
    44	  /* Video4Linux デバイス仕様の取得 */
    45	  if(ioctl(fd, VIDIOCGCAP, &vd)<0) {
    46	    perror("ioctl(VIDIOCGCAP)");
    47	    return -1;
    48	  }

これをやるとvdの中に fd がさす Video4Linux デバイスの仕様が格納されます。 struct video_capability の詳細については、 API 仕様 およびヘッダファイル videodev.h を参照してください。 v4ltest.cの 49--68 行目でこの結果をすべて書きだしています。 KW-606 で実行した場合は、次のようになりました。

	vd.name: "BT848(Miro)"
	vd.type=0x000000ef CAPTURE TUNER TELETEXT OVERLAY CLIPPING FRAMERAM SCALES
	vd.channels=4
	vd.audios=4
	vd.maxwidth=768
	vd.maxheight=576
	vd.minwidth=32
	vd.minheight=32

使用可能なチャネル情報の取得

チャネルの数は VIDIOCGCAP ioctl によって struct video_capabilitychannels メンバに格納されているので、 その数だけ VIDIOCGCHAN ioctl を発行して すべてのチャネルに関する情報を集めます。

    28	  struct video_channel vc[10];
   ...
    78	  /* 各チャネル仕様取得 */
    79	  for(n=0; n<vd.channels; n++) {
    80	    vc[n].channel=n;
    81	    if(ioctl(fd, VIDIOCGCHAN, &vc[n])<0) {
    82	      perror("ioctl(VIDIOCGCHAN)");
    83	      return -1;
    84	    }
   ...
    98	  }

struct video_channel の詳細については、 API 仕様 およびヘッダファイル videodev.h を参照してください。 v4ltest.cの 85--97 行で、各チャネルに対してこの結果を書きだしています。 KW-606 で実行した場合は、次のようになりました。

	vc[0].channel=0
	vc[0].name="Television"
	vc[0].tuners=1
	vc[0].flags=0x00000003 TUNER AUDIO
	vc[0].type=0x00000001 TV
	vc[0].norm=0

	vc[1].channel=1
	vc[1].name="Composite1"
	vc[1].tuners=0
	vc[1].flags=0x00000002 AUDIO
	vc[1].type=0x00000002 CAMERA
	vc[1].norm=0

	vc[2].channel=2
	vc[2].name="SVHS"
	vc[2].tuners=0
	vc[2].flags=0x00000002 AUDIO
	vc[2].type=0x00000002 CAMERA
	vc[2].norm=0

	vc[3].channel=3
	vc[3].name="Composite3"
	vc[3].tuners=0
	vc[3].flags=0x00000002 AUDIO
	vc[3].type=0x00000002 CAMERA
	vc[3].norm=0

キャプチャするチャネルの設定

キャプチャの対象とするチャネルを VIDIOCSCHAN ioctl によって選びます。 ここでは、 KW-606 であることを前提にして チャネル #1("Composite1", ビデオ端子入力) を選択するように決めうちしてしまっています。

   101	  /* "Composite1"を選択 */
   102	  vc[1].norm=VIDEO_MODE_NTSC;
   103	  if(ioctl(fd, VIDIOCSCHAN, &vc[1])<0) {
   104	    perror("ioctl(VIDIOCSCHAN)");
   105	    return -1;
   106	  }

この ioctlAPI 仕様 によると整数の引数をとるとされていますが、 Linux 2.1.119 付属の bttv ドライバでは上記のように struct video_channel を渡さないと ioctlがエラーになってしまいました。

API 仕様 には言及がありませんが、 さらに重要な設定として、 struct video_channel のメンバ norm に、ビデオフォーマットを指定する値を書きこんでおきます。 この値は ヘッダファイル videodev.h にある 次の値がそのまま使えるようです。

	#define VIDEO_MODE_PAL          0
	#define VIDEO_MODE_NTSC         1
	#define VIDEO_MODE_SECAM        2
	#define VIDEO_MODE_AUTO         3

以上については bttv を用いたアプリケーションである xawtv のソースコードを参考にしました。 これらは、bttv ドライバ固有の仕様の可能性があります。

ピクチャの設定

明るさや色彩の設定を、 VIDIOCSPICT ioctlによって変更できます。

    29	  struct video_picture vp;
   ...
   109	  /* 色調とかの設定(たいがい 0-65535) */
   110	  vp.brightness=32767;
   111	  vp.hue=32767;
   112	  vp.colour=32767;
   113	  vp.contrast=32767;
   114	  vp.whiteness=32767;
   115	  vp.depth=24;			/* color depth */
   116	  vp.palette=VIDEO_PALETTE_RGB24; /* パレット形式 */
   117	  if(ioctl(fd, VIDIOCSPICT, &vp)) {
   118	    perror("ioctl(VIDIOCSPICT)");
   119	    return -1;
   120	  }

struct video_picture の詳細については、 API 仕様 およびヘッダファイル videodev.h を参照してください。 設定値は 0 から 65535 に正規化されています。 なお、メンバ depth および palette の設定は、 このサンプルプログラムでは意味を持たないようです。

mmap

mmap システムコールによって、 Bt848 がデータを転送するメモリ空間を指定します。 mmap で確保すべきメモリの量は、 VIDIOCGMBF ioctl によって取得できます。

    30	  struct video_mbuf vm;
   ...
   123	  /* mmap 情報の取得 */
   124	  if(ioctl(fd, VIDIOCGMBUF, &vm)<0) {
   125	    perror("ioctl(VIDIOCGMBUF)");
   126	    return -1;
   127	  }

struct video_mbuf の詳細については、 API 仕様 およびヘッダファイル videodev.h を参照してください。 v4ltest.cの 128--132 行で、この結果を書きだしています。 KW-606 で実行した場合は、次のようになりました。

	vm.size=0x002a2000
	vm.frames=2
	vm.offsets[0]=0x00000000
	vm.offsets[1]=0x00151000

メンバ size は、 mmap によって確保すべきメモリの量です。 メンバ frames はこのデバイスが 扱うことのできるフレームの数です。 メンバ offsets は、各フレームの mmapによって得られたメモリ領域の先頭からのオフセットを格納する、 要素が frames 個の配列です。 この例ではフレームは 2 つ存在し、 それぞれのフレームは 0x151000 の大きさを持つことがわかります。 合計のサイズは 0x15100*2=0x2a2000 です。

この struct video_mbuf の内容を用いて、 mmap システムコールを発行します。 mmap システムコールの詳細については各種 UNIX の解説書および マニュアルページを参考にしてください。

    	  char *map;
   ...
   135	  /* mmap */
   136	  if((map=mmap(0, vm.size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0))==(char *)-1) {
   137	    perror("mmap");
   138	    return -1;
   139	  }

これによって、 Bt848 用のキャプチャデータが転送されるバッファが map を先頭とするメモリ空間に 2 画面分確保されました。

キャプチャ開始の指示

さて、以上でキャプチャを行う準備はできました。 実際にキャプチャを始めるには、VIDIOCMCAPTURE ioctl を発行します。

    31	  struct video_mmap vmm;
   ...
   142	  /* frame #0 にキャプチャ開始指示 */
   143	  vmm.frame=0;
   144	  vmm.width=WIDTH;
   145	  vmm.height=HEIGHT;
   146	  vmm.format=VIDEO_PALETTE_RGB24;
   147	  if(ioctl(fd, VIDIOCMCAPTURE, &vmm)<0) {
   148	    perror("ioctl(VIDIOCMCAPTURE)");
   149	    return -1;
   150	  }

struct video_mmap の詳細については、 API 仕様 およびヘッダファイル videodev.h を参照してください。 メンバ frame に、キャプチャすべきフレーム番号を指定します。 フレームは struct video_mbuf の メンバ frames の数だけあります。 さらに、キャプチャのサイズおよびフォーマットは、 メンバ width, height, format によって このときに指定することができます。

この ioctl システムコールは キャプチャが完了する前に終了し、すぐに制御が戻ります。

キャプチャ終了まで待つ

VIDIOCMCAPTURE ioctlを発行しただけでは キャプチャ処理はまだ完了していないので、 画像処理に移る前にこれを待たねばなりません。 それには VIDIOCSYNC ioctl を発行します。 この ioctl はキャプチャ処理終了を待つべきフレーム番号を指定する、 整数の引数をひとつとります。

          int n;
   ...
   153	  /* frame #0 キャプチャ終了待ち */
   154	  n=0;
   155	  if(ioctl(fd, VIDIOCSYNC, &n)<0) {
   156	    perror("ioctl(VIDIOCSYNC)");
   157	    return -1;
   158	  }

この ioctl システムコールは、 キャプチャ処理が終了するまでブロックし制御を戻しません。

画像処理

以上で一画面分のキャプチャが完了しました。 あとは map で始まるバッファにアクセスして 必要な画像処理を行います。

v4ltest.cでは、 キャプチャしたデータを 標準出力に ppm フォーマットとして書きだすことにしました。 各ピクセル 24bit のパック形式で格納されていますが、 そのままでは raw ppm フォーマットとは RGB の順序が異なるため、 すべてのピクセルに対してこれを正すということをやっています。

    25	  char *map, *p;
    26	  char tmp;
   ...
   161	  /* RGB 順番の入れかえ */
   162	  for(n=0, p=map+vm.offset[0]; n<WIDTH*HEIGHT; n++, p+=3) {
   163	    tmp=p[0]; p[0]=p[2]; p[2]=tmp;
   164	  }
   165	
   166	
   167	  /* ppm の書きだし */
   168	  printf("P6 %d %d 255\n", WIDTH, HEIGHT); fflush(stdout);
   169	  write(1, map+vm.offsets[0], WIDTH*HEIGHT*3);

効率のよい連続キャプチャの方法

bttv ドライバのように、 struct video_mbuf のメンバ frames が 2 以上の場合、 キャプチャ処理と画像処理を それぞれ別のフレームに対して行うことにより、 これらを同時に行うことができます。 frames が 2 の場合を例にとって説明します。

  1. フレーム #0 に対して、 キャプチャ処理開始の ioctl を発行します (VIDIOCMCAPTURE ioctl)。
  2. フレーム #1 に対して、 キャプチャ処理開始の ioctl を発行します。
  3. フレーム #0 に対して、 キャプチャ処理が終了するのを待ちます (VIDIOCSYNC ioctl)。
  4. フレーム #0 に対して、 画像処理を行います。
  5. フレーム #0 に対して、 キャプチャ処理開始の ioctl を発行します。
  6. フレーム #1 に対して、 キャプチャ処理が終了するのを待ちます。
  7. フレーム #1 に対して、 画像処理を行います。
  8. 2. に戻ります。

VIDIOCMCAPTURE ioctl は、 以前のキャプチャ処理が終了する前に続けて発行しても有効なことに注意します。 1. と 2. で連続して VIDIOCMCAPTURE ioctl を発行した結果、 フレーム #0 のキャプチャ処理が完了すると 自動的にフレーム #1 のキャプチャ処理に移ります。

これによって、同時にキャプチャ処理と画像処理を行うことができ、 さらに CPU も効率的に活用することができるようになります。

応用例

サターンやプレイステーションなどのゲーム機の映像信号を入力し、 コンピュータビジョンの技術を駆使して人間には到底真似のできないような 完璧なプレイをやってのけるプログラムを作っていただける方はいませんか?

ぷよぷよとか格闘げーとか対戦ものだとかなり熱いかもしれません。 何種類か作って PC 同士で戦わせたりして。 PC とゲーム機コントローラとのインタフェースに関しては、 相談に応じますので、ご連絡ください。

参考資料


八重樫 剛史
takeshi@jsk.t.u-tokyo.ac.jp
Last modified: Mon Oct 5 15:44:51 1998