🍇

ラズパイ4の64bitOSのgstreamerでH.264のハードウェアエンコードを試す

2023/04/19に公開

Raspberry Pi OSの64bit版のビデオエンコード、デコード処理

32bitのRaspberry Pi OSでは従来通りのmmalを使ったライブラリがありましたが、64bit版ではそれのサポートはなくなり、V4L2(Video for Linux 2)のインタフェースでのドライバに一本化されました。

gstreamerではそれに対応したプラグインがすでにあります。

$ gst-inspect-1.0 |grep v4l2
video4linux2:  v4l2src: Video (video4linux2) Source
video4linux2:  v4l2sink: Video (video4linux2) Sink
video4linux2:  v4l2radio: Radio (video4linux2) Tuner
video4linux2:  v4l2deviceprovider (GstDeviceProviderFactory)
video4linux2:  v4l2jpegdec: V4L2 JPEG Decoder
video4linux2:  v4l2h264dec: V4L2 H264 Decoder
video4linux2:  v4l2h264enc: V4L2 H.264 Encoder
video4linux2:  v4l2jpegenc: V4L2 JPEG Encoder
video4linux2:  v4l2convert: V4L2 Video Converter
video4linux2:  v4l2video18convert: V4L2 Video Converter
video4linux2:  v4l2video31jpegenc: V4L2 JPEG Encoder

この記事ではv4l2h264encv4l2jpegdecを試しました。

カーネルとgstreamerのバージョンは以下の通りです。

$ cat /proc/version 
Linux version 6.1.21-v8+ (dom@buildbot) (aarch64-linux-gnu-gcc-8 (Ubuntu/Linaro 8.4.0-3ubuntu1) 8.4.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023
$ gst-inspect-1.0 --version
gst-inspect-1.0 version 1.18.4
GStreamer 1.18.4
http://packages.qa.debian.org/gstreamer1.0

H.264のエンコード

USBのWebcamの映像をファイルに保存します。^Cで終了させます。

rec1.sh
#!/bin/sh -eux

gst-launch-1.0 v4l2src device="/dev/video0" ! \
'image/jpeg, width=1280, height=720, framerate=30/1' ! \
jpegdec ! \
videoconvert ! 'video/x-raw,format=I420,colorimetry=(string)bt601' ! \
v4l2h264enc  extra-controls="encode,video_bitrate=2500000;" ! \
'video/x-h264,level=(string)4,profile=high' ! \
h264parse ! qtmux ! filesink location=out.mp4 -e

うまくいかないときなど、ログをみたい場合には gst-launch-1.0-v オプションをつけます。

エンコーダのパラメータ設定

extra-controlsで指定できる項目はハードウェアエンコーダのデバイスである/dev/video11を見ます。

$ v4l2-ctl -d /dev/video11 -L

Codec Controls

                 video_b_frames 0x009909ca (int)    : min=0 max=0 step=1 default=0 value=0 flags=update
                 video_gop_size 0x009909cb (int)    : min=0 max=2147483647 step=1 default=60 value=60
             video_bitrate_mode 0x009909ce (menu)   : min=0 max=1 default=0 value=0 flags=update
				0: Variable Bitrate
				1: Constant Bitrate
                  video_bitrate 0x009909cf (int)    : min=25000 max=25000000 step=25000 default=10000000 value=10000000
           sequence_header_mode 0x009909d8 (menu)   : min=0 max=1 default=1 value=1
				0: Separate Buffer
				1: Joined With 1st Frame
         repeat_sequence_header 0x009909e2 (bool)   : default=0 value=0
                force_key_frame 0x009909e5 (button) : flags=write-only, execute-on-write
          h264_minimum_qp_value 0x00990a61 (int)    : min=0 max=51 step=1 default=20 value=20
          h264_maximum_qp_value 0x00990a62 (int)    : min=0 max=51 step=1 default=51 value=51
            h264_i_frame_period 0x00990a66 (int)    : min=0 max=2147483647 step=1 default=60 value=60
                     h264_level 0x00990a67 (menu)   : min=0 max=15 default=11 value=11
				0: 1
				1: 1b
				2: 1.1
				3: 1.2
				4: 1.3
				5: 2
				6: 2.1
				7: 2.2
				8: 3
				9: 3.1
				10: 3.2
				11: 4
				12: 4.1
				13: 4.2
				14: 5
				15: 5.1
                   h264_profile 0x00990a6b (menu)   : min=0 max=4 default=4 value=4
				0: Baseline
				1: Constrained Baseline
				2: Main
				4: High

GPUのメモリの割り当てを増やす

最初は映像のサイズが320x240だとうまくいくのに、1280x720にすると以下のようなnot enough memory のエラーが発生していました。

../sys/v4l2/gstv4l2videoenc.c(828): gst_v4l2_video_enc_handle_frame (): /GstPipeline:pipeline0/v4l2h264enc:v4l2h264enc0:
Maybe be due to not enough memory or failing driver

これは以下のようにして解決できました。

sudo raspi-config

4 Performance Options
  P2 GPU Memory
    256

をセットする。
このコマンドを終了すると自動でリブートします。

設定値の確認。

$ grep gpu_mem /boot/config.txt 
gpu_mem=256

MJPEGのデコードもハードウェアでやってみる

いろいろ試行錯誤した結果、以下のスクリプトでできました。

rec2.sh
#!/bin/sh -eux

gst-launch-1.0 v4l2src device="/dev/video0" ! \
'image/jpeg, width=1280, height=720, framerate=30/1' ! \
jpegparse ! v4l2jpegdec ! \
v4l2h264enc  extra-controls="encode,video_bitrate=2500000;" ! \
'video/x-h264,level=(string)4,profile=high' ! \
h264parse ! mpegtsmux ! filesink location=out.m2ts

どうもjpegparseを使用すると^Cによるシグナルをうまくハンドリングできないようで、mp4のファイルの後処理がうまくできませんでした。それで、強制終了させても問題ないコンテナフォーマットのmpeg2tsに変更しました。

htopでCPUの負荷をみると、rec1.shに比べて少し負荷が減っているのがわかります。
v4l2jpegdecとv4l2h264encの間をGPUのメモリで直接渡せるようにできればもっとCPUの負荷を減らせると思うのですが、とりあえず今日のところはここまでにします。

Discussion