TIL: v4l2loopback recipes

in Linux

v4l2loopback is a kernel module which creates virtual video devices. It allows you to use video streams from various sources as if they were input from a physical camera.

Load the module

This is how OBS does it. card_label will be what Firefox shows in its “allow camera” popup.

sudo modprobe v4l2loopback exclusive_caps=1 card_label='OBS Virtual Camera'

Other module options:

  • video_nr=X, will create /dev/videoX

Stream a video file to the device

ffmpeg -readrate 1 -i video.mov -f v4l2 /dev/video9

-readrate 1 (or -re 1) means to stream the input file in real-time speed. Without it it will pass in a blink of an eye.

Loop a video file indefinitely

This is how you do the Mission Impossible trick - record an empty corridor (or yourself sitting at a video call and nodding attentively) and play it back in a loop:

ffmpeg -readrate 1 -stream_loop -1 -i video.mov -f v4l2 /dev/video9

Stream camera data 1:1 to the loopback device

This passes the mjpeg frames directly from /dev/video0 (real camera) to /dev/video9 (v4l2loopback device). Key is -c:v copy.

ffmpeg -f v4l2 \
  -input_format mjpeg \
  -video_size 1920x1080 \
  -framerate 30 \
  -i /dev/video0 \
  -c:v copy \
  -f v4l2 \
  /dev/video9

Stream camera to loopback device with ffmpeg re-encoding

ffmpeg -f v4l2 \
  -input_format mjpeg \
  -video_size 1920x1080 \
  -framerate 30 \
  -i /dev/video0 \
  -f v4l2 \
  -pix_fmt yuyv422 \
  /dev/video9

Apply basic filters

Flip upside down and blur.

ffmpeg -f v4l2 \
  -input_format mjpeg \
  -video_size 1280x720 \
  -framerate 30 \
  -i /dev/video0 \
  -filter:v "vflip,gblur=sigma=5" \
  -f v4l2 \
  -pix_fmt yuyv422 \
  /dev/video9

Interesting filters to check out:

  • -filter:v "lagfun"
  • -filter:v "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2"

More elaborate examples

These effectively combine two inputs (note two -i flags) into s single stream using split and maskedmerge.

Apply blur only to left half of the image

ffmpeg \
  -f v4l2 \
  -input_format mjpeg -video_size 1280x720 -framerate 30 -fflags nobuffer -i /dev/video0 \
  -f lavfi \
  -i "gradients=size=1280x720:c0=white:c1=black:nb_colors=2:type=linear:x0=639:y0=0:x1=641:y1=0:speed=0" \
  -filter_complex "split [main][tmp]; [tmp] boxblur=luma_radius=10 [blurred]; [main][blurred][1:v]maskedmerge" \
  -f v4l2 -codec:v mjpeg -preset zerolatency -q:v 2 -pix_fmt yuvj420p /dev/video9

Blur background

This can be used for simple background blur in video conferencing. Needs a silhouette.png (1280x720px) which will be the mask for the filter.

ffmpeg \
  -f v4l2 \
  -input_format mjpeg -video_size 1280x720 -framerate 30 -fflags nobuffer -i /dev/video0 \
  -i silhouette.png \
  -filter_complex "split [main][tmp]; [tmp] boxblur=luma_radius=10 [blurred]; [main][blurred][1:v]maskedmerge" \
  -f v4l2 -codec:v mjpeg -preset zerolatency -q:v 2 -pix_fmt yuvj420p /dev/video9

Pixellize background

Same as above, but pixellize instead of blur

ffmpeg \
  -f v4l2 \
  -input_format mjpeg -video_size 1280x720 -framerate 30 -fflags nobuffer -i /dev/video0 \
  -i silhouette.png \
  -filter_complex "split [main][tmp]; [tmp] scale=iw/20:ih/20:flags=neighbor, scale=iw*20:ih*20:flags=neighbor [blurred]; [main][blurred][1:v]maskedmerge" \
  -f v4l2 -codec:v mjpeg -preset zerolatency -q:v 10 -pix_fmt yuvj420p /dev/video9

Aside: play webcam stream with lowest latency

My camera is /dev/video0. First check what formats the camera supports:

ffplay -f v4l2 -list_formats all /dev/video0
# ...
[video4linux2,v4l2 @ 0x60ffdd8c20c0] Raw       :     yuyv422 :           YUYV 4:2:2 : 640x480 160x120 176x144 320x240 432x240 352x288 640x360 800x448 864x480 1024x576 800x600 960x720 1280x720 1600x896 1920x1080
[video4linux2,v4l2 @ 0x60ffdd8c20c0] Compressed:       mjpeg :          Motion-JPEG : 640x480 160x120 176x144 320x240 432x240 352x288 640x360 800x448 864x480 1024x576 800x600 960x720 1280x720 1600x896 1920x1080
# ...

Or more detailed with v4l2-ctl:

v4l2-ctl --list-formats-ext -d /dev/video0

By default my Logitech C615 streams yuyv422, but at a low framerate. To play mjpeg instead:

ffplay -fflags nobuffer \
  -f v4l2 \
  -input_format mjpeg \
  -video_size 1920x1080 \
  -framerate 30 \
  /dev/video0

Here -fflags nobuffer prioritizes low latency playback.

With mpv:

mpv --demuxer-lavf-o=video_size=1280x720,input_format=mjpeg,framerate=30 \
  --profile=low-latency \
  --untimed \
  /dev/video0