Hardware video transcoding with FFMEG

Hi mzs, my appologies, time seems to have flown by this week.

It turns out that the webcam was indeed limited to 10fps @1280x720.

root@starfive:~# v4l2-ctl --list-formats-ext -d /dev/video4
ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'MJPG' (Motion-JPEG, compressed)
		Size: Discrete 3264x2448
			Interval: Discrete 0.067s (15.000 fps)
		Size: Discrete 2592x1944
			Interval: Discrete 0.050s (20.000 fps)
		Size: Discrete 2048x1536
			Interval: Discrete 0.050s (20.000 fps)
		Size: Discrete 1920x1080
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 1600x1200
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 1280x720
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 800x600
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.033s (30.000 fps)
	[1]: 'YUYV' (YUYV 4:2:2)
		Size: Discrete 640x480
			Interval: Discrete 0.033s (30.000 fps)
		Size: Discrete 800x600
			Interval: Discrete 0.050s (20.000 fps)
		Size: Discrete 1280x720
			Interval: Discrete 0.100s (10.000 fps)
1 Like

I’m not sure if this should be a new thread or not, I’m happy to start one if required.

As a result of the above, I bought a new webcam that is USB3 based and can therefore work at up to 60fps @1920x1080 which I initially thought would solve all my problems as I could just ask ffmpeg to run at 50fps.

root@starfive:~# v4l2-ctl --list-formats-ext -d /dev/video4
ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'MJPG' (Motion-JPEG, compressed)
		Size: Discrete 1920x1080
			Interval: Discrete 0.017s (60.000 fps)
		Size: Discrete 1280x720
			Interval: Discrete 0.017s (60.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.017s (60.000 fps)
	[1]: 'YUYV' (YUYV 4:2:2)
		Size: Discrete 1920x1080
			Interval: Discrete 0.017s (60.000 fps)
		Size: Discrete 1280x720
			Interval: Discrete 0.017s (60.000 fps)
		Size: Discrete 640x480
			Interval: Discrete 0.017s (60.000 fps)

I thought that something along the lines of ffmpeg -r 30 -f v4l2 -video_size 1920x1080 -y -i /dev/video4 -c:v hevc_omx ./test.mp4 would work, however after the following output and googling, it turns out that the 60fps listed above isn’t a maximum output figure, it’s the only supported frame rate.

ffmpeg version 4.4.1 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Debian 11.3.0-3)
  configuration: --prefix=/code_mm_20221111/target/usr --arch=riscv64 --target-os=linux --enable-gpl --disable-stripping --disable-static --enable-shared --enable-avfilter --disable-version3 --enable-logging --disable-extra-warnings --enable-avdevice --enable-avcodec --enable-avformat --enable-network --disable-gray --enable-swscale-alpha --disable-small --enable-dct --enable-fft --enable-mdct --enable-rdft --enable-libv4l2 --enable-alsa --enable-outdevs --enable-pthreads --enable-zlib --enable-indevs --enable-runtime-cpudetect --enable-pic --cpu=rv64imafd --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libdc1394 --enable-libdrm --enable-chromaprint --enable-libx264 --disable-frei0r --disable-gnutls --disable-ladspa --disable-libiec61883 --enable-omx --extra-ldflags=-L/code_mm_20221111/target/usr/lib --extra-cflags=-I/code_mm_20221111/target/usr/include/omx-il --extra-libs=-lOMX_Core
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
[video4linux2,v4l2 @ 0x2ab4594f10] The driver changed the time per frame from 1/30 to 1/60

At which point ffmpeg hangs (not even responding to the q command) with htop showing 1 core pinned at 100%.
Ctrl-C does quit ffmpeg and produces the following:

Input #0, video4linux2,v4l2, from '/dev/video4':
  Duration: N/A, bitrate: 1990656 kb/s
  Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 1920x1080, 1990656 kb/s, 60 fps, 60 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> hevc (hevc_omx))
Press [q] to stop, [?] for help
Finishing stream 0:0 without any data written to it.
[hevc_omx @ 0x2ab4598240] Using OMX.sf.video_encoder.hevc
Output #0, mp4, to './test.mp4':
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: hevc (hev1 / 0x31766568), yuv420p, 1920x1080, q=2-31, 200 kb/s, 30 fps, 15360 tbn
    Metadata:
      encoder         : Lavc58.134.100 hevc_omx
frame=    0 fps=0.0 q=0.0 Lsize=       0kB time=00:00:00.00 bitrate=N/A speed=   0x    
video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Exiting normally, received signal 2.

I’m assuming that ffmpeg is trying to use the CPU to encode at the full 60fps, so my question is, how do I force ffmpeg to drop frames before firing them at the hardware encoder?

According to this page ChangingFrameRate – FFmpeg -r takes effect after all filtering, but before encoding of the video stream has taken place. which would appear to do exactly what I want to do, however even running the command ffmpeg -f v4l2 -video_size 1920x1080 -y -i /dev/video4 -r 30 -c:v hevc_omx ./test.mp4 i.e. moving the -r switch from the input to the output still produces the same result.

I’ve just tried the command at 640x480 resolution and it appears to get close, though I’m not sure if that is more luck than judgement as the output talks about dropping frames.

root@starfive:~# ffmpeg -f v4l2 -video_size 640x480 -y -i /dev/video4 -r 30 -c:v hevc_omx  ./test.mp4
ffmpeg version 4.4.1 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Debian 11.3.0-3)
  configuration: --prefix=/code_mm_20221111/target/usr --arch=riscv64 --target-os=linux --enable-gpl --disable-stripping --disable-static --enable-shared --enable-avfilter --disable-version3 --enable-logging --disable-extra-warnings --enable-avdevice --enable-avcodec --enable-avformat --enable-network --disable-gray --enable-swscale-alpha --disable-small --enable-dct --enable-fft --enable-mdct --enable-rdft --enable-libv4l2 --enable-alsa --enable-outdevs --enable-pthreads --enable-zlib --enable-indevs --enable-runtime-cpudetect --enable-pic --cpu=rv64imafd --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libdc1394 --enable-libdrm --enable-chromaprint --enable-libx264 --disable-frei0r --disable-gnutls --disable-ladspa --disable-libiec61883 --enable-omx --extra-ldflags=-L/code_mm_20221111/target/usr/lib --extra-cflags=-I/code_mm_20221111/target/usr/include/omx-il --extra-libs=-lOMX_Core
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
Input #0, video4linux2,v4l2, from '/dev/video4':
  Duration: N/A, start: 4205.032346, bitrate: 294912 kb/s
  Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 640x480, 294912 kb/s, 60 fps, 60 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> hevc (hevc_omx))
Press [q] to stop, [?] for help
[hevc_omx @ 0x2ae85c4db0] Using OMX.sf.video_encoder.hevc
Output #0, mp4, to './test.mp4':
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: hevc (hev1 / 0x31766568), yuv420p(tv, progressive), 640x480, q=2-31, 200 kb/s, 30 fps, 15360 tbn
    Metadata:
      encoder         : Lavc58.134.100 hevc_omx
frame=  446 fps= 31 q=-0.0 Lsize=     361kB time=00:00:14.86 bitrate= 199.1kbits/s dup=7 drop=269 speed=1.02x    
video:359kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.734619%
Exiting normally, received signal 2.

With the above command, htop reports around 25-30% CPU usage on a single core.

1 Like

A quick though popped into my head, is the camera actually plugged into a USB 2.0 port and therefore it’s actually the USB subsystem that’s the problem? However a quick lsusb confirmed that the camera is connected at 5000Mbps.

root@starfive:~# lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
    |__ Port 1: Dev 3, If 0, Class=Video, Driver=uvcvideo, 5000M
    |__ Port 1: Dev 3, If 1, Class=Video, Driver=uvcvideo, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
1 Like

If you look at the message for every 1 frame out the the camera it was sending 2 to the encoder!

I’d try:
$ ffmpeg -t 60 -f v4l2 -input_format yuyv422 -video_size 1920x1080 -y -r 60 -i /dev/video4 -r 30 -c:v hevc_omx ./test.mp4
or to use 4 CPU threads, which may help if it is CPU bound:
$ ffmpeg -t 60 -f v4l2 -input_format yuyv422 -video_size 1920x1080 -y -i /dev/video4 -filter_threads 4 -filter:v fps=30 -c:v hevc_omx ./test.mp4

(“The default number of filter_threads is the number of available CPUs”, but that may not be detected correctly, always best to assume nothing). Are all 4 hart’s (HARware Threads) being used, or is just one CPU ? Maybe you need to add a “-cpucount 4” and remove the “-filter_threads 4”

I would test with a 60 second long output file (“-t 60”), it is long enough to see if you have a problem, and short enough that it will hopefully exit after 60 seconds.

Because “yuyv422” is raw uncompressed, but “mjpeg” is compressed, and I have no idea if it is defaulting to mjpeg or yuyv422. Let’s pick the simpler one to process since I have no idea how much CPU is involved in dropping frames from a compressed stream, or how much extra is involved in ffmpeg modifying each frame to be compatible with the encoder.

And explicitly tell it that the input rate is 60 fps and we want only 30 fps at the output.

EDIT: Looking at your output it does default to raw, but there is no harm in being specific.

EDIT(5): No idea how much CPU it would require to decompress mjpeg and convert it into raw at 30 fps but you could also try your old camera with:
$ ffmpeg -t 60 -cpucount 4 -f v4l2 -input_format mjpeg -video_size 1920x1080 -y -i /dev/video4 -c:v hevc_omx ./test.mp4

My gut feeling would be that it should take more resources, than simply dropping frames. But that jpeg decoding could, at least in theory, happen with hardware acceleration! And use less resources to get each where it needs to be processed further, so I do not know. It is worth a try.

I was ready to sing your name from the roof tops and buy you a beer until I realised that I was still testing it at 640x480 :man_facepalming:
Sadly, when I tried to run it at 1920x1080 neither adding -r 60 to the input nor changing -filter_threads to 4 made any difference. In both cases ffmpeg hung with just a single HART at 100% the others were 0, 0, < 10% even with -filter_threads 4. I did also try -cpucount - and recieved the following message:

Unrecognized option 'cpucount'.
Error splitting the argument list: Option not found

Interestingly, the final solution
$ ffmpeg -t 60 -cpucount 4 -f v4l2 -input_format mjpeg -video_size 1920x1080 -y -i /dev/video4 -c:v hevc_omx ./test.mp4 did actually produce some meaningful result. I had to remove -cpucount as it wasn’t recognised, but other than that ffmpeg ran for 60 seconds slowly ramping up to a maximum if 30fps and used ~ 25,51,0,0 % across the 4 HARTS

I believe that the JH7110 also has hardware JPEG decoding, so I might look at that next.

Interestingly, I tried -c:v mjpeg_omx on the input, ffmpeg printed the message:

[mjpeg_omx @ 0x2ae9efe1f0] OMX port 1 settings changed

Not only does it use more CPU, the single HART is now 100%, even if I subsequently try without specifying the omx decoder, it still uses 100% until I reboot.

Break it down, and split the tasks in three and see where the CPU is being chewed up.
Read 60 fps from the the camera for for 5 seconds (Because 5 seconds x 60 fps x 1920x1080 x 16 bits per pixel for yuyv422 would be ~1187 MiB. Writing data at about ~237 MiB/sec, maybe make a quick 2 GiB RAM disk just for the 5 second test!).

$ mkdir /tmp/ramdisk
$ sudo mount -t tmpfs -o size=2g tmpfs /tmp/ramdisk
$ sudo chmod 1777 /tmp/ramdisk

$ ffmpeg -t 5 -f v4l2 -input_format yuyv422 -video_size 1920x1080 -y -r 60 -i /dev/video4 /tmp/ramdisk/test_60.yuyv422
$ ffmpeg -s 1920x1080 -y -r 60 -i /tmp/ramdisk/test_60.yuyv422 -r 30 /tmp/ramdisk/test_30.yuyv422
$ ffmpeg -s 1920x1080 -y -r 30 -i /tmp/ramdisk/test_30.yuyv422 -c:v hevc_omx /tmp/ramdisk/test_30.mp4
$ sudo umount /tmp/ramdisk
$ sudo rmdir /tmp/ramdisk

The above ffmpeg commands probably have mistakes in them, but they should be close enough that you can work out where I messed up. The RAM disk stuff should be fine, just do not forget to move any files you want to keep to real storage before you power off or umount. umount gives back the RAM to the OS.

1 Like

Now that’s thinking outside of the box, I love it!
The ram disk was created fine,
$ ffmpeg -t 5 -f v4l2 -input_format yuyv422 -video_size 1920x1080 -y -r 60 -i /dev/video4 /tmp/ramdisk/test_60.yuyv422 didn’t run, it did the same as before, however when I hit Ctrl-C, it printed the following message:

[NULL @ 0x2ac263ab10] Unable to find a suitable output format for '/tmp/ramdisk/test_60.yuyv422'
/tmp/ramdisk/test_60.yuyv422: Invalid argument

So I added -f rawvideo to the output:
ffmpeg -t 5 -f v4l2 -input_format yuyv422 -video_size 1920x1080 -y -r 60 -i /dev/video4 -f rawvideo /tmp/ramdisk/test_60.yuyv422
which at least removed that error, however ultimately the output was the same, ffmpeg hangs, 1 HART 100%

ffmpeg version 4.4.1 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Debian 11.3.0-3)
  configuration: --prefix=/code_mm_20221111/target/usr --arch=riscv64 --target-os=linux --enable-gpl --disable-stripping --disable-static --enable-shared --enable-avfilter --disable-version3 --enable-logging --disable-extra-warnings --enable-avdevice --enable-avcodec --enable-avformat --enable-network --disable-gray --enable-swscale-alpha --disable-small --enable-dct --enable-fft --enable-mdct --enable-rdft --enable-libv4l2 --enable-alsa --enable-outdevs --enable-pthreads --enable-zlib --enable-indevs --enable-runtime-cpudetect --enable-pic --cpu=rv64imafd --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libdc1394 --enable-libdrm --enable-chromaprint --enable-libx264 --disable-frei0r --disable-gnutls --disable-ladspa --disable-libiec61883 --enable-omx --extra-ldflags=-L/code_mm_20221111/target/usr/lib --extra-cflags=-I/code_mm_20221111/target/usr/include/omx-il --extra-libs=-lOMX_Core
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
Input #0, video4linux2,v4l2, from '/dev/video4':
  Duration: N/A, bitrate: 1990656 kb/s
  Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 1920x1080, 1990656 kb/s, 60 fps, 60 tbr, 1000k tbn, 1000k tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> rawvideo (native))
Press [q] to stop, [?] for help
Finishing stream 0:0 without any data written to it.
Output #0, rawvideo, to '/tmp/ramdisk/test_60.yuyv422':
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: rawvideo (YUY2 / 0x32595559), yuyv422, 1920x1080, q=2-31, 1990656 kb/s, 60 fps, 60 tbn
    Metadata:
      encoder         : Lavc58.134.100 rawvideo
frame=    0 fps=0.0 q=0.0 Lsize=       0kB time=00:00:00.00 bitrate=N/A speed=   0x    
video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Output file is empty, nothing was encoded (check -ss / -t / -frames parameters if used)
Exiting normally, received signal 2.

Running the same command on an i5 laptop gives:
frame= 300 fps= 49 q=-0.0 Lsize= 1215000kB time=00:00:05.00 bitrate=1990656.0kbits/s speed=0.81x

This allowed me to at least generate the raw file which I then scp’d to the SBC and ran the other two commands.
ffmpeg -f rawvideo -pix_fmt yuyv422 -s 1920x1080 -y -r 60 -i /tmp/ramdisk/test_60.yuyv422 -r 30 -f rawvideo /tmp/ramdisk/test_30.yuyv422 converted at about 40 fps
ffmpeg -f rawvideo -s 1920x1080 -y -r 30 -i /tmp/ramdisk/test_30.yuyv422 -c:v hevc_omx /tmp/ramdisk/test_30.mp4 converted at about 35fps.
It quit with an “Invalid buffer size error”, but I’m assuming that is related to the random input file size or similar.

All of this is making me think that there is an issue else where, for example in the USB kernel driver or similar.
Whilst capturing the initial file on the laptop, I also ran usbtop which confirmed a transfer speed of 202299.42 kb/s so about what we were expecting.

1 Like