Webカメラ⇨GStreamer⇨仮想ビデオデバイス⇨FFmpegにて録画する

# はじめに
Linux環境でWebカメラの映像を仮想ビデオデバイスに出力し、その映像をFFmpegを使って録画する方法について説明します。仮想ビデオデバイスを使うことにより、1つのWebカメラ映像を、複数のアプリケーションで利用可能になります

# 作業環境
・Ubuntu 24.04.1 LTS \n \l
・PC：x86_64 ガレリア NvidiaのGPU搭載
・入力映像：Webカメラ `640x360` 30fps

・GStreamer Core Library version 1.24.2
~$ which gst-launch-1.0
/usr/bin/gst-launch-1.0

・ffmpeg version 6.1.1-3ubuntu5
~$ which ffmpeg
/usr/bin/ffmpeg


# 手順の全体像

1. **仮想ビデオデバイスの設定**：`v4l2loopback`モジュールを使って仮想ビデオデバイスを作成し、設定を確認します
2. **GStreamerで映像転送**：Webカメラ映像を仮想ビデオデバイスに転送します
3. **FFmpegで映像の録画**：仮想ビデオデバイスに流れた映像をFFmpegで録画します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3113363/6560ec07-269f-1387-5051-8ce71a92b594.png)

# 1. 仮想ビデオデバイスの設定

まず、`v4l2loopback`モジュールを利用して仮想ビデオデバイスを作成します

## シェルスクリプト

以下のシェルスクリプトで、`v4l2loopback`モジュールのインストールと仮想ビデオデバイスの設定を行います。仮想ビデオデバイスはなんぼあっても良いので、今回は4つ作成します

```bash
#!/bin/bash

# If you have older version, purge it
sudo modprobe -r v4l2loopback

# Install required packages
sudo apt update
sudo apt install -y git build-essential dkms linux-headers-$(uname -r) v4l2loopback-dkms

# Clone v4l2loopback from GitHub
git clone https://github.com/umlaeute/v4l2loopback.git
cd v4l2loopback

# Compile and install the module
make && sudo make install

# Load the v4l2loopback module with 4 devices
# "echo" for using also after reboot PC
echo "options v4l2loopback devices=4 video_nr=0,1,2,3 exclusive_caps=1" | sudo tee -a /etc/modprobe.d/v4l2loopback.conf
echo v4l2loopback | sudo tee -a /etc/modules-load.d/modules.conf

```
以下で、仮想ビデオデバイス（video0～video3）がインストールされたことを確認します
```bash
$ v4l2-ctl --list-devices
Dummy video device (0x0000) (platform:v4l2loopback-000):
	/dev/video0

Dummy video device (0x0001) (platform:v4l2loopback-001):
	/dev/video1

Dummy video device (0x0002) (platform:v4l2loopback-002):
	/dev/video2

Dummy video device (0x0003) (platform:v4l2loopback-003):
	/dev/video3

USB 2.0 Camera: USB 2.0 Camera (usb-0000:01:00.0-2):
	/dev/video4
	/dev/video5
	/dev/media0
```

ここで確認できたデバイスを使って、次の手順に進みます

# 2. GStreamerで仮想ビデオデバイスに映像を転送
次に、GStreamerを使ってWebカメラ（/dev/video4）から取得した映像を、仮想ビデオデバイス（/dev/video0）に転送します

```bash
gst-launch-1.0 -e -v v4l2src device=/dev/video4 ! videorate ! video/x-raw,framerate=30/1 ! videoconvert ! v4l2sink device=/dev/video0
```
・v4l2src device=/dev/video4：Webカメラから映像を取得します
・videorate：フレームレートを調整します
・video/x-raw,framerate=30/1：フレームレートを30fpsに設定します
・videoconvert：映像フォーマットを変換します
・v4l2sink device=/dev/video0：仮想ビデオデバイスに映像を転送します

# 3. FFmpegで映像をコピー
FFmpegを使って、仮想ビデオデバイス/dev/video0からの映像を/dev/video1にコピーします（本当はvideo0から直接保存したかったが……この理由は『ちなみに』にて記載）

```bash
ffmpeg -f v4l2 -r 30 -i /dev/video0 -f v4l2 -c:v copy /dev/video1
```
・-f v4l2：V4L2形式を使用します
・-r 30：フレームレートを30fpsに設定します
・-i /dev/video0：入力デバイスとして仮想ビデオデバイス/dev/video0を指定します
・-c:v copy：エンコードせずに映像をコピーします
・/dev/video1：出力デバイスとして仮想ビデオデバイス/dev/video1を指定します

# 4. FFmpegで映像を保存
映像を保存にもFFmpegを使います。以下のコマンドで映像をmp4ファイルに保存します

```bash
ffmpeg -f v4l2 -i /dev/video1 -c:v h264_nvenc -preset fast output.mp4
```
・-f v4l2：V4L2形式で映像を取得します
・-i /dev/video1：入力デバイスとして仮想ビデオデバイス/dev/video1を指定します
・-c:v h264_nvenc：NVIDIAのハードウェアエンコーダでH.264形式にエンコードします
・-preset fast：エンコード速度を優先する設定です
・output.mp4：出力ファイルの名前です

しかるべき映像が排出されれば完成です

# おわりに
本記事では、Linux上でWebカメラ映像を仮想ビデオデバイスを通じて録画する手順について説明しました。仮想ビデオデバイスを活用することで、映像処理の幅が広がると思います
# ちなみに
上記3.で/dev/video1を経由しない場合、エンコードが行われず（ずっとdrop）破損したmp4ファイルが排出される。rawvideo流れる仮想ビデオデバイス（v4l2loopback）直下での録画は難しそう。ffplayは見れるのに……謎です
# 追記_20241126
もうひとつの仮想ビデオデバイス経由せずとも録画できました
`-f v4l2`ではなく`-f rawvideo`が正解でした。それに合わせて保存部分のコマンド修正
```bash
ffmpeg -r 30 -f rawvideo -video_size 640x480 -pix_fmt yuyv422 -i /dev/video0 -c:v h264_nvenc -preset fast output.mp4
```
仮想ビデオデバイス（v4l2loopback）流入した時点で、v4l2系列の扱いになるとおもってた。ちがった。流入してなお、生データ扱いでした
```bash
~$ ffprobe /dev/video0
〜〜〜
Input #0, video4linux2,v4l2, from '/dev/video0':
  Duration: N/A, start: 2178.241541, bitrate: 147456 kb/s
  Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 640x480, 147456 kb/s, 30 fps, 30 tbr, 1000k tbn
```
# 参考
https://zenn.dev/ciffelia/articles/ace5a9a4b99786

https://blog.kotet.jp/2020/04/v4l2loopback/