Last modified:
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 プログラミング(建設中)が、かんたんでした。
実は本件は仕事のために調べていた。 依頼元が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 */
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))