Last modified:

フリーソフトウェア


2002/12/20

■V4L2 - Video For Linux Two アプリケーションサンプルソース

video4linux ←検索引っかけ用ワード

 Philipsのチップ SAA7130が載ったビデオキャプチャカードを Linuxで使ってみようとしたら、V4L2用ドライバしかないことに 気がついた。しかもV4L2は未だ開発中らしく、サンプルソースを見つけてきても 古くて使えなかったりする。 xawtvのソースを参照 & saa7134ドライバにprintkを挿入したりして観察しながら しばらくいじっていたら、やっと、とりあえずキャプチャができるようになった。

使ってみると、V4Lでは、mmap()する必要があった(と思う)が、 V4L2では、設定したら、read() するだけでキャプチャができる。便利だ。

せっかくなので 「V4L2デバイスから、1フレーム、rawなRGBデータでメモリに取り込む」 サンプルソースをここに置いておきます。
(オーバレイプレビューなどは、このサンプルでは扱ってません)
日本語コメントつき。 勝手に使ってください。 ただし上記のようにV4L2はこれからもコロコロ変わりそうなので要注意。 変わってしまったら、 どなたか、変更点をフォローしてまたサンプルソースを公開してくれると嬉しい。

参考:
BTTV2 on Video2Linux Two(tamaki@ie.niigata-u.ac.jp氏のページ)
saa7134 v4l2 driver
V4Lについては かんたん Video4Linux プログラミング(建設中)が、かんたんでした。


kernel 2.4.20にv4l2 new patchを当て、saa7134-0.2.2を動かした。
→数十フレームはキャプチャできるが、止まってしまう。うーん。

v4l2 old patchにして、saa7134-0.1.11にしてみるか…。
→やっぱり止まる。mmap()使用に書き直してみたがやはりダメ。ううーん。

私のプログラム(だけ?)がオカシイのではないという傍証のため、 xawtv付属のconsole/webcam を実行してみる。
→やはり、同様に、しばらくすると止まる。

2002/12/24
デバッグprintkをonにしてみる→dma timeout らしい?
→PCIが不安になり、Intel, VIA, SiSの各チップセットを試す。
→→どれでも、止まる…。

2002/12/24
Bt878 + bttv 0.9.3 は止まらないのになあ…。
ただし、以下の、私のサンプルソースでは、VIDIOC_S_FMT がエラーになる。 なぜだろう。
→とりあえず、なぜか?は追求せず、V4Lインタフェイスを使ったプログラムで試す。
→これは動く。しかし(V4L2対応の)、bttv 0.9.3 なのに、 v4l1-compatはロードされていない。のに、うごく。これでいいのだろうか?
→→bttvはV4Lのインタフェイスも内蔵しているので、これでいいのだ。
→V4L2でも動いた。 bttvではfmt.fmt.pix.bytesperline = 3 * w; するとエラーになる(0以外だとエラー)のだった。 あと、brightness等の設定値がsaaとは大幅に異なるのも要注意だ。

実は本件は仕事のために調べていた。 依頼元がSAA7130を使用予定だったのだが、Bt878に換えてもいいとのことなので、 これ以上SAA7130については追求しないことにした。

2002/12/25
書き直したサンプルソースを載せます。
V4L2新旧両API対応(条件コンパイル)。
mmap()/read()対応(条件コンパイル)。


/* video4linux 2 サンプルソース
   2002/12/20 linux-2.4.20, saa7134-0.2.2で動作確認
              read()使用
   2002/12/25 linux-2.4.20, bttv-0.9.3で動作確認
              mmap()にも対応

   ときどき、 3 という定数がでてくるが、 RGB24ビット==3バイト の意。
*/

/* bttv を使う場合はdefineする */
#define USE_BTTV

/* V4L2 2002/12版以降の版?を使う場合、define V4L2NEWする */
#define V4L2NEW

/* キャプチャにread()を使うか、mmap()を使うか。どちらかをdefineする */
#define USE_MMAP
//#define USE_READ

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>

//#include <linux/types.h>
//#include <linux/time.h>

/* 2002/12/24
   videodev2.hは、linux/time.h を使いたがるが、
   sys/time.hとバッティングしてしまう。
   videodev.h, videodev2.h をローカルにコピーし、
   videodev.h 内の #include <linux/videodev2.h> を "videodev2.h" に書き換え、
   videodev.2内の #include <linux/time.h> をコメントアウトした
*/
#include "videodev.h"

#include <errno.h>


char *device = "/dev/video0";

int vfd = -1;
struct v4l2_capability  cap;
struct v4l2_streamparm  streamparm;

struct v4l2_fmtdesc     fmtdesc[10];

int use_std;
#ifdef V4L2NEW
struct v4l2_standard    std[10];
#else
struct v4l2_enumstd     std[10];
#endif

int use_input;
struct v4l2_input       inp[10];

struct v4l2_format      fmt;

/* デフォルト(起動時の)ピクチャーコントロール  */
/* 注:これはsaa7134の場合。bttvの場合はすべて32767が初期値だった。
   bttvに↓のような値を設定してしまうと真っ暗になってしまい、
   ちゃんと動いていないのだと思いこんでしまう奴がいる(←オレ)ので注意。 */
int default_brightness = 128;
int default_contrast = 68;
int default_hue = 0;
int default_saturation = 64;

#ifdef USE_MMAP
struct v4l2_requestbuffers reqbuf;
struct v4l2_buffer vbuf;
unsigned char *map;
#endif

// 1画面分のバッファ。あくまでサンプルなので固定で確保した。
unsigned char fbuf[1024*768*3];

/* ビデオフォーマット(解像度)の対応確認  or 設定 */

static int try_set_format(char flag, int w, int h)
{
  int ret;

  memset(&fmt, 0, sizeof(struct v4l2_format));
#ifdef V4L2NEW
  fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
#else
  fmt.type = V4L2_BUF_TYPE_CAPTURE;
#endif
  fmt.fmt.pix.width = w;
  fmt.fmt.pix.height = h;
  fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
  // saa7134は BGRのみ対応
  //fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
#ifdef V4L2NEW
  fmt.fmt.pix.field = V4L2_FIELD_ANY;
#else
  fmt.fmt.pix.depth = 24;
  fmt.fmt.pix.flags = V4L2_FMT_FLAG_INTERLACED;
#endif
  // bytesperlineは勝手に設定してくれる模様→うそ?
#ifdef USE_BTTV
  /* bttv (0.9.3で確認)は、bytesperlineに0以外を入れるとエラーにしてしまう */
  fmt.fmt.pix.bytesperline = 0;
#else
  /* saa7134でも、 3 * wを代入する必要はない(0でいい)のかもしれない。未確認 */
  fmt.fmt.pix.bytesperline = 3 * w;
#endif

#ifdef V4L2NEW
  fmt.fmt.pix.sizeimage =   0;
#else
  fmt.fmt.pix.sizeimage =   fmt.fmt.pix.bytesperline * h;
#endif

#ifdef V4L2NEW
/* Try or Set Video Format */
  if (flag == 'S') {
    ret = ioctl(vfd, VIDIOC_S_FMT, &fmt);
  } else {
    ret = ioctl(vfd, VIDIOC_TRY_FMT, &fmt);
  }
#else
  ret = ioctl(vfd, VIDIOC_S_FMT, &fmt);
#endif
  printf("%c %dx%d,   ret=%d, capability %dx%d bytesperline=%d sizeimage=%d\n",
         flag, w, h, ret,
         fmt.fmt.pix.width, fmt.fmt.pix.height,
         fmt.fmt.pix.bytesperline, fmt.fmt.pix.sizeimage);
  return ret;
}

/* brightness, contrast等の取得 */

static int get_ctrl(char *name, int id, int *value)
{
  struct v4l2_control ctrl;

  ctrl.id = id;
  if (ioctl(vfd,  VIDIOC_G_CTRL, &ctrl) < 0) {
    return -1;
  }
  printf("now %s = %d\n", name, ctrl.value);
  *value = ctrl.value;
  return 0;
}

/* brightness, contrast等の変更 */

static int change_ctrl(char *name, int id, int value)
{
  int now;
  struct v4l2_control ctrl;

  if (get_ctrl(name, id, &now) != 0) {
    return -1;
  }
  ctrl.id = id;
  ctrl.value = value;
  if (ioctl(vfd,  VIDIOC_S_CTRL, &ctrl) < 0) {
    return -1;
  }
  printf("change %s = %d\n", name, ctrl.value);
  return 0;
}

/* 初期化 */

int init_v4l2(void)
{
  int i, ret;
#ifdef V4L2NEW
  v4l2_std_id id;
#endif

  vfd = open(device, O_RDWR);
  if (vfd < 0) {
    printf("cannot open %s\n", device);
    vfd = -1;
    return -1;
  }

  /* v4l2デバイスのquery */
  if (ioctl(vfd,  VIDIOC_QUERYCAP, &cap) < 0) {
    printf("VIDIOC_QUERYCAP err\n");
    goto _init_v4l2_error_return;
  }

#ifdef USE_READ
#  ifdef V4L2NEW
  if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
#  else
  if (cap.type != V4L2_TYPE_CAPTURE) {
#  endif
    printf("%s is not a video capture device.\n", device);
    goto init_v4l2_error_return;
  }
#endif

#ifdef USE_MMAP
  /* STREAMING に対応しているか問い合わせる */
#  ifdef V4L2NEW
  if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
#  else
  if (!(cap.flags & V4L2_FLAG_STREAMING)) {
#  endif
    printf("Device %s doesn't support streaming().\n", device);
    goto init_v4l2_error_return;
  }
#endif

#ifdef USE_READ
#  ifdef V4L2NEW
  if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
#  else
  if (!(cap.flags & V4L2_FLAG_READ)) {
#  endif
    printf("Device %s doesn't support read().\n", device);
    goto init_v4l2_error_return;
  }
#endif

  /* 入力端子を列挙する */
  use_input = -1;
  for (i = 0; i < 10; i++) {
    inp[i].index = i;
    if (ioctl(vfd, VIDIOC_ENUMINPUT, &inp[i]) < 0) {
      break;
    }
    /* S端子はどれか探してみる */
    printf("input %d: %s\n", i, inp[i].name);
    if (strcmp(inp[i].name, "S-Video") == 0) {
      printf("  S-Video\n");
    }
  }

  /* 対応Video Standard(Videoフォーマット(NTSC/PAL..)の列挙 */
  use_std = -1;
  for (i = 0; i < 10; i++) {
    std[i].index = i;
    if (ioctl(vfd, VIDIOC_ENUMSTD, &std[i]) < 0) {
      break;
    }
    /* NTSCに対応しているか探してみる */
#ifdef V4L2NEW
    printf("std %d : id : %x\n", i, std[i].id);
    if (std[i].id & V4L2_STD_NTSC_M) {
      printf("  NTSC\n");
      use_std = i;
    }
#else
    printf("std %d : id : %x\n", i, std[i].std.colorstandard);
    if (std[i].std.colorstandard == V4L2_COLOR_STD_NTSC) {
      printf("  NTSC\n");
      use_std = i;
    }
#endif
  }

  /* fmtdescの列挙(データ形式 (RGB, YUV..) */
  for (i = 0; i < 10; i++) {
    fmtdesc[i].index = i;
#ifdef V4L2NEW
    fmtdesc[i].type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
#endif

#ifdef V4L2NEW
    if (ioctl(vfd, VIDIOC_ENUM_FMT, &fmtdesc[i]) < 0) {
#else
    if (ioctl(vfd, VIDIOC_ENUM_PIXFMT, &fmtdesc[i]) < 0) {
#endif
      break;
    }
    /* RGB 24bitに対応しているか探してみる */
    printf("format %d : pixelfotmat:%x desc:%s\n",
	   i, fmtdesc[i].pixelformat, fmtdesc[i].description);
    if (fmtdesc[i].pixelformat == V4L2_PIX_FMT_RGB24) {
      printf("V4L2_PIX_FMT_RGB24\n");
      use_fmtdesc = i;
    }
    if (fmtdesc[i].pixelformat == V4L2_PIX_FMT_BGR24) {
      printf("V4L2_PIX_FMT_BGR24\n");
      use_fmtdesc = i;
    }
  }

#if 0
  /* キャプチャパラメータの取得 ?? */
  /* やらなくてもいいと思われる */
  if (ioctl(vfd,  VIDIOC_G_PARM, &streamparm) < 0) {
    printf("VIDIOC_G_PARM err\n");
    goto init_v4l2_error_return;
  }
#endif

  /* brightness等の取得 */
  get_ctrl("BRIGHTNESS", V4L2_CID_BRIGHTNESS, &default_brightness);
  get_ctrl("CONTRAST", V4L2_CID_CONTRAST,     &default_contrast);
  get_ctrl("HUE", V4L2_CID_HUE,               &default_hue);
  get_ctrl("SATURATION", V4L2_CID_SATURATION, &default_saturation);

  /* set std (NTSCに設定) */
#ifdef V4L2NEW
  id = V4L2_STD_NTSC;
  if (ioctl(vfd, VIDIOC_S_STD, &id) < 0) {
    printf("ERROR S_STD \n");
    return -1;
  }
#else
  if (ioctl(vfd, VIDIOC_S_STD, &std[use_std].std) < 0) {
    printf("ERROR S_STD \n");
    return -1;
  }
#endif

  /* 対応フォーマットのチェック */
  try_set_format('T', 1280, 1024);
  try_set_format('T', 1024, 768);
  try_set_format('T', 800, 600);
  try_set_format('T', 720, 480);
  try_set_format('T', 640, 480);
  try_set_format('T', 320, 240);
  try_set_format('T', 160, 120);
  try_set_format('T', 100, 200);

  return 0;

 init_v4l2_error_return:
  close(vfd);
  vfd = -1;
  return -1;
}

/* 1フレーム、キャプチャする
   ch 入力端子 w,h 解像度 (ex:320, 240)
   ctrlflag !=0 であれば、brightness等を設定する
*/

int v4l2_capture1frame(int ch, int w, int h,
		       int ctrlflag, int brightness, int contrast,
		       int hue, int saturation)
{
  int i, ret, n;
  char *src, *dst, tmp;

  if (vfd < 0) {
    return -1;
  }

  /* ビデオフォーマット(解像度)の設定 */
  try_set_format('S', w, h);

  /* 入力端子の選択  */
  if (ioctl(vfd, VIDIOC_S_INPUT, &ch) < 0) {
    printf("ERROR S_INPUT \n");
    return -1;
  }

  /* 指定があれば、brightness等を設定 */
  if (ctrlflag) {
    change_ctrl("BRIGHTNESS", V4L2_CID_BRIGHTNESS, brightness);
    change_ctrl("CONTRAST", V4L2_CID_CONTRAST, contrast);
    change_ctrl("HUE", V4L2_CID_HUE, hue);
    change_ctrl("SATURATION", V4L2_CID_SATURATION, saturation);
  }


#ifdef USE_MMAP
  reqbuf.count = 1;  /* 1フレームだけキャプチャ */
#ifdef V4L2NEW
  reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
#else
  reqbuf.type = V4L2_BUF_TYPE_CAPTURE;
#endif
  ret  = ioctl(vfd, VIDIOC_REQBUFS, &reqbuf);
  if (ret < 0 || reqbuf.count != 1) {
    printf("REQBUFS error.\n");
    return -1;
  }

  vbuf.index = 0;
#ifdef V4L2NEW
  vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
#else
  vbuf.type = V4L2_BUF_TYPE_CAPTURE;
#endif
  if (ioctl(vfd, VIDIOC_QUERYBUF, &vbuf)) {
    printf("QUERYBUF error.\n");
    return -1;
  }

#ifdef V4L2NEW
  map = mmap(0, vbuf.length, PROT_READ|PROT_WRITE, MAP_SHARED,
             vfd, vbuf.m.offset);
#else
  map = mmap(0, vbuf.length, PROT_READ|PROT_WRITE, MAP_SHARED,
             vfd, vbuf.offset);
#endif
  if ((int)map == -1) {
    printf("mmap() error.\n");
    return -1;
  }

  if (ioctl(vfd, VIDIOC_QBUF, &vbuf)) {
    printf("QBUF error.\n");
    munmap(map, vbuf.length);
    return -1;
  }

  /* キャプチャ開始 */
  if (ioctl(vfd, VIDIOC_STREAMON, &vbuf.type)) {
    printf("STREAMON error.\n");
    munmap(map, vbuf.length);
    return -1;
  }
  printf("STREAMON\n");

  /* キャプチャが終了するまで待つ */
  {
    int ok = 0;
    fd_set fds;
    struct timeval tv;

    while (!ok) {
      tv.tv_sec = 1;
      tv.tv_usec = 0;
      FD_ZERO(&fds);
      FD_SET(vfd, &fds);
      switch(select(vfd + 1, &fds, NULL, NULL, &tv)) {
      case -1:
        if (errno == EINTR) {
          continue;
        }
        printf("wait capture(select()) error.\n");
        munmap(map, vbuf.length);
        return -1;
      case 0:
        printf("capture timeout.\n");
        munmap(map, vbuf.length);
        return -1;
      default:
        ok = 1;
        break;
      }
    }
  }
  printf("wait capture ok.\n");

  /* キャプチャ完了しているのでバッファをキューから外す */
  if (ioctl(vfd, VIDIOC_DQBUF, &vbuf)) {
    printf("DQBUF error.\n");
    munmap(map, vbuf.length);
    return -1;
  }

  /* キャプチャ終了 */
  if (ioctl(vfd, VIDIOC_STREAMOFF, &vbuf.type)) {
    printf("STREAMOFF error.\n");
    munmap(map, vbuf.length);
    return -1;
  }
  printf("STREAMOFF\n");

  /* map にキャプチャ出来ているので、それを使って適当に・・・ */

  /* 使い終わったらunmapする */
  munmap(map, vbuf.length);
  return 0;

#else   /* read()版 */

#ifdef USE_BTTV
  n = read(vfd, fbuf, 3 * w * h);
#else
  n = read(vfd, fbuf, fmt.fmt.pix.bytesperline * h);
#endif
  if (n < 0) {
    printf("read() returned error %d\n", errno);
    return 1;
  }

  /* fbufにキャプチャ出来ている */

  return 0;
#endif
}

/* end of sample source */


2001/02/12

■olmemos - Outlook Memo Sucker

olmemos.zip (11270 bytes)
(exeの実行にはVisualBasic(VB6)ランタイムが必要。VB6.0で書いたソース付き)

Outlookの「メモ」を、1つずつ、 そのメモの題名をファイル名とするテキストファイルに落とすプログラムです。 (Windowsのファイル名として使えない文字は、全角に変換します)
「メモ」フォルダ内のサブフォルダにも(1段だけですが)対応しています。

export先に同名ファイルがあっても、 すべて上書きしてしまいますのでご注意ください。
タイムスタンプを比較してシンクロするとかそういった高級機能は 実装していません。 (exportしたファイルのタイムスタンプも、 exportしたときのものになってしまいます)

WindowsCEのPocketOutlookでは「メモ」を扱えないので、 しかたなく作成してみました。
私は、Outlook上でメモを更新したら、同期フォルダ上にexportし、 ActiveSyncでWindowsCEに送り込んでいます。
(毎回、全ファイルを転送することになるのが困りものですが... 私はLAN経由でSyncしているのであまり気にならないです ^_^;;)
そのうち、できればタイムスタンプの操作をやってみたいです。

動作確認環境:
 Windows2000 Professional (SP1)
 VisualBasic6.0 (SP3) Professional Edition
 Outlook2000 (SP1)
 (WindowsCE HPC Edition 3.01 (NTT-Docomo/NEC Sigmarion))
 (ActiveSync 3.1 (WindowsCE syncronize manager))


管理人 yza@yza.jp
※ xo っちゅうのも同一人物です。