第14章 プログラミングガイド

14.1. アプリケーションでカメラデバイスを扱う方法

ここではカメラデバイスを制御するアプリケーションを実装する方法について、C言語で実装されている既存のソースコードをサンプルに、カメラデバイスの初期化方法やデータの取得方法などについて説明します。

Linuxカーネルのカメラドライバは、Video for Linux Two(V4L2)というビデオキャプチャAPIとして実装するのが一般的です。Armadillo-810 カメラモジュール01 (Bコネクタ用)のデバイスドライバもV4L2デバイスとして実装されているため、ユーザーアプリケーションからは、このV4L2 APIで扱うことができます。

Video for Linux Two(V4L2)についての詳しい情報は、「LINUX MEDIA INFRASTRUCTURE API」の「Video for Linux Two API Specification」やLinuxの解説書籍などをご覧ください。

14.1.1. カメラデバイスを制御するシステムコール

カメラデバイスを制御するには、通常のファイルを操作する場合と同様にシステムコールを介して行われます。カメラデバイスを制御する場合に利用するシステムコールについて説明します。

システムコールのマニュアルを参照するには、次のようにします。

[ATDE ~]$ man [システムコール]

14.1.1.1. openシステムコール

ユーザーアプリケーションからV4L2 APIでカメラデバイスを制御するには、ビデオデバイスファイル(/dev/videoN)を通して行われます。デバイスファイルに対してアクションを行う場合は、通常のファイルと同様にopen()システムコールを使いファイルディスクリプタを取得します。

int open(const char *pathname, int flags);

図14.1 openシステムコールの書式


"flags"には、必ず O_RDWR (読み込み/書き込み許可)が含まれるように指定します。このフラグを指定せずにopen()した場合、ioctl()が失敗するなどの意図しない挙動となってしまう場合があります。

open()の戻り値がファイルディスクリプタとなります。open()でエラーが発生した場合は、戻り値が"-1"となります。

アプリケーションがカメラデバイスを開放する場合には、open()で取得したファイルディスクリプタを開放します。ファイルディスクリプタの開放には、close()システムコールを使います。

int close(int fd);

図14.2 closeシステムコールの書式


int fd;
fd = open("/dev/video0", O_RDWR);
if (fd == -1) {
  perror("open");
  return;
}
	     :
ファイルディスクリプタに対しての処理
	     :
close(fd);

図14.3 open()とclose()のサンプル


14.1.1.2. ioctlシステムコール

V4L2 APIを使用してカメラドライバにアクションを行うには、ioctl()システムコールを利用します。

int ioctl(int fd, int request, ...);

図14.4 ioctlシステムコールの書式


"request"には、V4L2のリクエストコードを指定します。ほとんどの場合、リクエストコードに渡すデータを第3引数に指定します。 代表的なV4L2リクエストコードを表14.1「画像キャプチャーで利用する代表的なV4L2リクエストコード」に示します。

表14.1 画像キャプチャーで利用する代表的なV4L2リクエストコード

リクエストコード説明
VIDIOC_QUERYCAP

カメラデバイスに保有する機能を問い合わせます。

アプリケーションが利用したい機能を保有しているかどうかを確認するために行います。

VIDIOC_S_FMT

VIDIOC_G_FMT

カメラデバイスに画像データのフォーマットを設定・取得します。

PixelFormatを指定する場合は、表14.2「Armadillo-810 カメラモジュール01 (Bコネクタ用)で対応可能なPixelFormat」を参照してください。

VIDIOC_REQBUFS画像データ用のバッファを要求します。
VIDIOC_QUERYBUFVIDIOC_REQBUFSで要求したバッファの情報を受けとります。

VIDIOC_QBUF

VIDIOC_DQBUF

バッファをストリーミングキューに繋いだり、外したりします。

VIDIOC_STREAMON

VIDIOC_STREAMOFF

ストリーミングを開始・停止します。開始されるとストリーミングキューに繋がれている空のバッファに画像データが書き込まれます。

VIDIOC_G_CTRL

VIDIOC_S_CTRL

コントロールの設定・取得を行います。主にホワイトバランスや色合いなどの調整に用います。[a]

[a] Armadillo-810 カメラモジュール01 (Bコネクタ用)のカメラドライバでは未対応となっています。


表14.2 Armadillo-810 カメラモジュール01 (Bコネクタ用)で対応可能なPixelFormat

PixelFromat説明

V4L2_PIX_FMT_YUYV

V4L2_PIX_FMT_UYVY

1ピクセルが16bit長のYUVデータ

V4L2_PIX_FMT_NV12

V4L2_PIX_FMT_NV21

1ピクセルが12bit長で輝度データと色差データが分離されたYUVデータ

V4L2_PIX_FMT_NV16

V4L2_PIX_FMT_NV61

1ピクセルが16bit長で輝度データと色差データが分離されたYUVデータ
V4L2_PIX_FMT_RGB5651ピクセルが16bit長のRGBデータ

14.1.1.3. mmapシステムコール

カメラドライバが確保した画像データ用のバッファを利用する場合、アプリケーションからアクセスできるメモリ空間にマッピングしなおす必要があります。この操作には、mmap()システムコールを利用します。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

図14.5 mmapシステムコールの書式


mmap()したメモリ空間が不要となった場合は、munmap()システムコールを使用してメモリ空間をアンマッピングします。

int munmap(void *addr, size_t length);

図14.6 munmapシステムコールの書式


14.1.1.4. selectシステムコール

カメラドライバが画像データの準備を完了するまでアプリケーションをウェイトさせておくには、select()システムコールを利用します。

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

図14.7 selectシステムコールの書式


14.1.2. V4L2実装例の解説

ここでは、カメラモデル開発セット付属のDVDに収録されているV4L2サンプルコードを例にとって説明します。ソースコード(v4l2-sample-[version].tar.gz)は、DVDの/sample/ディレクトリに収録されています。また、アットマークテクノ ダウンロードサイトからも取得することができます。

14.1.2.1. 初期化処理

まずはじめに、カメラデバイスをオープンしています。カメラデバイスを扱う場合は、このオープン処理が第一歩となります。Armadillo-810 カメラモジュール01 (Bコネクタ用)のデバイスファイルは、"/dev/video1"[24](CAMERA_DEV)となります。

84   fd = open(CAMERA_DEV, O_RDWR, 0);
85   if (fd < 0) {
86     perror("open");
87     return -1;
88   }

図14.8 【camera2ppm.c】カメラデバイスをオープン


次にキャプチャーする画像データのフォーマットを指定しています。640x480のVGAサイズ(CAMERA_WIDTH, CAMERA_HEIGHT)で、PixelFormatにはYUYV(V4L2_PIX_FMT_YUYV)を指定しています。ioctl(VIDIOC_S_FMT)が成功(ret == 0)した場合でも、指定したフォーマットがカメラドライバで未対応であった場合には、fmt内のデータが対応可能なフォーマットに書き換わっている場合があります。意図していないフォーマットで動作するのを防止するために、ioctl(VIDIOC_S_FMT)の後にfmt内のデータを確認しています。

 90   memset(&fmt, 0, sizeof(fmt));
 91   fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 92   fmt.fmt.pix.width = CAMERA_WIDTH;
 93   fmt.fmt.pix.height = CAMERA_HEIGHT;
 94   fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
 95   fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
 96   ret = xioctl(fd, VIDIOC_S_FMT, &fmt);
 97   if (ret < 0 || fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV ||
 98       fmt.fmt.pix.width <= 0 || fmt.fmt.pix.height <= 0) {
 99     perror("ioctl(VIDIOC_S_FMT)");
100     return -1;
101   }

図14.9 【camera2ppm.c】画像データフォーマットを設定


[ティップ]

このサンプルではioctl()のシグナルに対する使い勝手の悪さを克服するために、xioctl()という関数を実装しています。ioctl()の処理の最中にシグナルを受けた場合はエラー終了してしまいますが、エラー要因がシグナル受信(EINTR)だった場合に自動的にioctl()を再試行してくれるラッパー関数となっています。

56 static int xioctl(int fd, int request, void *arg)
57 {
58   for (; ; ) {
59     int ret = ioctl(fd, request, arg);
60     if (ret < 0) {
61       if (errno == EINTR)
62         continue;
63       return -errno;
64     }
65     break;
66   }
67
68   return 0;
69 }

続いて、カメラドライバに対して画像データ用のバッファを要求しています。メモリマップ用(V4L2_MEMORY_MMAP)のバッファを2面(MMAP_COUNT)としてioctl(VIDIOC_REQBUFS)を呼び出しています。

カメラドライバがioctl(VIDIOC_REQBUFS)を受けとると、ioctl(VIDIOC_S_FMT)で指定された画像サイズのバッファをメモリ空間から確保します。この場合に、画像サイズ × 面数の合計があまりにも大きな場合はioctl(VIDIOC_REQBUFS)はエラーとなってしまいます。アプリケーションで最低限必要な面数を指定するのがベストでしょう。

112   memset(&req, 0, sizeof(req));
113   req.count = MMAP_COUNT;
114   req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
115   req.memory = V4L2_MEMORY_MMAP;
116   ret = xioctl(fd, VIDIOC_REQBUFS, &req);
117   if (ret < 0) {
118     perror("ioctl(VIDIOC_REQBUFS)");
119     return -1;
120   }

図14.10 【camera2ppm.c】画像データバッファを要求


ioctl(VIDIOC_REQBUFS)で要求したバッファを面数分取得し、アプリケーションがアクセスできるようにバッファ用のメモリをユーザー空間にマッピング(mmap)しています。

123   for (i = 0; i < count; i++) {
124     memset(&buf, 0, sizeof(buf));
125     buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
126     buf.memory = V4L2_MEMORY_MMAP;
127     buf.index  = i;
128     ret = xioctl(fd, VIDIOC_QUERYBUF, &buf);
129     if (ret < 0) {
130       perror("ioctl(VIDIOC_QUERYBUF)");
131       return -1;
132     }
133
134     mmap_p[i] = mmap(NULL, buf.length, PROT_READ, MAP_SHARED, fd, buf.m.offset);
135     if (mmap_p[i] == MAP_FAILED) {
136       perror("mmap");
137       return -1;
138     }
139     mmap_l[i] = buf.length;
140   }

図14.11 【camera2ppm.c】画像データバッファの取得とユーザー空間へのマッピング


14.1.2.2. 画像データを取得するには

V4L2のキャプチャー処理の基本的な流れは、下記の工程を繰り返します。

  1. ビデオストリームに空の画像バッファをエンキュー(enqueue)

  2. ビデオドライバが空の画像バッファをフィル(fill)

  3. ビデオストリームからフィルされたバッファをデキュー(dequeue)

(I.) と (III.)はアプリケーションがioctl()を用いて行う工程です。(II.) はビデオドライバが担う工程となっています。バッファをエンキュー後、どのタイミングでデキューすればいいのか?と疑問を持たれるかもしれないのですが、これはselect()を利用することでデキューのタイミングを測ることができます。サンプルコードでもselect()を利用してデキューのタイミングを測っています。

初めてビデオストリームにエンキューする箇所です。ここではキャプチャーが開始されていないため(後述)エンキューされたとしてもフィルされることはありません。キャプチャー開始に備えてあらかじめエンキューしています。

142   for (i = 0; i < count; i++) {
143     memset(&buf, 0, sizeof(buf));
144     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
145     buf.memory = V4L2_MEMORY_MMAP;
146     buf.index = i;
147     ret = xioctl(fd, VIDIOC_QBUF, &buf);
148     if (ret < 0) {
149       perror("ioctl(VIDIOC_QBUF)");
150       return -1;
151     }
152   }

図14.12 【camera2ppm.c】ビデオストリームにバッファをエンキュー


デキューの処理部分は次のコードです。

178       memset(&buf, 0, sizeof(buf));
179       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
180       buf.memory = V4L2_MEMORY_MMAP;
181       ret = xioctl(fd, VIDIOC_DQBUF, &buf);
182       if (ret < 0 || buf.bytesused < (__u32)(2 * length)) {
183         perror("ioctl(VIDOC_DQBUF)");
184         return -1;
185       }

図14.13 【camera2ppm.c】ビデオストリームからバッファをデキュー


前述した(I.)〜(III.)の工程を処理しているのが次のコードです。

161   for (i = 0, pyuyv = yuyvbuf; i < PICTURE_NUM; i++) {
162     fd_set fds;
163
164     FD_ZERO(&fds); 1
165     FD_SET(fd, &fds); 2
166     for (; ; ) {
167       ret = select(fd + 1, &fds, NULL, NULL, NULL); 3
168       if (ret < 0) {
169         if (errno == EINTR) 4
170           continue;
171         perror("select");
172         return -1;
173       }
174       break;
175     }
176
177     if (FD_ISSET(fd, &fds)) { 5
178       memset(&buf, 0, sizeof(buf));
179       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
180       buf.memory = V4L2_MEMORY_MMAP;
181       ret = xioctl(fd, VIDIOC_DQBUF, &buf);
182       if (ret < 0 || buf.bytesused < (__u32)(2 * length)) {
183         perror("ioctl(VIDOC_DQBUF)");
184         return -1;
185       }
186
187       memcpy(pyuyv, mmap_p[buf.index], 2 * length); 6
188       pyuyv += 2 * length;
189
190       ret = xioctl(fd, VIDIOC_QBUF, &buf); 7
191       if (ret < 0) {
192         perror("ioctl(VIDIOC_QBUF)");
193         return -1;
194       }
195     }

1

ファイルディスクリプタ集合(fds)を空にしています。

2

ファイルディスクリプタ集合(fds)にカメラデバイスのファイルディスクリプタ(fd)をセットしています

3

ファイルディスクリプタ集合(fds)をreadfdsに指定してselect()を実行しています。こうすることで、カメラドライバがバッファをフィルするまでアプリケーションをウェイトさせることができます。

4

select()もioctl()と同様に処理中にシグナルを受信した場合にエラーとなってしまうため、エラー要因がシグナル受信(EINTR)であれば再試行される仕組みとなっています。

5

FD_ISSET()はファイルディスクリプタ集合(fds)の内、指定のファイルディスクリプタ(fd)が読み込み準備完了となっているかを検査することができます。

6

画像データをアプリケーションが持つYUYVデータ保管用のバッファにコピーしています。画像バッファを再利用するために画像データを退避させる目的でしょう。

7

画像バッファを再度エンキューしています。

図14.14 【camera2ppm.c】画像データの取得


14.1.2.3. キャプチャー開始と停止

ビデオストリームにエンキューした画像バッファをフィルするには、キャプチャーを開始しなくてはなりません。キャプチャーを開始すると空の画像バッファがなくなるまで順次フィルを行います。

154   type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
155   ret = xioctl(fd, VIDIOC_STREAMON, &type);
156   if (ret < 0) {
157     perror("ioctl(VIDIOC_STREAMON)");
158     return -1;
159   }

図14.15 【camera2ppm.c】キャプチャー開始


キャプチャーは停止することができます。

198   type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
199   xioctl(fd, VIDIOC_STREAMOFF, &type);

図14.16 【camera2ppm.c】キャプチャー停止


14.1.2.4. 取得した画像データのその後

ioctl(VIDIOC_S_FMT)でPixelFormatにV4L2_PIX_FMT_YUYVを設定しているため、取得できる画像データはYUYVフォーマットとなっています。

サンプルコードでは、取得した画像データからPPM(Portable Pix Map)ファイル(.ppm)を作成しています。PPMファイルは、RGB24ビットカラーを表現するフォーマットとなります。取得した画像データはYUYVフォーマットのためPPMファイルを作成するためには、YUYV to RGB24フォーマット変換をする必要があります。サンプルコードでは、yuyv_to_rgb()関数で行っています。

214   for (i = 0, pyuyv = yuyvbuf, prgb = rgbbuf; i < PICTURE_NUM;
215        i++, pyuyv += 2 * length, prgb += 3 * length)
216     yuyv_to_rgb(pyuyv, prgb, length);

図14.17 【camera2ppm.c】画像データをYUYVフォーマットからRGB24フォーマットに変換


RGB24フォーマットに変換された画像データからPPMファイルを作成しているのは、ppm_writefile()関数です。

225   ppm_writefile(rgbbuf, width, height, PICTURE_NUM);

図14.18 【camera2ppm.c】画像データ(RGB24)をPPMファイルとして保存


14.1.3. サンプルコードをArmadilloで動かしてみる

ここでは、サンプルコードを実際にArmadillo-810で動作させる手順を紹介します。サンプルコードの動作を確認したり、改変して挙動を確認したりする場合などに利用してください。

14.1.3.1. サンプルコードをビルドする

サンプルコードには、Armadillo-810に最適化してビルドすることができるMakefileが付属しています。makeコマンドを用いて簡単にビルドすることができます。

[ATDE ~/v4l2-sample]$ make
arm-linux-gnueabihf-gcc -Wall -Wextra -O3  -mcpu=cortex-a9 -march=armv7-a -mfpu=neon -c -o camera2ppm.o camera2ppm
arm-linux-gnueabihf-gcc  camera2ppm.o -lnetpbm -o v4l2-sample
[ATDE ~/v4l2-sample]$ ls v4l2-sample
v4l2-sample

図14.19 サンプルコードをビルド


14.1.3.2. v4l2-sampleをArmadillo-810上で実行させる

ビルドしてできあがったv4l2-sampleをArmadillo-810上で実行させてみましょう。Armadillo-810にファイルを転送する方法はいくつかありますが、ここではFTPを利用します。また、v4l2-sampleは、libnetpbmという共有ライブラリを必要とします。一緒に転送しておきます。

[ATDE ~/v4l2-sample]$ ftp armadillo810-0.local
Connected to armadillo810-0.local.
220 localhost FTP server (GNU inetutils 1.4.1) ready.
Name (armadillo810-0.local:atmark): ftp
331 Guest login ok, type your name as password.
Password:
230 Guest login ok, access restrictions apply.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd pub
250 CWD command successful.
ftp> put v4l2-sample
local: v4l2-sample remote: v4l2-sample
200 PORT command sucessful.
150 Opening BINARY mode data connection for 'v4l2-sample'.
226 Transfer complete.
9636 bytes sent in 0.00 secs (4252.2 kB/s)
ftp> put /usr/arm-linux-gnueabihf/lib/libnetpbm.so.10 libnetpbm.so.10
local: /usr/arm-linux-gnueabihf/lib/libnetpbm.so.10 remote: libnetpbm.so.10
200 PORT command sucessful.
150 Opening BINARY mode data connection for 'libnetpbm.so.10'.
226 Transfer complete.
88944 bytes sent in 0.01 secs (7883.4 kB/s)
ftp> 

図14.20 FTPでv4l2-sampleをArmadillo-810に転送


FTPでArmadillo-810にファイルを転送すると/home/ftp/pubにファイルが作成されています。

実行する前に、デフォルトイメージで動作しているuvc-gadgetを停止させます。uvc-gadgetは、カメラデバイスを利用しているためv4l2-sampleがカメラデバイスを利用することができない状態となっています。

[armadillo ~/home/ftp/pub]# killall uvc-gadget

図14.21 uvc-gadgetを停止


続いてv4l2-sample実行してみます。転送したライブラリを利用できるように、ライブラリのサーチパスにカレントディレクトリを指定しています。

[armadillo ~/home/ftp/pub]# LD_LIBRARY_PATH=. ./v4l2-sample
convert time: 4.948 msec/flame
[armadillo ~/home/ftp/pub]# ls
camera00.ppm     camera08.ppm     camera16.ppm     camera24.ppm
camera01.ppm     camera09.ppm     camera17.ppm     camera25.ppm
camera02.ppm     camera10.ppm     camera18.ppm     camera26.ppm
camera03.ppm     camera11.ppm     camera19.ppm     camera27.ppm
camera04.ppm     camera12.ppm     camera20.ppm     camera28.ppm
camera05.ppm     camera13.ppm     camera21.ppm     camera29.ppm
camera06.ppm     camera14.ppm     camera22.ppm     libnetpbm.so.10
camera07.ppm     camera15.ppm     camera23.ppm     v4l2-sample

図14.22 v4l2-sampleを実行


作成されたPPMファイルを参照するにはATDEにインストールされている"eog"というイメージビューワーを利用します。PPMファイルはFTPを利用して転送します。

ftp> get camera15.ppm
local: camera15.ppm remote: camera15.ppm
200 PORT command sucessful.
150 Opening BINARY mode data connection for 'camera15.ppm' (921615 bytes).
226 Transfer complete.
921615 bytes received in 0.26 secs (3432.5 kB/s)
ftp> quit
221 Goodbye.
[ATDE ~/v4l2-sample]$ eog camera15.ppm

図14.23 ATDEでPPMファイルを表示




[24] デフォルト状態のLinuxカーネルの場合。コンフィグレーションを変更した場合には他のデバイス番号となる場合があります。