Webカメラを使ってiOS ホームアプリで監視する

# はじめに
おうちにカメラを安くで仕掛けてiOSで監視したい。
iOSを監視（モードに）したいなら[こちら](https://qiita.com/garupon/items/78a2db6c3c90047a6e75)です。

<img src="https://drive.google.com/uc?id=1_0cfRNewOG60Wax7GrtgRiyBssx15hCq" width=50%>

しかし、Homeの[対応製品](https://www.apple.com/jp/shop/product/HKXW2J/A/d-link-omna-180-cam-hd%E3%82%AB%E3%83%A1%E3%83%A9?fnode=6d901799c281d38add7235bffb49de4cfae66f5da45fe6a80c378f318db1e66343495d0889605264540b9d7591e81655bfefce41c55c827a09f5b9569c6c334856dc6827a45d5c1c51852d6bc758e5d9563849bc4b59e49ab1a0c0149798ab501fe3bc7645ee46ea3e6b5526e4a016c8&fs=f%3Dsecuritycam-homekitcompatible%26fh%3D458c%252B4559%252B463d)は高かった。

ならば非公式で作ってやれ

# 注意事項

試しに構築した手順のため、セキュリティについては[ノーガードポリシー](https://ja.wikipedia.org/wiki/%E3%82%B5%E3%82%A4%E3%83%90%E3%83%BC%E3%83%BB%E3%83%8E%E3%83%BC%E3%82%AC%E3%83%BC%E3%83%89%E6%88%A6%E6%B3%95#.E3.82.B5.E3.82.A4.E3.83.90.E3.83.BC.E3.83.8E.E3.83.BC.E3.82.AC.E3.83.BC.E3.83.89.E3.83.BB.E3.83.9D.E3.83.AA.E3.82.B7.E3.83.BC)です。
継続して利用する場合は、適当に対策してください。

# 準備したもの

<table>
<caption>
</caption>
<thead>
<tr>
 <th>材料名</th>
 <th>数量</th>
 <th>備考</th>
</tr>
</thead>
<tbody>
<tr>
 <td>Linux機</td>
 <td>1台</td>
 <td>今回はお試しのため、VirtualBoxにCentOS7.3<br />実運用の場合はRaspberry Piがいいのかもしれない</td>
</tr>
<tr>
 <td>Webカメラ</td>
 <td>1個</td>
 <td>V4Lに対応というか、ffmpegで取り込みできるもの。<br />RTSPに対応したネットワークカメラでもいい。というか本来はそっちを想定している様子<br/ >適当に<a href="http://www2.elecom.co.jp/products/UCAM-C0220FENBK.html" target="_blank">安かったもの</a>を買ってきた。UVC対応なら確実なのか。</td>
<tr>
<td>Apple TVかiPad</td>
<td>1個</td>
<td>実際に見るデバイス以外に、外から使う場合は必須。これが<a href="https://support.apple.com/ja-jp/HT207057" target="_blank">ホームハブとなって</a>外からのアクセスを良しなに仲介してくれる</td>
</tr>
</tbody>
</table>

# 手順
## インストール

お試しなので「selinux=0」と習慣で「net.ifnames=0」でminimalをインストール
アドレスはDHCP、ホスト名は「homebridge」に。実運用時は適当に。
面倒なのでrootでやってます。実運用時は適当に。
とりあえずOSを入れてカメラをつなぐ。
VBoxで使うときにWebカメラでなくUSBデバイスでやって悩んだのはさておき、動作確認は後ほど行う。

後ほどmacアドレスを使うのでコピっておく。

## パッケージ追加

[homebridgeの公式手順](https://github.com/nfarina/homebridge)にある程度従いながら勝手なことをやっていく。

### EPELリポの追加とパッケージ最新化

「nss-mdns」が必須であるかどうかわからぬが、入れるためにepelを入れる。

```shell-session
[root@homebridge ~]# yum -y install epel-release && yum -y update
```

適当に再起動

### mDNS系の導入と動作確認

avahiは依存で入ってくれる。

```shell-session
[root@homebridge ~]# yum -y install nss-mdns avahi-compat-libdns_sd-devel
[root@homebridge ~]# systemctl start avahi-daemon
[root@homebridge ~]# ping -c 4 homebridge.local
PING homebridge.local (192.168.0.1) 56(84) bytes of data.
64 bytes from homebridge (192.168.0.1): icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from homebridge (192.168.0.1): icmp_seq=2 ttl=64 time=0.046 ms
64 bytes from homebridge (192.168.0.1): icmp_seq=3 ttl=64 time=0.032 ms
64 bytes from homebridge (192.168.0.1): icmp_seq=4 ttl=64 time=0.037 ms

--- homebridge.local ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3002ms
rtt min/avg/max/mdev = 0.023/0.034/0.046/0.010 ms
```

### Webカメラの確認

```shell-session
[root@homebridge ~]# yum -y install v4l-utils
[root@homebridge ~]# v4l2-ctl --list-devices
VirtualBox Webcam - UCAM-C0220F (usb-0000:00:06.0-1):
        /dev/video0
```

#### カメラの仕様

```shell-session
[root@homebridge ~]# v4l2-ctl --list-formats
ioctl: VIDIOC_ENUM_FMT
        Index       : 0
        Type        : Video Capture
        Pixel Format: 'MJPG' (compressed)
        Name        : MJPEG
[root@homebridge ~]# v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Index       : 0
        Type        : Video Capture
        Pixel Format: 'MJPG' (compressed)
        Name        : MJPEG
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x1024
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1600x1200
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 352x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 176x144
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x120
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
```

※VBox経由なのでmjpegになっている様子。実機接続だとrawだった気がします。
800x600以上の解像度で30fpsで取れないのも微妙ですが今回はお試しだし、妥協。
UVCでH264でFHDで30fpsでHDRで安いカメラは無いものか。[ああ、高い。](https://www.amazon.co.jp/dp/B073JN5F19)

#### カメラの設定

```shell-session
[root@homebridge ~]# v4l2-ctl -d 0 --set-fmt-video=width=1280,height=720 --set-parm=10
Frame rate set to 10.000 fps
```

4:3だと配信時に上下が切り取られますので、16：9っぽい解像度に。

細かい設定は[ここ](https://qiita.com/nonbiri15/items/9593d61a2be81f2b31a9)から辿るかググるといいかもしれない。

### ffpmegの導入と撮影テスト

ffpmegはビルドした方がHWエンコができていいかもしれませんが、お試しだし面倒なのでビルド済みを入れます。

```shell-session
[root@homebridge ~]# curl -LO https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-64bit-static.tar.xz
[root@homebridge ~]# tar xvf ffmpeg-git-64bit-static.tar.xz
[root@homebridge ~]# cd ffmpeg-git-yyyymmdd-64bit-static/
[root@homebridge ffmpeg-git-yyyymmdd-64bit-static]# cp ffmpeg ffprobe ffserver /usr/local/bin/.
[root@homebridge ffmpeg-git-yyyymmdd-64bit-static]# cd
[root@homebridge ~]# ffmpeg -i /dev/video0 -t 00:00:10 out.mp4
ffmpeg version N-89208-g8b62647a5f-static https://johnvansickle.com/ffmpeg/  Copyright (c) 2000-2017 the FFmpeg developers
  built with gcc 6.4.0 (Debian 6.4.0-10) 20171112
  configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc-6 --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gray --enable-libfribidi --enable-libass --enable-libvmaf --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-librtmp --enable-libsoxr --enable-libspeex --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libzimg
  libavutil      56.  2.100 / 56.  2.100
  libavcodec     58.  3.105 / 58.  3.105
  libavformat    58.  2.102 / 58.  2.102
  libavdevice    58.  0.100 / 58.  0.100
  libavfilter     7.  2.102 /  7.  2.102
  libswscale      5.  0.101 /  5.  0.101
  libswresample   3.  0.101 /  3.  0.101
  libpostproc    55.  0.100 / 55.  0.100
[mjpeg @ 0x53e4780] EOI missing, emulating
Input #0, video4linux2,v4l2, from '/dev/video0':
  Duration: N/A, start: 67101.668689, bitrate: N/A
    Stream #0:0: Video: mjpeg, yuvj422p(pc, bt470bg/unknown/unknown), 1280x720 [SAR 1:1 DAR 16:9], 10 fps, 10 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (mjpeg (native) -> h264 (libx264))
Press [q] to stop, [?] for help
（以下省略）
```
録画された「out.mp4」を取ってきて取れているか確認します。


## homebridgeとプラグインの導入

```shell-session
[root@homebridge ~]# yum -y install make gcc gcc-c++ npm
（中略）
[root@homebridge ~]# npm install -g homebridge homebridge-camera-ffmpeg
```

### homebridgeの仮起動

設定フォルダの作成と、とりあえず動作確認のために手動で起動

```shell-session
[root@homebridge ~]# homebridge
*** WARNING *** The program 'node' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=node>
*** WARNING *** The program 'node' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=node&f=DNSServiceRegister>
[2017/11/23 18:03:57] Loaded plugin: homebridge-camera-ffmpeg
[2017/11/23 18:03:57] Registering platform 'homebridge-camera-ffmpeg.Camera-ffmpeg'
[2017/11/23 18:03:57] ---
[2017/11/23 18:03:57] config.json (/root/.homebridge/config.json) not found.
Load homebridge-camera-ffmpeg.Camera-ffmpeg
（中略）
[2017/11/23 18:03:57] Homebridge is running on port 39877.

（Ctrl+Cで終了）
^C[2017/11/23 18:03:59] Got SIGINT, shutting down Homebridge...
[root@homebridge ~]#
```

色々怒られるが[前半は気にしない](https://www.npmjs.com/package/homebridge#errors-on-startup)
「Loaded plugin: homebridge-camera-ffmpeg」と「config.json (/root/.homebridge/config.json) not found.」が出ていればいい。

### homebridgeの設定ファイル

サンプルをコピーして設定を書く。
設定ファイルの置場所は、homebridgeを起動するユーザのルート基準

```shell-session
[root@homebridge ~]# cp /usr/lib/node_modules/homebridge/config-sample.json ~/.homebridge/config.json
[root@homebridge ~]# vi ~/.homebridge/config.json
```

<table>
<caption>
変更箇所とか
</caption>
<thead>
<tr>
 <th>行</th>
 <th>項目名</th>
 <th>値／備考</th>
</tr>
</thead>
<tbody>
<tr>
 <td>3</td>
 <td>name</td>
 <td>iOSに表示されるブリッジ名、とりあえず現状維持</td>
</tr>
<tr>
 <td>4</td>
 <td>username</td>
 <td>NICのMACを<b>大文字で</b></td>
<tr>
<td>5</td>
<td>pin</td>
<td>iOSに登録時に用いる認証キー。「000-00-000」は登録時にエラーとなる。</td>
</tr>
<tr>
<td>9</td>
<td>Description</td>
<td>とりあえず値を消しておく</td>
</tr>
<tr>
<td>11～23</td>
<td>accessories<br>platforms</td>
<td>サンプルなので消して、カメラ用に書き換え</td>
</tr>
</tbody>
</table>

```json:config.json
{
  "bridge": {
    "name": "Homebridge",
    "username": "08:00:27:41:6D:56",
    "port": 51826,
    "pin": "123-45-678"
  },
  "description": "",
  "platforms": [
    {
      "platform": "Camera-ffmpeg",
      "cameras": [
        {
          "name": "サンプルカメラ",
          "videoConfig": {
            "source": "-re -i /dev/video0",
            "stillImageSource": "-i /dev/video0 -v:c 1 -ss 1",
            "maxStreams": 2,
            "maxWidth": 1280,
            "maxHeight": 720,
            "maxFPS": 10,
            "vcodec": "libx264"
          }
        }
      ]
    }
  ]
}
```
ハードウェアエンコを使うのであればvcodecを適当にいじる。「h264_omx」とか「h264_nvenc」とか。
H264対応のカメラなら「copy」で軽くできるのか。
「stillImageSource」は一覧画面（最初に出した画面）に表示される画像の出力設定。Wi-Fiで10秒、3G/4Gで30秒ごとぐらいに更新される。Webカメラ直結の場合、即撮影は真っ暗になるので、「-SS 1」で少し遅延を入れている。

### homebridge起動

```shell-session
[root@homebridge ~]# homebridge
（中略）
[2017/11/23 18:38:11] Homebridge is running on port 51826.
[2017/11/23 18:38:11] サンプルカメラ is running on port 43430.
```

作ったカメラの名前とポートが出てくれば良い。

### iOS側の設定

ブリッジとカメラが別機器として出てくるので、いきなりQRを読ませるとブリッジしか登録されない。

<table>
<caption>
登録手順
</caption>
<tbody>
<tr>
 <td><img src="https://drive.google.com/uc?id=1Lv9gxwDeJwk_kAelnO_ZV-yCZ3hEwrPS"></td>
 <td>「コードがないか、スキャンできませんか？」をタップ</td>
</tr>
<tr>
 <td><img src="https://drive.google.com/uc?id=1TtgOWNTWbaDHjjI86rdnL9rkzuc_iFHn"></td>
 <td>設定したカメラがここで出てきているので、それをタップ</td>
</tr>
<tr>
 <td><img src="https://drive.google.com/uc?id=1mUBCCX2OeLx9AV4enXhh4Ni-ut-C6zyR"></td>
 <td>認定されていないが気にしない</td>
</tr>
<tr>
 <td><img src="https://drive.google.com/uc?id=1AsQoNVJI0Z4--mrmpbV8qZxQGeX7_IX5"></td>
 <td>ここでQRを読ませるか、pinを手入力</td>
</tr>
<tr>
 <td><img src="https://drive.google.com/uc?id=1gROkfCHx3vd3nCiaMWUD3dPC00bNqhFI"></td>
 <td>完了</td>
</tr>
</tbody>
</table>

# 最後に

このプラグインの公式サイトでは、RTSPで読みに行くサンプルとなっている。
また、起動後にiOSでカメラを登録して視聴すると

```shell-session
Snapshot -i /dev/video0 -v:c 1 -ss 1 -t 1 -s 480x270 -f image2 -
-re -i /dev/video0 -threads 0 -vcodec libx264 -an -pix_fmt yuv420p -r 10 -f rawvideo -tune zerolatency -vf scale=1280:720 -b:v 299k -bufsize 299k -payload_type 99 -ssrc 1 -f rtp -srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params usQEf+DVAKfu+wfhrC2SpfueUkpDlZfgnO+2EhXt srtp://192.168.0.2:54828?rtcpport=54828&localrtcpport=54828&pkt_size=1378
```

とログが出るが、実態はffmpegで処理をしている。
ライブ視聴時はiOSデバイスに直接、外からの場合はApple TVかiPadに対してSRTPでデータを送り付けている。

ffmpegでの処理なので、RTSPに対応したカメラや、他の配信システムとか、とにかくffmpegで対応できればいい様子。

最初は「/dev/video0 → ffmpeg → ffserver → homebridge」と考えたが、しょっぱなからffmpegを使っているなら直でいいやとやっちまっています。
それに前者だと撮りっぱなしのLED点きっぱなしになるので。

複数台かつ各所に設置するのであれば、LAN内ストリーミングをffmpegで取り込みか。

# おまけ（たぶん自動起動）

```shell-session
[root@homebridge ~]# npm install -g pm2
[root@homebridge ~]# pm2 start /usr/bin/homebridge
[root@homebridge ~]# pm2 status 0
lqqqqqqqqqqqqwqqqqwqqqqqqwqqqqqqqwqqqqqqqqwqqqqqqqqqwqqqqqqqqwqqqqqwqqqqqqqqqqqwqqqqqqwqqqqqqqqqqk
x App name   x id x mode x pid   x status x restart x uptime x cpu x mem       x user x watching x
tqqqqqqqqqqqqnqqqqnqqqqqqnqqqqqqqnqqqqqqqqnqqqqqqqqqnqqqqqqqqnqqqqqnqqqqqqqqqqqnqqqqqqnqqqqqqqqqqu
x homebridge x 0  x fork x 15053 x online x 1       x 5s     x 0%  x 53.3 MB   x root x disabled x
mqqqqqqqqqqqqvqqqqvqqqqqqvqqqqqqqvqqqqqqqqvqqqqqqqqqvqqqqqqqqvqqqqqvqqqqqqqqqqqvqqqqqqvqqqqqqqqqqj
 Use `pm2 show <id|name>` to get more details about an app
[root@homebridge ~]# pm2 save
[root@homebridge ~]# pm2 startup centos
（中略）
[root@homebridge ~]# systemctl enable pm2-root
```

2行目がもう少しマトモな方法がありそう。
再起動でカメラの設定が飛ぶ。暇なときに見直そう。