Raspberry Pi: 動画の撮影

Takami Torao RPi3 Model B+ Raspbian 11 #RaspberryPi
  • このエントリーをはてなブックマークに追加

概要

カメラの接続が完了したら Raspberry Pi で動画を撮影してみよう。Raspberry Pi は Pico 以外の全てのモデルに H.264 ハードウェアエンコーダーを搭載していることから、リアルタイムでの映像の加工やフォーマット変換、ストリーミングといった用途も可能である。

この記事では映像の記録方法の説明に集中するために必要最小限のオプションのみを指定するにとどめている。多機能な FFmpeg は特に数多くのオプションが存在する。目的にあった解像度やフレームレートで記録するためにはそれぞれのコマンドのドキュメントを参照。

Table of Contents

  1. 概要
  2. 接続と特性の確認
  3. 動画の撮影方法
    1. AVI 形式での映像記録
    2. 音声付きの映像記録
    3. テキスト情報付きの映像記録
    4. 動画のストリーミング方法
    5. その他の記録方法
      1. カメラ側での H.264 エンコード
      2. MP4 形式での記録
      3. libx264 によるソフトウェアエンコード
      4. Motion JPEG での記録
      5. 高フレームレートの撮影
  4. フォーマットの変換
  5. FFmpeg のエラー
  6. Appendix
    1. USB Web カメラ vs. カメラモジュール
    2. ハードウェアエンコード vs. ソフトウェアエンコード

接続と特性の確認

カメラの接続で紹介したようにカメラが認識されていることを lsusb (USB接続), v4l2-ctl --list-device で確認したあとは、デバイスが対応しているコーデック、解像度、フレームレートを次のコマンドで確認する。

  1. v4l2-ctl -d /dev/video0 --list-formats
  2. v4l2-ctl -d /dev/video0 --list-formats-ext
  3. ffmpeg -f v4l2 -list_formats all -i /dev/video0
pi@pirite:~ $ v4l2-ctl -d /dev/video0 --list-formats
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YUYV' (YUYV 4:2:2)
        [1]: 'MJPG' (Motion-JPEG, compressed)

pi@pirite:~ $ v4l2-ctl -d /dev/video2 --list-formats
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YU12' (Planar YUV 4:2:0)
        [1]: 'YUYV' (YUYV 4:2:2)
        [2]: 'RGB3' (24-bit RGB 8-8-8)
        [3]: 'JPEG' (JFIF JPEG, compressed)
        [4]: 'H264' (H.264, compressed)
        [5]: 'MJPG' (Motion-JPEG, compressed)
        [6]: 'YVYU' (YVYU 4:2:2)
        [7]: 'VYUY' (VYUY 4:2:2)
        [8]: 'UYVY' (UYVY 4:2:2)
        [9]: 'NV12' (Y/CbCr 4:2:0)
        [10]: 'BGR3' (24-bit BGR 8-8-8)
        [11]: 'YV12' (Planar YVU 4:2:0)
        [12]: 'NV21' (Y/CrCb 4:2:0)
        [13]: 'RX24' (32-bit XBGR 8-8-8-8)

上記の例では C270n は Raw (YUYV) と Motion JPEG に対応しており、カメラモジュールは Raw と Motion JPEG、h.264 コーデックに対応していることがわかる。

一般に、市販の Web カメラは PC 側でエンコードする前提で設計されているためカメラ側に h.264 ハードウェアエンコーダーを搭載していない。しかし、h.264 コーデックに対応したカメラであれば Raspberry Pi 側の h.264 ハードウェアエンコーダーを使用しなくてもよく負荷は少ない。

利用可能な解像度と FPS (frames per second) の詳細は次のコマンドで参照できる。

pi@pirite:~ $ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YUYV' (YUYV 4:2:2)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        ...
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x90
                        Interval: Discrete 0.033s (30.000 fps)
                        ...

pi@pirite:~ $ ffmpeg -f v4l2 -list_formats all -i /dev/video0
ffmpeg version 4.3.3-0+rpt2+deb11u1 Copyright (c) 2000-2021 the FFmpeg developers
[video4linux2,v4l2 @ 0x5594ba9fa0] Raw       :     yuyv422 :           YUYV 4:2:2 : 640x480 160x90 160x120 176x144 320x180 320x240 352x288 432x240 640x360 800x448 800x600 864x480 960x720 1024x576 1280x720 1600x896 1920x1080 2304x1296 2304x1536
[video4linux2,v4l2 @ 0x5594ba9fa0] Compressed:       mjpeg :          Motion-JPEG : 640x480 160x90 160x120 176x144 320x180 320x240 352x288 432x240 640x360 800x448 800x600 864x480 960x720 1024x576 1280x720 1600x896 1920x1080
/dev/video0: Immediate exit requested

製品の仕様で 1080p, 60FPS とされているケースでも、無圧縮であれば 2304x1536 (ただし 2FPS) の大きさまで対応していたり、逆に Motion-JPEG の 1280x720 以外は 30FPS までしか対応していないようなケースもある。

例えば以下は Logitech C922n での実行結果である。製品仕様は 1080p (1920x1080) / 60fps または 720p (1280x720) / 30fps 対応のカメラだが、以下の実行結果から Motion-JPEG 入力のケースに限ることがわかる。

Raspberry Pi の限界は低く、映像の記録と同時にリアルタイムでできることは多くない。特にソフトウェアエンコード/デコードのステップは負荷が高いため回避すべきである。Fig 1 の変換パターンで最も負荷の低い経路を選ぶのが良い。

graph LR; subgraph Camera Input; RAW["YUYV<br/><font size=2>(raw)"]; MJPG["Motion-JPEG<br/><font size=2>(compressed)"]; H264["h.264<br/><font size=2>(compressed)"]; end subgraph Decoder; MJPEGDEC("Motion-JPEG<br/><font size=2>(software decoder)"); H264DEC("h.264<br/><font size=2>(hardware decoder)"); end subgraph Encoder; H264ENC("h.264<br/><font size=2>(hardware encoder)"); end subgraph File Output; AVI[AVI]; MP4[MP4]; end RAW --> CNV["加工・変換"]; RAW --> H264ENC; MJPG --> MJPEGDEC --> CNV; H264 --> H264DEC --> CNV; CNV --> H264ENC; H264ENC --> AVI; H264ENC --> MP4; MJPG --> AVI; H264 --> AVI; H264 --> MP4; style MJPEGDEC fill:#D31C30,stroke:#8B0000,color:#FFF
Fig 1. 例えば映像に加工が必要なら Raw 入力で加工し h.264 変換して保存する経路が最適。Motion-JPEG 入力からの加工変換はソフトェアデコードが行われるため負荷が高い。高負荷では想定のフレームレートが。

動画の撮影方法

FFmpeg は動画の取り込みや保存、変換、加工を行うことができる多機能な動画編集ツールである。ここでは FFmpeg を使用して USB Web カメラから動画を撮影してみよう。まずは apt を使って ffmpeg をインストールする。

pi@pirite:~ $ sudo apt install -y ffmpeg
Reading package lists... Done
...

AVI 形式での映像記録

個人的に Raspberry Pi でのカメラ映像の保存は h.246 コーデックの AVI 形式を推奨する。動画フォーマットとしては MP4 形式がより一般的だが、経験的に MP4 は録画時の破損に弱く、FFmpeg や OS の異常終了、USB ストレージの抜け、Raspberry Pi 本体の電源切断などのデータ破損によって記録中だったファイル全体を再生/変換できなくなる

一方、インターリーブで保存する AVI 形式であれば異常終了した直前までの記録を正しく再生できることから、一発勝負の撮影や長時間の記録、証拠映像として残すような記録には MP4 より AVI での保存が向いている。最終的に MP4 形式が必要であっても、一次記録として AVI で記録を終えた後に MP4 に変換することは難しくないし CPU 負荷も大きくはない。

H.264は高圧縮な動画コーデックである。Raspberry Pi は初代 Model A から H.264 ハードウェアエンコーダを搭載して CPU の負荷なく高速に変換できることから、特に理由がなければエンコードには h.264 を選択すればよいだろう。以下の例は h264_v4l2m2m (V4L2 H.264 memory to memory encoder wrapper) を使用して H.246 コーデックの AVI 形式で保存している。

pi@pirite:~$ ffmpeg \
  -i /dev/video0 \
  -c:v h264_v4l2m2m \
  -pix_fmt yuv420p \
  testshot.avi

上記のコマンドオプションは「/dev/video0 の入力映像を h.264 エンコードして testshot.avi ファイルに (AVI 形式で) 保存」という意味である。FFmpeg は出力形式をファイルの拡張子で推測するため、上記のコマンドを .mp4 に変更するだけで MP4 形式で保存できる。

コマンドを起動するとメッセージが長々と出力され Q キーを入力すると終了する。

ffmpeg version 4.3.3-0+rpt2+deb11u1 Copyright (c) 2000-2021 the FFmpeg developers
...
Input #0, video4linux2,v4l2, from '/dev/video0':
  Duration: N/A, start: 6483.108721, bitrate: 110592 kb/s
    Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 640x360, 110592 kb/s, 30 fps, 30 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (h264_v4l2m2m))
Press [q] to stop, [?] for help
[h264_v4l2m2m @ 0xb98c20] Using device /dev/video11
[h264_v4l2m2m @ 0xb98c20] driver 'bcm2835-codec' on card 'bcm2835-codec-encode' in mplane mode
[h264_v4l2m2m @ 0xb98c20] requesting formats: output=YU12 capture=H264
[h264_v4l2m2m @ 0xb98c20] Failed to set gop size: Invalid argument
Output #0, avi, to 'testshot.avi':
  Metadata:
    ISFT            : Lavf58.45.100
    Stream #0:0: Video: h264 (h264_v4l2m2m) (H264 / 0x34363248), yuv420p, 640x360, q=-1--1, 200 kb/s, 30 fps, 30 tbn, 30 tbc
    Metadata:
      encoder         : Lavc58.91.100 h264_v4l2m2m
[h264_v4l2m2m @ 0xb98c20] ff_v4l2_buffer_buf_to_avpkt
...

映像がどのような形式で保存されたかを知るには apt でインストール可能な mediainfo コマンドを利用することができる。以下の映像は 640x360, 30fpx, 200kbps, H.264, 10.6 秒で 272kB の大きさとなっていることが分かる。

開く
pi@pirite:~$ mediainfo testshot.avi
General
Complete name                            : testshot.avi
Format                                   : AVI
Format/Info                              : Audio Video Interleave
File size                                : 272 KiB
Duration                                 : 10 s 633 ms
Overall bit rate mode                    : Constant
Overall bit rate                         : 209 kb/s
Writing application                      : Lavf58.45.100

Video
ID                                       : 0
Format                                   : AVC
Format/Info                              : Advanced Video Codec
Format profile                           : High@L4
Format settings                          : CABAC / 1 Ref Frames
Format settings, CABAC                   : Yes
Format settings, Reference frames        : 1 frame
Format settings, GOP                     : M=1, N=60
Codec ID                                 : H264
Duration                                 : 10 s 633 ms
Bit rate mode                            : Constant
Bit rate                                 : 200 kb/s
Width                                    : 640 pixels
Height                                   : 360 pixels
Display aspect ratio                     : 16:9
Frame rate                               : 30.000 FPS
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Bits/(Pixel*Frame)                       : 0.029
Stream size                              : 258 KiB (95%)

ここでは説明を簡潔にするために最小限のコマンドオプションのみを使用したが、多くの場合は以下のようなオプションを併用する。ここで指定順序に意味があることに注意。

映像の解像度

-s 1280x960 で映像の解像度を 1280x960 に設定する。対応していない解像度の場合は別の解像度に変更される。映像が大きくなると画質が荒くなるのでビットレートも上げたほうが良い。

pi@pirite:~$ ffmpeg -s 1280x960 -i /dev/video0 -c:v h264_v4l2m2m -pix_fmt yuv420p testshot.avi
動きの滑らかさ

-r 15 で 1 秒あたりの画像数を 15fps に設定する。安価な Web カメラであれば 30fps が上限だろう。動きの少ない映像なら低めに設定しても良い。

pi@pirite:~$ ffmpeg -r 15 -i /dev/video0 -c:v h264_v4l2m2m -pix_fmt yuv420p testshot.avi
映像の画質

-b:v 5000k で映像のビットレートを 5000kbps に設定する。ただし画質には限界があり、ビットレートを上げすぎてもデータサイズが大きくなるだけで画質は変わらないことに注意。固定ビットレートではなく、最小限のビットレートとこれ以上は画質が変わらないというビットレートを調べて -minrate, -maxrate で可変ビットレートとして指定したほうが良いかもしれない。

pi@pirite:~$ ffmpeg -i /dev/video0 -b:v 5000k -c:v h264_v4l2m2m -pix_fmt yuv420p testshot.avi
出力バッファサイズ

-bufsize 出力オプションはビットレートの自動調整とバッファフラッシュのタイミングに作用する。目安として -b:v と同じ値を指定するとおおむね 1 秒に 1 回程度フラッシュされるようになる。一般に -bufsize を低くするとシステム負荷が大きくなり、ビットレートの低下が起きやすくなることから、可能な限り大きな値を指定することが推奨されている。しかし、突発的な電源切断が発生しやすい環境でなるべく電源断の直前までの映像を記録しておきたい場合や、ストリーミングで遅延をなるべく少なくしたい場合には、デフォルト値では必要以上に時間が空きすぎることから、目標のビットレートを落とさない程度に小さな値を指定する方が良いと言える。最初は -b:v と同じ値を指定して、生成される動画のビットレートが低下していないかを見ながら調整すると良い。

pi@pirite:~$ ffmpeg -i /dev/video0 -b:v 5M -bufsize 5M -c:v h264_v4l2m2m -pix_fmt yuv420p testshot.avi

カメラの性能制限により、一般に高い解像度は高い FPS に対応していない。解像度と FPS の関係は v4l2-ctl -d /dev/video0 --list-formats-ext で確認することができる。

同様に、高解像度で高 FPS の設定は圧縮フォーマット (Motion-JPEG) でのみ対応というケースが多い。しかし Motion-JPEG はデコードに多くの CPU 時間を要し、Raspberry Pi の ARM プロセッサではリアルタイムでの変換が難しい。もし高解像度、高FPSが必要で Motion-JPEG で入力しなければならないのであれば、h.264 には変換せず無変換で保存するのがもっとも現実的である (方法は後述)。

また次のセクションで解説するようにマイク付きカメラであれば音声付きで保存することもできる。

音声付きの映像記録

一般的な Web カメラはオンライン会議やライブチャットができるように標準でマイクを搭載している。ここではカメラのマイクを使って映像とともに音声も記録してみよう。

まず USB カメラの音声入力がどのデバイスで認識されているかを arecord コマンドで確認する。以下の例では USB 接続している C270n の音声入力が card 1 の device 0 として認識されていることを示している。

pi@pirite:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: WEBCAM [C270 HD WEBCAM], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

次に先述の ffmpeg コマンドに音声記録用のオプションを追加する。以下の例は alsa を使って card=1, device=0 の音声入力デバイス (-i hw:1,0) からモノラル入力 (-ac 1) で記録するように指定している。

pi@pirite:~ $ ffmpeg \
  -i /dev/video0 \
  -f alsa -ac 1 -i hw:1,0 \
  -c:v h264_v4l2m2m \
  -pix_fmt yuv420p \
  testshot.avi

テキスト情報付きの映像記録

FFmpeg の drawtext フィルターを使うと映像にテキストを重ねて記録することができる。変化しない固定的なテキストや単純な時刻であればコマンドオプションのみで表示させることができるが、ここでは温度や位置情報など、別のセンサーデバイスのデータを映像と一緒に記録することを想定してみよう。

外部のデータを表示させるスキームは、コマンドラインオプションでテキストファイルを指定して「1 フレームごとにテキストを読み込む」という動作になる。したがって FFmpeg と一緒にテキストファイルに最新情報を書き込むプログラムも実行されていなければならない。

試験的に次のようなスクリプトを実行しよう。これは 1 秒おきにカウントアップする数値で /tmp/telop.txt ファイルの内容を書き換える (放置しても 1 時間で終了する)。

pi@pirite:~ $ n=0; \
while [ $n -lt 3600 ]; do \
  echo $n > /tmp/telop.tmp; \
  mv /tmp/telop.tmp /tmp/telop.txt; \
  n=`expr $n + 1`; \
  sleep 1; \
done &
[1] 7284
pi@pirite:~ $ cat /tmp/telop.txt
6
pi@pirite:~ $ cat /tmp/telop.txt
12

テキストを描画するための -vf ... オプションを追加して映像の記録を開始する。

pi@pirite:~ $ ffmpeg \
  -i /dev/video0 \
  -vf 'format=pix_fmts=yuv420p,drawtext=textfile=/tmp/telop.txt:fontsize=64:reload=1' \
  -c:v h264_v4l2m2m \
  -pix_fmt yuv420p \
  testshot.avi

以下は実際に C270 を使って撮影した映像である。カメラの映像と共にテキストファイルの内容が記録されていることがわかるだろう (画質が荒かったのでビットレートに 700k を指定した)。

drawtext オプションを調整することで文字のフォントや位置、大きさ、色、背景、透明度などをより詳細にカスタマイズすることができる。また -vf 内にコンマ区切りで drawtext オプションを複数指定することで複数行のように描画したり、複数の位置に描画することもできる。

FFmpeg によるテキストファイルの読み込み頻度が非常に高いため、書き込み途中の内容が意図せず表示されてしまわないようにファイルの更新はアトミックに行う必要がある。つまり、更新側のプログラムは別名のファイルに書き込んで mvrename() で置き換えなければならない。

高頻度の書き込みは SD カードやフラッシュメモリの劣化を早めることになる。長期的に使用するのであれば tmpfs (RAM ディスク) のパーティションを作成してそこをテキストファイルの保存場所とするのが良い。

動画のストリーミング方法

TBC

その他の記録方法

h.264 ハードウェアエンコーダー以外の方法や、試行錯誤してうまく機能しなかったフィルタなど。v4l2m2m を使って MPEG-4 形式で記録できているのであれば特に読む必要はない。

カメラ側での H.264 エンコード

Raspberry Pi 専用のカメラモジュールのようにカメラ側で H.264 ビデオエンコーディングに対応しているのであれば、映像入力に H.264 で指定すれば Raspberry Pi 側の負荷をさらに低くできる。

pi@pirite:~ $ ffmpeg \
  -input_format h264 -i /dev/video2 \
  -c:v copy \
  testshot.avi

USB Web カメラの時との違いは、カメラからの入力フォーマットに h264 を指定しており、FFmpeg のフィルタは何もしない (無変換) としている点である。

カメラモジュールであれば Raspberry Pi 標準の raspivid コマンドでも h.264 エンコードの動画で記録することができるが、素の h.264 形式のままではいささか取り回しが悪いため FFmpeg にパイプして AVI 形式か MP4 形式で保存すると良い。以下の例は 10 秒間の動画を記録している。

pi@pirite:~ $ raspivid -o - -t 10000 | ffmpeg -i - -c:v copy testshot.avi

以下は実際にカメラモジュールから取り込んだ H.264 エンコードの動画である (右半分のちらつきは液晶ディスプレイの光)。1920×1080、25fps、10 秒で約 20MB の大きさとなった。

カメラモジュールでも USB Web カメラと同様に FFmpeg のオプションを指定することで音声を含めて記録することができる。ただし、独立したマイク入力はカメラ映像と同期していないことから、入力の時差によって音ズレなどが発生することは考慮しておかなければならない。

なお、FFmpeg の機能を利用して映像のフィルタリングやフリップといった加工を行う場合は Raspberry Pi 側で h.264 デコード/エンコードが必要になる点に注意。

MP4 形式での記録

先述の通り FFmpeg はファイル拡張子を認識するため、出力ファイルの拡張子を .avi から .mp4 に変えることで MP4 形式で保存することができる。AVI も MP4 も H.264 を格納できるため必要最小限の範囲ではそれ以外の変更は必要ない (詳細な調整を行いたい場合はそれぞれ固有のオプションを指定する必要があるかもしれない)。

pi@pirite:~$ ffmpeg -i /dev/video0 -c:v h264_v4l2m2m -pix_fmt yuv420p testshot.mp4

FFmpeg での記録中に本体の電源が切断されたときなど、書き込み途中で破損した MP4 ファイルは Windows でも macOS でも全く再生することができないし、ネットでいくつか見つかる復旧方法のどれもうまく行かなかった。Fig 2 は実際に Windows で再生しようとしたときに表示される 0xc00d36c4 エラーである。

Fig 2. 記録中に中断された MP4 ファイルの再生。

h264_omx: Web を検索していると h264_omx を使用した h.264 ハードウェアエンコーディングの情報をよく見かけるが、2022/01 時点の Raspbian 11 (bullseye) の環境で利用可能な libOMX_Core.so または libOmxCore.so を見つけることができなかった。ソースビルドで入手できるのかもしれないが、h264_v4l2m2m で正しくエンコードができているのであれば特に必要はないだろう。

libx264 によるソフトウェアエンコード

FFmpeg で出力ファイルを .mp4 ファイルとしながら -c:v オプションを省略したり、明示的に -c:v libx264 オプションを指定すると、デフォルトの h.246 ソフトウェアエンコーダーである libx264 を使用する。libx264 で作成したファイルが Windows や macOS でも真っ黒で再生ボタンも機能しない映像(?)が表示される場合は -pix_fmt でピクセルフォーマットを指定するとうまく機能する。

pi@pirite:~$ ffmpeg \
  -i /dev/video0 \
  -c:v libx264 -pix_fmt yuv420p \
  testshot.mp4

Raspberry Pi での h.264 ソフトウェアエンコーダーは CPU 使用率が高く、何らかの理由でハードウェアエンコーダーが使用できない場合のみにとどめたほうが良いだろう。しかし libx264 のほうが汎用性が高く市販カメラとの互換性も良いのも事実である。相性の問題で h264_v4l2m2m ではエラーになるが libx264 ではエラーにならないケースもしばしば見られる。

Motion JPEG での記録

先に v4l2-clt で調べたとおり C270n は YUYV (無圧縮) の他に Motion JPEG のビデオフォーマットに対応している。Motion JPEG は MPEG-4 と比べてファイルサイズが大きくなるが、Raspberry Pi 本体の h.264 ハードウェアエンコーダーを専有しないため、複数のカメラから同時に記録するときなどに有用なことがあるかもしれない。

以下のコマンドはカメラの映像を Motion JPEG で受信してそのまま Matroska Video 形式で保存する。オプションに JPEG の圧縮率である -q:v 2 を指定すると容量が大きくなる代わりに映像の品質を最大まで上げることができる (2~31 の範囲で指定できる)。

pi@pirite:~ $ ffmpeg \
  -f v4l2 -input_format mjpeg -i /dev/video0 \
  -q:v 2 \
  -c:v copy \
  testshot.mkv

mkv ファイル自体は様々なフォーマットのデータを格納できるコンテナ形式なので分かりづらいが mediainfo コマンドを使用するとその内容が Motion JPEG 形式であることが分かる。

pi@pirite:~ $ mediainfo testshot.mkv
...
Video
ID                                       : 1
Format                                   : V_MJPEG
Codec ID                                 : V_MJPEG
Duration                                 : 16 s 490 ms
Bit rate                                 : 13.9 Mb/s
Width                                    : 640 pixels
Height                                   : 480 pixels
Display aspect ratio                     : 4:3
Frame rate mode                          : Variable
Color space                              : YUV
Stream size                              : 27.4 MiB (98%)
Default                                  : Yes
Forced                                   : No
Color range                              : Full
Matrix coefficients                      : BT.470 System B/G

上記のコマンドで保存した mkv 形式 (Motion JPEG) の映像は 640×480, 30fps, 10.7 秒で 17.8MB の大きさとなった。この mkv 形式の Motion JPEG は Windows 10 標準の「映画 & テレビ」ビューワーで表示することができる (またトリミング等で編集して保存すると MP4 に変換することもできる)。macOS では VLC などのメディアプレーヤーをインストールする必要がある。いずれにしても h.264 エンコードが可能な Windows/macOS マシンで mp4 に再変換したほうが取り回しは良いかもしれない。

高フレームレートの撮影

カメラが高フレームレートに対応していたとしても Raspberry Pi の能力でリアルタイムのコーディック変換やサイズ変換、テキスト描画などを行うことは現実的に厳しい。このためカメラの対応しているコーデックをそのまま無変換で保存するアプローチで考えるのが良い。

例えば Logitech C922n は Motion-JPEG の 1280x720 でのみ 60fps に対応しているが、Fig 1 から Motion-JPEG 入力で加工変換を行おうとするとソフトウェアデコードのステップが必要となり 60fps の速度は難しくなる。このため Motion JPEG での記録を参考に Motion-JPEG を無変換で AVI に記録する。

pi@pirite:~ $ ffmpeg -f v4l2 -input_format mjpeg -s 1280x720 -r 60 -i /dev/video0 -c:v copy testshot.avi
ffmpeg version 4.3.3-0+rpt2+deb11u1 Copyright (c) 2000-2021 the FFmpeg developers
Input #0, video4linux2,v4l2, from '/dev/video0':
  Duration: N/A, start: 235201.151538, bitrate: N/A
    Stream #0:0: Video: mjpeg (Baseline), yuvj422p(pc, bt470bg/unknown/unknown), 1280x720, 60 fps, 60 tbr, 1000k tbn, 1000k tbc
Output #0, avi, to 'testshot.avi':
  Metadata:
    ISFT            : Lavf58.45.100
    Stream #0:0: Video: mjpeg (Baseline) (MJPG / 0x47504A4D), yuvj422p(pc, bt470bg/unknown/unknown), 1280x720, q=2-31, 60 fps, 60 tbr, 60 tbn, 60 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
frame=  669 fps= 41 q=-1.0 Lsize=  108718kB time=00:00:18.30 bitrate=48667.5kbits/s speed=1.13x
video:108686kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.029111%

pi@pirite:~ $ mediainfo testshot.avi
General
Complete name                            : testshot.avi
Format                                   : AVI
Format/Info                              : Audio Video Interleave
File size                                : 106 MiB
Duration                                 : 18 s 300 ms
Overall bit rate                         : 48.7 Mb/s
Writing application                      : Lavf58.45.100

Video
ID                                       : 0
Format                                   : JPEG
Codec ID                                 : MJPG
Duration                                 : 18 s 300 ms
Bit rate                                 : 48.7 Mb/s
Width                                    : 1 280 pixels
Height                                   : 720 pixels
Display aspect ratio                     : 16:9
Frame rate                               : 60.000 FPS
Color space                              : YUV
Chroma subsampling                       : 4:2:2
Bit depth                                : 8 bits
Compression mode                         : Lossy
Bits/(Pixel*Frame)                       : 0.880
Stream size                              : 106 MiB (100%)

ここで -r オプションの指定位置に注意。-i より後ろに配置すると出力オプションとみなされ、実際は低フレームレートでの入力となっているかもしれない。記録した映像が首尾よく 60fps の映像が記録できたとしても、入力や加工の段階で一度低フレームレートとなったあとに引き伸ばされていないか注意する必要がある。

フォーマットの変換

この記事ではカメラからの一次保存形式に AVI を推奨したが、現実的な取り回しに関しては Windows や mac OS、各種ブラウザで再生できる MP4 形式で扱いたい要望もあるだろう。MP4 形式へは以下のコマンドで変換することができる。

pi@pirite:~$ ffmpeg -i testshot.avi testshot.mp4

特に、この記事の方法 ─ つまり h264_v4l2m2m を指定して作成した AVI ファイルでは、映像が既に H.264 エンコードされていることから -c:v copy オプションを指定してコンテナ形式だけを MP4 に変換することで、CPU をほとんど使用せず変換することができる (とはいえ 1 時間の映像で30秒~1分程度はかかるが)。

pi@pirite:~$ ffmpeg -i testshot.avi -c:v copy testshot.mp4

クアッドコアの CPU であれば -c:v copy 指定の変換を Raspberry Pi 上で行っても他の処理の影響を心配することはないだろう。

FFmpeg は MP4 以外にも様々な形式に変換できる。例えば Fig 3 は AVI 形式で保存した動画を 5fps のアニメーション GIF に変換したものである。

pi@pirite:~ $ ffmpeg -i testshot.avi -r 5 testshot.gif
Fig 3. h.264 + AVI で取り込んだ動画をアニメーション GIF に変換。

FFmpeg のエラー

Logitech C922n の問題

Logitech C922n の映像を Raspberry Pi で記録しようとすると以下のようなエラーが出た。

[avi @ 0x15bf8f0] Application provided invalid, non monotonically increasing dts to muxer in stream 0: 1 >= 1
av_interleaved_write_frame(): Invalid argument

これは C270n では起きないため C922n 固有の問題だろう。そしてハードウェアエンコーダーの h264_v4l2m2m の代わりにソフトウェアエンコーダーの libx264 を使用すると発生しないことから C922n と h264_v4l2m2m との相性の問題のように見える。

この問題はオプションに -ss 0:00 を追加したら発生しなくなった。最初期のフレームの DTS が不正な値になっているのだろうか?

pi@pirite:~ $ ffmpeg -ss 0:00 -i /dev/video0 -c:v h264_v4l2m2m footage.avi

Appendix

USB Web カメラ vs. カメラモジュール

USB Web カメラとカメラモジュールではどちらが良いか、という観点では、個人的には USB Web カメラを使用すれば良いという所感である。カメラモジュールは、静かな室内で固定した状態で使用し、なるべく安く済ませたいのであれば選択肢に入るだろうというところで。それぞれの特徴を以下に書き記す。

カメラモジュール
  • カメラ側の h.264 ハードウェアエンコーダーを使用できる (Raspberry Pi 本体側の負荷が低い)。
  • 比較的安価である。
  • 省スペース。
  • USB ポートを専有しない。
  • DIY 感が出る (プレゼンやデモでは重要)。
USB Web カメラ
  • 外殻を持ち基盤やケーブルが保護されている。
  • 一般にマイク (音声入力) が標準で付属している。
  • 複数のカメラを USB ポートに接続して同時に撮影できる。
  • ケーブルが長く Raspberry Pi 本体から離れた位置で撮影ができる。
  • カメラを PC でも使うことができる。
  • オートフォーカスや自動露光など機能的な選択肢が比較的豊富。
  • Linux OS で USB カメラを使う一般的な知識が応用できる。

双方の最も大きな違いは筐体の堅牢さだろう。手元のカメラモジュールのカメラ部分はコネクタで基盤と接続しているだけで micro SD カードよりも小さい部品である。これを屋外や車内に設置すればすぐに紛失してしまうだろう。

USB Web カメラであっても h.264 ビデオフォーマットに対応したモデルは存在する。例えば Logicool C925eC930e の他にも Amazon を検索すると安いものは 3000 円前後から見つかるようだ (いずれも動作未確認)。

ハードウェアエンコード vs. ソフトウェアエンコード

h.264 ハードウェアエンコーダとソフトウェアエンコーダーでは Raspberry Pi 本体の負荷はどの程度違うのだろうか。比較のためにそれぞれのエンコードを実行しながら vmstat 1 を起動して負荷を見てみよう。

まず h264_v4l2m2m を指定して h.264 ハードウェアエンコーダーを有効にしたケース。記録中も user, system の CPU 使用率はほとんど上昇せず idle が 100% 近くで推移していることがわかる。free memory に若干の減少が見られる。

pi@pirite:~ $ ffmpeg -i /dev/video0 -c:v h264_v4l2m2m -pix_fmt yuv420p testshot.avi
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 399400  48384 395032    0    0     0     0   75   67  0  0 100  0  0
 0  0      0 399400  48384 395032    0    0     0     0   74   58  0  0 100  0  0
 0  0      0 394108  48384 395032    0    0     0    24  514  449 12  1 87  0  0    ← 起動
 0  0      0 327900  48384 395032    0    0     0     0 1562 1150  3  5 92  0  0
 1  0      0 327992  48384 395036    0    0     0     0 1574 1279  1  0 99  0  0
 1  0      0 327740  48384 395036    0    0     0     0 1463 1097  1  0 99  0  0
 0  0      0 327740  48384 395036    0    0     0     0 1458 1096  1  1 98  0  0
 0  0      0 327740  48384 395036    0    0     0   100 1508 1224  1  0 99  0  0
 0  0      0 327740  48384 395036    0    0     0     0 1508 1134  1  0 99  0  0
 0  0      0 327740  48384 395036    0    0     0    40 1504 1140  1  0 99  0  0
 0  0      0 327740  48384 395036    0    0     0     0 1574 1228  1  0 99  0  0
 1  0      0 327740  48384 395036    0    0     0     0 1551 1221  1  0 99  0  0
 0  0      0 327740  48384 395036    0    0     0     0 1549 1253  1  0 99  0  0
 1  0      0 398728  48384 395136    0    0     0     0  433  388  1  2 98  0  0    ← 終了
 0  0      0 398884  48384 395180    0    0     0     0   76   64  0  0 100  0  0

続いて libx264 を指定して h.264 ソフトウェアエンコーダーを有効にしたケース。こちらは起動から徐々に user の CPU 使用率が上昇し数秒で 80% 前後となる。また実行中は memory の free 値も大きく減少している。

pi@pirite:~ $ ffmpeg -i /dev/video0 -c:v libx264 -pix_fmt yuv420p testshot.avi
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 398456  48460 395092    0    0     0     0   74   54  0  0 100  0  0
 0  0      0 398456  48460 395092    0    0     0     0   83   65  0  0 100  0  0
 0  0      0 394172  48460 395092    0    0     0     0  336  246 12  2 86  0  0    ← 起動
 0  0      0 336472  48460 395100    0    0     0    32  943  625  6  3 92  0  0
 0  0      0 311020  48460 395100    0    0     0     0 1031  849  5  0 95  0  0
 2  0      0 285316  48460 395100    0    0     0     0 1022  847  3  2 95  0  0
 0  0      0 259864  48460 395100    0    0     0     0  980  799  3  2 95  0  0
 1  0      0 233908  48460 395100    0    0     0     0  982  808  9  2 89  0  0
 1  0      0 227608  48464 395096    0    0     0    44  918  610 26  0 73  0  0
 5  0      0 196864  48464 395100    0    0     0     0 1125  743 63  1 37  0  0
 4  0      0 174436  48464 395100    0    0     0     0 1338  895 88  2 10  0  0
 6  0      0 163600  48464 395100    0    0     0     0 1322  891 92  1  8  0  0
 6  0      0 160072  48464 395356    0    0     0     0 1359  957 90  2  8  0  0
 5  0      0 159820  48464 395356    0    0     0     0 1373  991 84  5 12  0  0
 6  0      0 159820  48464 395356    0    0     0     0 1359  958 76 13 12  0  0
 6  0      0 159820  48464 395356    0    0     0     0 1305  893 79  9 12  0  0
 6  0      0 159316  48464 395612    0    0     0     0 1308  782 82  8 10  0  0
 6  0      0 159316  48464 395612    0    0     0    12 1505  806 81 10 10  0  0
 7  0      0 159064  48464 395612    0    0     0     0 1526  792 88  1 11  0  0
 7  0      0 158812  48464 395812    0    0     0     0 1515  796 81 10  9  0  0
 0  0      0 398464  48464 396324    0    0     0     0 1212  506 67  2 31  0  0    ← 終了
 0  0      0 398560  48468 396320    0    0     0    12   86   80  0  0 100  0  0

最後にカメラモジュール側の h.264 ハードウェアエンコーダーを使用するケース。CPU 使用率の上昇は見られず、free memory の減少量や割り込み、コンテキストスイッチの回数も少ない。比較した中では本体の負荷が最も低いと言えるだろう。

pi@pirite:~ $ ffmpeg -input_format h264 -i /dev/video2 -c:v copy testshot.avi
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 397640  48412 395080    0    0     0     0   73   64  0  0 100  0  0
 1  0      0 397388  48412 395080    0    0     0     0  110   95  0  1 99  0  0
 1  0      0 383024  48412 395080    0    0     0     0  304  340 12  2 86  0  0    ← 起動
 0  0      0 380724  48412 395340    0    0     0     0  486  718  5  2 93  0  0
 1  0      0 380724  48412 395540    0    0     0   416  833 1175  0  1 98  0  0
 0  0      0 380472  48412 395840    0    0     0     0  401  585  0  0 99  0  0
 0  0      0 380244  48412 396108    0    0     0     0  284  474  0  1 99  0  0
 0  0      0 379992  48412 396308    0    0     0     0  317  526  0  0 100  0  0
 0  0      0 379740  48416 396616    0    0     0    36  362  575  0  0 100  0  0
 0  0      0 379488  48416 396820    0    0     0     0  356  560  0  1 99  0  0
 0  0      0 379236  48416 397132    0    0     0     0  351  548  0  0 99  0  0
 0  0      0 378984  48416 397332    0    0     0     0  344  568  0  0 100  0  0    ← 終了
 0  0      0 394128  48416 398404    0    0     0    12   85   83  0  0 100  0  0

比較のため Web カメラが対応している Motion JPEG ビデオフォーマットの入力を無変換で Matroska Video 形式として保存するケース。CPU 使用率はほとんど上昇しない。Web カメラが対応しているエンコードでそのまま保存する方法は h.264 ハードウェアエンコーダーを専有しないため、複数の録画を同時に実行したいとき有用かもしれない。

pi@pirite:~ $ ffmpeg -input_format mjpeg -i /dev/video0 -c:v copy testshot.mkv
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 392580  49004 397828    0    0     0     0   84   63  0  0 100  0  0
 0  0      0 392580  49004 397828    0    0     0     0   81   57  0  0 100  0  0
 1  0      0 391572  49004 397828    0    0     0     0  143  129  1  1 98  0  0    ← 起動
 0  0      0 344416  49004 397828    0    0     0     0  421  262 11  2 87  0  0
 0  0      0 344048  49004 399528    0    0     0     0 1200  744  2  1 97  0  0
 0  0      0 341856  49004 401984    0    0     0     0 1250  824  1  1 98  0  0
 0  0      0 339588  49004 404284    0    0     0     0 1225  789  1  1 98  0  0
 0  0      0 337068  49004 406924    0    0     0     0 1221  792  1  1 98  0  0
 0  0      0 334800  49004 409264    0    0     0    44 1236  815  1  0 98  0  0
 0  0      0 332280  49004 411824    0    0     0     0 1223  786  1  0 98  0  0
 3  0      0 329760  49004 414204    0    0     0     0 1258  811  1  1 98  0  0
 0  0      0 327240  49004 416464    0    0     0     0 1224  786  0  1 99  0  0
 0  0      0 361764  49004 431380    0    0     0     0  141   94  0  1 99  0  0    ← 終了
 0  0      0 361772  49004 431380    0    0     0     0  112   81  0  0 100  0  0
 0  0      0 361772  49004 431380    0    0     0     0   72   58  0  0 100  0  0

この記事は Raspberry Pi 3 B+ 上の以下の環境で作業を行っている。またすべての操作はコマンドライン (CUI) 上で行うことを想定している。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
Codename:       bullseye

$ uname -a
Linux pirite 5.10.89-v7+ #1508 SMP Tue Jan 4 19:51:16 GMT 2022 armv7l GNU/Linux