D言語でUSBカメラを使う

# 概要

D 言語で USB カメラを使えるようにするパッケージは意外なことに見当たりません。
[GtkD](https://github.com/gtkd-developers/GtkD/) の gstreamer でできるような気もしましたが、
USB カメラを使いたいだけという需要を満たすためにそれだけのパッケージを作りました。

- 作ったもの : https://github.com/nonanonno/v4l2-d
- やったこと V4L2 を D 言語に移植
- Linux のみの話。Ubuntu 18.04 で動作確認している。 Windows は知らない

# V4L2 とは

Video for Linux API version 2 です。USB カメラを使える低レベル API です。
使い方は [V4L2 API とOpenCV を使ってビデオキャプチャ](https://qiita.com/ykatsu111/items/9ba57e9bb6c36f434526) とか [Capture images using V4L2 on Linux](https://jayrambhia.com/blog/capture-v4l2) 等を参考に。
詳しい仕様は https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/v4l2.html に書いてあると思いますが、詳しい話は別のところを投げます。

# V4L2 の D 移植

V4L2 API は、C 言語であれば `linux/videodev2.h` をインクルードすれば使えるようになります。
`linux/videodev2.h` にあるのはカメラデバイスへの命令を表す定数と構造体で、関数は特にありません。
使う時はこれらを `ioctl` に渡して、`mmap` したり `select` したりと、ひたすらシステムコールを呼ぶようにします。

システムコール自体は 特にパッケージを入れなくても D 言語にあるので、 `linux/videodev2.h` を D 言語にポーティングすれば良いとわかります。
手作業でやっても良いですが、2400 行程あって辛いので、C ヘッダを良い感じに D にしてくれる [dstep](https://github.com/jacob-carlborg/dstep) を使います。

```shell
dstep /usr/include/linux/videodev2.h -o videodev2.d
```

これでそれっぽいものが出きましたが、ビルドしてみるとエラーが出ます。

```shell
videodev2.d(2516): Error: found ) when expecting . following int
videodev2.d(2516): Error: found ; when expecting identifier following int.
videodev2.d(2517): Error: found enum when expecting ,
videodev2.d(2517): Error: found ; when expecting ,
videodev2.d(2518): Error: expression expected, not enum
videodev2.d(2518): Error: found VIDIOC_S_EDID when expecting ,
videodev2.d(2518): Error: expression expected, not =
videodev2.d(2518): Error: found _IOWR when expecting ,
videodev2.d(2518): Error: found ; when expecting ,
videodev2.d(2519): Error: expression expected, not enum
videodev2.d(2519): Error: found VIDIOC_G_OUTPUT when expecting ,
videodev2.d(2519): Error: expression expected, not =
videodev2.d(2519): Error: found _IOR when expecting ,
videodev2.d(2519): Error: found ; when expecting ,
videodev2.d(2520): Error: expression expected, not enum
videodev2.d(2520): Error: found VIDIOC_S_OUTPUT when expecting ,
videodev2.d(2520): Error: expression expected, not =
videodev2.d(2520): Error: found _IOWR when expecting ,
videodev2.d(2520): Error: found ) when expecting . following int
videodev2.d(2520): Error: found ; when expecting identifier following int.
```

どうやら引数に型を取るマクロの変換が上手くいっていないようです。
後は手作業で頑張っていきます。

とりあえず当該箇をこんな感じにマクロをテンプレートにしているところを修正。

```diff
index 84a300c..22b9dd8 100644
--- a/videodev2.d
+++ b/videodev2.d
@@ -2420,11 +2420,11 @@ enum VIDIOC_S_AUDIO = _IOW!v4l2_audio('V', 34);
 enum VIDIOC_QUERYCTRL = _IOWR('V', 36, v4l2_queryctrl);
 enum VIDIOC_QUERYMENU = _IOWR('V', 37, v4l2_querymenu);
 enum VIDIOC_G_INPUT = _IOR!int('V', 38);
-enum VIDIOC_S_INPUT = _IOWR('V', 39, int);
+enum VIDIOC_S_INPUT = _IOWR!int('V', 39);
 enum VIDIOC_G_EDID = _IOWR('V', 40, v4l2_edid);
 enum VIDIOC_S_EDID = _IOWR('V', 41, v4l2_edid);
 enum VIDIOC_G_OUTPUT = _IOR!int('V', 46);
-enum VIDIOC_S_OUTPUT = _IOWR('V', 47, int);
+enum VIDIOC_S_OUTPUT = _IOWR!int('V', 47);
 enum VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output);
 enum VIDIOC_G_AUDOUT = _IOR!v4l2_audioout('V', 49);
 enum VIDIOC_S_AUDOUT = _IOW!v4l2_audioout('V', 50);
```

今度は `__le32` がないとか `_IO` がないとか言われました。

https://www.wdic.org/w/TECH/__le32 よると、 `__le32` は `uint` の alias にすれば良さそうなので、`alias __le32 = uint;` を追加。

`_IO` 等のために `import core.sys.posix.sys.ioctl;` を追加。
増えたマクロ → template 変換ミスを修正。

これで以下のエラー程度に辿り着きました。

```
videodev2.d(2427): Error: undefined identifier v4l2_edid
videodev2.d(2428): Error: undefined identifier v4l2_edid
```

こちらは `linux/v4l2-common.h` に定義されているものなので、こちらも変換します。
D 言語ではモジュール名にハイフンは使えなさそうので、とりあえず v4l2_common.d にします。

```shell
dstep /usr/include/linux/v4l2-common.h -o v4l2_common.d
```

これを import するようにして、完成です！(あっけない)

結局やったことは、

- dstep で変換
- マクロ → template 変換ミスを修正
- ちょっと足りないのを追加

以上。
dstep 様々です。
では使ってみます。

# 動作確認

https://github.com/nonanonno/v4l2-d を clone して `dub --config=example -- /dev/video0` みたいに実行すれば画素値の一部が出力されます。フォーマットは YUYV です。

```shell
$ dub --config=example -- /dev/video4 
(略)
Running ./example /dev/video4
bus_info : usb-0000:00:14.0-1
card     : Intel(R) RealSense(TM) Depth Ca
driver   : uvcvideo
version  : 327701
640x480
buf.length   : 614400
buf.m.offset : 0
r : 0
614400
  16 128  17 128  17 127  17 127  17 127
  17 128  18 128  17 128  17 127  18 128
  16 128  16 128  16 127  17 127  17 127
  17 128  17 128  17 128  17 127  17 128
  17 128  17 128  16 128  16 128  17 128
  16 128  17 127  18 128  17 127  17 128
  17 128  17 128  17 128  16 128  16 128
  16 128  16 127  17 128  16 127  17 128
  16 128  17 128  17 128  17 128  16 128
  16 127  16 127  17 128  16 127  16 128
```

[こちら](https://github.com/nonanonno/v4l2-d/blob/master/examples/app.d) が example コードで、やっていることは前述の参考サイトと同じです。
エラーハンドリングを `assert` でやっているという手抜きなので リリースビルドすると死にます。

# 撮ってみた

SDLを使って描画。

![out.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73600/e1904864-574b-e876-cddf-f89e25b41492.png)