ROCK 5BのHDMI Inを使って4Kキャプチャしよう
Rock 5BにはHDMI Inがついて来る。4K60 YUV420まではキャプチャできるようで、スペック上はなかなか優秀だ。
ということで、これを活用してみる。
いろいろ試行錯誤した結果、素のffmpegやobsではうまくキャプチャできなかったが、独自にv4l2のmulti planar APIに対応させたffmpeg改造版とrkmppencを組み合わせることで、4K60 YUV420でキャプチャ & hwエンコードできるようになった。
まず、Radxa ROCK 5Bのキャプチャデバイスは、 /dev/video0 として認識される。
ここまではよさそうだが、いろいろな問題がある。まとめると、こんな感じ。
- Multiplanar formatを使っているが、ffmpegもOBSも対応していない
- ffmpegのv4l2読みがNV16/NV24に対応しない
- デバイスがffmpegの使用する一部の呼び出しに対応していない。
- VIDIOC_G_INPUT (読み込み状況の確認)
- VIDIOC_G_PARM (フレームレートの取得)
ffmpegとv4l2-ctlを使いながら、ひとつひとつ見てみよう。
まず、入力はRGB, YUV444(NV24), YUV422(NV16), YUV420(NV12)に対応するが、厄介なのは v4l2 multi-planar API を使用している点である。
OBSもffmpegもMultiplanar APIには非対応のようで、これが原因でエラーをはいてしまう。
また、現行のffmpegのv4l2はNV16、NV24に対応していないので、ここもうまくいかない原因となってしまう。
さらに一部の呼び出しに対応していないようで、これはかなり困った点だ。
例えば、VIDIOC_G_INPUT 呼び出し(v4l2-ctl --get-input)にも応答しない。ffmpegは、VIDIOC_G_INPUT 呼び出しで入力があるかを判定しており、応答がないとエラー終了してし
しまう。ついでに、VIDIOC_S_INPUTにも反応しない。
また、VIDIOC_G_PARM 呼び出し(v4l2-ctl --get-parm) にも応答しない。ffmpegは VIDIOC_G_PARM でフレームレートを取得しているが、これの応答がないため、フレームレートが取得されず、でたらめなフレームレートになってしまう。
また、下記のように --get-freq にも応答しない。
VIDIOC_G_DV_TIMINGS (v4l2-ctl --get-dv-timings) には対応しているため、これを使うと、フレームレートを取得できるようだ。
このように、いろいろ問題があるので、とりあえずffmpegのv4l2読み部分を改造して、なんとか読み込めるようにしてみた。
https://github.com/rigaya/FFmpeg/tree/v4l2-multiplanar
1. v4l2 multi-planar APIへの対応
v4l2のドキュメントを参考に、multi-planar APIへの対応をざっと行った。それなりにコードの書き換えが必要だった。
https://github.com/rigaya/FFmpeg/commit/6ecd35e9d8e74da49a03e82b203c5fd6f1c084d8
2. NV16/NV24への対応
対応するといっても、これは単に定義を追加してあげるだけ。
https://github.com/rigaya/FFmpeg/commit/d7bf5fb1dcdd7ddbd173935ca026b17c219a2583
3. VIDIOC_G_INPUT/VIDIOC_S_INPUT に応答しない問題への対応
新たに ignore_input_error というオプションを追加する。 現状だと、VIDIOC_G_INPUT/VIDIOC_S_INPUTが常にエラーを返すので、ここで処理が止まってしまって先に進めない。そこで、"-ignore_input_error 1" とした場合には、ユーザーがchannelを指定した場合に限り、VIDIOC_S_INPUTがエラーを返してもエラー終了するのではなく、警告のみ表示して先に進むよう処理を変える。
https://github.com/rigaya/FFmpeg/commit/a91b4136cccf69954cef70e3f5fd3da3aabbc15a
4. VIDIOC_G_PARM がエラーを返し、フレームレートが設定されない問題への対応
VIDIOC_G_PARM がエラーを返した場合に、VIDIOC_G_DV_TIMINGS を使ってこちらからフレームレートを推定するようにする。
取得できるのは、毎秒のpixel数でフレームレートではないので、若干値がずれるが、多少であれば丸めてそれっぽいフレームレートにしてしまう。
https://github.com/rigaya/FFmpeg/commit/0d6e971afec7d5e997ef197a327e14d8b86443c2
ここまでやって、やっとffmpegでキャプチャできるようになる。
この改造版ffmpegを使って試しにキャプチャしてみる。
ffmpeg -f v4l2 -thread_queue_size 4096 -channel 0 -ignore_input_error 1 -i /dev/video0 -pix_fmt yuv420p -c:v libx264 -crf 20 -preset ultrafast ~/test.ts
1080p60なので、x264のultrafastを使ってもかなりCPU使用率は高め。それでも、現実的にエンコードできている。
HDMI Inの音声
次に、音声も読み込んでみよう。音声の入力デバイスは "arecord -l" で確認可能。
ただ、最初は残念ながら音声がないと言われてしまった。
実際には、デバイスは見えていないだけで、存在はしている。
dmesgを見ても認識はされてそうだった。
原因は気づいてしまえば簡単で、権限の問題だった。ユーザーを "audio" groupに追加することで解決した。
これでshellを立ち上げなおすと、
hdmiinは "カード4" なので、これをffmpegで読み込むにはalsaを使って、
ffmpeg -f alsa -thread_queue_size 4096 -i hw:4 -f v4l2 -thread_queue_size 4096 -channel 0 -ignore_input_error 1 -i /dev/video0 -pix_fmt yuv420p -c:v libx264 -crf 20 -preset ultrafast ~/test.ts
とすればよかった。
キャプチャ映像をhwエンコード
先ほどはx264でswエンコードしていたので、CPU使用率が高かったが、hwエンコードすれば、CPU使用率を抑えられる。
前回作成したhwエンコーダのrkmppencを使ってみる。
まず、先ほど作成した改造版ffmpegのライブラリ(特にlibavdevice)をビルド・インストールして、さらにこれにリンクさせたrkmppencをビルドした。
リンク先がパッケージのlibavdeviceでなく、自分でビルドしたlibavdeviceであることを確認しよう。
そのうえで、下記コマンドを実行。
rkmppenc --audio-source hw:4:format=alsa,codec=aac -i /dev/video0 --input-format v4l2 --input-option ignore_input_error:1 --input-option channel:0 --input-option ts:abs --cqp 20 --gop-len 120 -o ~/test.ts
無事hwエンコードでCPU使用率をかなり低く抑えながら、キャプチャできている。
下記はNintendo SwitchのHDMIをキャプチャしたとき。1080p RGBで問題なくキャプチャできている。
今度はPC(RTX4080)の4K出力をキャプチャした場合。YUV420出力ではあるが、こちらも問題なくキャプチャできた。
いろいろと調整が必要だったものの、ROCK 5BのHDMI Inで4Kキャプチャができて、さらに強力なhwエンコーダのおかげで、これをリアルタイムエンコードできることも確認できた。
本当はキャプチャするならOBSでしたいところではあるけど、SBCでリアルタイムエンコードしながらHDMIの4Kキャプチャができるのはなかなか面白いと思う。
ということで、これを活用してみる。
いろいろ試行錯誤した結果、素のffmpegやobsではうまくキャプチャできなかったが、独自にv4l2のmulti planar APIに対応させたffmpeg改造版とrkmppencを組み合わせることで、4K60 YUV420でキャプチャ & hwエンコードできるようになった。
まず、Radxa ROCK 5Bのキャプチャデバイスは、 /dev/video0 として認識される。
rigaya@rock-5b:~$ v4l2-ctl --device /dev/video0 --all
Driver Info:
Driver name : rk_hdmirx
Card type : rk_hdmirx
Bus info : fdee0000.hdmirx-controller
Driver version : 5.10.110
Capabilities : 0x84201000
Video Capture Multiplanar
Streaming
Extended Pix Format
Device Capabilities
Device Caps : 0x04201000
Video Capture Multiplanar
Streaming
Extended Pix Format
Priority: 2
DV timings:
Active width: 1920
Active height: 1080
Total width: 2200
Total height: 1125
Frame format: progressive
Polarities: -vsync -hsync
Pixelclock: 148348000 Hz (59.94 frames per second)
Horizontal frontporch: 84
Horizontal sync: 48
Horizontal backporch: 148
Vertical frontporch: 4
Vertical sync: 5
Vertical backporch: 36
Standards:
Flags:
DV timings capabilities:
Minimum Width: 640
Maximum Width: 4096
Minimum Height: 480
Maximum Height: 2160
Minimum PClock: 20000000
Maximum PClock: 600000000
Standards: CTA-861
Capabilities: Interlaced, Progressive
Format Video Capture Multiplanar:
Width/Height : 1920/1080
Pixel Format : 'NV24' (Y/CbCr 4:4:4)
Field : None
Number of planes : 1
Flags : premultiplied-alpha, 0x000000fe
Colorspace : Unknown (0x10098a48)
Transfer Function : Default
YCbCr/HSV Encoding: Unknown (0x000000ff)
Quantization : Default
Plane 0 :
Bytes per Line : 1920
Size Image : 6220800
Digital Video Controls
power_present 0x00a00964 (bitmask): max=0x00000001 default=0x00000000 value=0x00000001 flags=read-only
ここまではよさそうだが、いろいろな問題がある。まとめると、こんな感じ。
- Multiplanar formatを使っているが、ffmpegもOBSも対応していない
- ffmpegのv4l2読みがNV16/NV24に対応しない
- デバイスがffmpegの使用する一部の呼び出しに対応していない。
- VIDIOC_G_INPUT (読み込み状況の確認)
- VIDIOC_G_PARM (フレームレートの取得)
ffmpegとv4l2-ctlを使いながら、ひとつひとつ見てみよう。
まず、入力はRGB, YUV444(NV24), YUV422(NV16), YUV420(NV12)に対応するが、厄介なのは v4l2 multi-planar API を使用している点である。
rigaya@rock-5b:~$ v4l2-ctl --device /dev/video0 --list-formats
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture Multiplanar
[0]: 'BGR3' (24-bit BGR 8-8-8)
[1]: 'NV24' (Y/CbCr 4:4:4)
[2]: 'NV16' (Y/CbCr 4:2:2)
[3]: 'NV12' (Y/CbCr 4:2:0)
OBSもffmpegもMultiplanar APIには非対応のようで、これが原因でエラーをはいてしまう。
ffmpeg -f v4l2 -i /dev/video0 -c:v libx264 ~/out.ts
[video4linux2,v4l2 @ 0x558b621f80] Not a video capture device.
/dev/video0: No such device
また、現行のffmpegのv4l2はNV16、NV24に対応していないので、ここもうまくいかない原因となってしまう。
さらに一部の呼び出しに対応していないようで、これはかなり困った点だ。
例えば、VIDIOC_G_INPUT 呼び出し(v4l2-ctl --get-input)にも応答しない。ffmpegは、VIDIOC_G_INPUT 呼び出しで入力があるかを判定しており、応答がないとエラー終了してし
しまう。ついでに、VIDIOC_S_INPUTにも反応しない。
rigaya@rock-5b:~$ v4l2-ctl --get-input
VIDIOC_G_INPUT: failed: Inappropriate ioctl for device
また、VIDIOC_G_PARM 呼び出し(v4l2-ctl --get-parm) にも応答しない。ffmpegは VIDIOC_G_PARM でフレームレートを取得しているが、これの応答がないため、フレームレートが取得されず、でたらめなフレームレートになってしまう。
また、下記のように --get-freq にも応答しない。
rigaya@rock-5b:~$ v4l2-ctl --get-parm
VIDIOC_G_PARM: failed: Inappropriate ioctl for device
rigaya@rock-5b:~$ v4l2-ctl --get-freq
VIDIOC_G_TUNER: failed: Inappropriate ioctl for device
VIDIOC_G_FREQUENCY: failed: Inappropriate ioctl for device
VIDIOC_G_DV_TIMINGS (v4l2-ctl --get-dv-timings) には対応しているため、これを使うと、フレームレートを取得できるようだ。
rigaya@rock-5b:~$ v4l2-ctl --get-dv-timings
DV timings:
Active width: 1920
Active height: 1080
Total width: 2200
Total height: 1125
Frame format: progressive
Polarities: -vsync -hsync
Pixelclock: 148348000 Hz (59.94 frames per second)
Horizontal frontporch: 84
Horizontal sync: 48
Horizontal backporch: 148
Vertical frontporch: 4
Vertical sync: 5
Vertical backporch: 36
Standards:
Flags:
このように、いろいろ問題があるので、とりあえずffmpegのv4l2読み部分を改造して、なんとか読み込めるようにしてみた。
https://github.com/rigaya/FFmpeg/tree/v4l2-multiplanar
1. v4l2 multi-planar APIへの対応
v4l2のドキュメントを参考に、multi-planar APIへの対応をざっと行った。それなりにコードの書き換えが必要だった。
https://github.com/rigaya/FFmpeg/commit/6ecd35e9d8e74da49a03e82b203c5fd6f1c084d8
2. NV16/NV24への対応
対応するといっても、これは単に定義を追加してあげるだけ。
https://github.com/rigaya/FFmpeg/commit/d7bf5fb1dcdd7ddbd173935ca026b17c219a2583
3. VIDIOC_G_INPUT/VIDIOC_S_INPUT に応答しない問題への対応
新たに ignore_input_error というオプションを追加する。 現状だと、VIDIOC_G_INPUT/VIDIOC_S_INPUTが常にエラーを返すので、ここで処理が止まってしまって先に進めない。そこで、"-ignore_input_error 1" とした場合には、ユーザーがchannelを指定した場合に限り、VIDIOC_S_INPUTがエラーを返してもエラー終了するのではなく、警告のみ表示して先に進むよう処理を変える。
https://github.com/rigaya/FFmpeg/commit/a91b4136cccf69954cef70e3f5fd3da3aabbc15a
4. VIDIOC_G_PARM がエラーを返し、フレームレートが設定されない問題への対応
VIDIOC_G_PARM がエラーを返した場合に、VIDIOC_G_DV_TIMINGS を使ってこちらからフレームレートを推定するようにする。
取得できるのは、毎秒のpixel数でフレームレートではないので、若干値がずれるが、多少であれば丸めてそれっぽいフレームレートにしてしまう。
https://github.com/rigaya/FFmpeg/commit/0d6e971afec7d5e997ef197a327e14d8b86443c2
ここまでやって、やっとffmpegでキャプチャできるようになる。
この改造版ffmpegを使って試しにキャプチャしてみる。
ffmpeg -f v4l2 -thread_queue_size 4096 -channel 0 -ignore_input_error 1 -i /dev/video0 -pix_fmt yuv420p -c:v libx264 -crf 20 -preset ultrafast ~/test.ts
1080p60なので、x264のultrafastを使ってもかなりCPU使用率は高め。それでも、現実的にエンコードできている。
HDMI Inの音声
次に、音声も読み込んでみよう。音声の入力デバイスは "arecord -l" で確認可能。
ただ、最初は残念ながら音声がないと言われてしまった。
rigaya@rock-5b:~$ arecord -l
arecord: device_list:276: サウンドカードが見つかりません...
実際には、デバイスは見えていないだけで、存在はしている。
rigaya@rock-5b:~$ cat /proc/asound/cards
0 [rockchiphdmi0 ]: rockchip-hdmi0 - rockchip-hdmi0
rockchip-hdmi0
1 [rockchiphdmi1 ]: rockchip-hdmi1 - rockchip-hdmi1
rockchip-hdmi1
2 [rockchipdp0 ]: rockchip_dp0 - rockchip,dp0
rockchip,dp0
3 [rockchipes8316 ]: rockchip-es8316 - rockchip-es8316
rockchip-es8316
4 [rockchiphdmiin ]: rockchip_hdmiin - rockchip,hdmiin
rockchip,hdmiin
dmesgを見ても認識はされてそうだった。
dmesg | grep audio
[ 429.654609] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_audio_interrupts_setup: 1
[ 429.817567] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_delayed_work_audio: enable audio
[ 429.817577] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_delayed_work_audio: restart audio fs(44100 -> 48000) ch(0 -> 2)
[ 429.817589] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_audio_fifo_init
[ 478.615980] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_audio_interrupts_setup: 1
[ 478.628920] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_delayed_work_audio: enable audio
[ 478.628930] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_delayed_work_audio: restart audio fs(44100 -> 48000) ch(0 -> 2)
[ 478.628941] rk_hdmirx fdee0000.hdmirx-controller: hdmirx_audio_fifo_init
原因は気づいてしまえば簡単で、権限の問題だった。ユーザーを "audio" groupに追加することで解決した。
sudo gpasswd -a ${USER} audio
これでshellを立ち上げなおすと、
rigaya@rock-5b:~$ arecord -l
**** ハードウェアデバイス CAPTURE のリスト ****
カード 3: rockchipes8316 [rockchip-es8316], デバイス 0: fe470000.i2s-ES8316 HiFi es8316.7-0011-0 [fe470000.i2s-ES8316 HiFi es8316.7-0011-0]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
カード 4: rockchiphdmiin [rockchip,hdmiin], デバイス 0: fddf8000.i2s-dummy_codec hdmiin-dc-0 [fddf8000.i2s-dummy_codec hdmiin-dc-0]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
hdmiinは "カード4" なので、これをffmpegで読み込むにはalsaを使って、
ffmpeg -f alsa -thread_queue_size 4096 -i hw:4 -f v4l2 -thread_queue_size 4096 -channel 0 -ignore_input_error 1 -i /dev/video0 -pix_fmt yuv420p -c:v libx264 -crf 20 -preset ultrafast ~/test.ts
とすればよかった。
キャプチャ映像をhwエンコード
先ほどはx264でswエンコードしていたので、CPU使用率が高かったが、hwエンコードすれば、CPU使用率を抑えられる。
前回作成したhwエンコーダのrkmppencを使ってみる。
まず、先ほど作成した改造版ffmpegのライブラリ(特にlibavdevice)をビルド・インストールして、さらにこれにリンクさせたrkmppencをビルドした。
リンク先がパッケージのlibavdeviceでなく、自分でビルドしたlibavdeviceであることを確認しよう。
rigaya@rock-5b:~/app/rkmppenc$ ldd rkmppenc | grep libavdevice
libavdevice.so.60 => /usr/local/lib/libavdevice.so.60 (0x0000007fb1f21000)
そのうえで、下記コマンドを実行。
rkmppenc --audio-source hw:4:format=alsa,codec=aac -i /dev/video0 --input-format v4l2 --input-option ignore_input_error:1 --input-option channel:0 --input-option ts:abs --cqp 20 --gop-len 120 -o ~/test.ts
無事hwエンコードでCPU使用率をかなり低く抑えながら、キャプチャできている。
下記はNintendo SwitchのHDMIをキャプチャしたとき。1080p RGBで問題なくキャプチャできている。
今度はPC(RTX4080)の4K出力をキャプチャした場合。YUV420出力ではあるが、こちらも問題なくキャプチャできた。
いろいろと調整が必要だったものの、ROCK 5BのHDMI Inで4Kキャプチャができて、さらに強力なhwエンコーダのおかげで、これをリアルタイムエンコードできることも確認できた。
本当はキャプチャするならOBSでしたいところではあるけど、SBCでリアルタイムエンコードしながらHDMIの4Kキャプチャができるのはなかなか面白いと思う。