Raspberry Piに接続したUSBカメラの映像をRTSPで配信する


| 項目 | 内容　|
| --- | --- |
| 環境 |Raspberry Pi 3|
| OS | Raspberry Pi OS (64-bit)  2022/1/28版 |
| USBカメラ | ロジクール Webcam C200 |

# USBカメラの映像をダイレクトにRTSP配信

v4l2rtspserverを使います。 

https://github.com/mpromonet/v4l2rtspserver

Dockerイメージも用意されているので簡単です。

```terminal
docker run -p 8554:8554 --device=/dev/video0 -it mpromonet/v4l2rtspserver /dev/video0
```

```console
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
log level:500

[NOTICE] /v4l2rtspserver/main.cpp:297
        Version: 0.2.4-12-g3b777e1 live555 version:2022.01.21
[NOTICE] /v4l2rtspserver/src/V4l2RTSPServer.cpp:37
        Create V4L2 Source.../dev/video0
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:133
        driver:uvcvideo capabilities:84a00001 mandatory:4000001
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:136
        /dev/video0 support capture
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:139
        /dev/video0 support streaming
[ERROR] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:212
        /dev/video0: Cannot set pixelformat to:H264 format is:YUYV
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:225
        /dev/video0:MJPG size:640x480 bufferSize:213333
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:246
        fps:1/25
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2Device.cpp:247
        nbBuffer:0
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2MmapDevice.cpp:49
        Device /dev/video0
[NOTICE] /v4l2rtspserver/v4l2wrapper/src/V4l2MmapDevice.cpp:73
        Device /dev/video0 nb buffer:10
[NOTICE] /v4l2rtspserver/src/V4l2RTSPServer.cpp:62
        Create Source .../dev/video0
[NOTICE] /v4l2rtspserver/inc/BaseServerMediaSubsession.h:49
        format:video/JPEG
[NOTICE] /v4l2rtspserver/inc/V4l2RTSPServer.h:85
        Play this stream using the URL "rtsp://172.17.0.2:8554/unicast"
[NOTICE] /v4l2rtspserver/src/V4L2DeviceSource.cpp:96
```

配信URLはログに出力されます（下から2行目）。
IPアドレスはDockerコンテナ内のIPアドレスなので、外部から接続する際はRaspberr PiのIPアドレスを使用します。

# VLC media playerでRTSPを受信

WindowsのVLC media playerでRTSPを受信してみます。

[メディア] - [ネットワークストリームを開く] からRTSPのURLを指定して受信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/ea130a40-030f-a012-2ae0-379950a966fe.png)

表示されました。
カメラがかなり古いので、`Motion JPEG`形式での配信となっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f7c99ac2-a583-cd76-3357-265721e23b9a.png)

次は、これをH.264形式に変換して配信してみたいと思います。

# USBカメラの映像をH.264形式に変換してRTSP配信

やり方は`v4l2rtspserver`のREADMEに記載されています。

> # Using v4l2loopback
> For camera providing uncompress format v4l2tools can compress the video to an intermediate virtual V4L2 device v4l2loopback:
> 
> ```
/dev/video0 (camera device)-> v4l2compress -> /dev/video10 (v4l2loopback device) -> v4l2rtspserver
> ```

まずは、`v4l2loopback`をインストールし、`v4l2tools`をDockerで動かす方法で試しました。

## v4l2loopbackのインストール

APTで`v4l2loopback-dkms`をインストールします。
カーネルヘッダーが追加で必要ですので一緒にインストールします。

```terminal
sudo apt install raspberrypi-kernel-headers v4l2loopback-dkms -y
```

## v4l2loopbackの有効化

有効化の前に、現状を確認します。

```terminal
ls -l /dev/video*
```

```terminal
crw-rw----+ 1 root video 81, 12 Feb  5 05:39 /dev/video0
crw-rw----+ 1 root video 81, 13 Feb  5 05:39 /dev/video1
crw-rw----+ 1 root video 81,  6 Feb  5 05:39 /dev/video10
crw-rw----+ 1 root video 81,  9 Feb  5 05:39 /dev/video11
crw-rw----+ 1 root video 81, 10 Feb  5 05:39 /dev/video12
crw-rw----+ 1 root video 81,  0 Feb  5 05:39 /dev/video13
crw-rw----+ 1 root video 81,  1 Feb  5 05:39 /dev/video14
crw-rw----+ 1 root video 81,  2 Feb  5 05:39 /dev/video15
crw-rw----+ 1 root video 81,  3 Feb  5 05:39 /dev/video16
crw-rw----+ 1 root video 81, 11 Feb  5 05:39 /dev/video18
crw-rw----+ 1 root video 81,  4 Feb  5 05:39 /dev/video20
crw-rw----+ 1 root video 81,  5 Feb  5 05:39 /dev/video21
crw-rw----+ 1 root video 81,  7 Feb  5 05:39 /dev/video22
crw-rw----+ 1 root video 81,  8 Feb  5 05:39 /dev/video23
```

5番が空いているので5番を使います。（video_nrで指定）

```terminal
sudo modprobe v4l2loopback video_nr=5
```

```terminal
ls -l /dev/video*
```

```terminal
crw-rw----+ 1 root video 81, 12 Feb  5 05:39 /dev/video0
crw-rw----+ 1 root video 81, 13 Feb  5 05:39 /dev/video1
crw-rw----+ 1 root video 81,  6 Feb  5 05:39 /dev/video10
crw-rw----+ 1 root video 81,  9 Feb  5 05:39 /dev/video11
crw-rw----+ 1 root video 81, 10 Feb  5 05:39 /dev/video12
crw-rw----+ 1 root video 81,  0 Feb  5 05:39 /dev/video13
crw-rw----+ 1 root video 81,  1 Feb  5 05:39 /dev/video14
crw-rw----+ 1 root video 81,  2 Feb  5 05:39 /dev/video15
crw-rw----+ 1 root video 81,  3 Feb  5 05:39 /dev/video16
crw-rw----+ 1 root video 81, 11 Feb  5 05:39 /dev/video18
crw-rw----+ 1 root video 81,  4 Feb  5 05:39 /dev/video20
crw-rw----+ 1 root video 81,  5 Feb  5 05:39 /dev/video21
crw-rw----+ 1 root video 81,  7 Feb  5 05:39 /dev/video22
crw-rw----+ 1 root video 81,  8 Feb  5 05:39 /dev/video23
crw-rw----+ 1 root video 81, 14 Feb  5 05:41 /dev/video5
```

`/dev/video5`が増えたのが確認できました。

## v4l2toolsでh.264に変換

Dockerで起動します。

```terminal
docker run --device=/dev/video0 --device=/dev/video5 -it mpromonet/v4l2tools -fH264 /dev/video0 /dev/video5
```

`/dev/video0`をインプットにしてh.264形式に変換して`/dev/video5`にアウトプットするイメージです。

##h.264に変換した映像を配信

配信のインプットを`/dev/video5`に変えると、h.264形式での配信となります。

```
docker run -p 8554:8554 --device=/dev/video5 -it mpromonet/v4l2rtspserver /dev/video5
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/2b5232f2-b397-6bfc-50c3-b6fc1ed40b06.png)

動画の形式がh.264に変換されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/69b1dd88-9839-9e58-2aed-738316df5449.png)

Docker ComposeのYamlで表現するとこんな感じです。

```yaml
version: "3.9"

services:
  v4l2tools:
    image: mpromonet/v4l2tools
    devices:
      - /dev/video0
      - /dev/video5
    command:
      - -fH264
      - /dev/video0
      - /dev/video5
    tty: true
  v4l2rtspserver:
    image: mpromonet/v4l2rtspserver
    devices:
      - /dev/video5
    command:
      - /dev/video5
    ports:
      - "8554:8554"
    tty: true
    depends_on:
      - v4l2tools
```

# USBカメラの映像をH.264形式に変換してRTSP配信（ハードウェアエンコード）

`v4l2tools`のページに、Raspberry Piの場合には、OMXという仕組みを使ったハードウェアエンコードにも対応していると記載があります。

> ## Tools for Raspberry
> 
> * v4l2compress_omx
> 
> read YUV from a V4L2 capture device, compress in H264 format using OMX and write to a V4L2 output device

このバイナリはDocker Hubで配布されているイメージには含まれていないようなので、自前でビルドします。
（配布されているイメージに対応するDockerfileは[ここ](https://github.com/mpromonet/v4l2tools/blob/master/Dockerfile)で公開されていますので、これをもとにします。

OMXに必要なライブラリーは[ここ](https://github.com/raspberrypi/firmware)で公開されていますが、32-bit版しかないので、ベースイメージは`arm32v7/debian`を使用します。

```Dockerfile:Dockerfile
ARG IMAGE=arm32v7/debian
FROM $IMAGE as builder

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates g++ autoconf automake libtool xz-utils cmake make pkg-config git wget libx264-dev libx265-dev libjpeg-dev libvpx-dev \
    && git clone --depth 1 https://github.com/raspberrypi/firmware.git \
    && cp -rf firmware/opt/vc /opt/ \
    && git clone --depth 1 https://github.com/mpromonet/v4l2tools.git \
    && cd v4l2tools \
    && make \
    && make install PREFIX=/usr/local && apt-get clean && rm -rf /var/lib/apt/lists/

FROM $IMAGE
WORKDIR /usr/local/share/v4l2tools
COPY --from=builder /usr/local/bin/ /usr/local/bin/
COPY --from=builder /opt/vc/lib/ /opt/vc/lib/

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates x264 x265 libjpeg-dev libvpx6 \
    && apt-get clean && rm -rf /var/lib/apt/lists/

ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/vc/lib

ENTRYPOINT [ "/usr/local/bin/v4l2compress" ]
CMD [ "" ]
```

```terminal
docker build -t v4l2tools .
```

docker起動時に`--device=/dev/vchiq`が追加で必要になります。
entrypointも`/usr/local/bin/v4l2compress_omx`で上書きが必要です。

```
docker run --device=/dev/video0 --device=/dev/video5 --device=/dev/vchiq --entrypoint=/usr/local/bin/v4l2compress_omx -it v4l2tools -fH264 /dev/video0 /dev/video5
```

v4l2rtspserverは先程と同じ形で起動します。

```
docker run -p 8554:8554 --device=/dev/video5 -it mpromonet/v4l2rtspserver /dev/video5
```


Docker Composeの場合はこんな感じ

```yaml:docker-compose.yaml
version: "3.9"

services:
  v4l2tools:
    build:
      dockerfile: Dockerfile
      context: .
    devices:
      - /dev/video0
      - /dev/video5
      - /dev/vchiq
    command:
      - -fH264
      - /dev/video0
      - /dev/video5
    entrypoint:
      - /usr/local/bin/v4l2compress_omx
    tty: true
  v4l2rtspserver:
    image: mpromonet/v4l2rtspserver
    devices:
      - /dev/video5
    command:
      - /dev/video5
    ports:
      - "8554:8554"
    tty: true
    depends_on:
      - v4l2tools
```

ソフトウェアエンコードとハードウェアエンコードのCPU使用率の大まかな比較です。
（TOPコマンドを目視による確認）

| 条件 | CPU使用率 |
| --- | --: |
| ソフトウェアエンコード時 | 29.4% |
| ハードウェアエンコード時 | 15.5% |

CPU使用率が下がっていい感じですね。

# おまけ

カメラなしでもとにかくRTSP配信したい場合

ffmpeg -> /dev/video6(v4l2loopback) ->　v4l2compress -> /dev/video5(v4l2loopback) > v4l2rtspserver -> RTSPストリーム

```terminal
sudo modprobe --remove v4l2loopback
sudo modprobe v4l2loopback video_nr=5,6
```

```terminal:ffmpeg->/dev/video6
ffmpeg -re -f lavfi -i testsrc2=size=640x480:rate=10 -pix_fmt yuv420p -f v4l2 /dev/video6
```

```terminal:/dev/video6->v4l2compress->/dev/video5
docker run --device=/dev/video6 --device=/dev/video5 --device=/dev/vchiq --entrypoint=/usr/local/bin/v4l2compress_omx -it v4l2tools -fH264 /dev/video6 /dev/video5
```

```terminal:/dev/video5->v4l2rtspserver->RTSPストリーム
docker run -p 8554:8554 --device=/dev/video5 -it mpromonet/v4l2rtspserver /dev/video5
```