diff -uprN v4l2loopback-0.12.5.orig/currentversion.sh v4l2loopback-0.12.5/currentversion.sh
--- v4l2loopback-0.12.5.orig/currentversion.sh	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/currentversion.sh	2024-04-24 13:36:28.394460919 +0800
@@ -1,6 +1,7 @@
 #!/bin/sh
 
-grep "^#define V4L2LOOPBACK_VERSION_CODE KERNEL_VERSION" v4l2loopback.c \
-| sed -e 's|^#define V4L2LOOPBACK_VERSION_CODE KERNEL_VERSION||' \
-      -e 's|^[^0-9]*||' -e 's|[^0-9]*$||' \
-      -e 's|[^0-9][^0-9]*|.|g'
+num() {
+	grep "^#define V4L2LOOPBACK_VERSION_$1 " v4l2loopback.h | awk '{print $3}'
+}
+
+echo $(num MAJOR).$(num MINOR).$(num BUGFIX)
diff -uprN v4l2loopback-0.12.5.orig/dkms.conf v4l2loopback-0.12.5/dkms.conf
--- v4l2loopback-0.12.5.orig/dkms.conf	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/dkms.conf	2024-04-24 13:36:28.394460919 +0800
@@ -1,5 +1,5 @@
 PACKAGE_NAME="v4l2loopback"
-PACKAGE_VERSION="0.12.5"
+PACKAGE_VERSION="0.12.5-omn"
 
 # Items below here should not have to change with each driver version
 MAKE[0]="make KERNEL_DIR=${kernel_source_dir} all"
@@ -8,5 +8,4 @@ CLEAN="make clean"
 BUILT_MODULE_NAME[0]="$PACKAGE_NAME"
 DEST_MODULE_LOCATION[0]="/extra"
 
-REMAKE_INITRD="no"
 AUTOINSTALL="yes"
diff -uprN v4l2loopback-0.12.5.orig/doc/v4l2_formats.txt v4l2loopback-0.12.5/doc/v4l2_formats.txt
--- v4l2loopback-0.12.5.orig/doc/v4l2_formats.txt	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/doc/v4l2_formats.txt	2024-04-24 13:36:28.394460919 +0800
@@ -83,15 +83,15 @@ V4L2_PIX_FMT_SGBRG12  /* 12  GBGB.. RGRG
 V4L2_PIX_FMT_SGRBG12  /* 12  GRGR.. BGBG.. */
 V4L2_PIX_FMT_SRGGB12  /* 12  RGRG.. GBGB.. */
 	/* 10bit raw bayer a-law compressed to 8 bits */
-V4L2_PIX_FMT_SBGGR10ALAW8 
-V4L2_PIX_FMT_SGBRG10ALAW8 
-V4L2_PIX_FMT_SGRBG10ALAW8 
-V4L2_PIX_FMT_SRGGB10ALAW8 
+V4L2_PIX_FMT_SBGGR10ALAW8
+V4L2_PIX_FMT_SGBRG10ALAW8
+V4L2_PIX_FMT_SGRBG10ALAW8
+V4L2_PIX_FMT_SRGGB10ALAW8
 	/* 10bit raw bayer DPCM compressed to 8 bits */
-V4L2_PIX_FMT_SBGGR10DPCM8 
-V4L2_PIX_FMT_SGBRG10DPCM8 
-V4L2_PIX_FMT_SGRBG10DPCM8 
-V4L2_PIX_FMT_SRGGB10DPCM8 
+V4L2_PIX_FMT_SBGGR10DPCM8
+V4L2_PIX_FMT_SGBRG10DPCM8
+V4L2_PIX_FMT_SGRBG10DPCM8
+V4L2_PIX_FMT_SRGGB10DPCM8
 	/*
 	 * 10bit raw bayer, expanded to 16 bits
 	 * xxxxrrrrrrrrrrxxxxgggggggggg xxxxggggggggggxxxxbbbbbbbbbb...
diff -uprN v4l2loopback-0.12.5.orig/.editorconfig v4l2loopback-0.12.5/.editorconfig
--- v4l2loopback-0.12.5.orig/.editorconfig	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/.editorconfig	2024-04-24 13:36:28.394460919 +0800
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+[*.[ch]]
+indent_style = tab
+indent_size = 8
+trim_trailing_whitespace = true
+[*.sym]
+indent_style = tab
+indent_size = 4
+trim_trailing_whitespace = true
diff -uprN v4l2loopback-0.12.5.orig/examples/.gitignore v4l2loopback-0.12.5/examples/.gitignore
--- v4l2loopback-0.12.5.orig/examples/.gitignore	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/examples/.gitignore	1970-01-01 08:00:00.000000000 +0800
@@ -1,2 +0,0 @@
-test
-yuv4mpeg_to_v4l2
diff -uprN v4l2loopback-0.12.5.orig/examples/README v4l2loopback-0.12.5/examples/README
--- v4l2loopback-0.12.5.orig/examples/README	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/examples/README	2024-04-24 13:36:28.398460992 +0800
@@ -47,8 +47,8 @@ $ vlc v4l2:///dev/videoX # X is same dev
 yuv420_infiniteloop
 -------------------
 
-A simple example for looping over frames of a raw yuv420 video file without 
-bothering other video players. Download and unzip an example sequence 
+A simple example for looping over frames of a raw yuv420 video file without
+bothering other video players. Download and unzip an example sequence
 akiyo_qcif.yuv from [here](http://trace.eas.asu.edu/yuv/akiyo/akiyo_qcif.7z).
 
 Usage
@@ -63,10 +63,10 @@ $ ./yuv420_infiniteloop /dev/video1 akiy
 It can be viewed with:
 $ ffplay devicename
 
-The YUV format is for raw sequences, more samples can be obtained 
-from [here](http://trace.eas.asu.edu/yuv/). And they can be created using 
+The YUV format is for raw sequences, more samples can be obtained
+from [here](http://trace.eas.asu.edu/yuv/). And they can be created using
 ffmpeg like this:
 
 $ ffmpeg -i inputfile.mp4 -c:v rawvideo outputfile.yuv
 
-More details can be found in [ffmpeg wiki](https://github.com/stoyanovgeorge/ffmpeg/wiki/Encode-Raw-Video).
\ No newline at end of file
+More details can be found in [ffmpeg wiki](https://github.com/stoyanovgeorge/ffmpeg/wiki/Encode-Raw-Video).
diff -uprN v4l2loopback-0.12.5.orig/examples/restarting-writer.sh v4l2loopback-0.12.5/examples/restarting-writer.sh
--- v4l2loopback-0.12.5.orig/examples/restarting-writer.sh	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/examples/restarting-writer.sh	2024-04-24 13:36:28.398460992 +0800
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 device=${1:-/dev/video0}
-echo Using $device
+echo "Using $device"
 
 run_writers() {
     declare -a nbufs
@@ -16,14 +16,14 @@ run_writers() {
 
 #v4l2-ctl -d $device -c keep_format=1 || exit 1
 ./utils/v4l2loopback-ctl set-caps "video/x-raw, format=UYVY, width=640, height=480, framerate=(fraction)25/1" $device || exit 1
-v4l2-ctl -d $device -c sustain_framerate=0 || exit 1
-v4l2-ctl -d $device -c timeout=2000 || exit 1
+v4l2-ctl -d "$device" -c sustain_framerate=0 || exit 1
+v4l2-ctl -d "$device" -c timeout=2000 || exit 1
 gst-launch-1.0 videotestsrc num-buffers=1 ! v4l2sink device=$device || exit 1
 {
     run_writers
     sleep 10
     # can see a flash of green here
-    v4l2-ctl -d $device -c sustain_framerate=1 || exit 1
+    v4l2-ctl -d "$device" -c sustain_framerate=1 || exit 1
     run_writers
     sleep 10
     run_writers
diff -uprN v4l2loopback-0.12.5.orig/examples/test.c v4l2loopback-0.12.5/examples/test.c
--- v4l2loopback-0.12.5.orig/examples/test.c	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/examples/test.c	2024-04-24 13:36:28.398460992 +0800
@@ -51,7 +51,7 @@
 # define FRAME_FORMAT V4L2_PIX_FMT_YVU420
 #endif
 
-static int debug=0;
+static int debug = 0;
 
 
 int format_properties(const unsigned int format,
@@ -76,7 +76,7 @@ size_t lw, fw;
 
 	if(linewidth)*linewidth=lw;
 	if(framewidth)*framewidth=fw;
-	
+
 	return 1;
 }
 
diff -uprN v4l2loopback-0.12.5.orig/examples/yuv420_infiniteloop.c v4l2loopback-0.12.5/examples/yuv420_infiniteloop.c
--- v4l2loopback-0.12.5.orig/examples/yuv420_infiniteloop.c	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/examples/yuv420_infiniteloop.c	2024-04-24 13:36:28.398460992 +0800
@@ -1,11 +1,11 @@
-/* Read a yuv file directly and play with infinte loop
+/* Read a yuv file directly and play with infinite loop
  *
  * Example:
  * $ ./yuv420_infiniteloop /dev/video1 akiyo_qcif.yuv 176 144 30
 
  * This will loop a yuv file named akiyo_qcif.yuv over video 1
- * 
- * Modified by T. Xu <x.tongda@nyu.edu> from yuv4mpeg_to_v4l2 example, 
+ *
+ * Modified by T. Xu <x.tongda@nyu.edu> from yuv4mpeg_to_v4l2 example,
  * original Copyright (C) 2011  Eric C. Cooper <ecc@cmu.edu>
  * Released under the GNU General Public License
  */
@@ -36,12 +36,12 @@ fail(char *msg)
 	exit(1);
 }
 
-struct yuv_setup 
+struct yuv_setup
 process_args(int argc, char **argv)
 {
 	prog = argv[0];
-	struct yuv_setup setup;	
-	if (argc != 6){
+	struct yuv_setup setup;
+	if (argc != 6) {
 		fail("invalid argument");
 	} else {
 		setup.device = argv[1];
@@ -56,10 +56,10 @@ process_args(int argc, char **argv)
 
 void
 copy_frames(struct yuv_setup setup, int dev_fd)
-{	
+{
 
 	FILE * yuv_file = fopen (setup.file_name,"rb");
-	if (yuv_file == NULL){
+	if (yuv_file == NULL) {
 		fail("can not open yuv file");
 	}
 
@@ -72,9 +72,9 @@ copy_frames(struct yuv_setup setup, int
 	while (1) {
 		int read_size = fread(frame, 1, setup.frame_bytes, yuv_file);
 		usleep(1.0f/setup.fps * 1000000.0f);
-		if (read_size == setup.frame_bytes){
+		if (read_size == setup.frame_bytes) {
 			write(dev_fd, frame, setup.frame_bytes);
-		} else if (read_size == 0){
+		} else if (read_size == 0) {
 			fclose(yuv_file);
 			yuv_file = fopen (setup.file_name,"rb");
 		} else {
@@ -96,7 +96,7 @@ open_video(struct yuv_setup setup)
 		fail("cannot open video device");
 	}
 	v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
-	if (ioctl(dev_fd, VIDIOC_G_FMT, &v) == -1){
+	if (ioctl(dev_fd, VIDIOC_G_FMT, &v) == -1) {
 		fail("cannot setup video device");
 	}
 	v.fmt.pix.width = setup.frame_width;
@@ -104,7 +104,7 @@ open_video(struct yuv_setup setup)
 	v.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
 	v.fmt.pix.sizeimage = setup.frame_bytes;
 	v.fmt.pix.field = V4L2_FIELD_NONE;
-	if (ioctl(dev_fd, VIDIOC_S_FMT, &v) == -1){
+	if (ioctl(dev_fd, VIDIOC_S_FMT, &v) == -1) {
 		fail("cannot setup video device");
 	}
 
diff -uprN v4l2loopback-0.12.5.orig/examples/yuv4mpeg_to_v4l2.c v4l2loopback-0.12.5/examples/yuv4mpeg_to_v4l2.c
--- v4l2loopback-0.12.5.orig/examples/yuv4mpeg_to_v4l2.c	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/examples/yuv4mpeg_to_v4l2.c	2024-04-24 13:36:28.402461066 +0800
@@ -102,7 +102,7 @@ read_header(char *magic)
     free(p0);
     return 0;
   }
-  
+
 	q = p = p0;
 	first = 1;
 	done = 0;
diff -uprN v4l2loopback-0.12.5.orig/.github/ISSUE_TEMPLATE/bug_report.md v4l2loopback-0.12.5/.github/ISSUE_TEMPLATE/bug_report.md
--- v4l2loopback-0.12.5.orig/.github/ISSUE_TEMPLATE/bug_report.md	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/.github/ISSUE_TEMPLATE/bug_report.md	1970-01-01 08:00:00.000000000 +0800
@@ -1,75 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: ''
-assignees: ''
-
----
-
-### Step 1: Read this
-
-Thanks for your feedback. It is invaluable for making `v4l2loopback` a better software.
-
-To help us making the most of your feedback (so we can e.g. fix bugs more quickly), please make sure to provide the information requested in this template.
-Also make sure to remove any non-relevant parts (so we can focus on the essential problem).
-
-Please keep in mind that the development of `v4l2loopback` is done by volunteers.
-They are spending their spare time to give you a hopfully nice product and to help you if you have troubles - for free.
-
-Please look through the list of issues (*open* and **closed** ones alike), to see whether you problem has been reported before. Probably you can find a solution to your problem without having to create a new ticket.
-
-#### Remove Cruft
-
-Please *remove* these instructions (and other non-relevant information) from your report.
-If your report looks like a copy of the template, it might get closed immediately.
-
-#### Title
-Please chose an apropriate title: "*does not work*" or "*found a bug*" are way too generic.
-Try to find a one-liner that says what is not working (e.g. "*module fails to load*").
-
-Also try not to use relative terms.
-E.g. "*fails to build with latest kernel*" is bad, because the latest kernel at the time you create the bug report might not be the latest kernel when the problem is being worked on.
-
-#### Accessibility
-Sometimes pictures say more.
-However, mostly they prevent the use of advanced tools (like "search" or "copy&paste").
-And always they prevent people who don't use graphical browser to access the tracker from reading your content.
-So, to make the web a better place, we ask you to post *text* rather than *screenshots of text* whenever feasible (pretty much always).
-
-
-### Step 2: Describe your environment
-
-  * `v4l2loopback` version: _____
-
-          sudo dmesg  | grep -i v4l2loopback
-
-  * kernel version: _____
-
-          uname -a
-
-  * Distribution (+version): _____
-
-          lsb_release -a
-  
-### Step 3: Describe the problem:
-
-#### Steps to reproduce:
-
-  1. _____
-  2. _____
-  3. _____
-  
-#### Observed Results:
-
-  * What happened?  This could be a description, log output, etc.
-  
-#### Expected Results:
-
-  * What did you expect to happen?
-  
-#### Relevant Code:
-
-  ```
-  // TODO(you): code here to reproduce the problem
-  ```
diff -uprN v4l2loopback-0.12.5.orig/.github/ISSUE_TEMPLATE.md v4l2loopback-0.12.5/.github/ISSUE_TEMPLATE.md
--- v4l2loopback-0.12.5.orig/.github/ISSUE_TEMPLATE.md	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/.github/ISSUE_TEMPLATE.md	1970-01-01 08:00:00.000000000 +0800
@@ -1,68 +0,0 @@
-### Step 1: Read this
-
-Thanks for your feedback. It is invaluable for making `v4l2loopback` a better
-software.
-
-To help us making the most of your feedback (so we can e.g. fix bugs more quickly), please make sure to provide the information requested in this template.
-Also make sure to remove any non-relevant parts (so we can focus on the essential problem).
-
-Please keep in mind that the development of `v4l2loopback` is done by volunteers.
-They are spending their spare time to give you a hopfully nice product and to help you if you have troubles - for free.
-
-Please look through the list of issues (*open* and **closed** ones alike), to see whether you problem has been reported before. Probably you can find a solution to your problem without having to create a new ticket.
-
-#### Remove Cruft
-
-Please *remove* these instructions (and other non-relevant information) from your report.
-If your report looks like a copy of the template, it might get closed immediately.
-
-#### Title
-Please chose an apropriate title: "*does not work*" or "*found a bug*" are way too generic.
-Try to find a one-liner that says what is not working (e.g. "*module fails to load*").
-
-Also try not to use relative terms.
-E.g. "*fails to build with latest kernel*" is bad, because the latest kernel at the time you create the bug report might not be the latest kernel when the problem is being worked on.
-
-#### Accessibility
-Sometimes pictures say more.
-However, mostly they prevent the use of advanced tools (like "search" or "copy&paste").
-And always they prevent people who don't use graphical browser to access the tracker from reading your content.
-So, to make the web a better place, we ask you to post *text* rather than *screenshots of text* whenever feasible (pretty much always).
-
-
-### Step 2: Describe your environment
-
-  * `v4l2loopback` version: _____
-
-          sudo dmesg  | grep -i v4l2loopback
-
-  * kernel version: _____
-
-          uname -a
-
-  * Distribution (+version): _____
-
-          lsb_release -a
-  
-### Step 3: Describe the problem:
-
-#### Steps to reproduce:
-
-  1. _____
-  2. _____
-  3. _____
-  
-#### Observed Results:
-
-  * What happened?  This could be a description, log output, etc.
-  
-#### Expected Results:
-
-  * What did you expect to happen?
-  
-#### Relevant Code:
-
-  ```
-  // TODO(you): code here to reproduce the problem
-  ```
-
diff -uprN v4l2loopback-0.12.5.orig/.gitignore v4l2loopback-0.12.5/.gitignore
--- v4l2loopback-0.12.5.orig/.gitignore	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/.gitignore	1970-01-01 08:00:00.000000000 +0800
@@ -1,9 +0,0 @@
-.tmp_versions/
-*.cmd
-*.yuv
-Module.symvers
-modules.order
-v4l2loopback.ko
-v4l2loopback.mod.c
-v4l2loopback.mod.o
-v4l2loopback.o
diff -uprN v4l2loopback-0.12.5.orig/Kbuild v4l2loopback-0.12.5/Kbuild
--- v4l2loopback-0.12.5.orig/Kbuild	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/Kbuild	2024-04-24 13:36:28.402461066 +0800
@@ -0,0 +1 @@
+obj-m		:= v4l2loopback.o
diff -uprN v4l2loopback-0.12.5.orig/Makefile v4l2loopback-0.12.5/Makefile
--- v4l2loopback-0.12.5.orig/Makefile	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/Makefile	2024-04-24 13:36:28.402461066 +0800
@@ -1,7 +1,14 @@
+ifneq ($(wildcard .gitversion),)
+# building a snapshot version
+override KCPPFLAGS += -DSNAPSHOT_VERSION='"$(shell git describe --always --dirty 2>/dev/null || shell git describe --always 2>/dev/null || echo snapshot)"'
+endif
+
+include Kbuild
+ifeq ($(KBUILD_MODULES),)
+
 KERNELRELEASE	?= `uname -r`
 KERNEL_DIR	?= /lib/modules/$(KERNELRELEASE)/build
 PWD		:= $(shell pwd)
-obj-m		:= v4l2loopback.o
 
 PREFIX ?= /usr/local
 BINDIR  = $(PREFIX)/bin
@@ -34,13 +41,14 @@ MODULE_OPTIONS = devices=2
 
 # we don't control the .ko file dependencies, as it is done by kernel
 # makefiles. therefore v4l2loopback.ko is a phony target actually
-.PHONY: v4l2loopback.ko
+.PHONY: v4l2loopback.ko utils
+
+all: v4l2loopback.ko utils
 
-all: v4l2loopback.ko
 v4l2loopback: v4l2loopback.ko
 v4l2loopback.ko:
 	@echo "Building v4l2-loopback driver..."
-	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
+	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) KCPPFLAGS="$(KCPPFLAGS)" modules
 
 install-all: install install-utils install-man
 install:
@@ -61,6 +69,7 @@ clean:
 	rm -f *~
 	rm -f Module.symvers Module.markers modules.order
 	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
+	$(MAKE) -C utils clean
 
 distclean: clean
 	rm -f man/v4l2loopback-ctl.1
@@ -72,4 +81,19 @@ modprobe: v4l2loopback.ko
 	sudo insmod ./v4l2loopback.ko $(MODULE_OPTIONS)
 
 man/v4l2loopback-ctl.1: utils/v4l2loopback-ctl
-	help2man -N --name "control v4l2 loopback devices" $^ > $@
+	help2man -N --name "control v4l2 loopback devices" \
+		--no-discard-stderr --help-option=-h --version-option=-v \
+		$^ > $@
+
+utils: utils/v4l2loopback-ctl
+utils/v4l2loopback-ctl: utils/v4l2loopback-ctl.c
+	$(MAKE) -C utils
+
+.clang-format:
+	curl "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/.clang-format" > $@
+
+.PHONY: clang-format
+clang-format: .clang-format
+	clang-format -i *.c *.h utils/*.c
+
+endif # !kbuild
diff -uprN v4l2loopback-0.12.5.orig/NEWS v4l2loopback-0.12.5/NEWS
--- v4l2loopback-0.12.5.orig/NEWS	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/NEWS	2024-04-24 13:36:28.402461066 +0800
@@ -139,7 +139,7 @@ v4l2loopback-0.4
  - fixed issues with non-trivial colorspaces (e.g. I420)
 		this should allow for more clients to work out-of-the-box
 		(e.g. no more caps-tweak with gstreamer)
- - fixed timestamps 
+ - fixed timestamps
 		this allows playback with players that need monotonous
 		timestamps (e.g. ffmpeg)
  - cleaned up code
diff -uprN v4l2loopback-0.12.5.orig/README.md v4l2loopback-0.12.5/README.md
--- v4l2loopback-0.12.5.orig/README.md	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/README.md	2024-04-24 13:36:28.406461139 +0800
@@ -1,53 +1,89 @@
 v4l2loopback - a kernel module to create V4L2 loopback devices
 ==============================================================
 
-this module allows you to create "virtual video devices"
-normal (v4l2) applications will read these devices as if they were ordinary
+This module allows you to create "virtual video devices".
+Normal (v4l2) applications will read these devices as if they were ordinary
 video devices, but the video will not be read from e.g. a capture card but
 instead it is generated by another application.
-this allows you for instance to apply some nifty video effects on your
+This allows you for instance to apply some nifty video effects on your
 Skype video...
-it also allows some more serious things (e.g. I've been using it to add
+It also allows some more serious things (e.g. I've been using it to add
 streaming capabilities to an application by the means of hooking GStreamer into
 the loopback devices).
 
 # NEWS
-to get the main features of each new release, see the NEWS file.
-you could also have a look at the ChangeLog (which gets automatically generated and might
+To get the main features of each new release, see the NEWS file.
+You could also have a look at the ChangeLog (which gets automatically generated and might
 only be of limited use...
 
 # ISSUES
-for current issues, checkout https://github.com/umlaeute/v4l2loopback/issues
-please use the issue-tracker for reporting any problems
+For current issues, checkout https://github.com/umlaeute/v4l2loopback/issues
+Please use the issue-tracker for reporting any problems.
+
+Before you create a new ticket in our issue tracker, please make sure that you have read
+*this* document and followed any instructions found within.
+
+Also, please search the issue-tracker *before* reporting any problems: it's much better
+to add your information to an existing ticket than to create a new ticket with essentially
+the same information.
+
+## SEEKING HELP
+The issue tracker is meant to track specific bugs in the code (and new features).
+However, it is ill-suited as a user support forum.
+
+If you have general questions or problems, please use the `v4l2loopback` tag
+on [Unix & Linux](https://unix.stackexchange.com/questions/tagged/v4l2loopback) instead:
+https://unix.stackexchange.com/questions/tagged/v4l2loopback
+
 
 # DEPENDENCIES
-the v4l2loopback module is a *kernel module*.
-in order to build it, you *must have* the kernel headers installed that match
+In order to build (compile,...) anything, you must have a *working* build-environment
+(compiler, GNU make,...).
+The kernel can be somewhat picky if you try to load a module that was compiled with
+a different compiler as was used to compile the kernel itself.
+So make sure to have the right compiler in place.
+
+The v4l2loopback module is a *kernel module*.
+In order to build it, you *must have* the kernel headers installed that match
 the linux kernel with which you want to use the module (in most cases this will
 be the kernel that you are currently running).
-please note, that kernel headers and kernel image must have *exactly the same* version.
-for example, `3.18.0-trunk-rpi` is a different version that `3.18.7-v7+`, even though
-the first few number are the same.
-(modules will be incompatible if the versions don't match. if you are lucky, the module will
-simply refuse to load. if you are unlucky, your computer will spit in your eye or do worse.)
+Please note, that kernel headers and kernel image must have *exactly the same* version.
+For example, `3.18.0-trunk-rpi` is a different version than `3.18.7-v7+`, even though
+the first few numbers are the same.
+(Modules will be incompatible if the versions don't match. If you are lucky, the module will
+simply refuse to load. If you are unlucky, your computer will spit in your eye or do worse.)
+
+There are distribution-specific differences on how to get the correct kernel headers
+(or to install a compilation toolchain).
+Documenting all those possibilities would go far beyond the scope of `v4l2loopback`.
+Please understand that we cannot provide support for questions regarding dependencies.
+
 
 # BUILD
-to build the kernel module run:
+To build the kernel module, run:
 
     $ make
 
-this should give you a file named "v4l2loopback.ko", which is the kernel module
+This should give you a file named "v4l2loopback.ko", which is the kernel module
+
+## BUILD AGAIN
+You cannot load a module built for a specific version of the kernel into another version of the kernel.
+So, if you have successfully built the module previously and have updated your kernel (and the matching headers)
+In the meantime, you really must clean the build before re-compiling the module.
+So run this *before* starting the build again:
+
+    $ make clean
 
 # INSTALL
-to install the module run "make install" (you might have to be 'root' to have
+To install the module, run "make install" (you might have to be 'root' to have
 all necessary permissions to install the module).
 
-if your system has "sudo", do:
+If your system has "sudo", do:
 
     $ make && sudo make install
     $ sudo depmod -a
 
-if your system lacks "sudo", do:
+If your system lacks "sudo", do:
 
     $ make
     $ su
@@ -61,68 +97,103 @@ if your system lacks "sudo", do:
 automatically load additional kernel modules required by v4l2loopback.
 The call may not be necessary on modern systems.)
 
+See below for [distribution-specific build instructions](#DISTRIBUTIONS)
+or when using frameworks like [`DKMS`](#DKMS).
+
+
 # RUN
-load the v4l2loopback module as root :
+Load the v4l2loopback module as root :
 
     # modprobe v4l2loopback
 
-using sudo use:
+Using `sudo` use:
 
     $ sudo modprobe v4l2loopback
 
-this will create an additional video-device, e.g. /dev/video0 (the number
-depends on whether you already had video devices on your system), which can be
-fed by various programs.
-tested feeders:
-- GStreamer-0.10: using the  "v4l2sink" element
+You can check which loopback devices are created by listing contents of
+`/sys/devices/virtual/video4linux` directory. E.g. if there are two
+`v4l2loopback` devices `/dev/video0` and `/dev/video3` you would get:
+
+    $ ls -1 /sys/devices/virtual/video4linux
+    video0
+    video3
+
+These devices are ready to accept contents to show.
+
+Tested feeders:
+- GStreamer-1.0: using the  "v4l2sink" element
 - Gem(>=0.93) using the "recordV4L2" plugin
-in theory most programs capable of _writing to_ a v4l2 device should work.
 
-the data sent to the v4l2loopback device can then be read by any v4l2-capable
+In theory most programs capable of _writing to_ a v4l2 device should work.
+
+The data sent to the v4l2loopback device can then be read by any v4l2-capable
 application.
 
-you can find a number of scenarios on the wiki at
+You can find a number of scenarios on the wiki at
 	http://github.com/umlaeute/v4l2loopback/wiki
 
+## Troubleshooting
+If you have a secure-boot enabled kernel, you might not be able to simply build a kernel module and insert it.
+(You will get **SSL error**s when building the module.)
+This is actually a security feature (as it prevents malicious code to be inserted into kernel-space).
+
+If you are not allowed to insert the kernel module (running `modprobe`, or `insmod`), you have a few options
+(consult your distribution's documentation on how to perform any of these steps):
+- disable secure-boot and reboot
+- sign the module binary with a whitelisted key (this probably only applies if you are creating a distribution)
+
+You could also just try building the module [via `DKMS`](#DKMS), and hope that it does all the magic for you.
+
 # OPTIONS
-if you need several independent loopback devices, you can pass the "devices"
+If you need several independent loopback devices, you can pass the "devices"
 option, when loading the module; e.g.
 
     # modprobe v4l2loopback devices=4
 
-will give you 4 loopback devices (e.g. `/dev/video1` ... `/dev/video5`)
+Will give you 4 loopback devices (e.g. `/dev/video1` ... `/dev/video5`)
 
-you can also specify the device IDs manually; e.g.
+You can also specify the device IDs manually; e.g.
 
     # modprobe v4l2loopback video_nr=3,4,7
 
-will create 3 devices (`/dev/video3`, `/dev/video4` & `/dev/video7`)
+Will create 3 devices (`/dev/video3`, `/dev/video4` & `/dev/video7`)
 
     # modprobe v4l2loopback video_nr=3,4,7 card_label="device number 3","the number four","the last one"
 
-will create 3 devices with the card names passed as the second parameter:
+Will create 3 devices with the card names passed as the second parameter:
 - `/dev/video3` -> *device number 3*
 - `/dev/video4` -> *the number four*
 - `/dev/video7` -> *the last one*
 
 
-if you encounter problems detecting your device with Chrome/WebRTC you can try 'exclusive_caps' mode:
+If you encounter problems detecting your device with Chrome/WebRTC you can try 'exclusive_caps' mode:
 
     # modprobe v4l2loopback exclusive_caps=1
-    
-will enable 'exclusive_caps' mode that only reports CAPTURE/OUTPUT capabilities exclusively.
-the newly created device will announce OUTPUT capabilities only (so ordinary webcam applications
-(including Chrome) won't see it). as soon as you have attached a producer to the device, it will
+
+This will enable 'exclusive_caps' mode that only reports CAPTURE/OUTPUT capabilities exclusively.
+The newly created device will announce OUTPUT capabilities only (so ordinary webcam applications
+(including Chrome) won't see it). As soon as you have attached a producer to the device, it will
 start announcing CAPTURE capabilities only (so applications that refuse to open devices that have
 other capabilities apart from capturing can open it too.)
-   
+
+## CHANGING OPTIONS
+Options that you provided when loading the module (e.g. via `modprobe`) cannot be easily changed
+on the fly.
+In order to change these options, you must first unload the module with `rmmod`
+(which will only work if no application is any longer accessing one of the loopback devices)
+and then load it again (with the new options).
+
+See also the section about [DYNAMIC DEVICE MANAGEMENT](#dynamic-device-management).
+
+
+
 # ATTRIBUTES
 you can set and/or query some per-device attributes via sysfs, in a human
-readable format. see `/sys/devices/virtual/video4linux/video*/`
+readable format. See `/sys/devices/virtual/video4linux/video*/`
 
 also there are some V4L2 controls that you can list with
 
-    $ v4l2-ctl -d /dev/video1 -l
+    $ v4l2-ctl -d /dev/video0 -l
 
 - `keep_format(0/1)`: while set to 1, once negotiated format will be fixed forever,
                   until the setting is set back to 0
@@ -133,34 +204,63 @@ also there are some V4L2 controls that y
 - `timeout_image_io(0/1)`: if set to 1, the next opener will write to timeout frame
                        buffer
 
-# FORCING FPS
+# CHANGING THE RUNTIME BEHAVIOUR
+## FORCING FPS
 
-    $ v4l2loopback-ctl set-fps 25 /dev/video0
+    $ v4l2loopback-ctl set-fps /dev/video0 25
 
 or
 
     $ echo '@100' | sudo tee /sys/devices/virtual/video4linux/video0/format
 
-# FORCING A GSTREAMER (1.0) CAPS
+## FORCING FORMAT
 
-    $ v4l2loopback-ctl set-caps "video/x-raw,format=UYVY,width=640,height=480" /dev/video0
+    $ v4l2loopback-ctl set-caps /dev/video0 "UYVY:640x480"
+
+Please note that *GStreamer-style caps* (e.g. `video/x-raw,format=UYVY,width=640,height=480`) or no longer supported!
+
+## SETTING STREAM TIMEOUT
+
+You can define a timeout (in milliseconds), after which the loopback device will start outputting NULL frames,
+if the producer suddenly stopped.
 
-# SETTING STREAM TIMEOUT
 ~~~
 $ v4l2-ctl -d /dev/video0 -c timeout=3000
-(will output null frames by default)
-$ v4l2loopback-ctl set-timeout-image service-unavailable.png /dev/video0
-this currently requires GStreamer 0.10 installed
+~~~
+
+Alternatively, you can calso provide a timeout-image, which will be displayed (instead of the NULL frames),
+if the producer doesn't send any new frames for a given period:
+
+~~~
+$ v4l2loopback-ctl set-timeout-image -t 3000 /dev/video0 service-unavailable.png
+(this currently requires GStreamer 1.0 installed)
+~~~
+
+## DYNAMIC DEVICE MANAGEMENT
+You can create (and delete) loopback devices on the fly, using the `add` (resp. `delete`) commands of the `v4l2loopback-ctl` utility.
+
+When creating a new device, module options might be ignored. So you must specify them explicitly.
+
+To create a new device `/dev/video7` that has a label "loopy doopy", use:
+
+~~~
+$ sudo v4l2loopback-ctl add -n "loopy doopy" /dev/video7
+~~~
+
+Deleting devices is as simple as:
+
+~~~
+$ sudo v4l2loopback-ctl delete /dev/video7
 ~~~
 
 # KERNELs
-the original module has been developed for linux-2.6.28;
-i don't have a system with such an old kernel anymore, so i don't know whether
+The original module has been developed for linux-2.6.28;
+I don't have a system with such an old kernel anymore, so I don't know whether
 it still works.
-further development has been done mainly on linux-2.6.32 and linux-2.6.35, with
+Further development has been done mainly on linux-2.6.32 and linux-2.6.35, with
 newer kernels being continually tested as they enter Debian.
 
-support:
+Support:
 - >= <kbd>5.0.0</kbd>		should work
 - >= <kbd>4.0.0</kbd>		should work
 - >= <kbd>3.0.0</kbd>		might work
@@ -171,27 +271,86 @@ support:
 v4l2loopack is now (since 2010-10-13) available as a Debian-package.
 https://packages.debian.org/source/stable/v4l2loopback
 
-this means, that it is also part of Debian-derived distributions, including
+This means, that it is also part of Debian-derived distributions, including
 Ubuntu (starting with natty).
-the most convenient way is to install the package "v4l2loopback-dkms":
+The most convenient way is to install the package "v4l2loopback-dkms":
 
-    # aptitude install v4l2loopback-dkms
+    # apt-get install v4l2loopback-dkms
 
-this should automatically build and install the module for your current kernel
+This should automatically build and install the module for your current kernel
 (provided you have the matching kernel-headers installed).
-another option is to install the "v4l2loopback-source" package.
-in this case you should be able to simply do (as root):
+Another option is to install the "v4l2loopback-source" package.
+In this case you should be able to simply do (as root):
 
-    # aptitude install v4l2loopback-source module-assistant
+    # apt-get install v4l2loopback-source module-assistant
     # module-assistant auto-install v4l2loopback-source
 
+# DKMS
+The *Dynamic Kernel Module Support framework* (DKMS) is designed to allow
+individual kernel modules to be upgraded without changing the whole kernel.
+It is also very easy to rebuild modules as you upgrade kernels.
+
+If your distribution doesn't provide `v4l2loopback`-packages (or they are too old)
+and you are experiencing troubles with code-signing, you probably should try this.
+
+E.g. to build the v4l2loopback-v0.12.5 (but check the webpage for newer releases first!),
+use something like the following (you might need to run the `dkms` commands as superuser/root):
+
+~~~
+version=0.12.5
+# download and extract the tarball (tar requires superuser privileges)
+curl -L https://github.com/umlaeute/v4l2loopback/archive/v${version}.tar.gz | tar xvz -C /usr/src
+# build and install the DKMS-module (requires superuser privileges)
+dkms add -m v4l2loopback -v ${version}
+dkms build -m v4l2loopback -v ${version}
+dkms install -m v4l2loopback -v ${version}
+~~~~
+
+| distribution       | dependencies          |
+|--------------------|-----------------------|
+| Fedora,...         | gcc kernel-devel dkms |
+| Debian, Ubuntu,... | dkms                  |
+
+
+# LOAD THE MODULE AT BOOT
+
+One can avoid manually loading the module by letting systemd load the module
+at boot, by creating a file `/etc/modules-load.d/v4l2loopback.conf` with just
+the name of the module:
+
+~~~
+v4l2loopback
+~~~
+
+This is especially convenient when `v4l2loopback` is installed with DKMS or with
+a package provided by your Linux distribution.
+
+If needed, one can specify default module options by creating
+`/etc/modprobe.d/v4l2loopback.conf` in the following form:
+
+~~~
+options v4l2loopback video_nr=3,4,7
+options v4l2loopback card_label="device number 3,the number four,the last one"
+~~~
+
+One can only add one option per line. These options also become the defaults when
+manually calling `modprobe v4l2loopback`. Note that the double quotes can only
+be used at the beginning and the end of the option's value, as opposed to when
+they are specified on the command line.
+
+If your system boots with an initial ramdisk, which is the case for most
+modern distributions, you need to update this ramdisk with the settings above,
+before they take effect at boot time. In Ubuntu, this image is updated with
+`sudo update-initramfs`. The equivalent on Fedora is `sudo dracut -f`.
+
+
 # DOWNLOAD
-the most up-to-date version of this module can be found at
+The most up-to-date version of this module can be found at
 http://github.com/umlaeute/v4l2loopback/.
 
 # LICENSE/COPYING
 
-- Copyright (c) 2010-2016 IOhannes m zmoelnig
+- Copyright (c) 2010-2021 IOhannes m zmoelnig
 - Copyright (c) 2016 Gavin Qiu
 - Copyright (c) 2016 George Chriss
 - Copyright (c) 2014-2015 Tasos Sahanidis
diff -uprN v4l2loopback-0.12.5.orig/release.sh v4l2loopback-0.12.5/release.sh
--- v4l2loopback-0.12.5.orig/release.sh	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/release.sh	2024-04-24 13:36:28.406461139 +0800
@@ -25,8 +25,8 @@ usage() {
  fatal "usage: $0 [<LASTVERSION>] <CURVERSION>" 1>&2
 }
 
-getoldversion () {
-  dpkg-parsechangelog --count 1 -l${CHANGELOG} |  egrep "^Version:" | head -1 | cut -f2 -d' '
+getoldversion() {
+  dpkg-parsechangelog --count 1 -l${CHANGELOG} | grep -E "^Version:" | head -1 | cut -f2 -d' '
 }
 getmoduleversion() {
   grep "^#define V4L2LOOPBACK_VERSION_CODE KERNEL_VERSION" v4l2loopback.c \
@@ -38,8 +38,8 @@ getgitbranch() {
   git rev-parse --abbrev-ref HEAD
 }
 
-if [ "$(getgitbranch)" != "master" ]; then
- fatal "current branch '$(getgitbranch)' is not 'master'"
+if [ "$(getgitbranch)" != "main" ]; then
+ fatal "current branch '$(getgitbranch)' is not 'main'"
 fi
 
 if [ "x$2" = "x" ]; then
@@ -81,7 +81,7 @@ echo "updating to ${NEWVERSION}"
 
 OK=false
 mkdir debian
-cp ${CHANGELOG} debian/changelog
+cp "${CHANGELOG}" debian/changelog
 gbp dch -R --since "v${OLDVERSION}" -N "${NEWVERSION}" && cat debian/changelog > ${CHANGELOG} && OK=true
 rm -rf debian
 
diff -uprN v4l2loopback-0.12.5.orig/tests/test_dqbuf.c v4l2loopback-0.12.5/tests/test_dqbuf.c
--- v4l2loopback-0.12.5.orig/tests/test_dqbuf.c	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/tests/test_dqbuf.c	2024-04-24 13:36:28.406461139 +0800
@@ -78,7 +78,7 @@ main (int argc, char **argv)
 
     for (p = 0; p < (bufs[i].bytesused >> 2); p++)
       ((unsigned int*)data[i])[p] = 0xFF00FF00;
-  } 
+  }
 
   if (ioctl (fd, VIDIOC_QBUF, &bufs[0]) < 0)
     sysfail ("QBUF");
@@ -99,7 +99,7 @@ main (int argc, char **argv)
       sysfail ("QBUF");
 
 
-    printf ("\tQUEUED=%d\tDONE=%d\n", 
+    printf ("\tQUEUED=%d\tDONE=%d\n",
 	    bufs[i].flags & V4L2_BUF_FLAG_QUEUED,
 	    bufs[i].flags & V4L2_BUF_FLAG_DONE);
 
@@ -117,7 +117,7 @@ main (int argc, char **argv)
     i = buf.index;
 
     if ((bufs[i].flags & V4L2_BUF_FLAG_QUEUED) == 0) {
-      printf ("BUG #2: Driver should not dequeue a buffer that was not intially queued\n");
+      printf ("BUG #2: Driver should not dequeue a buffer that was not initially queued\n");
     }
 
 #if 0
diff -uprN v4l2loopback-0.12.5.orig/udev/60-persistent-v4l2loopback.rules v4l2loopback-0.12.5/udev/60-persistent-v4l2loopback.rules
--- v4l2loopback-0.12.5.orig/udev/60-persistent-v4l2loopback.rules	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/udev/60-persistent-v4l2loopback.rules	2024-04-24 13:36:28.406461139 +0800
@@ -0,0 +1,12 @@
+# make sure that the /dev/v4l2loopback module can be used by the 'video' group
+# note: this allows all members of 'video' to create and remove v4l2loopback
+# devices....
+KERNEL=="v4l2loopback", GROUP="video"
+
+# persistent device names for loopback devices
+SUBSYSTEM=="video4linux", KERNEL=="video*", DEVPATH=="*/virtual/*", SYMLINK+="v4l/by-id/v4l2loopback-$attr{name}-video"
+
+# grant access to sysfs properties for group 'video'
+# - read access to all properties
+# - write access to 'max_openers' and 'format'
+ACTION=="add", SUBSYSTEM=="video4linux", DEVPATH=="*/virtual/*" RUN+="/usr/bin/find %S%p -maxdepth 1 -type f -exec /bin/chgrp video {} +" RUN+="/usr/bin/find %S%p -maxdepth 1 -type f ( -name max_openers -or -name format ) -exec /bin/chmod g+rw {} +"
diff -uprN v4l2loopback-0.12.5.orig/utils/Makefile v4l2loopback-0.12.5/utils/Makefile
--- v4l2loopback-0.12.5.orig/utils/Makefile	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/utils/Makefile	2024-04-24 13:36:28.410461212 +0800
@@ -0,0 +1,10 @@
+CPPFLAGS += -I..
+
+.PHONY: default clean
+
+programs = v4l2loopback-ctl
+
+default: $(programs)
+
+clean:
+	-rm $(programs)
diff -uprN v4l2loopback-0.12.5.orig/utils/v4l2loopback-ctl v4l2loopback-0.12.5/utils/v4l2loopback-ctl
--- v4l2loopback-0.12.5.orig/utils/v4l2loopback-ctl	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/utils/v4l2loopback-ctl	1970-01-01 08:00:00.000000000 +0800
@@ -1,156 +0,0 @@
-#!/bin/bash
-
-usage() {
-## ugly linebreaks in order to make help2man more happy
-    cat <<!
-Usage:
-    $0 [command] [args]
-
-Commands:
-     set-fps <FPS> /dev/videoX  Set device fps (if format is already
-                                negotiated).
-
-     set-timeout-image <IMAGE> /dev/videoX  Set timeout on device, and 
-                                            optionally the placeholder picture,
-                                            instead of default null frame.
-
-     set-caps <CAPS> /dev/videoX  Fix format for the device (using GStreamer
-                                  caps), so it will be maintained until reset,
-                                  regardless of future readers/writers.
-
-Options:
-    FPS      frames per second, either as integer ('30') or fraction ('50/2')
-    IMAGE    image file ("no-input.png"), or 'videotestsrc'
-    CAPS     GStreamer caps ("video/x-raw, format=UYVY, width=640, height=480"),
-             or 'any'
-    /dev/videoX  the v4l2-loopback device file you want to act on
-!
-    exit 1
-}
-
-version() {
-cat <<!
-$0 0.6
-
-Copyright (c) 2012 IOhannes m zmoelnig <zmoelnig@iem.at>
-Copyright (c) 2012 Anton Novikov (random.plant@gmail.com)
-!
-    exit 1
-}
-die() {
-    echo "$@" 1>&2
-    exit 1
-}
-
-parse_device() {
-    if [ -e "$1" ]; then
-        device="$1"
-    elif [ -e "/dev/video$1" ]; then
-        device="/dev/video$1"
-    else
-        die "can't parse device"
-    fi
-    sysfs=/sys/devices/virtual/video4linux/${device##*/}
-}
-
-check_application() {
- which $1 > /dev/null || die "can't find $1"
-}
-
-
-set_fps() {
-    fps="$1"
-    parse_device "$2"
-    [ -z "$fps" ] && usage
-
-    echo "@$fps" | sudo tee "$sysfs/format" >/dev/null || die "Set fps failed"
-    echo OK
-}
-
-set_caps() {
-    caps="$1"
-    parse_device "$2"
-    [ -z "$caps" ] && usage
-
-    [ "$caps" = 'any' ] && {
-        check_application v4l2-ctl
-        v4l2-ctl -d $device -c keep_format=0
-        return
-    }
-
-    [ -n "`cat $sysfs/format`" ] && die "Device is busy"
-    declare -a params
-    declare -a nofps_params
-    IFS=',' read -ra params <<< "$caps"
-    for p in "${params[@]}"; do
-        if echo "$p" | grep -q framerate; then
-            fps=`echo "$p" | sed -r 's/.*(=|\))//g'`
-            echo "$fps" | grep -q '[^0-9/]' && die "could not parse framerate param"
-            echo "@$fps" | sudo tee $sysfs/format >/dev/null || die "could not set fps attr"
-            #echo "fps: $fps"
-        else
-            nofps_params=("${nofps_params[@]}" "$p")
-        fi
-    done
-    {
-        IFS=','
-        nofps_caps="${nofps_params[*]}"
-    }
-    #echo "nofps_caps: $nofps_caps"
-    check_application v4l2-ctl
-    check_application gst-launch-1.0
-
-    v4l2-ctl -d $device -c keep_format=1 || exit 1
-    v4l2-ctl -d $device -c sustain_framerate=1 || exit 1
-    gst-launch-1.0 videotestsrc num-buffers=1 ! "$nofps_caps" ! v4l2sink device=$device || die "output to $device failed"
-}
-
-set_timeout_image() {
-    imagefile="$1"
-    parse_device "$2"
-
-    [ -z "$imagefile" ] && return
-    [ -n "`cat $sysfs/format`" ] || die "Device has no format negotiated"
-
-    check_application v4l2-ctl
-    check_application gst-launch-1.0
-
-    v4l2-ctl -d $device -c timeout_image_io=1
-    if [ "$imagefile" = 'videotestsrc' ]; then
-        gst-launch-1.0 videotestsrc num-buffers=1 ! v4l2sink device=$device
-    else
-        uri="file://`readlink -f $imagefile`" || die "no file"
-        echo "Reading from $uri"
-        gst-launch-1.0 uridecodebin uri=$uri ! videoconvert ! videoscale ! imagefreeze ! identity error-after=2 ! v4l2sink show-preroll-frame=false device=$device
-    fi
-    timeout=`v4l2-ctl -d $device -C timeout | sed -n 's/^timeout: //p'`
-    [ -n "$timeout" ] || die "couldn't get timeout"
-    echo "timeout: $timeout"
-    [ "$timeout" = 0 ] && {
-        echo 'Timeout is currently disabled; you can set it to some positive value, e.g.:'
-        echo "$ v4l2-ctl -d $device -c timeout=3000"
-    }
-}
-
-command="$1"
-shift
-case "$command" in
-    "-h"|"--help")
-	usage
-	;;
-    "-v"|"--version")
-	version
-	;;
-    "set-fps")
-        set_fps "$@"
-        ;;
-    "set-caps")
-        set_caps "$@"
-        ;;
-    "set-timeout-image")
-        set_timeout_image "$@"
-        ;;
-    *)
-        usage
-        ;;
-esac
diff -uprN v4l2loopback-0.12.5.orig/utils/v4l2loopback-ctl.c v4l2loopback-0.12.5/utils/v4l2loopback-ctl.c
--- v4l2loopback-0.12.5.orig/utils/v4l2loopback-ctl.c	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/utils/v4l2loopback-ctl.c	2024-04-24 13:36:28.410461212 +0800
@@ -0,0 +1,1140 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/ioctl.h>
+#include <linux/videodev2.h>
+
+#include <errno.h>
+
+#include "v4l2loopback.h"
+
+#define CONTROLDEVICE "/dev/v4l2loopback"
+
+#if 0
+#define MARK() dprintf(2, "%s:%d @ %s\n", __FILE__, __LINE__, __func__)
+#else
+#define MARK()
+#endif
+
+struct v4l2l_format {
+	char *name;
+	int fourcc; /* video4linux 2 */
+	int depth; /* bit/pixel */
+	int flags;
+};
+#define FORMAT_FLAGS_PLANAR 0x01
+#define FORMAT_FLAGS_COMPRESSED 0x02
+#include "../v4l2loopback_formats.h"
+
+/********************/
+/* helper functions */
+
+/* running externals programs */
+static char *which(char *outbuf, size_t bufsize, const char *filename)
+{
+	struct stat statbuf;
+	char *paths, *saveptr = NULL;
+	if (filename && '/' == *filename) {
+		/* an absolute filename */
+		int err = stat(filename, &statbuf);
+		if (!err) {
+			snprintf(outbuf, bufsize, "%s", filename);
+			return outbuf;
+		}
+		return NULL;
+	}
+	for (paths = getenv("PATH");; paths = NULL) {
+		char *path = strtok_r(paths, ":", &saveptr);
+		int err;
+		if (path == NULL)
+			return NULL;
+		snprintf(outbuf, bufsize, "%s/%s", path, filename);
+		err = stat(outbuf, &statbuf);
+		if (!err)
+			return outbuf;
+	}
+	return NULL;
+}
+
+static pid_t pid;
+void exec_cleanup(int signal)
+{
+	if (pid) {
+		switch (signal) {
+		default:
+			break;
+		case SIGINT:
+			kill(pid, SIGTERM);
+			break;
+		}
+	}
+
+	while (waitpid((pid_t)(-1), 0, WNOHANG) > 0) {
+	}
+}
+static int my_execv(char *const *cmdline)
+{
+	char exe[1024];
+	//pid_t pid;
+	int res = 0;
+	char *const *argp = cmdline;
+	if (!which(exe, 1024, cmdline[0])) {
+		dprintf(2, "cannot find %s - is it installed???\n", cmdline[0]);
+		return 1;
+	}
+#if 0
+	dprintf(2, "%s:", exe);
+	while (*argp) {
+		dprintf(2, " %s", *argp++);
+	};
+	dprintf(2, "\n");
+#endif
+
+	pid = fork();
+	if (pid == 0) { /* this is the child-process */
+		res = execv(exe, cmdline);
+		if (res < 0) {
+			dprintf(2, "ERROR running helper program (%d, %d)", res,
+				errno);
+			dprintf(2, "failed program was:\n\t");
+			while (*cmdline)
+				dprintf(2, " %s", *cmdline++);
+			dprintf(2, "\n");
+			exit(0);
+		}
+		exit(0);
+	} else if (pid > 0) { /* we are parent: wait for child */
+		int status = 0;
+		int waitoptions = 0;
+		signal(SIGCHLD, exec_cleanup);
+		signal(SIGINT, exec_cleanup);
+		waitpid(pid, &status, waitoptions);
+		pid = 0;
+		if (WIFEXITED(status))
+			return WEXITSTATUS(status);
+		return 0;
+	} else { /* pid < 0, error */
+		dprintf(2, "ERROR: child fork failed\n");
+		exit(1);
+	}
+	return 0;
+}
+
+/* misc */
+static int my_atoi(const char *name, const char *s)
+{
+	char *endptr = 0;
+	int n = strtol(s, &endptr, 10);
+	if (*endptr) {
+		dprintf(2, "%s must be a number (got: '%s')\n", name, s);
+		exit(1);
+	}
+	return n;
+}
+static char *fourcc2str(unsigned int fourcc, char buf[4])
+{
+	buf[0] = (fourcc >> 0) & 0xFF;
+	buf[1] = (fourcc >> 8) & 0xFF;
+	buf[2] = (fourcc >> 16) & 0xFF;
+	buf[3] = (fourcc >> 24) & 0xFF;
+
+	return buf;
+}
+unsigned int str2fourcc(char buf[4])
+{
+	return (buf[0]) + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24);
+}
+
+/* helper functions */
+/********************/
+
+static unsigned int _get_control_id(int fd, const char *control)
+{
+	const size_t length = strnlen(control, 1024);
+	const unsigned next = V4L2_CTRL_FLAG_NEXT_CTRL;
+	struct v4l2_queryctrl qctrl;
+	int id;
+
+	memset(&qctrl, 0, sizeof(qctrl));
+	while (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) {
+		if (!strncmp(qctrl.name, control, length))
+			return qctrl.id;
+		qctrl.id |= next;
+	}
+	for (id = V4L2_CID_USER_BASE; id < V4L2_CID_LASTP1; id++) {
+		qctrl.id = id;
+		if (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) {
+			if (!strncmp(qctrl.name, control, length))
+				return qctrl.id;
+		}
+	}
+	for (qctrl.id = V4L2_CID_PRIVATE_BASE;
+	     ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0; qctrl.id++) {
+		if (!strncmp(qctrl.name, control, length)) {
+			unsigned int id = qctrl.id;
+			return id;
+		}
+	}
+	return 0;
+}
+
+static int set_control_i(int fd, const char *control, int value)
+{
+	struct v4l2_control ctrl;
+	memset(&ctrl, 0, sizeof(ctrl));
+	ctrl.id = _get_control_id(fd, control);
+	ctrl.value = value;
+	if (ctrl.id && ioctl(fd, VIDIOC_S_CTRL, &ctrl) == 0) {
+		int value = ctrl.value;
+		return value;
+	}
+	return 0;
+}
+static int get_control_i(int fd, const char *control)
+{
+	struct v4l2_control ctrl;
+	memset(&ctrl, 0, sizeof(ctrl));
+	ctrl.id = _get_control_id(fd, control);
+
+	if (ctrl.id && ioctl(fd, VIDIOC_G_CTRL, &ctrl) == 0) {
+		int value = ctrl.value;
+		return value;
+	}
+	return 0;
+}
+
+/********************/
+/* main logic       */
+typedef enum {
+	VERSION,
+	HELP,
+	ADD,
+	DELETE,
+	QUERY,
+	SET_FPS,
+	GET_FPS,
+	SET_CAPS,
+	GET_CAPS,
+	SET_TIMEOUTIMAGE,
+	_UNKNOWN
+} t_command;
+
+static int help_shortcmdline(int brief, const char *program,
+			     const char *argstring)
+{
+	dprintf(2, "\n");
+	//if(!brief)dprintf(2, "  -->");
+	dprintf(2, "\t");
+	dprintf(2, "%s %s", program, argstring);
+	return brief;
+}
+static void help_add(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n adding devices ('add')"
+			   "\n ======================");
+	if (help_shortcmdline(brief, program,
+			      "add {<flags>} [<device> [<outputdevice>]]"))
+		return;
+	dprintf(2,
+		"\n <flags>  \tany of the following flags may be present"
+		"\n\t -n <name>           : pretty name for the device"
+		"\n\t -w <max_width>      : maximum allowed frame width"
+		"\n\t -h <max_height>     : maximum allowed frame height"
+		"\n\t -x <exclusive_caps> : whether to announce OUTPUT/CAPTURE capabilities exclusively"
+		"\n\t -b <buffers>        : buffers to queue"
+		"\n\t -o <max_openers>    : maximum allowed concurrent openers"
+		"\n\t -v                  : verbose mode (print properties of device after successfully creating it)"
+		"\n"
+		"\n <device>\tif given, create a specific device (otherwise just create a free one)."
+		"\n         \teither specify a device name (e.g. '/dev/video1') or a device number ('1')."
+		"\n <outputdevice>\tif given, use separate output & capture devices (otherwise they are the same).");
+}
+static void help_delete(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n deleting devices ('delete')"
+			   "\n ===========================");
+	if (help_shortcmdline(brief, program, "delete <device>"))
+		return;
+	dprintf(2,
+		"\n <device>\tcan be given one more more times (to delete multiple devices at once)."
+		"\n         \teither specify a device name (e.g. '/dev/video1') or a device number ('1').");
+}
+static void help_query(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n querying devices ('query')"
+			   "\n ==========================");
+	if (help_shortcmdline(brief, program, "query <device>"))
+		return;
+	dprintf(2,
+		"\n <device>\tcan be given one more more times (to query multiple devices at once)."
+		"\n         \teither specify a device name (e.g. '/dev/video1') or a device number ('1').");
+}
+static void help_setfps(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n setting framerate ('set-fps')"
+			   "\n =============================");
+	if (help_shortcmdline(brief, program, "set-fps <device> <fps>"))
+		return;
+	dprintf(2,
+		"\n <device>\teither specify a device name (e.g. '/dev/video1') or a device number ('1')."
+		"\n    <fps>\tframes per second, either as integer ('30') or fraction ('50/2').");
+}
+static void help_getfps(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n getting framerate ('get-fps')"
+			   "\n =============================");
+	if (help_shortcmdline(brief, program, "get-fps <device>"))
+		return;
+}
+static void help_setcaps(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n setting capabilities ('set-caps')"
+			   "\n =================================");
+	if (help_shortcmdline(brief, program, "set-caps <device> <caps>"))
+		return;
+	dprintf(2,
+		"\n <device>\teither specify a device name (e.g. '/dev/video1') or a device number ('1')."
+		"\n   <caps>\tformat specification, e.g. 'UYVY:3840x2160@60/1 (<fourcc>:<width>x<height>@<fps>)"
+		"\n");
+	if (!argc) {
+		dprintf(2, "\nknown fourcc-codes"
+			   "\n------------------"
+			   "\n");
+		char fourcc[5];
+		const size_t num_formats = sizeof(formats) / sizeof(*formats);
+		size_t i = 0;
+		for (i = 0; i < num_formats; i++) {
+			const struct v4l2l_format *fmt = formats + i;
+			memset(fourcc, 0, 5);
+			dprintf(2, "%4s\t%d\t%s\n",
+				fourcc2str(fmt->fourcc, fourcc), fmt->fourcc,
+				fmt->name);
+		}
+	}
+}
+static void help_getcaps(const char *program, int brief, int argc, char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n getting capabilities ('get-caps')"
+			   "\n =================================");
+	if (help_shortcmdline(brief, program, "get-caps <device>"))
+		return;
+}
+static void help_settimeoutimage(const char *program, int brief, int argc,
+				 char **argv)
+{
+	if (!brief)
+		dprintf(2, "\n setting timeout image ('set-timeout-image')"
+			   "\n ===========================================");
+	if (help_shortcmdline(brief, program,
+			      "set-timeout-image {<flags>} <device> <image>"))
+		return;
+	dprintf(2,
+		"\n  <flags>\tany of the following flags may be present"
+		"\n\t -t <timeout>           : timeout (in ms)"
+		"\n"
+		"\n <device>\teither specify a device name (e.g. '/dev/video1') or a device number ('1')."
+		"\n  <image>\timage file");
+}
+static void help_none(const char *program, int brief, int argc, char **argv)
+{
+}
+typedef void (*t_help)(const char *, int, int, char **);
+static t_help get_help(t_command cmd)
+{
+	switch (cmd) {
+	default:
+		break;
+	case ADD:
+		return help_add;
+	case DELETE:
+		return help_delete;
+	case QUERY:
+		return help_query;
+	case SET_FPS:
+		return help_setfps;
+	case GET_FPS:
+		return help_getfps;
+	case SET_CAPS:
+		return help_setcaps;
+	case GET_CAPS:
+		return help_getcaps;
+	case SET_TIMEOUTIMAGE:
+		return help_settimeoutimage;
+	}
+	return help_none;
+}
+
+static void help(const char *name, int status)
+{
+	t_command cmd;
+	dprintf(2, "usage: %s [general commands]", name);
+	dprintf(2, "\n\n");
+	dprintf(2, "\n general commands"
+		   "\n ================"
+		   "\n\t-v : print version and exit"
+		   "\n\t-h : print this help and exit");
+	/* brief helps */
+	for (cmd = ADD; cmd < _UNKNOWN; cmd++)
+		get_help(cmd)("", 1, 0, 0);
+	dprintf(2, "\n\n");
+
+	/* long helps */
+	for (cmd = ADD; cmd < _UNKNOWN; cmd++) {
+		get_help(cmd)(name, 0, 0, 0);
+		dprintf(2, "\n\n");
+	}
+
+	exit(status);
+}
+static void usage(const char *name)
+{
+	help(name, 1);
+}
+static void usage_topic(const char *name, t_command cmd, int argc, char **argv)
+{
+	t_help hlp = get_help(cmd);
+	if (help_none == hlp)
+		usage(name);
+	else
+		hlp(name, 0, argc, argv);
+	dprintf(2, "\n");
+	exit(1);
+}
+
+static const char *my_realpath(const char *path, char *resolved_path)
+{
+	char *str = realpath(path, resolved_path);
+	return str ? str : path;
+}
+static int parse_device(const char *devicename_)
+{
+	char devicenamebuf[4096];
+	const char *devicename = my_realpath(devicename_, devicenamebuf);
+	int ret = strncmp(devicename, "/dev/video", 10);
+	const char *device = (ret) ? devicename : (devicename + 10);
+	char *endptr = 0;
+	int dev = strtol(device, &endptr, 10);
+	if (!*endptr)
+		return dev;
+
+	return -1;
+}
+
+static void print_conf(struct v4l2_loopback_config *cfg)
+{
+	MARK();
+	if (!cfg) {
+		printf("configuration: %p\n", cfg);
+		return;
+	}
+	MARK();
+	printf("\tcapture_device#  : %d"
+	       "\n\toutput_device#   : %d"
+	       "\n\tcard_label       : %s"
+	       "\n\tmax_width        : %d"
+	       "\n\tmax_height       : %d"
+	       "\n\tannounce_all_caps: %d"
+	       "\n\tmax_buffers      : %d"
+	       "\n\tmax_openers      : %d"
+	       "\n\tdebug            : %d"
+	       "\n",
+	       cfg->capture_nr, cfg->output_nr, cfg->card_label, cfg->max_width,
+	       cfg->max_height, cfg->announce_all_caps, cfg->max_buffers,
+	       cfg->max_openers, cfg->debug);
+	MARK();
+}
+
+static struct v4l2_loopback_config *
+make_conf(struct v4l2_loopback_config *cfg, const char *label, int max_width,
+	  int max_height, int exclusive_caps, int buffers, int openers,
+	  int capture_device, int output_device)
+{
+	if (!cfg)
+		return 0;
+	if (!label && max_width <= 0 && max_height <= 0 && exclusive_caps < 0 &&
+	    buffers <= 0 && openers <= 0 && capture_device < 0 &&
+	    output_device < 0)
+		return 0;
+	cfg->capture_nr = capture_device;
+	cfg->output_nr = output_device;
+	cfg->card_label[0] = 0;
+	if (label)
+		snprintf(cfg->card_label, 32, "%s", label);
+	cfg->max_height = max_height;
+	cfg->max_width = max_width;
+	cfg->announce_all_caps = (exclusive_caps < 0) ? -1 : !exclusive_caps;
+	cfg->max_buffers = buffers;
+	cfg->max_openers = openers;
+	cfg->debug = 0;
+	return cfg;
+}
+
+static int add_device(int fd, struct v4l2_loopback_config *cfg, int verbose)
+{
+	MARK();
+	int ret = ioctl(fd, V4L2LOOPBACK_CTL_ADD, cfg);
+	MARK();
+	if (ret < 0) {
+		perror("failed to create device");
+		return 1;
+	}
+	MARK();
+
+	printf("/dev/video%d\n", ret);
+
+	if (verbose > 0) {
+		MARK();
+		struct v4l2_loopback_config config;
+		memset(&config, 0, sizeof(config));
+		config.output_nr = config.capture_nr = ret;
+		ret = ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config);
+		if (!ret)
+			perror("failed querying newly added device");
+		MARK();
+		print_conf(&config);
+		MARK();
+	}
+	return (!ret);
+}
+
+static int delete_device(int fd, const char *devicename)
+{
+	int dev = parse_device(devicename);
+	if (dev < 0) {
+		dprintf(2, "ignoring illegal devicename '%s'\n", devicename);
+		return 1;
+	}
+	if (ioctl(fd, V4L2LOOPBACK_CTL_REMOVE, dev) < 0)
+		perror(devicename);
+
+	return 0;
+}
+
+static int query_device(int fd, const char *devicename)
+{
+	int err;
+	struct v4l2_loopback_config config;
+	int dev = parse_device(devicename);
+	if (dev < 0) {
+		dprintf(2, "ignoring illegal devicename '%s'\n", devicename);
+		return 1;
+	}
+
+	memset(&config, 0, sizeof(config));
+	config.output_nr = config.capture_nr = dev;
+	err = ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config);
+	if (err)
+		perror("query failed");
+	else {
+		printf("%s\n", devicename);
+		print_conf(&config);
+		return 0;
+	}
+	return err;
+}
+static int open_videodevice(const char *devicename, int mode)
+{
+	int fd = open(devicename, mode);
+	if (fd < 0) {
+		int devnr = parse_device(devicename);
+		if (devnr >= 0) {
+			char devname[100];
+			snprintf(devname, 99, "/dev/video%d", devnr);
+			devname[99] = 0;
+			fd = open(devname, mode);
+		}
+	}
+	return fd;
+}
+static int open_controldevice()
+{
+	int fd = open(CONTROLDEVICE, 0);
+	if (fd < 0) {
+		perror("unable to open control device '" CONTROLDEVICE "'");
+		exit(1);
+	}
+}
+
+static int open_sysfs_file(const char *devicename, const char *filename,
+			   int flags)
+{
+	int fd = -1;
+	char sysdev[100];
+	int dev = parse_device(devicename);
+	if (dev < 0) {
+		dprintf(2, "ignoring illegal devicename '%s'\n", devicename);
+		return -1;
+	}
+	snprintf(sysdev, sizeof(sysdev) - 1,
+		 "/sys/devices/virtual/video4linux/video%d/%s", dev, filename);
+	sysdev[sizeof(sysdev) - 1] = 0;
+	fd = open(sysdev, flags);
+	if (fd < 0) {
+		perror("unable to open /sys-device");
+		return -1;
+	}
+	//dprintf(2, "%s\n", sysdev);
+	return fd;
+}
+
+static int parse_fps(const char *fps, int *numerator, int *denominator)
+{
+	int num = 0;
+	int denom = 1;
+	if (sscanf(fps, "%d/%d", &num, &denom) <= 0) {
+		return 1;
+	}
+	if (numerator)
+		*numerator = num;
+	if (denominator)
+		*denominator = denom;
+	return 0;
+}
+static int is_fps(const char *fps)
+{
+	return parse_fps(fps, 0, 0);
+}
+
+static int set_fps(const char *devicename, const char *fps)
+{
+	int result = 1;
+	char _fps[100];
+	int fd = open_sysfs_file(devicename, "format", O_WRONLY);
+	if (fd < 0)
+		return 1;
+	snprintf(_fps, sizeof(_fps) - 1, "@%s", fps);
+	_fps[sizeof(_fps) - 1] = 0;
+
+	if (write(fd, _fps, strnlen(_fps, sizeof(_fps))) < 0) {
+		perror("failed to set fps");
+		goto done;
+	}
+
+	result = 0;
+done:
+	close(fd);
+	return result;
+}
+
+typedef struct _caps {
+	unsigned int fourcc;
+	int width, height;
+	int fps_num, fps_denom;
+} t_caps;
+
+static void print_caps(t_caps *caps)
+{
+	char fourcc[4];
+	if (!caps) {
+		dprintf(2, "no caps\n");
+		return;
+	}
+	dprintf(2, "FOURCC : %.4s\n", fourcc2str(caps->fourcc, fourcc));
+	dprintf(2, "dimen  : %dx%d\n", caps->width, caps->height);
+	dprintf(2, "fps    : %d/%d\n", caps->fps_num, caps->fps_denom);
+}
+static int parse_caps(const char *buffer, t_caps *caps)
+{
+	char fourcc[5];
+	memset(caps, 0, sizeof(*caps));
+	memset(fourcc, 0, sizeof(*fourcc));
+	caps->fps_denom = 1;
+
+	if (!(buffer && *buffer))
+		return 1;
+
+	if (sscanf(buffer, "%4c:%dx%d@%d/%d", fourcc, &caps->width,
+		   &caps->height, &caps->fps_num, &caps->fps_denom) <= 0) {
+	}
+	caps->fourcc = str2fourcc(fourcc);
+	return (0 == caps->fourcc);
+}
+static int read_caps(const char *devicename, t_caps *caps)
+{
+	int result = 1;
+	char _caps[100];
+	int len;
+	int fd = open_sysfs_file(devicename, "format", O_RDONLY);
+	if (fd < 0)
+		return 1;
+
+	len = read(fd, _caps, 100);
+	if (len <= 0) {
+		if (len)
+			perror("failed to read fps");
+		goto done;
+	}
+	_caps[100 - 1] = 0;
+	if (caps) {
+		if (parse_caps(_caps, caps)) {
+			dprintf(2, "unable to parse format '%s'\n", _caps);
+			goto done;
+		}
+	}
+	result = 0;
+done:
+	close(fd);
+	return result;
+}
+
+static int get_fps(const char *devicename)
+{
+	t_caps caps;
+	struct v4l2_streamparm parm;
+	int fd = -1;
+	int num = -1, denom = -1;
+	int ret = 0;
+
+	if (!read_caps(devicename, &caps)) {
+		num = caps.fps_num;
+		denom = caps.fps_denom;
+		goto done;
+	}
+
+	/* get the framerate via ctls */
+	fd = open_videodevice(devicename, O_RDWR);
+	if (fd < 0)
+		goto done;
+
+	memset(&parm, 0, sizeof(parm));
+	parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	if (ioctl(fd, VIDIOC_G_PARM, &parm) == 0) {
+		const struct v4l2_fract *tf = &parm.parm.output.timeperframe;
+		num = tf->numerator;
+		denom = tf->denominator;
+		goto done;
+	}
+
+	memset(&parm, 0, sizeof(parm));
+	parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	if (ioctl(fd, VIDIOC_G_PARM, &parm) == 0) {
+		const struct v4l2_fract *tf = &parm.parm.output.timeperframe;
+		num = tf->numerator;
+		denom = tf->denominator;
+		goto done;
+	}
+
+	ret = 1;
+done:
+	if (fd >= 0)
+		close(fd);
+	printf("%d/%d\n", num, denom);
+	return ret;
+}
+static int set_caps(const char *devicename, const char *capsstring)
+{
+	int result = 1;
+	int fd = open_videodevice(devicename, O_RDWR);
+	struct v4l2_format vid_format;
+	struct v4l2_capability vid_caps;
+	t_caps caps;
+
+	/* now open up the device */
+	if (fd < 0)
+		goto done;
+
+	if (!strncmp("any", capsstring, 4)) {
+		/* skip caps-parsing */
+	} else if (!strncmp("video/", capsstring, 6)) {
+		dprintf(2,
+			"ERROR: GStreamer-style caps are no longer supported!\n");
+		dprintf(2,
+			"ERROR: use '<FOURCC>:<width>x<height>[@<fps>] instead\n");
+		dprintf(2,
+			"       e.g. 'UYVY:640x480@30/1' or 'RGBA:1024x768'\n");
+		goto done;
+	} else if (parse_caps(capsstring, &caps)) {
+		dprintf(2, "unable to parse format '%s'\n", capsstring);
+		goto done;
+	}
+	//print_caps(&caps);
+
+	/* check whether this is actually a video-device */
+	if (ioctl(fd, VIDIOC_QUERYCAP, &vid_caps) == -1) {
+		perror("VIDIOC_QUERYCAP");
+		goto done;
+	}
+
+	if (!strncmp("any", capsstring, 4)) {
+		set_control_i(fd, "keep_format", 0);
+		//set_control_i(fd, "sustain_framerate", 0);
+		result = 0;
+		goto done;
+	}
+
+	/* try to get the default values for the format first */
+	memset(&vid_format, 0, sizeof(vid_format));
+
+	vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	if (ioctl(fd, VIDIOC_G_FMT, &vid_format) == -1) {
+		perror("VIDIOC_G_FMT");
+	}
+
+	/* and set those caps that we have */
+	if (caps.width)
+		vid_format.fmt.pix.width = caps.width;
+	if (caps.height)
+		vid_format.fmt.pix.height = caps.height;
+	if (caps.fourcc)
+		vid_format.fmt.pix.pixelformat = caps.fourcc;
+
+	if (ioctl(fd, VIDIOC_S_FMT, &vid_format) == -1) {
+		perror("unable to set requested format");
+		goto done;
+	}
+
+	set_control_i(fd, "keep_format", 1);
+
+	/* finally, try setting the fps */
+	if (caps.fps_num && caps.fps_denom) {
+		char fps[100];
+		int didit;
+		snprintf(fps, 100, "%d/%d", caps.fps_num, caps.fps_denom);
+		didit = set_fps(devicename, fps);
+		if (!didit) {
+			set_control_i(fd, "sustain_framerate", 1);
+		}
+		close(fd);
+		fd = -1;
+		return didit;
+	}
+
+	result = 0;
+done:
+	if (fd >= 0)
+		close(fd);
+	return result;
+}
+static int get_caps(const char *devicename)
+{
+	int format = 0;
+	t_caps caps;
+	char fourcc[4];
+	if (read_caps(devicename, &caps))
+		return 1;
+	switch (format) {
+	default:
+		printf("%.4s:%dx%d@%d/%d\n", fourcc2str(caps.fourcc, fourcc),
+		       caps.width, caps.height, caps.fps_num, caps.fps_denom);
+		break;
+	case 1: /* GStreamer-1.0 */
+
+		/* FOURCC is different everywhere... */
+		switch (caps.fourcc) {
+		default:
+			break;
+		case 0x56595559: /* YUYV */
+			caps.fourcc = str2fourcc("YUY2");
+			break;
+		}
+
+		printf("video/x-raw,format=%.4s,width=%d,height=%d,framerate=%d/%d\n",
+		       fourcc2str(caps.fourcc, fourcc), caps.width, caps.height,
+		       caps.fps_num, caps.fps_denom);
+		break;
+	}
+	return 0;
+}
+static int set_timeoutimage(const char *devicename, const char *imagefile,
+			    int timeout)
+{
+	int fd = -1;
+	char imagearg[4096], imagefile2[4096], devicearg[4096];
+	char *args[] = { "gst-launch-1.0",
+			 "uridecodebin",
+			 0,
+			 "!",
+			 "videoconvert",
+			 "!",
+			 "videoscale",
+			 "!",
+			 "imagefreeze",
+			 "!",
+			 "identity",
+			 "error-after=3",
+			 "!",
+			 "v4l2sink",
+			 "show-preroll-frame=false",
+			 0,
+			 0 };
+	snprintf(imagearg, 4096, "uri=file://%s",
+		 realpath(imagefile, imagefile2));
+	snprintf(devicearg, 4096, "device=%s", devicename);
+	imagearg[4095] = devicearg[4095] = 0;
+	args[2] = imagearg;
+	args[15] = devicearg;
+
+	fd = open_videodevice(devicename, O_RDWR);
+	if (fd >= 0) {
+		set_control_i(fd, "timeout_image_io", 1);
+		close(fd);
+	}
+
+	dprintf(2,
+		"v======================================================================v\n");
+	if (my_execv(args)) {
+		/*
+          dprintf(2, "ERROR: setting time-out image failed\n");
+          return 1;
+          */
+	}
+	dprintf(2,
+		"^======================================================================^\n");
+
+	fd = open_videodevice(devicename, O_RDWR);
+	if (fd >= 0) {
+		/* finally check the timeout */
+		if (timeout < 0) {
+			timeout = get_control_i(fd, "timeout");
+		} else {
+			timeout = set_control_i(fd, "timeout", timeout);
+		}
+		if (timeout <= 0) {
+			dprintf(2,
+				"Timeout is currently disabled; you can set it to some positive value, e.g.:\n");
+			dprintf(2, "    $  v4l2-ctl -d %s -c timeout=3000\n",
+				devicename);
+		}
+
+		close(fd);
+	}
+}
+
+static t_command get_command(const char *command)
+{
+	if (!strncmp(command, "-h", 2))
+		return HELP;
+	if (!strncmp(command, "-?", 2))
+		return HELP;
+	if (!strncmp(command, "-v", 2))
+		return VERSION;
+	if (!strncmp(command, "add", 4))
+		return ADD;
+	if (!strncmp(command, "del", 3))
+		return DELETE;
+	if (!strncmp(command, "query", 5))
+		return QUERY;
+	if (!strncmp(command, "set-fps", 7))
+		return SET_FPS;
+	if (!strncmp(command, "get-fps", 7))
+		return GET_FPS;
+	if (!strncmp(command, "set-caps", 8))
+		return SET_CAPS;
+	if (!strncmp(command, "get-caps", 8))
+		return GET_CAPS;
+	if (!strncmp(command, "set-timeout-image", 17))
+		return SET_TIMEOUTIMAGE;
+	return _UNKNOWN;
+}
+
+typedef int (*t_argcheck)(const char *);
+static int called_deprecated(const char *device, const char *argument,
+			     const char *programname, const char *cmdname,
+			     const char *argname, t_argcheck argcheck)
+{
+	/* check if <device> does not look like a device, but <argument> does
+   * if so, assume that the user swapped the two */
+	/* if the <device> looks about right, optionally do some extra
+   * <argument>-check, to see if it can be used
+   */
+
+	int deviceswapped = 0;
+	int argswapped = 0;
+
+	if (argcheck)
+		argswapped =
+			((argcheck(argument) != 0) && (argcheck(device) == 0));
+
+	if (!argswapped)
+		deviceswapped = (parse_device(device) < 0 &&
+				 parse_device(argument) >= 0);
+
+	if (argswapped || deviceswapped) {
+		dprintf(2, "WARNING: '%s %s <%s> <image>' is deprecated!\n",
+			programname, cmdname, argname);
+		dprintf(2, "WARNING: use '%s %s <device> <%s>' instead.\n",
+			programname, cmdname, argname);
+		return 1;
+	}
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int i;
+	int fd = -1;
+	int verbose = 0;
+	t_command cmd;
+
+	char *label = 0;
+	int max_width = -1;
+	int max_height = -1;
+	int exclusive_caps = -1;
+	int buffers = -1;
+	int openers = -1;
+
+	int ret = 0;
+
+	int c;
+
+	if (argc < 2)
+		usage(argv[0]);
+	cmd = get_command(argv[1]);
+	switch (cmd) {
+	case _UNKNOWN:
+		dprintf(2, "unknown command '%s'\n\n", argv[1]);
+		usage(argv[0]);
+		break;
+	case HELP:
+		help(argv[0], 0);
+		break;
+	case ADD:
+		while ((c = getopt(argc - 1, argv + 1, "vn:w:h:x:b:o:")) != -1)
+			switch (c) {
+			case 'v':
+				verbose++;
+				break;
+			case 'n':
+				label = optarg;
+				break;
+			case 'w':
+				max_width = my_atoi("max_width", optarg);
+				break;
+			case 'h':
+				max_height = my_atoi("max_height", optarg);
+				break;
+			case 'x':
+				exclusive_caps =
+					my_atoi("exclusive_caps", optarg);
+				break;
+			case 'b':
+				buffers = my_atoi("buffers", optarg);
+				break;
+			case 'o':
+				openers = my_atoi("openers", optarg);
+				break;
+			default:
+				usage_topic(argv[0], cmd, argc - 2, argv + 2);
+				return 1;
+			}
+		fd = open_controldevice();
+		do {
+			struct v4l2_loopback_config cfg;
+			int capture_nr = -1, output_nr = -1;
+			if ((optind + 1) == argc) {
+				/* no device given: pick some */
+			} else if ((optind + 2) == argc) {
+				/* single device given: use it for both input and output */
+				capture_nr = output_nr =
+					parse_device(argv[optind + 1]);
+			} else if ((optind + 3) == argc) {
+				/* two devices given: capture_device and output_device */
+				capture_nr = parse_device(argv[optind + 1]);
+				output_nr = parse_device(argv[optind + 2]);
+			} else {
+				usage_topic(argv[0], cmd, argc - 2, argv + 2);
+			}
+			ret = add_device(fd,
+					 make_conf(&cfg, label, max_width,
+						   max_height, exclusive_caps,
+						   buffers, openers, capture_nr,
+						   output_nr),
+					 verbose);
+		} while (0);
+		break;
+	case DELETE:
+		if (argc == 2)
+			usage_topic(argv[0], cmd, argc - 2, argv + 2);
+		fd = open_controldevice();
+		for (i = 2; i < argc; i++) {
+			ret += (delete_device(fd, argv[i]) != 0);
+		}
+		ret = (ret > 0);
+		break;
+	case QUERY:
+		if (argc == 2)
+			usage_topic(argv[0], cmd, argc - 2, argv + 2);
+		fd = open_controldevice();
+		for (i = 2; i < argc; i++) {
+			ret += query_device(fd, argv[i]);
+		}
+		ret = (ret > 0);
+		break;
+	case SET_FPS:
+		if (argc != 4)
+			usage_topic(argv[0], cmd, argc - 2, argv + 2);
+		if (called_deprecated(argv[2], argv[3], argv[0], "set-fps",
+				      "fps", is_fps)) {
+			ret = set_fps(argv[3], argv[2]);
+		} else
+			ret = set_fps(argv[2], argv[3]);
+		break;
+	case GET_FPS:
+		if (argc != 3)
+			usage_topic(argv[0], cmd, argc - 2, argv + 2);
+		ret = get_fps(argv[2]);
+		break;
+	case SET_CAPS:
+		if (argc != 4)
+			usage_topic(argv[0], cmd, argc - 2, argv + 2);
+		if (called_deprecated(argv[2], argv[3], argv[0], "set-caps",
+				      "caps", 0)) {
+			ret = set_caps(argv[3], argv[2]);
+		} else {
+			ret = set_caps(argv[2], argv[3]);
+		}
+		break;
+	case GET_CAPS:
+		if (argc != 3)
+			usage_topic(argv[0], cmd, argc - 2, argv + 2);
+		ret = get_caps(argv[2]);
+		break;
+	case SET_TIMEOUTIMAGE:
+		if ((4 == argc) && (strncmp("-t", argv[2], 4)) &&
+		    (called_deprecated(argv[2], argv[3], argv[0],
+				       "set-timeout-image", "image", 0))) {
+			ret = set_timeoutimage(argv[3], argv[2], -1);
+		} else {
+			int timeout = -1;
+			while ((c = getopt(argc - 1, argv + 1, "t:")) != -1)
+				switch (c) {
+				case 't':
+					timeout = my_atoi("timeout", optarg);
+					break;
+				default:
+					usage_topic(argv[0], cmd, argc - 2,
+						    argv + 2);
+				}
+			if (optind + 3 != argc)
+				usage_topic(argv[0], cmd, argc - 2, argv + 2);
+			ret = set_timeoutimage(argv[1 + optind],
+					       argv[2 + optind], timeout);
+		}
+		break;
+	case VERSION:
+		printf("%s v%d.%d.%d-omn\n", argv[0], V4L2LOOPBACK_VERSION_MAJOR,
+		       V4L2LOOPBACK_VERSION_MINOR, V4L2LOOPBACK_VERSION_BUGFIX);
+		break;
+	default:
+		dprintf(2, "not implemented '%s'\n", argv[1]);
+		break;
+	}
+
+	if (fd >= 0)
+		close(fd);
+
+	return ret;
+}
diff -uprN v4l2loopback-0.12.5.orig/v4l2loopback.c v4l2loopback-0.12.5/v4l2loopback.c
--- v4l2loopback-0.12.5.orig/v4l2loopback.c	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/v4l2loopback.c	2024-04-24 13:37:48.503876401 +0800
@@ -21,19 +21,30 @@
 #include <linux/videodev2.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/capability.h>
+#include <linux/eventpoll.h>
 #include <media/v4l2-ioctl.h>
 #include <media/v4l2-common.h>
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
-# define HAVE__V4L2_DEVICE
-# include <media/v4l2-device.h>
+#define HAVE__V4L2_DEVICE
+#include <media/v4l2-device.h>
 #endif
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
-# define HAVE__V4L2_CTRLS
-# include <media/v4l2-ctrls.h>
+#define HAVE__V4L2_CTRLS
+#include <media/v4l2-ctrls.h>
 #endif
+#include <media/v4l2-event.h>
 
-#if LINUX_VERSION_CODE < KERNEL_VERSION(3,6,1)
-# define kstrtoul strict_strtoul
+#include <linux/miscdevice.h>
+#include "v4l2loopback.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0)
+#define strscpy strlcpy
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 1)
+#define kstrtoul strict_strtoul
 #endif
 
 #if defined(timer_setup) && defined(from_timer)
@@ -41,18 +52,20 @@
 #endif
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
-# define VFL_TYPE_VIDEO VFL_TYPE_GRABBER
+#define VFL_TYPE_VIDEO VFL_TYPE_GRABBER
 #endif
 
-#define V4L2LOOPBACK_VERSION_CODE KERNEL_VERSION(0, 12, 5)
+#define V4L2LOOPBACK_VERSION_CODE                                              \
+	KERNEL_VERSION(V4L2LOOPBACK_VERSION_MAJOR, V4L2LOOPBACK_VERSION_MINOR, \
+		       V4L2LOOPBACK_VERSION_BUGFIX)
 
 MODULE_DESCRIPTION("V4L2 loopback video device");
-MODULE_AUTHOR("Vasily Levin, " \
-              "IOhannes m zmoelnig <zmoelnig@iem.at>,"  \
-              "Stefan Diewald,"                         \
-              "Anton Novikov"                           \
-	      "et al."					\
-		);
+MODULE_AUTHOR("Vasily Levin, "
+	      "IOhannes m zmoelnig <zmoelnig@iem.at>,"
+	      "Stefan Diewald,"
+	      "Anton Novikov"
+	      "et al.");
+MODULE_VERSION("0.12.5-omn");
 MODULE_LICENSE("GPL");
 
 /*
@@ -61,20 +74,31 @@ MODULE_LICENSE("GPL");
 #define STRINGIFY(s) #s
 #define STRINGIFY2(s) STRINGIFY(s)
 
-#define dprintk(fmt, args...)                                           \
-	do { if (debug > 0) {                                                 \
-		printk(KERN_INFO "v4l2-loopback[" STRINGIFY2(__LINE__) "]: " fmt, ##args); \
-	} } while (0)
-
-#define MARK()                                                          \
-	do { if (debug > 1) {                                                  \
-		printk(KERN_INFO "%s:%d[%s]\n", __FILE__, __LINE__, __func__);	\
-	} } while (0)
-
-#define dprintkrw(fmt, args...)                                         \
-	do { if (debug > 2) {                                                 \
-		printk(KERN_INFO "v4l2-loopback[" STRINGIFY2(__LINE__)"]: " fmt, ##args); \
-	} } while (0)
+#define dprintk(fmt, args...)                                                  \
+	do {                                                                   \
+		if (debug > 0) {                                               \
+			printk(KERN_INFO "v4l2-loopback[" STRINGIFY2(          \
+				       __LINE__) "]: " fmt,                    \
+			       ##args);                                        \
+		}                                                              \
+	} while (0)
+
+#define MARK()                                                                 \
+	do {                                                                   \
+		if (debug > 1) {                                               \
+			printk(KERN_INFO "%s:%d[%s]\n", __FILE__, __LINE__,    \
+			       __func__);                                      \
+		}                                                              \
+	} while (0)
+
+#define dprintkrw(fmt, args...)                                                \
+	do {                                                                   \
+		if (debug > 2) {                                               \
+			printk(KERN_INFO "v4l2-loopback[" STRINGIFY2(          \
+				       __LINE__) "]: " fmt,                    \
+			       ##args);                                        \
+		}                                                              \
+	} while (0)
 
 /*
  * compatibility hacks
@@ -85,46 +109,50 @@ struct v4l2_ctrl_handler {
 	int error;
 };
 struct v4l2_ctrl_config {
-        void *ops;
-        u32 id;
+	void *ops;
+	u32 id;
 	const char *name;
 	int type;
-        s32 min;
-        s32 max;
-        u32 step;
-        s32 def;
+	s32 min;
+	s32 max;
+	u32 step;
+	s32 def;
 };
-int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler*hdl,
+int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl,
 			   unsigned nr_of_controls_hint)
 {
-	hdl->error=0;
+	hdl->error = 0;
 	return 0;
 }
 void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl)
 {
 }
-void*v4l2_ctrl_new_custom(struct v4l2_ctrl_handler *hdl,
-			  const struct v4l2_ctrl_config*conf,
-			  void*priv)
+void *v4l2_ctrl_new_custom(struct v4l2_ctrl_handler *hdl,
+			   const struct v4l2_ctrl_config *conf, void *priv)
 {
 	return NULL;
 }
 #endif /* HAVE__V4L2_CTRLS */
 
-
 #ifndef HAVE__V4L2_DEVICE
 /* dummy v4l2_device struct/functions */
-# define V4L2_DEVICE_NAME_SIZE (20 + 16)
+#define V4L2_DEVICE_NAME_SIZE (20 + 16)
 struct v4l2_device {
 	char name[V4L2_DEVICE_NAME_SIZE];
-	struct v4l2_ctrl_handler*ctrl_handler;
+	struct v4l2_ctrl_handler *ctrl_handler;
 };
-static inline int  v4l2_device_register  (void *dev, void *v4l2_dev) { return 0; }
-static inline void v4l2_device_unregister(struct v4l2_device *v4l2_dev) { return; }
+static inline int v4l2_device_register(void *dev, void *v4l2_dev)
+{
+	return 0;
+}
+static inline void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
+{
+	return;
+}
 #endif /*  HAVE__V4L2_DEVICE */
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
-# define v4l2_file_operations file_operations
+#define v4l2_file_operations file_operations
 #endif
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37)
 void *v4l2l_vzalloc(unsigned long size)
@@ -135,10 +163,11 @@ void *v4l2l_vzalloc(unsigned long size)
 	return data;
 }
 #else
-# define v4l2l_vzalloc vzalloc
+#define v4l2l_vzalloc vzalloc
 #endif
 
-static inline void v4l2l_get_timestamp(struct v4l2_buffer *b) {
+static inline void v4l2l_get_timestamp(struct v4l2_buffer *b)
+{
 	/* ktime_get_ts is considered deprecated, so use ktime_get_ts64 if possible */
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
 	struct timespec ts;
@@ -152,27 +181,34 @@ static inline void v4l2l_get_timestamp(s
 	b->timestamp.tv_usec = (ts.tv_nsec / NSEC_PER_USEC);
 }
 
+#if !defined(__poll_t)
+typedef unsigned __poll_t;
+#endif
 
 /* module constants
  *  can be overridden during he build process using something like
- *      make KCPPFLAGS="-DMAX_DEVICES=100"
+ *	make KCPPFLAGS="-DMAX_DEVICES=100"
  */
 
-
 /* maximum number of v4l2loopback devices that can be created */
 #ifndef MAX_DEVICES
-# define MAX_DEVICES 8
+#define MAX_DEVICES 8
+#endif
+
+/* whether the default is to announce capabilities exclusively or not */
+#ifndef V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS
+#define V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS 0
 #endif
 
 /* when a producer is considered to have gone stale */
 #ifndef MAX_TIMEOUT
-# define MAX_TIMEOUT (100 * 1000) /* in msecs */
+#define MAX_TIMEOUT (100 * 1000) /* in msecs */
 #endif
 
 /* max buffers that can be mapped, actually they
  * are all mapped to max_buffers buffers */
 #ifndef MAX_BUFFERS
-# define MAX_BUFFERS 32
+#define MAX_BUFFERS 32
 #endif
 
 /* module parameters */
@@ -180,9 +216,12 @@ static int debug = 0;
 module_param(debug, int, S_IRUGO | S_IWUSR);
 MODULE_PARM_DESC(debug, "debugging level (higher values == more verbose)");
 
-static int max_buffers = 2;
+#define V4L2LOOPBACK_DEFAULT_MAX_BUFFERS 2
+static int max_buffers = V4L2LOOPBACK_DEFAULT_MAX_BUFFERS;
 module_param(max_buffers, int, S_IRUGO);
-MODULE_PARM_DESC(max_buffers, "how many buffers should be allocated");
+MODULE_PARM_DESC(max_buffers,
+		 "how many buffers should be allocated [DEFAULT: " STRINGIFY2(
+			 V4L2LOOPBACK_DEFAULT_MAX_BUFFERS) "]");
 
 /* how many times a device can be opened
  * the per-module default value can be overridden on a per-device basis using
@@ -192,107 +231,126 @@ MODULE_PARM_DESC(max_buffers, "how many
  *   one opener for the producer and one opener for the consumer
  *   however, we leave that to the user
  */
-static int max_openers = 10;
+#define V4L2LOOPBACK_DEFAULT_MAX_OPENERS 10
+static int max_openers = V4L2LOOPBACK_DEFAULT_MAX_OPENERS;
 module_param(max_openers, int, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(max_openers, "how many users can open loopback device");
-
+MODULE_PARM_DESC(
+	max_openers,
+	"how many users can open the loopback device [DEFAULT: " STRINGIFY2(
+		V4L2LOOPBACK_DEFAULT_MAX_OPENERS) "]");
 
 static int devices = -1;
 module_param(devices, int, 0);
 MODULE_PARM_DESC(devices, "how many devices should be created");
 
-
-static int video_nr[MAX_DEVICES] = { [0 ... (MAX_DEVICES - 1)] = -1 };
+static int video_nr[MAX_DEVICES] = { [0 ...(MAX_DEVICES - 1)] = -1 };
 module_param_array(video_nr, int, NULL, 0444);
-MODULE_PARM_DESC(video_nr, "video device numbers (-1=auto, 0=/dev/video0, etc.)");
+MODULE_PARM_DESC(video_nr,
+		 "video device numbers (-1=auto, 0=/dev/video0, etc.)");
 
 static char *card_label[MAX_DEVICES];
 module_param_array(card_label, charp, NULL, 0000);
-MODULE_PARM_DESC(card_label, "card labels for every device");
+MODULE_PARM_DESC(card_label, "card labels for each device");
 
-static bool exclusive_caps[MAX_DEVICES] = { [0 ... (MAX_DEVICES - 1)] = 0 };
+static bool exclusive_caps[MAX_DEVICES] = {
+	[0 ...(MAX_DEVICES - 1)] = V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS
+};
 module_param_array(exclusive_caps, bool, NULL, 0444);
 /* FIXXME: wording */
-MODULE_PARM_DESC(exclusive_caps, "whether to announce OUTPUT/CAPTURE capabilities exclusively or not");
-
+MODULE_PARM_DESC(
+	exclusive_caps,
+	"whether to announce OUTPUT/CAPTURE capabilities exclusively or not  [DEFAULT: " STRINGIFY2(
+		V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS) "]");
 
 /* format specifications */
-#define V4L2LOOPBACK_SIZE_MIN_WIDTH   48
-#define V4L2LOOPBACK_SIZE_MIN_HEIGHT  32
-#define V4L2LOOPBACK_SIZE_MAX_WIDTH   8192
-#define V4L2LOOPBACK_SIZE_MAX_HEIGHT  8192
+#define V4L2LOOPBACK_SIZE_MIN_WIDTH 48
+#define V4L2LOOPBACK_SIZE_MIN_HEIGHT 32
+#define V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH 8192
+#define V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT 8192
 
-#define V4L2LOOPBACK_SIZE_DEFAULT_WIDTH   640
-#define V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT  480
+#define V4L2LOOPBACK_SIZE_DEFAULT_WIDTH 640
+#define V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT 480
 
-static int max_width = V4L2LOOPBACK_SIZE_MAX_WIDTH;
+static int max_width = V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH;
 module_param(max_width, int, S_IRUGO);
-MODULE_PARM_DESC(max_width, "maximum frame width");
-static int max_height = V4L2LOOPBACK_SIZE_MAX_HEIGHT;
+MODULE_PARM_DESC(max_width, "maximum allowed frame width [DEFAULT: " STRINGIFY2(
+				    V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH) "]");
+static int max_height = V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT;
 module_param(max_height, int, S_IRUGO);
-MODULE_PARM_DESC(max_height, "maximum frame height");
+MODULE_PARM_DESC(max_height,
+		 "maximum allowed frame height [DEFAULT: " STRINGIFY2(
+			 V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT) "]");
 
+static DEFINE_IDR(v4l2loopback_index_idr);
+static DEFINE_MUTEX(v4l2loopback_ctl_mutex);
 
 /* control IDs */
 #ifndef HAVE__V4L2_CTRLS
-# define V4L2LOOPBACK_CID_BASE  (V4L2_CID_PRIVATE_BASE)
+#define V4L2LOOPBACK_CID_BASE (V4L2_CID_PRIVATE_BASE)
 #else
-# define V4L2LOOPBACK_CID_BASE  (V4L2_CID_USER_BASE | 0xf000)
+#define V4L2LOOPBACK_CID_BASE (V4L2_CID_USER_BASE | 0xf000)
 #endif
-#define CID_KEEP_FORMAT        (V4L2LOOPBACK_CID_BASE + 0)
-#define CID_SUSTAIN_FRAMERATE  (V4L2LOOPBACK_CID_BASE + 1)
-#define CID_TIMEOUT            (V4L2LOOPBACK_CID_BASE + 2)
-#define CID_TIMEOUT_IMAGE_IO   (V4L2LOOPBACK_CID_BASE + 3)
+#define CID_KEEP_FORMAT (V4L2LOOPBACK_CID_BASE + 0)
+#define CID_SUSTAIN_FRAMERATE (V4L2LOOPBACK_CID_BASE + 1)
+#define CID_TIMEOUT (V4L2LOOPBACK_CID_BASE + 2)
+#define CID_TIMEOUT_IMAGE_IO (V4L2LOOPBACK_CID_BASE + 3)
 
 static int v4l2loopback_s_ctrl(struct v4l2_ctrl *ctrl);
 static const struct v4l2_ctrl_ops v4l2loopback_ctrl_ops = {
 	.s_ctrl = v4l2loopback_s_ctrl,
 };
 static const struct v4l2_ctrl_config v4l2loopback_ctrl_keepformat = {
-        .ops = &v4l2loopback_ctrl_ops,
-        .id = CID_KEEP_FORMAT,
-        .name = "keep_format",
-        .type = V4L2_CTRL_TYPE_BOOLEAN,
-        .min = 0,
-        .max = 1,
-        .step = 1,
-        .def = 0,
+	// clang-format off
+	.ops	= &v4l2loopback_ctrl_ops,
+	.id	= CID_KEEP_FORMAT,
+	.name	= "keep_format",
+	.type	= V4L2_CTRL_TYPE_BOOLEAN,
+	.min	= 0,
+	.max	= 1,
+	.step	= 1,
+	.def	= 0,
+	// clang-format on
 };
 static const struct v4l2_ctrl_config v4l2loopback_ctrl_sustainframerate = {
-        .ops = &v4l2loopback_ctrl_ops,
-        .id = CID_SUSTAIN_FRAMERATE,
-        .name = "sustain_framerate",
-        .type = V4L2_CTRL_TYPE_BOOLEAN,
-        .min = 0,
-        .max = 1,
-        .step = 1,
-        .def = 0,
+	// clang-format off
+	.ops	= &v4l2loopback_ctrl_ops,
+	.id	= CID_SUSTAIN_FRAMERATE,
+	.name	= "sustain_framerate",
+	.type	= V4L2_CTRL_TYPE_BOOLEAN,
+	.min	= 0,
+	.max	= 1,
+	.step	= 1,
+	.def	= 0,
+	// clang-format on
 };
 static const struct v4l2_ctrl_config v4l2loopback_ctrl_timeout = {
-        .ops = &v4l2loopback_ctrl_ops,
-        .id = CID_TIMEOUT,
-        .name = "timeout",
-        .type = V4L2_CTRL_TYPE_INTEGER,
-        .min = 0,
-        .max = MAX_TIMEOUT,
-        .step = 1,
-        .def = 0,
+	// clang-format off
+	.ops	= &v4l2loopback_ctrl_ops,
+	.id	= CID_TIMEOUT,
+	.name	= "timeout",
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.min	= 0,
+	.max	= MAX_TIMEOUT,
+	.step	= 1,
+	.def	= 0,
+	// clang-format on
 };
 static const struct v4l2_ctrl_config v4l2loopback_ctrl_timeoutimageio = {
-        .ops = &v4l2loopback_ctrl_ops,
-        .id = CID_TIMEOUT_IMAGE_IO,
-        .name = "timeout_image_io",
-        .type = V4L2_CTRL_TYPE_BOOLEAN,
-        .min = 0,
-        .max = 1,
-        .step = 1,
-        .def = 0,
+	// clang-format off
+	.ops	= &v4l2loopback_ctrl_ops,
+	.id	= CID_TIMEOUT_IMAGE_IO,
+	.name	= "timeout_image_io",
+	.type	= V4L2_CTRL_TYPE_BOOLEAN,
+	.min	= 0,
+	.max	= 1,
+	.step	= 1,
+	.def	= 0,
+	// clang-format on
 };
 
-
 /* module structures */
 struct v4l2loopback_private {
-	int devicenr;
+	int device_nr;
 };
 
 /* TODO(vasaka) use typenames which are common to kernel, but first find out if
@@ -321,17 +379,18 @@ struct v4l2_loopback_device {
 				  (close to) nominal framerate */
 
 	/* buffers stuff */
-	u8 *image;         /* pointer to actual buffers data */
-	unsigned long int imagesize;  /* size of buffers data */
-	int buffers_number;  /* should not be big, 4 is a good choice */
-	struct v4l2l_buffer buffers[MAX_BUFFERS];	/* inner driver buffers */
+	u8 *image; /* pointer to actual buffers data */
+	unsigned long int imagesize; /* size of buffers data */
+	int buffers_number; /* should not be big, 4 is a good choice */
+	struct v4l2l_buffer buffers[MAX_BUFFERS]; /* inner driver buffers */
 	int used_buffers; /* number of the actually used buffers */
-	int max_openers;  /* how many times can this device be opened */
+	int max_openers; /* how many times can this device be opened */
 
 	int write_position; /* number of last written frame + 1 */
 	struct list_head outbufs_list; /* buffers in output DQBUF order */
-	int bufpos2index[MAX_BUFFERS]; /* mapping of (read/write_position % used_buffers)
-					* to inner buffer index */
+	int bufpos2index
+		[MAX_BUFFERS]; /* mapping of (read/write_position % used_buffers)
+                        * to inner buffer index */
 	long buffer_size;
 
 	/* sustain_framerate stuff */
@@ -350,15 +409,19 @@ struct v4l2_loopback_device {
 	/* sync stuff */
 	atomic_t open_count;
 
-
-	int ready_for_capture;/* set to true when at least one writer opened
-			       * device and negotiated format */
+	int ready_for_capture; /* set to the number of writers that opened the
+                                * device and negotiated format. */
 	int ready_for_output; /* set to true when no writer is currently attached
 			       * this differs slightly from !ready_for_capture,
 			       * e.g. when using fallback images */
-	int announce_all_caps;/* set to false, if device caps (OUTPUT/CAPTURE)
-			       * should only be announced if the resp. "ready"
-			       * flag is set; default=TRUE */
+	int announce_all_caps; /* set to false, if device caps (OUTPUT/CAPTURE)
+                                * should only be announced if the resp. "ready"
+                                * flag is set; default=TRUE */
+
+	int max_width;
+	int max_height;
+
+	char card_label[32];
 
 	wait_queue_head_t read_event;
 	spinlock_t lock;
@@ -366,9 +429,11 @@ struct v4l2_loopback_device {
 
 /* types of opener shows what opener wants to do with loopback */
 enum opener_type {
-	UNNEGOTIATED = 0,
-	READER = 1,
-	WRITER = 2,
+	// clang-format off
+	UNNEGOTIATED	= 0,
+	READER		= 1,
+	WRITER		= 2,
+	// clang-format on
 };
 
 /* struct keeping state and type of opener */
@@ -379,39 +444,33 @@ struct v4l2_loopback_opener {
 			    * write_position - 1 if reader went out of sync */
 	unsigned int reread_count;
 	struct v4l2_buffer *buffers;
-	int buffers_number;  /* should not be big, 4 is a good choice */
+	int buffers_number; /* should not be big, 4 is a good choice */
 	int timeout_image_io;
+
+	struct v4l2_fh fh;
 };
 
+#define fh_to_opener(ptr) container_of((ptr), struct v4l2_loopback_opener, fh)
+
 /* this is heavily inspired by the bttv driver found in the linux kernel */
 struct v4l2l_format {
 	char *name;
-	int  fourcc;          /* video4linux 2      */
-	int  depth;           /* bit/pixel          */
-	int  flags;
+	int fourcc; /* video4linux 2 */
+	int depth; /* bit/pixel */
+	int flags;
 };
 /* set the v4l2l_format.flags to PLANAR for non-packed formats */
-#define FORMAT_FLAGS_PLANAR       0x01
-#define FORMAT_FLAGS_COMPRESSED   0x02
+#define FORMAT_FLAGS_PLANAR 0x01
+#define FORMAT_FLAGS_COMPRESSED 0x02
 
-#ifndef V4L2_PIX_FMT_VP9
-#define V4L2_PIX_FMT_VP9  v4l2_fourcc('V', 'P', '9', '0')
-#endif
-#ifndef V4L2_PIX_FMT_HEVC
-#define V4L2_PIX_FMT_HEVC  v4l2_fourcc('H', 'E', 'V', 'C')
-#endif
-
-static const struct v4l2l_format formats[] = {
 #include "v4l2loopback_formats.h"
-};
 
 static const unsigned int FORMATS = ARRAY_SIZE(formats);
 
-
 static char *fourcc2str(unsigned int fourcc, char buf[4])
 {
-	buf[0] = (fourcc >>  0) & 0xFF;
-	buf[1] = (fourcc >>  8) & 0xFF;
+	buf[0] = (fourcc >> 0) & 0xFF;
+	buf[1] = (fourcc >> 8) & 0xFF;
 	buf[2] = (fourcc >> 16) & 0xFF;
 	buf[3] = (fourcc >> 24) & 0xFF;
 
@@ -427,17 +486,15 @@ static const struct v4l2l_format *format
 			return formats + i;
 	}
 
-	dprintk("unsupported format '%c%c%c%c'\n",
-			(fourcc >>  0) & 0xFF,
-			(fourcc >>  8) & 0xFF,
-			(fourcc >> 16) & 0xFF,
-			(fourcc >> 24) & 0xFF);
+	dprintk("unsupported format '%c%c%c%c'\n", (fourcc >> 0) & 0xFF,
+		(fourcc >> 8) & 0xFF, (fourcc >> 16) & 0xFF,
+		(fourcc >> 24) & 0xFF);
 	return NULL;
 }
 
 static void pix_format_set_size(struct v4l2_pix_format *f,
-		const struct v4l2l_format *fmt,
-		unsigned int width, unsigned int height)
+				const struct v4l2l_format *fmt,
+				unsigned int width, unsigned int height)
 {
 	f->width = width;
 	f->height = height;
@@ -446,7 +503,7 @@ static void pix_format_set_size(struct v
 		f->bytesperline = width; /* Y plane */
 		f->sizeimage = (width * height * fmt->depth) >> 3;
 	} else if (fmt->flags & FORMAT_FLAGS_COMPRESSED) {
-	        /* doesn't make sense for compressed formats */
+		/* doesn't make sense for compressed formats */
 		f->bytesperline = 0;
 		f->sizeimage = (width * height * fmt->depth) >> 3;
 	} else {
@@ -456,14 +513,14 @@ static void pix_format_set_size(struct v
 }
 
 static int set_timeperframe(struct v4l2_loopback_device *dev,
-		struct v4l2_fract *tpf)
+			    struct v4l2_fract *tpf)
 {
-        if((tpf->denominator < 1) || (tpf->numerator < 1)) {
-	  return -EINVAL;
+	if ((tpf->denominator < 1) || (tpf->numerator < 1)) {
+		return -EINVAL;
 	}
 	dev->capture_param.timeperframe = *tpf;
-	dev->frame_jiffies = max(1UL,
-		msecs_to_jiffies(1000) * tpf->numerator / tpf->denominator);
+	dev->frame_jiffies = max(1UL, msecs_to_jiffies(1000) * tpf->numerator /
+					      tpf->denominator);
 	return 0;
 }
 
@@ -473,7 +530,7 @@ static struct v4l2_loopback_device *v4l2
 /* available via sysfs: /sys/devices/virtual/video4linux/video* */
 
 static ssize_t attr_show_format(struct device *cd,
-		struct device_attribute *attr, char *buf)
+				struct device_attribute *attr, char *buf)
 {
 	/* gets the current format as "FOURCC:WxH@f/s", e.g. "YUYV:320x240@1000/30" */
 	struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
@@ -485,50 +542,56 @@ static ssize_t attr_show_format(struct d
 	tpf = &dev->capture_param.timeperframe;
 
 	fourcc2str(dev->pix_format.pixelformat, buf4cc);
-        buf4cc[4]=0;
+	buf4cc[4] = 0;
 	if (tpf->numerator == 1)
 		snprintf(buf_fps, sizeof(buf_fps), "%d", tpf->denominator);
 	else
-		snprintf(buf_fps, sizeof(buf_fps), "%d/%d",
-				tpf->denominator, tpf->numerator);
-	return sprintf(buf, "%4s:%dx%d@%s\n",
-		buf4cc, dev->pix_format.width, dev->pix_format.height, buf_fps);
+		snprintf(buf_fps, sizeof(buf_fps), "%d/%d", tpf->denominator,
+			 tpf->numerator);
+	return sprintf(buf, "%4s:%dx%d@%s\n", buf4cc, dev->pix_format.width,
+		       dev->pix_format.height, buf_fps);
 }
 
 static ssize_t attr_store_format(struct device *cd,
-		struct device_attribute *attr, const char *buf, size_t len)
+				 struct device_attribute *attr, const char *buf,
+				 size_t len)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
 	int fps_num = 0, fps_den = 1;
 
+	if (!dev)
+		return -ENODEV;
+
 	/* only fps changing is supported */
 	if (sscanf(buf, "@%d/%d", &fps_num, &fps_den) > 0) {
-		struct v4l2_fract f = {
-			.numerator   = fps_den,
-			.denominator = fps_num
-		};
-		int err=0;
-		if((err=set_timeperframe(dev, &f)) < 0)
+		struct v4l2_fract f = { .numerator = fps_den,
+					.denominator = fps_num };
+		int err = 0;
+		if ((err = set_timeperframe(dev, &f)) < 0)
 			return err;
 		return len;
 	}
 	return -EINVAL;
 }
 
-static DEVICE_ATTR(format, S_IRUGO | S_IWUSR, attr_show_format, attr_store_format);
+static DEVICE_ATTR(format, S_IRUGO | S_IWUSR, attr_show_format,
+		   attr_store_format);
 
 static ssize_t attr_show_buffers(struct device *cd,
-		struct device_attribute *attr, char *buf)
+				 struct device_attribute *attr, char *buf)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
 
+	if (!dev)
+		return -ENODEV;
+
 	return sprintf(buf, "%d\n", dev->used_buffers);
 }
 
 static DEVICE_ATTR(buffers, S_IRUGO, attr_show_buffers, NULL);
 
 static ssize_t attr_show_maxopeners(struct device *cd,
-		struct device_attribute *attr, char *buf)
+				    struct device_attribute *attr, char *buf)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd);
 
@@ -536,7 +599,8 @@ static ssize_t attr_show_maxopeners(stru
 }
 
 static ssize_t attr_store_maxopeners(struct device *cd,
-		struct device_attribute *attr, const char *buf, size_t len)
+				     struct device_attribute *attr,
+				     const char *buf, size_t len)
 {
 	struct v4l2_loopback_device *dev = NULL;
 	unsigned long curr = 0;
@@ -559,8 +623,8 @@ static ssize_t attr_store_maxopeners(str
 	return len;
 }
 
-
-static DEVICE_ATTR(max_openers, S_IRUGO | S_IWUSR, attr_show_maxopeners, attr_store_maxopeners);
+static DEVICE_ATTR(max_openers, S_IRUGO | S_IWUSR, attr_show_maxopeners,
+		   attr_store_maxopeners);
 
 static void v4l2loopback_remove_sysfs(struct video_device *vdev)
 {
@@ -578,7 +642,10 @@ static void v4l2loopback_create_sysfs(st
 {
 	int res = 0;
 
-#define V4L2_SYSFS_CREATE(x)     res = device_create_file(&vdev->dev, &dev_attr_##x); if (res < 0) break
+#define V4L2_SYSFS_CREATE(x)                                                   \
+	res = device_create_file(&vdev->dev, &dev_attr_##x);                   \
+	if (res < 0)                                                           \
+	break
 	if (!vdev)
 		return;
 	do {
@@ -594,20 +661,48 @@ static void v4l2loopback_create_sysfs(st
 }
 
 /* global module data */
-struct v4l2_loopback_device *devs[MAX_DEVICES];
-
+/* find a device based on it's device-number (e.g. '3' for /dev/video3) */
+struct v4l2loopback_lookup_cb_data {
+	int device_nr;
+	struct v4l2_loopback_device *device;
+};
+static int v4l2loopback_lookup_cb(int id, void *ptr, void *data)
+{
+	struct v4l2_loopback_device *device = ptr;
+	struct v4l2loopback_lookup_cb_data *cbdata = data;
+	if (cbdata && device && device->vdev) {
+		if (device->vdev->num == cbdata->device_nr) {
+			cbdata->device = device;
+			cbdata->device_nr = id;
+			return 1;
+		}
+	}
+	return 0;
+}
+static int v4l2loopback_lookup(int device_nr,
+			       struct v4l2_loopback_device **device)
+{
+	struct v4l2loopback_lookup_cb_data data = {
+		.device_nr = device_nr,
+		.device = NULL,
+	};
+	int err = idr_for_each(&v4l2loopback_index_idr, &v4l2loopback_lookup_cb,
+			       &data);
+	if (1 == err) {
+		if (device)
+			*device = data.device;
+		return data.device_nr;
+	}
+	return -ENODEV;
+}
 static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd)
 {
 	struct video_device *loopdev = to_video_device(cd);
 	struct v4l2loopback_private *ptr =
 		(struct v4l2loopback_private *)video_get_drvdata(loopdev);
-	int nr = ptr->devicenr;
+	int nr = ptr->device_nr;
 
-	if (nr < 0 || nr >= devices) {
-		printk(KERN_ERR "v4l2-loopback: illegal device %d\n", nr);
-		return NULL;
-	}
-	return devs[nr];
+	return idr_find(&v4l2loopback_index_idr, nr);
 }
 
 static struct v4l2_loopback_device *v4l2loopback_getdevice(struct file *f)
@@ -615,13 +710,9 @@ static struct v4l2_loopback_device *v4l2
 	struct video_device *loopdev = video_devdata(f);
 	struct v4l2loopback_private *ptr =
 		(struct v4l2loopback_private *)video_get_drvdata(loopdev);
-	int nr = ptr->devicenr;
+	int nr = ptr->device_nr;
 
-	if (nr < 0 || nr >= devices) {
-		printk(KERN_ERR "v4l2-loopback: illegal device %d\n", nr);
-		return NULL;
-	}
-	return devs[nr];
+	return idr_find(&v4l2loopback_index_idr, nr);
 }
 
 /* forward declarations */
@@ -654,28 +745,26 @@ static inline void unset_flags(struct v4
 	buffer->buffer.flags &= ~V4L2_BUF_FLAG_DONE;
 }
 
-static void vidioc_fill_name(char *buf, int len, int nr)
-{
-	if (card_label[nr] != NULL) {
-		snprintf(buf, len, card_label[nr]);
-	} else {
-		snprintf(buf, len, "Dummy video device (0x%04X)", nr);
-	}
-}
-
 /* V4L2 ioctl caps and params calls */
 /* returns device capabilities
  * called on VIDIOC_QUERYCAP
  */
-static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
+static int vidioc_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-	int devnr = ((struct v4l2loopback_private *)video_get_drvdata(dev->vdev))->devicenr;
-        __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
-
-	strlcpy(cap->driver, "v4l2 loopback", sizeof(cap->driver));
-	vidioc_fill_name(cap->card, sizeof(cap->card), devnr);
-	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:v4l2loopback-%03d", devnr);
+	int labellen = (sizeof(cap->card) < sizeof(dev->card_label)) ?
+			       sizeof(cap->card) :
+				     sizeof(dev->card_label);
+	int device_nr =
+		((struct v4l2loopback_private *)video_get_drvdata(dev->vdev))
+			->device_nr;
+	__u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
+
+	strscpy(cap->driver, "v4l2 loopback", sizeof(cap->driver));
+	snprintf(cap->card, labellen, dev->card_label);
+	snprintf(cap->bus_info, sizeof(cap->bus_info),
+		 "platform:v4l2loopback-%03d", device_nr);
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0)
 	/* since 3.1.0, the v4l2-core system is supposed to set the version */
@@ -689,7 +778,6 @@ static int vidioc_querycap(struct file *
 	if (dev->announce_all_caps) {
 		capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT;
 	} else {
-
 		if (dev->ready_for_capture) {
 			capabilities |= V4L2_CAP_VIDEO_CAPTURE;
 		}
@@ -698,7 +786,10 @@ static int vidioc_querycap(struct file *
 		}
 	}
 
-	dev->vdev->device_caps = cap->device_caps = cap->capabilities = capabilities;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
+	dev->vdev->device_caps =
+#endif /* >=linux-4.7.0 */
+		cap->device_caps = cap->capabilities = capabilities;
 
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
 	cap->capabilities |= V4L2_CAP_DEVICE_CAPS;
@@ -708,7 +799,8 @@ static int vidioc_querycap(struct file *
 	return 0;
 }
 
-static int vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *argp)
+static int vidioc_enum_framesizes(struct file *file, void *fh,
+				  struct v4l2_frmsizeenum *argp)
 {
 	struct v4l2_loopback_device *dev;
 
@@ -738,8 +830,8 @@ static int vidioc_enum_framesizes(struct
 		argp->stepwise.min_width = V4L2LOOPBACK_SIZE_MIN_WIDTH;
 		argp->stepwise.min_height = V4L2LOOPBACK_SIZE_MIN_HEIGHT;
 
-		argp->stepwise.max_width = max_width;
-		argp->stepwise.max_height = max_height;
+		argp->stepwise.max_width = dev->max_width;
+		argp->stepwise.max_height = dev->max_height;
 
 		argp->stepwise.step_width = 1;
 		argp->stepwise.step_height = 1;
@@ -750,16 +842,17 @@ static int vidioc_enum_framesizes(struct
 /* returns frameinterval (fps) for the set resolution
  * called on VIDIOC_ENUM_FRAMEINTERVALS
  */
-static int vidioc_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *argp)
+static int vidioc_enum_frameintervals(struct file *file, void *fh,
+				      struct v4l2_frmivalenum *argp)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-	struct v4l2_loopback_opener *opener = file->private_data;
+	struct v4l2_loopback_opener *opener = fh_to_opener(fh);
 
 	if (dev->ready_for_capture) {
 		if (opener->vidioc_enum_frameintervals_calls > 0)
 			return -EINVAL;
 		if (argp->width == dev->pix_format.width &&
-				argp->height == dev->pix_format.height) {
+		    argp->height == dev->pix_format.height) {
 			argp->type = V4L2_FRMIVAL_TYPE_DISCRETE;
 			argp->discrete = dev->capture_param.timeperframe;
 			opener->vidioc_enum_frameintervals_calls++;
@@ -775,7 +868,8 @@ static int vidioc_enum_frameintervals(st
 /* returns device formats
  * called on VIDIOC_ENUM_FMT, with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_CAPTURE
  */
-static int vidioc_enum_fmt_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+static int vidioc_enum_fmt_cap(struct file *file, void *fh,
+			       struct v4l2_fmtdesc *f)
 {
 	struct v4l2_loopback_device *dev;
 	MARK();
@@ -787,12 +881,9 @@ static int vidioc_enum_fmt_cap(struct fi
 	if (dev->ready_for_capture) {
 		const __u32 format = dev->pix_format.pixelformat;
 
-		snprintf(f->description, sizeof(f->description),
-				"[%c%c%c%c]",
-				(format >>  0) & 0xFF,
-				(format >>  8) & 0xFF,
-				(format >> 16) & 0xFF,
-				(format >> 24) & 0xFF);
+		snprintf(f->description, sizeof(f->description), "[%c%c%c%c]",
+			 (format >> 0) & 0xFF, (format >> 8) & 0xFF,
+			 (format >> 16) & 0xFF, (format >> 24) & 0xFF);
 
 		f->pixelformat = dev->pix_format.pixelformat;
 	} else {
@@ -803,10 +894,11 @@ static int vidioc_enum_fmt_cap(struct fi
 	return 0;
 }
 
-/* returns current video format format fmt
+/* returns current video format
  * called on VIDIOC_G_FMT, with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_CAPTURE
  */
-static int vidioc_g_fmt_cap(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_g_fmt_cap(struct file *file, void *priv,
+			    struct v4l2_format *fmt)
 {
 	struct v4l2_loopback_device *dev;
 	MARK();
@@ -827,7 +919,8 @@ static int vidioc_g_fmt_cap(struct file
  * obey this decision
  * called on VIDIOC_TRY_FMT, with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_CAPTURE
  */
-static int vidioc_try_fmt_cap(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_try_fmt_cap(struct file *file, void *priv,
+			      struct v4l2_format *fmt)
 {
 	struct v4l2_loopback_device *dev;
 	char buf[5];
@@ -854,19 +947,20 @@ static int vidioc_try_fmt_cap(struct fil
  * current one, but it is possible to set subregions of input TODO(vasaka)
  * called on VIDIOC_S_FMT, with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_CAPTURE
  */
-static int vidioc_s_fmt_cap(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_s_fmt_cap(struct file *file, void *priv,
+			    struct v4l2_format *fmt)
 {
 	return vidioc_try_fmt_cap(file, priv, fmt);
 }
 
-
 /* ------------------ OUTPUT ----------------------- */
 
 /* returns device formats;
  * LATER: allow all formats
  * called on VIDIOC_ENUM_FMT, with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_OUTPUT
  */
-static int vidioc_enum_fmt_out(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+static int vidioc_enum_fmt_out(struct file *file, void *fh,
+			       struct v4l2_fmtdesc *f)
 {
 	struct v4l2_loopback_device *dev;
 	const struct v4l2l_format *fmt;
@@ -886,20 +980,21 @@ static int vidioc_enum_fmt_out(struct fi
 
 		f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 		/* f->flags = ??; */
-		snprintf(f->description, sizeof(f->description), "%s", fmt->name);
+		snprintf(f->description, sizeof(f->description), "%s",
+			 fmt->name);
 
 		f->pixelformat = dev->pix_format.pixelformat;
 	} else {
 		/* fill in a dummy format */
-                /* coverity[unsigned_compare] */
+		/* coverity[unsigned_compare] */
 		if (f->index < 0 || f->index >= FORMATS)
 			return -EINVAL;
 
 		fmt = &formats[f->index];
 
 		f->pixelformat = fmt->fourcc;
-		snprintf(f->description, sizeof(f->description), "%s", fmt->name);
-
+		snprintf(f->description, sizeof(f->description), "%s",
+			 fmt->name);
 	}
 	f->flags = 0;
 
@@ -912,7 +1007,8 @@ static int vidioc_enum_fmt_out(struct fi
  * it should return ALL of available formats,
  * called on VIDIOC_G_FMT, with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_OUTPUT
  */
-static int vidioc_g_fmt_out(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_g_fmt_out(struct file *file, void *priv,
+			    struct v4l2_format *fmt)
 {
 	struct v4l2_loopback_device *dev;
 	MARK();
@@ -935,7 +1031,8 @@ static int vidioc_g_fmt_out(struct file
  * if format is negotiated do not change it
  * called on VIDIOC_TRY_FMT with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_OUTPUT
  */
-static int vidioc_try_fmt_out(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_try_fmt_out(struct file *file, void *priv,
+			      struct v4l2_format *fmt)
 {
 	struct v4l2_loopback_device *dev;
 	MARK();
@@ -952,10 +1049,10 @@ static int vidioc_try_fmt_out(struct fil
 		__u32 pixfmt = fmt->fmt.pix.pixelformat;
 		const struct v4l2l_format *format = format_by_fourcc(pixfmt);
 
-		if (w > max_width)
-			w = max_width;
-		if (h > max_height)
-			h = max_height;
+		if (w > dev->max_width)
+			w = dev->max_width;
+		if (h > dev->max_height)
+			h = dev->max_height;
 
 		dprintk("trying image %dx%d\n", w, h);
 
@@ -971,7 +1068,11 @@ static int vidioc_try_fmt_out(struct fil
 		pix_format_set_size(&fmt->fmt.pix, format, w, h);
 
 		fmt->fmt.pix.pixelformat = format->fourcc;
-		fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 0, 0)
+		if ((fmt->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) ||
+		    (fmt->fmt.pix.colorspace > V4L2_COLORSPACE_DCI_P3))
+			fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+#endif
 
 		if (V4L2_FIELD_ANY == fmt->fmt.pix.field)
 			fmt->fmt.pix.field = V4L2_FIELD_NONE;
@@ -987,7 +1088,8 @@ static int vidioc_try_fmt_out(struct fil
  * read/write IO
  * called on VIDIOC_S_FMT with v4l2_buf_type set to V4L2_BUF_TYPE_VIDEO_OUTPUT
  */
-static int vidioc_s_fmt_out(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_s_fmt_out(struct file *file, void *priv,
+			    struct v4l2_format *fmt)
 {
 	struct v4l2_loopback_device *dev;
 	char buf[5];
@@ -997,7 +1099,8 @@ static int vidioc_s_fmt_out(struct file
 	dev = v4l2loopback_getdevice(file);
 	ret = vidioc_try_fmt_out(file, priv, fmt);
 
-	dprintk("s_fmt_out(%d) %d...%d\n", ret, dev->ready_for_capture, dev->pix_format.sizeimage);
+	dprintk("s_fmt_out(%d) %d...%d\n", ret, dev->ready_for_capture,
+		dev->pix_format.sizeimage);
 
 	buf[4] = 0;
 	dprintk("outFOURCC=%s\n", fourcc2str(dev->pix_format.pixelformat, buf));
@@ -1013,7 +1116,7 @@ static int vidioc_s_fmt_out(struct file
 	return ret;
 }
 
-/*#define V4L2L_OVERLAY*/
+// #define V4L2L_OVERLAY
 #ifdef V4L2L_OVERLAY
 /* ------------------ OVERLAY ----------------------- */
 /* currently unsupported */
@@ -1022,27 +1125,29 @@ static int vidioc_s_fmt_out(struct file
  * once the gstreamer element is fixed, remove the overlay dummies
  */
 #warning OVERLAY dummies
-static int vidioc_g_fmt_overlay(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_g_fmt_overlay(struct file *file, void *priv,
+				struct v4l2_format *fmt)
 {
 	return 0;
 }
 
-static int vidioc_s_fmt_overlay(struct file *file, void *priv, struct v4l2_format *fmt)
+static int vidioc_s_fmt_overlay(struct file *file, void *priv,
+				struct v4l2_format *fmt)
 {
 	return 0;
 }
 #endif /* V4L2L_OVERLAY */
 
-
 /* ------------------ PARAMs ----------------------- */
 
 /* get some data flow parameters, only capability, fps and readbuffers has
  * effect on this driver
  * called on VIDIOC_G_PARM
  */
-static int vidioc_g_parm(struct file *file, void *priv, struct v4l2_streamparm *parm)
+static int vidioc_g_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
 {
-	/* do not care about type of opener, hope this enums would always be
+	/* do not care about type of opener, hope these enums would always be
 	 * compatible */
 	struct v4l2_loopback_device *dev;
 	MARK();
@@ -1056,25 +1161,28 @@ static int vidioc_g_parm(struct file *fi
  * effect on this driver
  * called on VIDIOC_S_PARM
  */
-static int vidioc_s_parm(struct file *file, void *priv, struct v4l2_streamparm *parm)
+static int vidioc_s_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
 {
 	struct v4l2_loopback_device *dev;
-	int err=0;
+	int err = 0;
 	MARK();
 
 	dev = v4l2loopback_getdevice(file);
 	dprintk("vidioc_s_parm called frate=%d/%d\n",
-			parm->parm.capture.timeperframe.numerator,
-			parm->parm.capture.timeperframe.denominator);
+		parm->parm.capture.timeperframe.numerator,
+		parm->parm.capture.timeperframe.denominator);
 
 	switch (parm->type) {
 	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
-                if ((err=set_timeperframe(dev, &parm->parm.capture.timeperframe)) < 0)
-		  return err;
+		if ((err = set_timeperframe(
+			     dev, &parm->parm.capture.timeperframe)) < 0)
+			return err;
 		break;
 	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
-                if ((err=set_timeperframe(dev, &parm->parm.capture.timeperframe)) < 0)
-		  return err;
+		if ((err = set_timeperframe(
+			     dev, &parm->parm.capture.timeperframe)) < 0)
+			return err;
 		break;
 	default:
 		return -1;
@@ -1089,7 +1197,7 @@ static int vidioc_s_parm(struct file *fi
  * added to support effecttv
  * called on VIDIOC_S_STD
  */
-static int vidioc_s_std(struct file *file, void *private_data, v4l2_std_id *_std)
+static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *_std)
 {
 	v4l2_std_id req_std = 0, supported_std = 0;
 	const v4l2_std_id all_std = V4L2_STD_ALL, no_std = 0;
@@ -1107,11 +1215,10 @@ static int vidioc_s_std(struct file *fil
 	return 0;
 }
 
-
 /* gets a fake video standard
  * called on VIDIOC_G_STD
  */
-static int vidioc_g_std(struct file *file, void *private_data, v4l2_std_id *norm)
+static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm)
 {
 	if (norm)
 		*norm = V4L2_STD_ALL;
@@ -1120,7 +1227,7 @@ static int vidioc_g_std(struct file *fil
 /* gets a fake video standard
  * called on VIDIOC_QUERYSTD
  */
-static int vidioc_querystd(struct file *file, void *private_data, v4l2_std_id *norm)
+static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm)
 {
 	if (norm)
 		*norm = V4L2_STD_ALL;
@@ -1131,7 +1238,8 @@ static int vidioc_querystd(struct file *
 /* get ctrls info
  * called on VIDIOC_QUERYCTRL
  */
-static int vidioc_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *q)
+static int vidioc_queryctrl(struct file *file, void *fh,
+			    struct v4l2_queryctrl *q)
 {
 	const struct v4l2_ctrl_config *cnf = 0;
 	switch (q->id) {
@@ -1154,7 +1262,7 @@ static int vidioc_queryctrl(struct file
 		BUG();
 
 	strcpy(q->name, cnf->name);
-	q->default_value=cnf->def;
+	q->default_value = cnf->def;
 	q->type = cnf->type;
 	q->minimum = cnf->min;
 	q->maximum = cnf->max;
@@ -1164,7 +1272,6 @@ static int vidioc_queryctrl(struct file
 	return 0;
 }
 
-
 static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *c)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
@@ -1189,17 +1296,16 @@ static int vidioc_g_ctrl(struct file *fi
 	return 0;
 }
 
-
-static int v4l2loopback_set_ctrl( struct v4l2_loopback_device *dev,
-				  u32 id,
-				  s64 val)
+static int v4l2loopback_set_ctrl(struct v4l2_loopback_device *dev, u32 id,
+				 s64 val)
 {
 	switch (id) {
 	case CID_KEEP_FORMAT:
 		if (val < 0 || val > 1)
 			return -EINVAL;
 		dev->keep_format = val;
-		try_free_buffers(dev);
+		try_free_buffers(
+			dev); /* will only free buffers if !keep_format */
 		break;
 	case CID_SUSTAIN_FRAMERATE:
 		if (val < 0 || val > 1)
@@ -1231,7 +1337,8 @@ static int v4l2loopback_set_ctrl( struct
 
 static int v4l2loopback_s_ctrl(struct v4l2_ctrl *ctrl)
 {
-	struct v4l2_loopback_device *dev = container_of(ctrl->handler, struct v4l2_loopback_device, ctrl_handler);
+	struct v4l2_loopback_device *dev = container_of(
+		ctrl->handler, struct v4l2_loopback_device, ctrl_handler);
 	return v4l2loopback_set_ctrl(dev, ctrl->id, ctrl->val);
 }
 static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *c)
@@ -1243,13 +1350,14 @@ static int vidioc_s_ctrl(struct file *fi
 /* returns set of device outputs, in our case there is only one
  * called on VIDIOC_ENUMOUTPUT
  */
-static int vidioc_enum_output(struct file *file, void *fh, struct v4l2_output *outp)
+static int vidioc_enum_output(struct file *file, void *fh,
+			      struct v4l2_output *outp)
 {
 	__u32 index = outp->index;
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
 	MARK();
 
-        if (!dev->announce_all_caps && !dev->ready_for_output)
+	if (!dev->announce_all_caps && !dev->ready_for_output)
 		return -ENOTTY;
 
 	if (0 != index)
@@ -1259,15 +1367,15 @@ static int vidioc_enum_output(struct fil
 	memset(outp, 0, sizeof(*outp));
 
 	outp->index = index;
-	strlcpy(outp->name, "loopback in", sizeof(outp->name));
+	strscpy(outp->name, "loopback in", sizeof(outp->name));
 	outp->type = V4L2_OUTPUT_TYPE_ANALOG;
 	outp->audioset = 0;
 	outp->modulator = 0;
 #ifdef V4L2LOOPBACK_WITH_STD
 	outp->std = V4L2_STD_ALL;
-# ifdef V4L2_OUT_CAP_STD
+#ifdef V4L2_OUT_CAP_STD
 	outp->capabilities |= V4L2_OUT_CAP_STD;
-# endif /*  V4L2_OUT_CAP_STD */
+#endif /*  V4L2_OUT_CAP_STD */
 #endif /* V4L2LOOPBACK_WITH_STD */
 
 	return 0;
@@ -1279,7 +1387,7 @@ static int vidioc_enum_output(struct fil
 static int vidioc_g_output(struct file *file, void *fh, unsigned int *i)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-        if (!dev->announce_all_caps && !dev->ready_for_output)
+	if (!dev->announce_all_caps && !dev->ready_for_output)
 		return -ENOTTY;
 	if (i)
 		*i = 0;
@@ -1292,7 +1400,7 @@ static int vidioc_g_output(struct file *
 static int vidioc_s_output(struct file *file, void *fh, unsigned int i)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-        if (!dev->announce_all_caps && !dev->ready_for_output)
+	if (!dev->announce_all_caps && !dev->ready_for_output)
 		return -ENOTTY;
 
 	if (i)
@@ -1301,12 +1409,12 @@ static int vidioc_s_output(struct file *
 	return 0;
 }
 
-
 /* returns set of device inputs, in our case there is only one,
  * but later I may add more
  * called on VIDIOC_ENUMINPUT
  */
-static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *inp)
+static int vidioc_enum_input(struct file *file, void *fh,
+			     struct v4l2_input *inp)
 {
 	__u32 index = inp->index;
 	MARK();
@@ -1318,7 +1426,7 @@ static int vidioc_enum_input(struct file
 	memset(inp, 0, sizeof(*inp));
 
 	inp->index = index;
-	strlcpy(inp->name, "loopback", sizeof(inp->name));
+	strscpy(inp->name, "loopback", sizeof(inp->name));
 	inp->type = V4L2_INPUT_TYPE_CAMERA;
 	inp->audioset = 0;
 	inp->tuner = 0;
@@ -1326,9 +1434,9 @@ static int vidioc_enum_input(struct file
 
 #ifdef V4L2LOOPBACK_WITH_STD
 	inp->std = V4L2_STD_ALL;
-# ifdef V4L2_IN_CAP_STD
+#ifdef V4L2_IN_CAP_STD
 	inp->capabilities |= V4L2_IN_CAP_STD;
-# endif
+#endif
 #endif /* V4L2LOOPBACK_WITH_STD */
 
 	return 0;
@@ -1340,7 +1448,7 @@ static int vidioc_enum_input(struct file
 static int vidioc_g_input(struct file *file, void *fh, unsigned int *i)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-        if (!dev->announce_all_caps && !dev->ready_for_capture)
+	if (!dev->announce_all_caps && !dev->ready_for_capture)
 		return -ENOTTY;
 	if (i)
 		*i = 0;
@@ -1353,7 +1461,7 @@ static int vidioc_g_input(struct file *f
 static int vidioc_s_input(struct file *file, void *fh, unsigned int i)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-        if (!dev->announce_all_caps && !dev->ready_for_capture)
+	if (!dev->announce_all_caps && !dev->ready_for_capture)
 		return -ENOTTY;
 	if (i == 0)
 		return 0;
@@ -1366,7 +1474,8 @@ static int vidioc_s_input(struct file *f
  * only mmap streaming supported
  * called on VIDIOC_REQBUFS
  */
-static int vidioc_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
+static int vidioc_reqbufs(struct file *file, void *fh,
+			  struct v4l2_requestbuffers *b)
 {
 	struct v4l2_loopback_device *dev;
 	struct v4l2_loopback_opener *opener;
@@ -1374,9 +1483,10 @@ static int vidioc_reqbufs(struct file *f
 	MARK();
 
 	dev = v4l2loopback_getdevice(file);
-	opener = file->private_data;
+	opener = fh_to_opener(fh);
 
-	dprintk("reqbufs: %d\t%d=%d\n", b->memory, b->count, dev->buffers_number);
+	dprintk("reqbufs: %d\t%d=%d\n", b->memory, b->count,
+		dev->buffers_number);
 	if (opener->timeout_image_io) {
 		if (b->memory != V4L2_MEMORY_MMAP)
 			return -EINVAL;
@@ -1387,7 +1497,7 @@ static int vidioc_reqbufs(struct file *f
 	init_buffers(dev);
 	switch (b->memory) {
 	case V4L2_MEMORY_MMAP:
-		/* do nothing here, buffers are always allocated*/
+		/* do nothing here, buffers are always allocated */
 		if (b->count < 1 || dev->buffers_number < 1)
 			return 0;
 
@@ -1399,7 +1509,8 @@ static int vidioc_reqbufs(struct file *f
 		 * at this point */
 		if (list_empty(&dev->outbufs_list)) {
 			for (i = 0; i < dev->used_buffers; ++i)
-				list_add_tail(&dev->buffers[i].list_head, &dev->outbufs_list);
+				list_add_tail(&dev->buffers[i].list_head,
+					      &dev->outbufs_list);
 		}
 
 		/* also, if dev->used_buffers is going to be decreased, we should remove
@@ -1407,7 +1518,8 @@ static int vidioc_reqbufs(struct file *f
 		if (b->count < dev->used_buffers) {
 			struct v4l2l_buffer *pos, *n;
 
-			list_for_each_entry_safe(pos, n, &dev->outbufs_list, list_head) {
+			list_for_each_entry_safe (pos, n, &dev->outbufs_list,
+						  list_head) {
 				if (pos->buffer.index >= b->count)
 					list_del(&pos->list_head);
 			}
@@ -1415,8 +1527,10 @@ static int vidioc_reqbufs(struct file *f
 			/* after we update dev->used_buffers, buffers in outbufs_list will
 			 * correspond to dev->write_position + [0;b->count-1] range */
 			i = dev->write_position;
-			list_for_each_entry(pos, &dev->outbufs_list, list_head) {
-				dev->bufpos2index[i % b->count] = pos->buffer.index;
+			list_for_each_entry (pos, &dev->outbufs_list,
+					     list_head) {
+				dev->bufpos2index[i % b->count] =
+					pos->buffer.index;
 				++i;
 			}
 		}
@@ -1447,10 +1561,10 @@ static int vidioc_querybuf(struct file *
 	type = b->type;
 	index = b->index;
 	dev = v4l2loopback_getdevice(file);
-	opener = file->private_data;
+	opener = fh_to_opener(fh);
 
 	if ((b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
-			(b->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) {
+	    (b->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) {
 		return -EINVAL;
 	}
 	if (b->index > max_buffers)
@@ -1463,23 +1577,26 @@ static int vidioc_querybuf(struct file *
 
 	b->type = type;
 	b->index = index;
-	dprintkrw("buffer type: %d (of %d with size=%ld)\n", b->memory, dev->buffers_number, dev->buffer_size);
+	dprintkrw("buffer type: %d (of %d with size=%ld)\n", b->memory,
+		  dev->buffers_number, dev->buffer_size);
 
 	/*  Hopefully fix 'DQBUF return bad index if queue bigger then 2 for capture'
-		https://github.com/umlaeute/v4l2loopback/issues/60 */
+            https://github.com/umlaeute/v4l2loopback/issues/60 */
 	b->flags &= ~V4L2_BUF_FLAG_DONE;
 	b->flags |= V4L2_BUF_FLAG_QUEUED;
 
 	return 0;
 }
 
-static void buffer_written(struct v4l2_loopback_device *dev, struct v4l2l_buffer *buf)
+static void buffer_written(struct v4l2_loopback_device *dev,
+			   struct v4l2l_buffer *buf)
 {
 	del_timer_sync(&dev->sustain_timer);
 	del_timer_sync(&dev->timeout_timer);
 	spin_lock_bh(&dev->lock);
 
-	dev->bufpos2index[dev->write_position % dev->used_buffers] = buf->buffer.index;
+	dev->bufpos2index[dev->write_position % dev->used_buffers] =
+		buf->buffer.index;
 	list_move_tail(&buf->list_head, &dev->outbufs_list);
 	++dev->write_position;
 	dev->reread_count = 0;
@@ -1491,7 +1608,7 @@ static void buffer_written(struct v4l2_l
 /* put buffer to queue
  * called on VIDIOC_QBUF
  */
-static int vidioc_qbuf(struct file *file, void *private_data, struct v4l2_buffer *buf)
+static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
 {
 	struct v4l2_loopback_device *dev;
 	struct v4l2_loopback_opener *opener;
@@ -1499,7 +1616,7 @@ static int vidioc_qbuf(struct file *file
 	int index;
 
 	dev = v4l2loopback_getdevice(file);
-	opener = file->private_data;
+	opener = fh_to_opener(fh);
 
 	if (buf->index > max_buffers)
 		return -EINVAL;
@@ -1515,7 +1632,8 @@ static int vidioc_qbuf(struct file *file
 		set_queued(b);
 		return 0;
 	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
-		dprintkrw("output QBUF pos: %d index: %d\n", dev->write_position, index);
+		dprintkrw("output QBUF pos: %d index: %d\n",
+			  dev->write_position, index);
 		if (buf->timestamp.tv_sec == 0 && buf->timestamp.tv_usec == 0)
 			v4l2l_get_timestamp(&b->buffer);
 		else
@@ -1525,9 +1643,9 @@ static int vidioc_qbuf(struct file *file
 		buffer_written(dev, b);
 
 		/*  Hopefully fix 'DQBUF return bad index if queue bigger then 2 for capture'
-			https://github.com/umlaeute/v4l2loopback/issues/60 */
+                    https://github.com/umlaeute/v4l2loopback/issues/60 */
 		buf->flags &= ~V4L2_BUF_FLAG_DONE;
-	        buf->flags |= V4L2_BUF_FLAG_QUEUED;
+		buf->flags |= V4L2_BUF_FLAG_QUEUED;
 
 		wake_up_all(&dev->read_event);
 		return 0;
@@ -1536,15 +1654,15 @@ static int vidioc_qbuf(struct file *file
 	}
 }
 
-static int can_read(struct v4l2_loopback_device *dev, struct v4l2_loopback_opener *opener)
+static int can_read(struct v4l2_loopback_device *dev,
+		    struct v4l2_loopback_opener *opener)
 {
 	int ret;
 
 	spin_lock_bh(&dev->lock);
 	check_timers(dev);
-	ret = dev->write_position > opener->read_position
-		|| dev->reread_count > opener->reread_count
-		|| dev->timeout_happened;
+	ret = dev->write_position > opener->read_position ||
+	      dev->reread_count > opener->reread_count || dev->timeout_happened;
 	spin_unlock_bh(&dev->lock);
 	return ret;
 }
@@ -1552,13 +1670,14 @@ static int can_read(struct v4l2_loopback
 static int get_capture_buffer(struct file *file)
 {
 	struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file);
-	struct v4l2_loopback_opener *opener = file->private_data;
+	struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data);
 	int pos, ret;
 	int timeout_happened;
 
 	if ((file->f_flags & O_NONBLOCK) &&
 	    (dev->write_position <= opener->read_position &&
-	     dev->reread_count <= opener->reread_count && !dev->timeout_happened))
+	     dev->reread_count <= opener->reread_count &&
+	     !dev->timeout_happened))
 		return -EAGAIN;
 	wait_event_interruptible(dev->read_event, can_read(dev, opener));
 
@@ -1567,10 +1686,12 @@ static int get_capture_buffer(struct fil
 		if (dev->reread_count > opener->reread_count + 2)
 			opener->reread_count = dev->reread_count - 1;
 		++opener->reread_count;
-		pos = (opener->read_position + dev->used_buffers - 1) % dev->used_buffers;
+		pos = (opener->read_position + dev->used_buffers - 1) %
+		      dev->used_buffers;
 	} else {
 		opener->reread_count = 0;
-		if (dev->write_position > opener->read_position + 2)
+		if (dev->write_position >
+		    opener->read_position + dev->used_buffers)
 			opener->read_position = dev->write_position - 1;
 		pos = opener->read_position % dev->used_buffers;
 		++opener->read_position;
@@ -1593,7 +1714,7 @@ static int get_capture_buffer(struct fil
 /* put buffer to dequeue
  * called on VIDIOC_DQBUF
  */
-static int vidioc_dqbuf(struct file *file, void *private_data, struct v4l2_buffer *buf)
+static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
 {
 	struct v4l2_loopback_device *dev;
 	struct v4l2_loopback_opener *opener;
@@ -1601,7 +1722,7 @@ static int vidioc_dqbuf(struct file *fil
 	struct v4l2l_buffer *b;
 
 	dev = v4l2loopback_getdevice(file);
-	opener = file->private_data;
+	opener = fh_to_opener(fh);
 	if (opener->timeout_image_io) {
 		*buf = dev->timeout_image_buffer.buffer;
 		return 0;
@@ -1612,8 +1733,10 @@ static int vidioc_dqbuf(struct file *fil
 		index = get_capture_buffer(file);
 		if (index < 0)
 			return index;
-		dprintkrw("capture DQBUF pos: %d index: %d\n", opener->read_position - 1, index);
-		if (!(dev->buffers[index].buffer.flags&V4L2_BUF_FLAG_MAPPED)) {
+		dprintkrw("capture DQBUF pos: %d index: %d\n",
+			  opener->read_position - 1, index);
+		if (!(dev->buffers[index].buffer.flags &
+		      V4L2_BUF_FLAG_MAPPED)) {
 			dprintk("trying to return not mapped buf[%d]\n", index);
 			return -EINVAL;
 		}
@@ -1621,7 +1744,8 @@ static int vidioc_dqbuf(struct file *fil
 		*buf = dev->buffers[index].buffer;
 		return 0;
 	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
-		b = list_entry(dev->outbufs_list.prev, struct v4l2l_buffer, list_head);
+		b = list_entry(dev->outbufs_list.prev, struct v4l2l_buffer,
+			       list_head);
 		list_move_tail(&b->list_head, &dev->outbufs_list);
 		dprintkrw("output DQBUF index: %d\n", b->buffer.index);
 		unset_flags(b);
@@ -1638,14 +1762,14 @@ static int vidioc_dqbuf(struct file *fil
 /* start streaming
  * called on VIDIOC_STREAMON
  */
-static int vidioc_streamon(struct file *file, void *private_data, enum v4l2_buf_type type)
+static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
 {
 	struct v4l2_loopback_device *dev;
 	struct v4l2_loopback_opener *opener;
 	MARK();
 
 	dev = v4l2loopback_getdevice(file);
-	opener = file->private_data;
+	opener = fh_to_opener(fh);
 
 	switch (type) {
 	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
@@ -1655,8 +1779,8 @@ static int vidioc_streamon(struct file *
 			int ret = allocate_buffers(dev);
 			if (ret < 0)
 				return ret;
-			dev->ready_for_capture = 1;
 		}
+		dev->ready_for_capture++;
 		return 0;
 	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
 		opener->type = READER;
@@ -1666,17 +1790,32 @@ static int vidioc_streamon(struct file *
 	default:
 		return -EINVAL;
 	}
-        return -EINVAL;
+	return -EINVAL;
 }
 
 /* stop streaming
  * called on VIDIOC_STREAMOFF
  */
-static int vidioc_streamoff(struct file *file, void *private_data, enum v4l2_buf_type type)
+static int vidioc_streamoff(struct file *file, void *fh,
+			    enum v4l2_buf_type type)
 {
+	struct v4l2_loopback_device *dev;
 	MARK();
 	dprintk("%d\n", type);
-	return 0;
+
+	dev = v4l2loopback_getdevice(file);
+
+	switch (type) {
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+		if (dev->ready_for_capture > 0)
+			dev->ready_for_capture--;
+		return 0;
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		return 0;
+	default:
+		return -EINVAL;
+	}
+	return -EINVAL;
 }
 
 #ifdef CONFIG_VIDEO_V4L1_COMPAT
@@ -1694,6 +1833,17 @@ static int vidiocgmbuf(struct file *file
 }
 #endif
 
+static int vidioc_subscribe_event(struct v4l2_fh *fh,
+				  const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subscribe_event(fh, sub);
+	}
+
+	return -EINVAL;
+}
+
 /* file operations */
 static void vm_open(struct vm_area_struct *vma)
 {
@@ -1728,11 +1878,11 @@ static int v4l2_loopback_mmap(struct fil
 	struct v4l2l_buffer *buffer = NULL;
 	MARK();
 
-	start = (unsigned long) vma->vm_start;
-	size = (unsigned long) (vma->vm_end - vma->vm_start);
+	start = (unsigned long)vma->vm_start;
+	size = (unsigned long)(vma->vm_end - vma->vm_start);
 
 	dev = v4l2loopback_getdevice(file);
-	opener = file->private_data;
+	opener = fh_to_opener(file->private_data);
 
 	if (size > dev->buffer_size) {
 		dprintk("userspace tries to mmap too much, fail\n");
@@ -1740,12 +1890,13 @@ static int v4l2_loopback_mmap(struct fil
 	}
 	if (opener->timeout_image_io) {
 		/* we are going to map the timeout_image_buffer */
-		if ((vma->vm_pgoff << PAGE_SHIFT) != dev->buffer_size * MAX_BUFFERS) {
+		if ((vma->vm_pgoff << PAGE_SHIFT) !=
+		    dev->buffer_size * MAX_BUFFERS) {
 			dprintk("invalid mmap offset for timeout_image_io mode\n");
 			return -EINVAL;
 		}
 	} else if ((vma->vm_pgoff << PAGE_SHIFT) >
-			dev->buffer_size * (dev->buffers_number - 1)) {
+		   dev->buffer_size * (dev->buffers_number - 1)) {
 		dprintk("userspace tries to mmap too far, fail\n");
 		return -EINVAL;
 	}
@@ -1762,14 +1913,16 @@ static int v4l2_loopback_mmap(struct fil
 		int i;
 		for (i = 0; i < dev->buffers_number; ++i) {
 			buffer = &dev->buffers[i];
-			if ((buffer->buffer.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
+			if ((buffer->buffer.m.offset >> PAGE_SHIFT) ==
+			    vma->vm_pgoff)
 				break;
 		}
 
 		if (NULL == buffer)
 			return -EINVAL;
 
-		addr = (unsigned long) dev->image + (vma->vm_pgoff << PAGE_SHIFT);
+		addr = (unsigned long)dev->image +
+		       (vma->vm_pgoff << PAGE_SHIFT);
 	}
 
 	while (size > 0) {
@@ -1795,30 +1948,48 @@ static int v4l2_loopback_mmap(struct fil
 	return 0;
 }
 
-static unsigned int v4l2_loopback_poll(struct file *file, struct poll_table_struct *pts)
+static unsigned int v4l2_loopback_poll(struct file *file,
+				       struct poll_table_struct *pts)
 {
 	struct v4l2_loopback_opener *opener;
 	struct v4l2_loopback_device *dev;
+	__poll_t req_events = poll_requested_events(pts);
 	int ret_mask = 0;
 	MARK();
 
-	opener = file->private_data;
-	dev    = v4l2loopback_getdevice(file);
+	opener = fh_to_opener(file->private_data);
+	dev = v4l2loopback_getdevice(file);
+
+	if (req_events & POLLPRI) {
+		if (!v4l2_event_pending(&opener->fh))
+			poll_wait(file, &opener->fh.wait, pts);
+		if (v4l2_event_pending(&opener->fh)) {
+			ret_mask |= POLLPRI;
+			if (!(req_events & DEFAULT_POLLMASK))
+				return ret_mask;
+		}
+	}
 
 	switch (opener->type) {
 	case WRITER:
-		ret_mask = POLLOUT | POLLWRNORM;
+		ret_mask |= POLLOUT | POLLWRNORM;
 		break;
 	case READER:
-		poll_wait(file, &dev->read_event, pts);
+		if (!can_read(dev, opener)) {
+			if (ret_mask)
+				return ret_mask;
+			poll_wait(file, &dev->read_event, pts);
+		}
 		if (can_read(dev, opener))
-			ret_mask =  POLLIN | POLLRDNORM;
+			ret_mask |= POLLIN | POLLRDNORM;
+		if (v4l2_event_pending(&opener->fh))
+			ret_mask |= POLLPRI;
 		break;
 	default:
-		ret_mask = -POLLERR;
+		break;
 	}
-	MARK();
 
+	MARK();
 	return ret_mask;
 }
 
@@ -1829,16 +2000,16 @@ static int v4l2_loopback_open(struct fil
 	struct v4l2_loopback_device *dev;
 	struct v4l2_loopback_opener *opener;
 	MARK();
-
 	dev = v4l2loopback_getdevice(file);
-
 	if (dev->open_count.counter >= dev->max_openers)
 		return -EBUSY;
 	/* kfree on close */
 	opener = kzalloc(sizeof(*opener), GFP_KERNEL);
 	if (opener == NULL)
 		return -ENOMEM;
-	file->private_data = opener;
+
+	v4l2_fh_init(&opener->fh, video_devdata(file));
+	file->private_data = &opener->fh;
 	atomic_inc(&dev->open_count);
 
 	opener->timeout_image_io = dev->timeout_image_io;
@@ -1852,6 +2023,8 @@ static int v4l2_loopback_open(struct fil
 			return r;
 		}
 	}
+
+	v4l2_fh_add(&opener->fh);
 	dprintk("opened dev:%p with image:%p\n", dev, dev ? dev->image : NULL);
 	MARK();
 	return 0;
@@ -1861,14 +2034,13 @@ static int v4l2_loopback_close(struct fi
 {
 	struct v4l2_loopback_opener *opener;
 	struct v4l2_loopback_device *dev;
-	int iswriter=0;
+	int iswriter = 0;
 	MARK();
 
+	opener = fh_to_opener(file->private_data);
+	dev = v4l2loopback_getdevice(file);
 
-	opener = file->private_data;
-	dev    = v4l2loopback_getdevice(file);
-
-	if(WRITER == opener->type)
+	if (WRITER == opener->type)
 		iswriter = 1;
 
 	atomic_dec(&dev->open_count);
@@ -1877,43 +2049,48 @@ static int v4l2_loopback_close(struct fi
 		del_timer_sync(&dev->timeout_timer);
 	}
 	try_free_buffers(dev);
+
+	v4l2_fh_del(&opener->fh);
+	v4l2_fh_exit(&opener->fh);
+
 	kfree(opener);
-	if(iswriter) {
+	if (iswriter) {
 		dev->ready_for_output = 1;
 	}
 	MARK();
 	return 0;
 }
 
-static ssize_t v4l2_loopback_read(struct file *file,
-		char __user *buf, size_t count, loff_t *ppos)
+static ssize_t v4l2_loopback_read(struct file *file, char __user *buf,
+				  size_t count, loff_t *ppos)
 {
 	int read_index;
 	struct v4l2_loopback_device *dev;
 	struct v4l2_buffer *b;
 	MARK();
 
-	dev    = v4l2loopback_getdevice(file);
+	dev = v4l2loopback_getdevice(file);
 
 	read_index = get_capture_buffer(file);
-        if (read_index < 0)
-          return read_index;
+	if (read_index < 0)
+		return read_index;
 	if (count > dev->buffer_size)
 		count = dev->buffer_size;
 	b = &dev->buffers[read_index].buffer;
 	if (count > b->bytesused)
 		count = b->bytesused;
-	if (copy_to_user((void *)buf, (void *)(dev->image + b->m.offset), count)) {
+	if (copy_to_user((void *)buf, (void *)(dev->image + b->m.offset),
+			 count)) {
 		printk(KERN_ERR
-			"v4l2-loopback: failed copy_to_user() in read buf\n");
+		       "v4l2-loopback: failed copy_to_user() in read buf\n");
 		return -EFAULT;
 	}
 	dprintkrw("leave v4l2_loopback_read()\n");
 	return count;
 }
 
-static ssize_t v4l2_loopback_write(struct file *file,
-		const char __user *buf, size_t count, loff_t *ppos)
+static ssize_t v4l2_loopback_write(struct file *file, const char __user *buf,
+				   size_t count, loff_t *ppos)
 {
 	struct v4l2_loopback_device *dev;
 	int write_index;
@@ -1922,7 +2099,7 @@ static ssize_t v4l2_loopback_write(struc
 
 	dev = v4l2loopback_getdevice(file);
 
-	/* there's at least one writer, so don'stop announcing output capabilities */
+	/* there's at least one writer, so don't stop announcing output capabilities */
 	dev->ready_for_output = 0;
 
 	if (!dev->ready_for_capture) {
@@ -1938,10 +2115,11 @@ static ssize_t v4l2_loopback_write(struc
 	write_index = dev->write_position % dev->used_buffers;
 	b = &dev->buffers[write_index].buffer;
 
-	if (copy_from_user((void *)(dev->image + b->m.offset), (void *)buf, count)) {
+	if (copy_from_user((void *)(dev->image + b->m.offset), (void *)buf,
+			   count)) {
 		printk(KERN_ERR
-			"v4l2-loopback: failed copy_from_user() in write buf, could not write %zu\n",
-			count);
+		       "v4l2-loopback: failed copy_from_user() in write buf, could not write %zu\n",
+		       count);
 		return -EFAULT;
 	}
 	v4l2l_get_timestamp(b);
@@ -2006,7 +2184,8 @@ static int allocate_buffers(struct v4l2_
 
 	dev->imagesize = dev->buffer_size * dev->buffers_number;
 
-	dprintk("allocating %ld = %ldx%d\n", dev->imagesize, dev->buffer_size, dev->buffers_number);
+	dprintk("allocating %ld = %ldx%d\n", dev->imagesize, dev->buffer_size,
+		dev->buffers_number);
 
 	dev->image = vmalloc(dev->imagesize);
 	if (dev->timeout_jiffies > 0)
@@ -2034,20 +2213,20 @@ static void init_buffers(struct v4l2_loo
 
 	for (i = 0; i < dev->buffers_number; ++i) {
 		struct v4l2_buffer *b = &dev->buffers[i].buffer;
-		b->index             = i;
-		b->bytesused         = bytesused;
-		b->length            = buffer_size;
-		b->field             = V4L2_FIELD_NONE;
-		b->flags             = 0;
+		b->index = i;
+		b->bytesused = bytesused;
+		b->length = buffer_size;
+		b->field = V4L2_FIELD_NONE;
+		b->flags = 0;
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 1)
-		b->input             = 0;
+		b->input = 0;
 #endif
-		b->m.offset          = i * buffer_size;
-		b->memory            = V4L2_MEMORY_MMAP;
-		b->sequence          = 0;
-		b->timestamp.tv_sec  = 0;
+		b->m.offset = i * buffer_size;
+		b->memory = V4L2_MEMORY_MMAP;
+		b->sequence = 0;
+		b->timestamp.tv_sec = 0;
 		b->timestamp.tv_usec = 0;
-		b->type              = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		b->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 
 		v4l2l_get_timestamp(b);
 	}
@@ -2074,35 +2253,34 @@ static int allocate_timeout_image(struct
 static void init_vdev(struct video_device *vdev, int nr)
 {
 	MARK();
-	vidioc_fill_name(vdev->name, sizeof(vdev->name), nr);
 
 #ifdef V4L2LOOPBACK_WITH_STD
-	vdev->tvnorms      = V4L2_STD_ALL;
+	vdev->tvnorms = V4L2_STD_ALL;
 #endif /* V4L2LOOPBACK_WITH_STD */
 
-	vdev->vfl_type     = VFL_TYPE_VIDEO;
-	vdev->fops         = &v4l2_loopback_fops;
-	vdev->ioctl_ops    = &v4l2_loopback_ioctl_ops;
-	vdev->release      = &video_device_release;
-	vdev->minor        = -1;
+	vdev->vfl_type = VFL_TYPE_VIDEO;
+	vdev->fops = &v4l2_loopback_fops;
+	vdev->ioctl_ops = &v4l2_loopback_ioctl_ops;
+	vdev->release = &video_device_release;
+	vdev->minor = -1;
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
-	vdev->device_caps  =
-		V4L2_CAP_DEVICE_CAPS |
+	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
+			    V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
 #ifdef V4L2_CAP_VIDEO_M2M
-		V4L2_CAP_VIDEO_M2M |
-#endif
-                V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
-                V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+	vdev->device_caps |= V4L2_CAP_VIDEO_M2M;
 #endif
+#endif /* >=linux-4.7.0 */
+
 	if (debug > 1)
-		#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 20, 0)
-			vdev->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG;
-		#else
-			vdev->dev_debug = V4L2_DEV_DEBUG_IOCTL | V4L2_DEV_DEBUG_IOCTL_ARG;
-		#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 20, 0)
+		vdev->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG;
+#else
+		vdev->dev_debug =
+			V4L2_DEV_DEBUG_IOCTL | V4L2_DEV_DEBUG_IOCTL_ARG;
+#endif
 
-	/* since kernel-3.7, there is a new field 'vfl_dir' that has to be
-	 * set to VFL_DIR_M2M for bidrectional devices */
+		/* since kernel-3.7, there is a new field 'vfl_dir' that has to be
+	 * set to VFL_DIR_M2M for bidirectional devices */
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
 	vdev->vfl_dir = VFL_DIR_M2M;
 #endif
@@ -2114,11 +2292,11 @@ static void init_vdev(struct video_devic
 static void init_capture_param(struct v4l2_captureparm *capture_param)
 {
 	MARK();
-	capture_param->capability               = 0;
-	capture_param->capturemode              = 0;
-	capture_param->extendedmode             = 0;
-	capture_param->readbuffers              = max_buffers;
-	capture_param->timeperframe.numerator   = 1;
+	capture_param->capability = 0;
+	capture_param->capturemode = 0;
+	capture_param->extendedmode = 0;
+	capture_param->readbuffers = max_buffers;
+	capture_param->timeperframe.numerator = 1;
 	capture_param->timeperframe.denominator = 30;
 }
 
@@ -2130,25 +2308,30 @@ static void check_timers(struct v4l2_loo
 	if (dev->timeout_jiffies > 0 && !timer_pending(&dev->timeout_timer))
 		mod_timer(&dev->timeout_timer, jiffies + dev->timeout_jiffies);
 	if (dev->sustain_framerate && !timer_pending(&dev->sustain_timer))
-		mod_timer(&dev->sustain_timer, jiffies + dev->frame_jiffies * 3 / 2);
+		mod_timer(&dev->sustain_timer,
+			  jiffies + dev->frame_jiffies * 3 / 2);
 }
 #ifdef HAVE_TIMER_SETUP
 static void sustain_timer_clb(struct timer_list *t)
 {
-	struct v4l2_loopback_device *dev = from_timer(dev,t,sustain_timer);
+	struct v4l2_loopback_device *dev = from_timer(dev, t, sustain_timer);
 #else
 static void sustain_timer_clb(unsigned long nr)
 {
-	struct v4l2_loopback_device *dev = devs[nr];
+	struct v4l2_loopback_device *dev =
+		idr_find(&v4l2loopback_index_idr, nr);
 #endif
 	spin_lock(&dev->lock);
 	if (dev->sustain_framerate) {
 		dev->reread_count++;
-		dprintkrw("reread: %d %d\n", dev->write_position, dev->reread_count);
+		dprintkrw("reread: %d %d\n", dev->write_position,
+			  dev->reread_count);
 		if (dev->reread_count == 1)
-			mod_timer(&dev->sustain_timer, jiffies + max(1UL, dev->frame_jiffies / 2));
+			mod_timer(&dev->sustain_timer,
+				  jiffies + max(1UL, dev->frame_jiffies / 2));
 		else
-			mod_timer(&dev->sustain_timer, jiffies + dev->frame_jiffies);
+			mod_timer(&dev->sustain_timer,
+				  jiffies + dev->frame_jiffies);
 		wake_up_all(&dev->read_event);
 	}
 	spin_unlock(&dev->lock);
@@ -2156,11 +2339,12 @@ static void sustain_timer_clb(unsigned l
 #ifdef HAVE_TIMER_SETUP
 static void timeout_timer_clb(struct timer_list *t)
 {
-	struct v4l2_loopback_device *dev = from_timer(dev,t,timeout_timer);
+	struct v4l2_loopback_device *dev = from_timer(dev, t, timeout_timer);
 #else
 static void timeout_timer_clb(unsigned long nr)
 {
-	struct v4l2_loopback_device *dev = devs[nr];
+	struct v4l2_loopback_device *dev =
+		idr_find(&v4l2loopback_index_idr, nr);
 #endif
 	spin_lock(&dev->lock);
 	if (dev->timeout_jiffies > 0) {
@@ -2172,53 +2356,138 @@ static void timeout_timer_clb(unsigned l
 }
 
 /* init loopback main structure */
-static int v4l2_loopback_init(struct v4l2_loopback_device *dev, int nr)
-{
-	int ret;
-	struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler;
+#define DEFAULT_FROM_CONF(confmember, default_condition, default_value)        \
+	((conf) ?                                                              \
+		 ((conf->confmember default_condition) ? (default_value) :     \
+							       (conf->confmember)) : \
+		       default_value)
+
+static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr)
+{
+	struct v4l2_loopback_device *dev;
+	struct v4l2_ctrl_handler *hdl;
+
+	int err = -ENOMEM;
+
+	int _max_width = DEFAULT_FROM_CONF(
+		max_width, <= V4L2LOOPBACK_SIZE_MIN_WIDTH, max_width);
+	int _max_height = DEFAULT_FROM_CONF(
+		max_height, <= V4L2LOOPBACK_SIZE_MIN_HEIGHT, max_height);
+	bool _announce_all_caps = (conf && conf->announce_all_caps >= 0) ?
+					  (conf->announce_all_caps) :
+						V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS;
+
+	int _max_buffers = DEFAULT_FROM_CONF(max_buffers, <= 0, max_buffers);
+	int _max_openers = DEFAULT_FROM_CONF(max_openers, <= 0, max_openers);
+
+	int nr = -1;
+	if (conf) {
+		if (conf->capture_nr >= 0 &&
+		    conf->output_nr == conf->capture_nr) {
+			nr = conf->capture_nr;
+		} else if (conf->capture_nr < 0 && conf->output_nr < 0) {
+			nr = -1;
+		} else if (conf->capture_nr < 0) {
+			nr = conf->output_nr;
+		} else if (conf->output_nr < 0) {
+			nr = conf->capture_nr;
+		} else {
+			printk(KERN_ERR
+			       "split OUTPUT and CAPTURE devices not yet supported.");
+			printk(KERN_INFO
+			       "both devices must have the same number (%d != %d).",
+			       conf->output_nr, conf->capture_nr);
+			return -EINVAL;
+		}
+	}
+
+	if (idr_find(&v4l2loopback_index_idr, nr))
+		return -EEXIST;
+
+	dprintk("creating v4l2loopback-device #%d\n", nr);
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	/* allocate id, if @id >= 0, we're requesting that specific id */
+	if (nr >= 0) {
+		err = idr_alloc(&v4l2loopback_index_idr, dev, nr, nr + 1,
+				GFP_KERNEL);
+		if (err == -ENOSPC)
+			err = -EEXIST;
+	} else {
+		err = idr_alloc(&v4l2loopback_index_idr, dev, 0, 0, GFP_KERNEL);
+	}
+	if (err < 0)
+		goto out_free_dev;
+	nr = err;
+	err = -ENOMEM;
+
+	if (conf && conf->card_label && *(conf->card_label)) {
+		snprintf(dev->card_label, sizeof(dev->card_label), "%s",
+			 conf->card_label);
+	} else {
+		snprintf(dev->card_label, sizeof(dev->card_label),
+			 "Dummy video device (0x%04X)", nr);
+	}
 	snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
-                        "v4l2loopback-%03d", nr);
-        ret = v4l2_device_register(NULL, &dev->v4l2_dev);
-        if (ret)
-                return ret;
+		 "v4l2loopback-%03d", nr);
 
+	err = v4l2_device_register(NULL, &dev->v4l2_dev);
+	if (err)
+		goto out_free_idr;
 	MARK();
+
 	dev->vdev = video_device_alloc();
 	if (dev->vdev == NULL) {
-		ret=-ENOMEM;
-		goto error;
+		err = -ENOMEM;
+		goto out_unregister;
 	}
-
-	video_set_drvdata(dev->vdev, kzalloc(sizeof(struct v4l2loopback_private), GFP_KERNEL));
+	video_set_drvdata(dev->vdev,
+			  kzalloc(sizeof(struct v4l2loopback_private),
+				  GFP_KERNEL));
 	if (video_get_drvdata(dev->vdev) == NULL) {
-		ret=-ENOMEM;
-		goto error;
+		err = -ENOMEM;
+		goto out_unregister;
 	}
-	((struct v4l2loopback_private *)video_get_drvdata(dev->vdev))->devicenr = nr;
+
+	MARK();
+	snprintf(dev->vdev->name, sizeof(dev->vdev->name), dev->card_label);
+
+	((struct v4l2loopback_private *)video_get_drvdata(dev->vdev))
+		->device_nr = nr;
 
 	init_vdev(dev->vdev, nr);
 	dev->vdev->v4l2_dev = &dev->v4l2_dev;
 	init_capture_param(&dev->capture_param);
-	set_timeperframe(dev, &dev->capture_param.timeperframe);
+	err = set_timeperframe(dev, &dev->capture_param.timeperframe);
+	if (err)
+		goto out_unregister;
 	dev->keep_format = 0;
 	dev->sustain_framerate = 0;
-	dev->buffers_number = max_buffers;
-	dev->used_buffers = max_buffers;
-	dev->max_openers = max_openers;
+
+	dev->announce_all_caps = _announce_all_caps;
+	dev->max_width = _max_width;
+	dev->max_height = _max_height;
+	dev->max_openers = _max_openers;
+	dev->buffers_number = dev->used_buffers = _max_buffers;
+
 	dev->write_position = 0;
+
+	MARK();
 	spin_lock_init(&dev->lock);
 	INIT_LIST_HEAD(&dev->outbufs_list);
 	if (list_empty(&dev->outbufs_list)) {
 		int i;
 
 		for (i = 0; i < dev->used_buffers; ++i)
-			list_add_tail(&dev->buffers[i].list_head, &dev->outbufs_list);
+			list_add_tail(&dev->buffers[i].list_head,
+				      &dev->outbufs_list);
 	}
 	memset(dev->bufpos2index, 0, sizeof(dev->bufpos2index));
 	atomic_set(&dev->open_count, 0);
 	dev->ready_for_capture = 0;
-	dev->ready_for_output  = 1;
-	dev->announce_all_caps = (!exclusive_caps[nr]);
+	dev->ready_for_output = 1;
 
 	dev->buffer_size = 0;
 	dev->image = NULL;
@@ -2230,154 +2499,306 @@ static int v4l2_loopback_init(struct v4l
 	setup_timer(&dev->sustain_timer, sustain_timer_clb, nr);
 	setup_timer(&dev->timeout_timer, timeout_timer_clb, nr);
 #endif
-        dev->reread_count = 0;
+	dev->reread_count = 0;
 	dev->timeout_jiffies = 0;
 	dev->timeout_image = NULL;
 	dev->timeout_happened = 0;
 
-	ret = v4l2_ctrl_handler_init(hdl, 1);
-	if(ret)
-		goto error;
+	hdl = &dev->ctrl_handler;
+	err = v4l2_ctrl_handler_init(hdl, 4);
+	if (err)
+		goto out_unregister;
 	v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_keepformat, NULL);
 	v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_sustainframerate, NULL);
 	v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_timeout, NULL);
 	v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_timeoutimageio, NULL);
-        if (hdl->error) {
-                ret = hdl->error;
-                goto error;
-        }
+	if (hdl->error) {
+		err = hdl->error;
+		goto out_free_handler;
+	}
 	dev->v4l2_dev.ctrl_handler = hdl;
 
+	err = v4l2_ctrl_handler_setup(hdl);
+	if (err)
+		goto out_free_handler;
+
 	/* FIXME set buffers to 0 */
 
 	/* Set initial format */
 	dev->pix_format.width = 0; /* V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; */
 	dev->pix_format.height = 0; /* V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; */
 	dev->pix_format.pixelformat = formats[0].fourcc;
-	dev->pix_format.colorspace = V4L2_COLORSPACE_SRGB; /* do we need to set this ? */
+	dev->pix_format.colorspace =
+		V4L2_COLORSPACE_SRGB; /* do we need to set this ? */
 	dev->pix_format.field = V4L2_FIELD_NONE;
 
 	dev->buffer_size = PAGE_ALIGN(dev->pix_format.sizeimage);
-	dprintk("buffer_size = %ld (=%d)\n", dev->buffer_size, dev->pix_format.sizeimage);
-	allocate_buffers(dev);
+	dprintk("buffer_size = %ld (=%d)\n", dev->buffer_size,
+		dev->pix_format.sizeimage);
+	err = allocate_buffers(dev);
+	if (err && dev->buffer_size)
+		goto out_free_handler;
 
 	init_waitqueue_head(&dev->read_event);
 
+	/* register the device -> it creates /dev/video* */
+	if (video_register_device(dev->vdev, VFL_TYPE_VIDEO, nr) < 0) {
+		printk(KERN_ERR
+		       "v4l2loopback: failed video_register_device()\n");
+		err = -EFAULT;
+		goto out_free_device;
+	}
+	v4l2loopback_create_sysfs(dev->vdev);
+
 	MARK();
+	if (ret_nr)
+		*ret_nr = dev->vdev->num;
 	return 0;
 
-error:
-        v4l2_ctrl_handler_free(&dev->ctrl_handler);
-        v4l2_device_unregister(&dev->v4l2_dev);
-	kfree(dev->vdev);
-        return ret;
+out_free_device:
+	video_device_release(dev->vdev);
+out_free_handler:
+	v4l2_ctrl_handler_free(&dev->ctrl_handler);
+out_unregister:
+	v4l2_device_unregister(&dev->v4l2_dev);
+out_free_idr:
+	idr_remove(&v4l2loopback_index_idr, nr);
+out_free_dev:
+	kfree(dev);
+	return err;
+}
 
-};
+static void v4l2_loopback_remove(struct v4l2_loopback_device *dev)
+{
+	free_buffers(dev);
+	v4l2loopback_remove_sysfs(dev->vdev);
+	kfree(video_get_drvdata(dev->vdev));
+	video_unregister_device(dev->vdev);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	v4l2_ctrl_handler_free(&dev->ctrl_handler);
+	kfree(dev);
+}
+
+static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd,
+				       unsigned long parm)
+{
+	struct v4l2_loopback_device *dev;
+	struct v4l2_loopback_config conf;
+	struct v4l2_loopback_config *confptr = &conf;
+	int device_nr;
+	int ret;
+
+	ret = mutex_lock_killable(&v4l2loopback_ctl_mutex);
+	if (ret)
+		return ret;
+
+	ret = -EINVAL;
+	switch (cmd) {
+	default:
+		ret = -ENOSYS;
+		break;
+		/* add a v4l2loopback device (pair), based on the user-provided specs */
+	case V4L2LOOPBACK_CTL_ADD:
+		if (parm) {
+			if ((ret = copy_from_user(&conf, (void *)parm,
+						  sizeof(conf))) < 0)
+				break;
+		} else
+			confptr = NULL;
+		ret = v4l2_loopback_add(confptr, &device_nr);
+		if (ret >= 0)
+			ret = device_nr;
+		break;
+		/* remove a v4l2loopback device (both capture and output) */
+	case V4L2LOOPBACK_CTL_REMOVE:
+		ret = v4l2loopback_lookup((int)parm, &dev);
+		if (ret >= 0 && dev) {
+			int nr = ret;
+			ret = -EBUSY;
+			if (dev->open_count.counter > 0)
+				break;
+			idr_remove(&v4l2loopback_index_idr, nr);
+			v4l2_loopback_remove(dev);
+			ret = 0;
+		};
+		break;
+		/* get information for a loopback device.
+                 * this is mostly about limits (which cannot be queried directly with  VIDIOC_G_FMT and friends
+                 */
+	case V4L2LOOPBACK_CTL_QUERY:
+		if (!parm)
+			break;
+		if ((ret = copy_from_user(&conf, (void *)parm, sizeof(conf))) <
+		    0)
+			break;
+		device_nr =
+			(conf.output_nr < 0) ? conf.capture_nr : conf.output_nr;
+		MARK();
+		/* get the device from either capture_nr or output_nr (whatever is valid) */
+		if ((ret = v4l2loopback_lookup(device_nr, &dev)) < 0)
+			break;
+		MARK();
+		/* if we got the device from output_nr and there is a valid capture_nr,
+                 * make sure that both refer to the same device (or bail out)
+                 */
+		if ((device_nr != conf.capture_nr) && (conf.capture_nr >= 0) &&
+		    (ret != v4l2loopback_lookup(conf.capture_nr, 0)))
+			break;
+		MARK();
+		/* if otoh, we got the device from capture_nr and there is a valid output_nr,
+                 * make sure that both refer to the same device (or bail out)
+                 */
+		if ((device_nr != conf.output_nr) && (conf.output_nr >= 0) &&
+		    (ret != v4l2loopback_lookup(conf.output_nr, 0)))
+			break;
+		MARK();
+
+		/* v4l2_loopback_config identified a single device, so fetch the data */
+		snprintf(conf.card_label, sizeof(conf.card_label), "%s",
+			 dev->card_label);
+		MARK();
+		conf.output_nr = conf.capture_nr = dev->vdev->num;
+		conf.max_width = dev->max_width;
+		conf.max_height = dev->max_height;
+		conf.announce_all_caps = dev->announce_all_caps;
+		conf.max_buffers = dev->buffers_number;
+		conf.max_openers = dev->max_openers;
+		conf.debug = debug;
+		MARK();
+		if (copy_to_user((void *)parm, &conf, sizeof(conf))) {
+			ret = -EFAULT;
+			break;
+		}
+		MARK();
+		ret = 0;
+		;
+		break;
+	}
+
+	MARK();
+	mutex_unlock(&v4l2loopback_ctl_mutex);
+	MARK();
+	return ret;
+}
 
 /* LINUX KERNEL */
+
+static const struct file_operations v4l2loopback_ctl_fops = {
+	// clang-format off
+	.owner		= THIS_MODULE,
+	.open		= nonseekable_open,
+	.unlocked_ioctl	= v4l2loopback_control_ioctl,
+	.compat_ioctl	= v4l2loopback_control_ioctl,
+	.llseek		= noop_llseek,
+	// clang-format on
+};
+
+static struct miscdevice v4l2loopback_misc = {
+	// clang-format off
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= "v4l2loopback",
+	.fops		= &v4l2loopback_ctl_fops,
+	// clang-format on
+};
+
 static const struct v4l2_file_operations v4l2_loopback_fops = {
-	.owner   = THIS_MODULE,
-	.open    = v4l2_loopback_open,
-	.release = v4l2_loopback_close,
-	.read    = v4l2_loopback_read,
-	.write   = v4l2_loopback_write,
-	.poll    = v4l2_loopback_poll,
-	.mmap    = v4l2_loopback_mmap,
-	.unlocked_ioctl   = video_ioctl2,
+	// clang-format off
+	.owner		= THIS_MODULE,
+	.open		= v4l2_loopback_open,
+	.release	= v4l2_loopback_close,
+	.read		= v4l2_loopback_read,
+	.write		= v4l2_loopback_write,
+	.poll		= v4l2_loopback_poll,
+	.mmap		= v4l2_loopback_mmap,
+	.unlocked_ioctl	= video_ioctl2,
+	// clang-format on
 };
 
 static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = {
-	.vidioc_querycap         = &vidioc_querycap,
+	// clang-format off
+	.vidioc_querycap		= &vidioc_querycap,
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
-	.vidioc_enum_framesizes  = &vidioc_enum_framesizes,
-	.vidioc_enum_frameintervals = &vidioc_enum_frameintervals,
+	.vidioc_enum_framesizes		= &vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals	= &vidioc_enum_frameintervals,
 #endif
 
 #ifndef HAVE__V4L2_CTRLS
-	.vidioc_queryctrl         = &vidioc_queryctrl,
-	.vidioc_g_ctrl            = &vidioc_g_ctrl,
-	.vidioc_s_ctrl            = &vidioc_s_ctrl,
+	.vidioc_queryctrl		= &vidioc_queryctrl,
+	.vidioc_g_ctrl			= &vidioc_g_ctrl,
+	.vidioc_s_ctrl			= &vidioc_s_ctrl,
 #endif /* HAVE__V4L2_CTRLS */
 
-	.vidioc_enum_output       = &vidioc_enum_output,
-	.vidioc_g_output          = &vidioc_g_output,
-	.vidioc_s_output          = &vidioc_s_output,
-
-	.vidioc_enum_input       = &vidioc_enum_input,
-	.vidioc_g_input          = &vidioc_g_input,
-	.vidioc_s_input          = &vidioc_s_input,
-
-	.vidioc_enum_fmt_vid_cap = &vidioc_enum_fmt_cap,
-	.vidioc_g_fmt_vid_cap    = &vidioc_g_fmt_cap,
-	.vidioc_s_fmt_vid_cap    = &vidioc_s_fmt_cap,
-	.vidioc_try_fmt_vid_cap  = &vidioc_try_fmt_cap,
-
-	.vidioc_enum_fmt_vid_out = &vidioc_enum_fmt_out,
-	.vidioc_s_fmt_vid_out    = &vidioc_s_fmt_out,
-	.vidioc_g_fmt_vid_out    = &vidioc_g_fmt_out,
-	.vidioc_try_fmt_vid_out  = &vidioc_try_fmt_out,
+	.vidioc_enum_output		= &vidioc_enum_output,
+	.vidioc_g_output		= &vidioc_g_output,
+	.vidioc_s_output		= &vidioc_s_output,
+
+	.vidioc_enum_input		= &vidioc_enum_input,
+	.vidioc_g_input			= &vidioc_g_input,
+	.vidioc_s_input			= &vidioc_s_input,
+
+	.vidioc_enum_fmt_vid_cap	= &vidioc_enum_fmt_cap,
+	.vidioc_g_fmt_vid_cap		= &vidioc_g_fmt_cap,
+	.vidioc_s_fmt_vid_cap		= &vidioc_s_fmt_cap,
+	.vidioc_try_fmt_vid_cap		= &vidioc_try_fmt_cap,
+
+	.vidioc_enum_fmt_vid_out	= &vidioc_enum_fmt_out,
+	.vidioc_s_fmt_vid_out		= &vidioc_s_fmt_out,
+	.vidioc_g_fmt_vid_out		= &vidioc_g_fmt_out,
+	.vidioc_try_fmt_vid_out		= &vidioc_try_fmt_out,
 
 #ifdef V4L2L_OVERLAY
-	.vidioc_s_fmt_vid_overlay = &vidioc_s_fmt_overlay,
-	.vidioc_g_fmt_vid_overlay = &vidioc_g_fmt_overlay,
+	.vidioc_s_fmt_vid_overlay	= &vidioc_s_fmt_overlay,
+	.vidioc_g_fmt_vid_overlay	= &vidioc_g_fmt_overlay,
 #endif
 
 #ifdef V4L2LOOPBACK_WITH_STD
-	.vidioc_s_std            = &vidioc_s_std,
-	.vidioc_g_std            = &vidioc_g_std,
-	.vidioc_querystd         = &vidioc_querystd,
+	.vidioc_s_std			= &vidioc_s_std,
+	.vidioc_g_std			= &vidioc_g_std,
+	.vidioc_querystd		= &vidioc_querystd,
 #endif /* V4L2LOOPBACK_WITH_STD */
 
-	.vidioc_g_parm           = &vidioc_g_parm,
-	.vidioc_s_parm           = &vidioc_s_parm,
+	.vidioc_g_parm			= &vidioc_g_parm,
+	.vidioc_s_parm			= &vidioc_s_parm,
 
-	.vidioc_reqbufs          = &vidioc_reqbufs,
-	.vidioc_querybuf         = &vidioc_querybuf,
-	.vidioc_qbuf             = &vidioc_qbuf,
-	.vidioc_dqbuf            = &vidioc_dqbuf,
+	.vidioc_reqbufs			= &vidioc_reqbufs,
+	.vidioc_querybuf		= &vidioc_querybuf,
+	.vidioc_qbuf			= &vidioc_qbuf,
+	.vidioc_dqbuf			= &vidioc_dqbuf,
 
-	.vidioc_streamon         = &vidioc_streamon,
-	.vidioc_streamoff        = &vidioc_streamoff,
+	.vidioc_streamon		= &vidioc_streamon,
+	.vidioc_streamoff		= &vidioc_streamoff,
 
 #ifdef CONFIG_VIDEO_V4L1_COMPAT
-	.vidiocgmbuf             = &vidiocgmbuf,
+	.vidiocgmbuf			= &vidiocgmbuf,
 #endif
+
+	.vidioc_subscribe_event		= &vidioc_subscribe_event,
+	.vidioc_unsubscribe_event	= &v4l2_event_unsubscribe,
+	// clang-format on
 };
 
-static void zero_devices(void)
+static int free_device_cb(int id, void *ptr, void *data)
 {
-	int i;
-
-	MARK();
-	for (i = 0; i < MAX_DEVICES; i++)
-		devs[i] = NULL;
+	struct v4l2_loopback_device *dev = ptr;
+	v4l2_loopback_remove(dev);
+	return 0;
 }
-
 static void free_devices(void)
 {
-	int i;
-
-	MARK();
-	for (i = 0; i < devices; i++) {
-		if (NULL != devs[i]) {
-			free_buffers(devs[i]);
-			v4l2loopback_remove_sysfs(devs[i]->vdev);
-			kfree(video_get_drvdata(devs[i]->vdev));
-			video_unregister_device(devs[i]->vdev);
-			v4l2_device_unregister(&devs[i]->v4l2_dev);
-			v4l2_ctrl_handler_free(&devs[i]->ctrl_handler);
-			kfree(devs[i]);
-			devs[i] = NULL;
-		}
-	}
+	idr_for_each(&v4l2loopback_index_idr, &free_device_cb, NULL);
+	idr_destroy(&v4l2loopback_index_idr);
 }
 
 static int __init v4l2loopback_init_module(void)
 {
+	int err;
 	int i;
 	MARK();
 
-	zero_devices();
+	err = misc_register(&v4l2loopback_misc);
+	if (err < 0)
+		return err;
+
 	if (devices < 0) {
 		devices = 1;
 
@@ -2392,89 +2813,107 @@ static int __init v4l2loopback_init_modu
 
 	if (devices > MAX_DEVICES) {
 		devices = MAX_DEVICES;
-		printk(KERN_INFO "v4l2loopback: number of devices is limited to: %d\n", MAX_DEVICES);
+		printk(KERN_INFO
+		       "v4l2loopback: number of initial devices is limited to: %d\n",
+		       MAX_DEVICES);
 	}
 
 	if (max_buffers > MAX_BUFFERS) {
 		max_buffers = MAX_BUFFERS;
-		printk(KERN_INFO "v4l2loopback: number of buffers is limited to: %d\n", MAX_BUFFERS);
+		printk(KERN_INFO
+		       "v4l2loopback: number of buffers is limited to: %d\n",
+		       MAX_BUFFERS);
 	}
 
 	if (max_openers < 0) {
-		printk(KERN_INFO "v4l2loopback: allowing %d openers rather than %d\n", 2, max_openers);
+		printk(KERN_INFO
+		       "v4l2loopback: allowing %d openers rather than %d\n",
+		       2, max_openers);
 		max_openers = 2;
 	}
 
 	if (max_width < 1) {
-		max_width = V4L2LOOPBACK_SIZE_MAX_WIDTH;
-		printk(KERN_INFO "v4l2loopback: using max_width %d\n", max_width);
+		max_width = V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH;
+		printk(KERN_INFO "v4l2loopback: using max_width %d\n",
+		       max_width);
 	}
 	if (max_height < 1) {
-		max_height = V4L2LOOPBACK_SIZE_MAX_HEIGHT;
-		printk(KERN_INFO "v4l2loopback: using max_height %d\n", max_height);
+		max_height = V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT;
+		printk(KERN_INFO "v4l2loopback: using max_height %d\n",
+		       max_height);
 	}
 
 	/* kfree on module release */
 	for (i = 0; i < devices; i++) {
-		int ret;
-		dprintk("creating v4l2loopback-device #%d\n", i);
-		devs[i] = kzalloc(sizeof(*devs[i]), GFP_KERNEL);
-		if (devs[i] == NULL) {
-			free_devices();
-			return -ENOMEM;
-		}
-		ret = v4l2_loopback_init(devs[i], i);
-		if (ret < 0) {
-			free_devices();
-			return ret;
-		}
-		/* register the device -> it creates /dev/video* */
-		if (video_register_device(devs[i]->vdev, VFL_TYPE_VIDEO, video_nr[i]) < 0) {
-			video_device_release(devs[i]->vdev);
-			printk(KERN_ERR "v4l2loopback: failed video_register_device()\n");
+		struct v4l2_loopback_config cfg = {
+			// clang-format off
+			.output_nr		= video_nr[i],
+			.capture_nr		= video_nr[i],
+			.max_width		= max_width,
+			.max_height		= max_height,
+			.announce_all_caps	= (!exclusive_caps[i]),
+			.max_buffers		= max_buffers,
+			.max_openers		= max_openers,
+			.debug			= debug,
+			// clang-format on
+		};
+		cfg.card_label[0] = 0;
+		if (card_label[i])
+			snprintf(cfg.card_label, sizeof(cfg.card_label), "%s",
+				 card_label[i]);
+		err = v4l2_loopback_add(&cfg, 0);
+		if (err) {
 			free_devices();
-			return -EFAULT;
+			goto error;
 		}
-		v4l2loopback_create_sysfs(devs[i]->vdev);
 	}
 
 	dprintk("module installed\n");
 
-	printk(KERN_INFO "v4l2loopback driver version %d.%d.%d loaded\n",
-			(V4L2LOOPBACK_VERSION_CODE >> 16) & 0xff,
-			(V4L2LOOPBACK_VERSION_CODE >>  8) & 0xff,
-			(V4L2LOOPBACK_VERSION_CODE) & 0xff);
+	printk(KERN_INFO "v4l2loopback driver version %d.%d.%d%s loaded\n",
+	       // clang-format off
+	       (V4L2LOOPBACK_VERSION_CODE >> 16) & 0xff,
+	       (V4L2LOOPBACK_VERSION_CODE >>  8) & 0xff,
+	       (V4L2LOOPBACK_VERSION_CODE      ) & 0xff,
+#ifdef SNAPSHOT_VERSION
+	       " (" STRINGIFY2(SNAPSHOT_VERSION) ")"
+#else
+	       ""
+#endif
+	       );
+	// clang-format on
 
 	return 0;
+error:
+	misc_deregister(&v4l2loopback_misc);
+	return err;
 }
 
+#ifdef MODULE
 static void v4l2loopback_cleanup_module(void)
 {
 	MARK();
 	/* unregister the device -> it deletes /dev/video* */
 	free_devices();
+	/* and get rid of /dev/v4l2loopback */
+	misc_deregister(&v4l2loopback_misc);
 	dprintk("module removed\n");
 }
-
-#ifdef MODULE
-int __init init_module(void)
-{
-        return v4l2loopback_init_module();
-}
-void __exit cleanup_module(void) {
-        return v4l2loopback_cleanup_module();
-}
-#else
-late_initcall(v4l2loopback_init_module);
 #endif
 
+MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR);
 
+module_init(v4l2loopback_init_module);
+module_exit(v4l2loopback_cleanup_module);
 
 /*
  * fake usage of unused functions
  */
 #ifdef HAVE__V4L2_CTRLS
-static int vidioc_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *q)  __attribute__ ((unused));
-static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *c)  __attribute__ ((unused));
-static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *c)  __attribute__ ((unused));
+static int vidioc_queryctrl(struct file *file, void *fh,
+			    struct v4l2_queryctrl *q) __attribute__((unused));
+static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *c)
+	__attribute__((unused));
+static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *c)
+	__attribute__((unused));
 #endif /* HAVE__V4L2_CTRLS */
diff -uprN v4l2loopback-0.12.5.orig/v4l2loopback_formats.h v4l2loopback-0.12.5/v4l2loopback_formats.h
--- v4l2loopback-0.12.5.orig/v4l2loopback_formats.h	2020-04-20 01:10:05.000000000 +0800
+++ v4l2loopback-0.12.5/v4l2loopback_formats.h	2024-04-24 13:36:28.414461286 +0800
@@ -1,422 +1,437 @@
-  /* here come the packed formats */
-{
-   .name     = "32 bpp RGB, le",
-     .fourcc   = V4L2_PIX_FMT_BGR32,
-     .depth    = 32,
-     .flags    = 0,
-     },
-{
-   .name     = "32 bpp RGB, be",
-     .fourcc   = V4L2_PIX_FMT_RGB32,
-     .depth    = 32,
-     .flags    = 0,
-     },
-{
-   .name     = "24 bpp RGB, le",
-     .fourcc   = V4L2_PIX_FMT_BGR24,
-     .depth    = 24,
-     .flags    = 0,
-     },
-{
-   .name     = "24 bpp RGB, be",
-     .fourcc   = V4L2_PIX_FMT_RGB24,
-     .depth    = 24,
-     .flags    = 0,
-     },
+static const struct v4l2l_format formats[] = {
+#ifndef V4L2_PIX_FMT_VP9
+#define V4L2_PIX_FMT_VP9 v4l2_fourcc('V', 'P', '9', '0')
+#endif
+#ifndef V4L2_PIX_FMT_HEVC
+#define V4L2_PIX_FMT_HEVC v4l2_fourcc('H', 'E', 'V', 'C')
+#endif
+
+	/* here come the packed formats */
+	{
+		.name = "32 bpp RGB, le",
+		.fourcc = V4L2_PIX_FMT_BGR32,
+		.depth = 32,
+		.flags = 0,
+	},
+	{
+		.name = "32 bpp RGB, be",
+		.fourcc = V4L2_PIX_FMT_RGB32,
+		.depth = 32,
+		.flags = 0,
+	},
+	{
+		.name = "24 bpp RGB, le",
+		.fourcc = V4L2_PIX_FMT_BGR24,
+		.depth = 24,
+		.flags = 0,
+	},
+	{
+		.name = "24 bpp RGB, be",
+		.fourcc = V4L2_PIX_FMT_RGB24,
+		.depth = 24,
+		.flags = 0,
+	},
+#ifdef V4L2_PIX_FMT_RGBA32
+	{
+		.name = "32 bpp RGBA",
+		.fourcc = V4L2_PIX_FMT_RGBA32,
+		.depth = 32,
+		.flags = 0,
+	},
+#endif
 #ifdef V4L2_PIX_FMT_RGB332
-{
-   .name     = "8 bpp RGB-3-3-2",
-     .fourcc   = V4L2_PIX_FMT_RGB332,
-     .depth    = 8,
-     .flags    = 0,
-     },
+	{
+		.name = "8 bpp RGB-3-3-2",
+		.fourcc = V4L2_PIX_FMT_RGB332,
+		.depth = 8,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_RGB332 */
 #ifdef V4L2_PIX_FMT_RGB444
-{
-   .name     = "16 bpp RGB (xxxxrrrr ggggbbbb)",
-     .fourcc   = V4L2_PIX_FMT_RGB444,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp RGB (xxxxrrrr ggggbbbb)",
+		.fourcc = V4L2_PIX_FMT_RGB444,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_RGB444 */
 #ifdef V4L2_PIX_FMT_RGB555
-{
-   .name     = "16 bpp RGB-5-5-5",
-     .fourcc   = V4L2_PIX_FMT_RGB555,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp RGB-5-5-5",
+		.fourcc = V4L2_PIX_FMT_RGB555,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_RGB555 */
 #ifdef V4L2_PIX_FMT_RGB565
-{
-   .name     = "16 bpp  RGB-5-6-5",
-     .fourcc   = V4L2_PIX_FMT_RGB565,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp RGB-5-6-5",
+		.fourcc = V4L2_PIX_FMT_RGB565,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_RGB565 */
 #ifdef V4L2_PIX_FMT_RGB555X
-{
-   .name     = "16 bpp RGB-5-5-5 BE",
-     .fourcc   = V4L2_PIX_FMT_RGB555X,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp RGB-5-5-5 BE",
+		.fourcc = V4L2_PIX_FMT_RGB555X,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_RGB555X */
 #ifdef V4L2_PIX_FMT_RGB565X
-{
-   .name     = "16 bpp RGB-5-6-5 BE",
-     .fourcc   = V4L2_PIX_FMT_RGB565X,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp RGB-5-6-5 BE",
+		.fourcc = V4L2_PIX_FMT_RGB565X,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_RGB565X */
 #ifdef V4L2_PIX_FMT_BGR666
-{
-   .name     = "18 bpp BGR-6-6-6",
-     .fourcc   = V4L2_PIX_FMT_BGR666,
-     .depth    = 18,
-     .flags    = 0,
-     },
+	{
+		.name = "18 bpp BGR-6-6-6",
+		.fourcc = V4L2_PIX_FMT_BGR666,
+		.depth = 18,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_BGR666 */
-{
-   .name     = "4:2:2, packed, YUYV",
-     .fourcc   = V4L2_PIX_FMT_YUYV,
-     .depth    = 16,
-     .flags    = 0,
-     },
-{
-   .name     = "4:2:2, packed, UYVY",
-     .fourcc   = V4L2_PIX_FMT_UYVY,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "4:2:2, packed, YUYV",
+		.fourcc = V4L2_PIX_FMT_YUYV,
+		.depth = 16,
+		.flags = 0,
+	},
+	{
+		.name = "4:2:2, packed, UYVY",
+		.fourcc = V4L2_PIX_FMT_UYVY,
+		.depth = 16,
+		.flags = 0,
+	},
 #ifdef V4L2_PIX_FMT_YVYU
-{
-   .name     = "4:2:2, packed YVYU",
-     .fourcc   = V4L2_PIX_FMT_YVYU,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "4:2:2, packed YVYU",
+		.fourcc = V4L2_PIX_FMT_YVYU,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif
 #ifdef V4L2_PIX_FMT_VYUY
-{
-   .name     = "4:2:2, packed VYUY",
-     .fourcc   = V4L2_PIX_FMT_VYUY,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "4:2:2, packed VYUY",
+		.fourcc = V4L2_PIX_FMT_VYUY,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif
-{
-   .name     = "4:2:2, packed YYUV",
-     .fourcc   = V4L2_PIX_FMT_YYUV,
-     .depth    = 16,
-     .flags    = 0,
-     },
-{
-   .name     = "YUV-8-8-8-8",
-     .fourcc   = V4L2_PIX_FMT_YUV32,
-     .depth    = 32,
-     .flags    = 0,
-     },
-{
-   .name     = "8 bpp, Greyscale",
-     .fourcc   = V4L2_PIX_FMT_GREY,
-     .depth    = 8,
-     .flags    = 0,
-     },
+	{
+		.name = "4:2:2, packed YYUV",
+		.fourcc = V4L2_PIX_FMT_YYUV,
+		.depth = 16,
+		.flags = 0,
+	},
+	{
+		.name = "YUV-8-8-8-8",
+		.fourcc = V4L2_PIX_FMT_YUV32,
+		.depth = 32,
+		.flags = 0,
+	},
+	{
+		.name = "8 bpp, Greyscale",
+		.fourcc = V4L2_PIX_FMT_GREY,
+		.depth = 8,
+		.flags = 0,
+	},
 #ifdef V4L2_PIX_FMT_Y4
-{
-   .name     = "4 bpp Greyscale",
-     .fourcc   = V4L2_PIX_FMT_Y4,
-     .depth    = 4,
-     .flags    = 0,
-     },
+	{
+		.name = "4 bpp Greyscale",
+		.fourcc = V4L2_PIX_FMT_Y4,
+		.depth = 4,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_Y4 */
 #ifdef V4L2_PIX_FMT_Y6
-{
-   .name     = "6 bpp Greyscale",
-     .fourcc   = V4L2_PIX_FMT_Y6,
-     .depth    = 6,
-     .flags    = 0,
-     },
+	{
+		.name = "6 bpp Greyscale",
+		.fourcc = V4L2_PIX_FMT_Y6,
+		.depth = 6,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_Y6 */
 #ifdef V4L2_PIX_FMT_Y10
-{
-   .name     = "10 bpp Greyscale",
-     .fourcc   = V4L2_PIX_FMT_Y10,
-     .depth    = 10,
-     .flags    = 0,
-     },
+	{
+		.name = "10 bpp Greyscale",
+		.fourcc = V4L2_PIX_FMT_Y10,
+		.depth = 10,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_Y10 */
 #ifdef V4L2_PIX_FMT_Y12
-{
-   .name     = "12 bpp Greyscale",
-     .fourcc   = V4L2_PIX_FMT_Y12,
-     .depth    = 12,
-     .flags    = 0,
-     },
+	{
+		.name = "12 bpp Greyscale",
+		.fourcc = V4L2_PIX_FMT_Y12,
+		.depth = 12,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_Y12 */
-{
-   .name     = "16 bpp, Greyscale",
-     .fourcc   = V4L2_PIX_FMT_Y16,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp, Greyscale",
+		.fourcc = V4L2_PIX_FMT_Y16,
+		.depth = 16,
+		.flags = 0,
+	},
 #ifdef V4L2_PIX_FMT_YUV444
-{
-   .name     = "16 bpp xxxxyyyy uuuuvvvv",
-     .fourcc   = V4L2_PIX_FMT_YUV444,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp xxxxyyyy uuuuvvvv",
+		.fourcc = V4L2_PIX_FMT_YUV444,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_YUV444 */
 #ifdef V4L2_PIX_FMT_YUV555
-{
-   .name     = "16 bpp YUV-5-5-5",
-     .fourcc   = V4L2_PIX_FMT_YUV555,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp YUV-5-5-5",
+		.fourcc = V4L2_PIX_FMT_YUV555,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_YUV555 */
 #ifdef V4L2_PIX_FMT_YUV565
-{
-   .name     = "16 bpp YUV-5-6-5",
-     .fourcc   = V4L2_PIX_FMT_YUV565,
-     .depth    = 16,
-     .flags    = 0,
-     },
+	{
+		.name = "16 bpp YUV-5-6-5",
+		.fourcc = V4L2_PIX_FMT_YUV565,
+		.depth = 16,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_YUV565 */
 
 /* bayer formats */
 #ifdef V4L2_PIX_FMT_SRGGB8
-{
-  .name     = "Bayer RGGB 8bit",
-    .fourcc   = V4L2_PIX_FMT_SRGGB8,
-    .depth    = 8,
-    .flags    = 0,
-    },
+	{
+		.name = "Bayer RGGB 8bit",
+		.fourcc = V4L2_PIX_FMT_SRGGB8,
+		.depth = 8,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_SRGGB8 */
 #ifdef V4L2_PIX_FMT_SGRBG8
-{
-  .name     = "Bayer GRBG 8bit",
-    .fourcc   = V4L2_PIX_FMT_SGRBG8,
-    .depth    = 8,
-    .flags    = 0,
-    },
+	{
+		.name = "Bayer GRBG 8bit",
+		.fourcc = V4L2_PIX_FMT_SGRBG8,
+		.depth = 8,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_SGRBG8 */
 #ifdef V4L2_PIX_FMT_SGBRG8
-{
-  .name     = "Bayer GBRG 8bit",
-    .fourcc   = V4L2_PIX_FMT_SGBRG8,
-    .depth    = 8,
-    .flags    = 0,
-    },
+	{
+		.name = "Bayer GBRG 8bit",
+		.fourcc = V4L2_PIX_FMT_SGBRG8,
+		.depth = 8,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_SGBRG8 */
 #ifdef V4L2_PIX_FMT_SBGGR8
-{
-  .name     = "Bayer BA81 8bit",
-    .fourcc   = V4L2_PIX_FMT_SBGGR8,
-    .depth    = 8,
-    .flags    = 0,
-    },
+	{
+		.name = "Bayer BA81 8bit",
+		.fourcc = V4L2_PIX_FMT_SBGGR8,
+		.depth = 8,
+		.flags = 0,
+	},
 #endif /* V4L2_PIX_FMT_SBGGR8 */
 
-
-
- /* here come the planar formats */
-{
-   .name     = "4:1:0, planar, Y-Cr-Cb",
-     .fourcc   = V4L2_PIX_FMT_YVU410,
-     .depth    = 9,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
-{
-   .name     = "4:2:0, planar, Y-Cr-Cb",
-     .fourcc   = V4L2_PIX_FMT_YVU420,
-     .depth    = 12,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
-{
-   .name     = "4:1:0, planar, Y-Cb-Cr",
-     .fourcc   = V4L2_PIX_FMT_YUV410,
-     .depth    = 9,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
-{
-   .name     = "4:2:0, planar, Y-Cb-Cr",
-     .fourcc   = V4L2_PIX_FMT_YUV420,
-     .depth    = 12,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
+	/* here come the planar formats */
+	{
+		.name = "4:1:0, planar, Y-Cr-Cb",
+		.fourcc = V4L2_PIX_FMT_YVU410,
+		.depth = 9,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
+	{
+		.name = "4:2:0, planar, Y-Cr-Cb",
+		.fourcc = V4L2_PIX_FMT_YVU420,
+		.depth = 12,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
+	{
+		.name = "4:1:0, planar, Y-Cb-Cr",
+		.fourcc = V4L2_PIX_FMT_YUV410,
+		.depth = 9,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
+	{
+		.name = "4:2:0, planar, Y-Cb-Cr",
+		.fourcc = V4L2_PIX_FMT_YUV420,
+		.depth = 12,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
 #ifdef V4L2_PIX_FMT_YUV422P
-{
-   .name     = "16 bpp YVU422 planar",
-     .fourcc   = V4L2_PIX_FMT_YUV422P,
-     .depth    = 16,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
+	{
+		.name = "16 bpp YVU422 planar",
+		.fourcc = V4L2_PIX_FMT_YUV422P,
+		.depth = 16,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
 #endif /* V4L2_PIX_FMT_YUV422P */
 #ifdef V4L2_PIX_FMT_YUV411P
-{
-   .name     = "16 bpp YVU411 planar",
-     .fourcc   = V4L2_PIX_FMT_YUV411P,
-     .depth    = 16,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
+	{
+		.name = "16 bpp YVU411 planar",
+		.fourcc = V4L2_PIX_FMT_YUV411P,
+		.depth = 16,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
 #endif /* V4L2_PIX_FMT_YUV411P */
 #ifdef V4L2_PIX_FMT_Y41P
-{
-   .name     = "12 bpp YUV 4:1:1",
-     .fourcc   = V4L2_PIX_FMT_Y41P,
-     .depth    = 12,
-     .flags    = FORMAT_FLAGS_PLANAR,
-     },
+	{
+		.name = "12 bpp YUV 4:1:1",
+		.fourcc = V4L2_PIX_FMT_Y41P,
+		.depth = 12,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
 #endif /* V4L2_PIX_FMT_Y41P */
 #ifdef V4L2_PIX_FMT_NV12
-{
-	.name     = "12 bpp Y/CbCr 4:2:0 ",
-		.fourcc   = V4L2_PIX_FMT_NV12,
-		.depth    = 12,
-		.flags    = FORMAT_FLAGS_PLANAR,
-     },
+	{
+		.name = "12 bpp Y/CbCr 4:2:0 ",
+		.fourcc = V4L2_PIX_FMT_NV12,
+		.depth = 12,
+		.flags = FORMAT_FLAGS_PLANAR,
+	},
 #endif /* V4L2_PIX_FMT_NV12 */
 
- /* here come the compressed formats */
+/* here come the compressed formats */
 
 #ifdef V4L2_PIX_FMT_MJPEG
-{
-   .name     = "Motion-JPEG",
-     .fourcc   = V4L2_PIX_FMT_MJPEG,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "Motion-JPEG",
+		.fourcc = V4L2_PIX_FMT_MJPEG,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_MJPEG */
 #ifdef V4L2_PIX_FMT_JPEG
-{
-   .name     = "JFIF JPEG",
-     .fourcc   = V4L2_PIX_FMT_JPEG,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "JFIF JPEG",
+		.fourcc = V4L2_PIX_FMT_JPEG,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_JPEG */
 #ifdef V4L2_PIX_FMT_DV
-{
-   .name     = "DV1394",
-     .fourcc   = V4L2_PIX_FMT_DV,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "DV1394",
+		.fourcc = V4L2_PIX_FMT_DV,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_DV */
 #ifdef V4L2_PIX_FMT_MPEG
-{
-   .name     = "MPEG-1/2/4 Multiplexed",
-     .fourcc   = V4L2_PIX_FMT_MPEG,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "MPEG-1/2/4 Multiplexed",
+		.fourcc = V4L2_PIX_FMT_MPEG,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_MPEG */
 #ifdef V4L2_PIX_FMT_H264
-{
-   .name     = "H264 with start codes",
-     .fourcc   = V4L2_PIX_FMT_H264,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "H264 with start codes",
+		.fourcc = V4L2_PIX_FMT_H264,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_H264 */
 #ifdef V4L2_PIX_FMT_H264_NO_SC
-{
-   .name     = "H264 without start codes",
-     .fourcc   = V4L2_PIX_FMT_H264_NO_SC,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "H264 without start codes",
+		.fourcc = V4L2_PIX_FMT_H264_NO_SC,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_H264_NO_SC */
 #ifdef V4L2_PIX_FMT_H264_MVC
-{
-   .name     = "H264 MVC",
-     .fourcc   = V4L2_PIX_FMT_H264_MVC,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "H264 MVC",
+		.fourcc = V4L2_PIX_FMT_H264_MVC,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_H264_MVC */
 #ifdef V4L2_PIX_FMT_H263
-{
-   .name     = "H263",
-     .fourcc   = V4L2_PIX_FMT_H263,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "H263",
+		.fourcc = V4L2_PIX_FMT_H263,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_H263 */
 #ifdef V4L2_PIX_FMT_MPEG1
-{
-   .name     = "MPEG-1 ES",
-     .fourcc   = V4L2_PIX_FMT_MPEG1,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "MPEG-1 ES",
+		.fourcc = V4L2_PIX_FMT_MPEG1,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_MPEG1 */
 #ifdef V4L2_PIX_FMT_MPEG2
-{
-   .name     = "MPEG-2 ES",
-     .fourcc   = V4L2_PIX_FMT_MPEG2,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "MPEG-2 ES",
+		.fourcc = V4L2_PIX_FMT_MPEG2,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_MPEG2 */
 #ifdef V4L2_PIX_FMT_MPEG4
-{
-   .name     = "MPEG-4 part 2 ES",
-     .fourcc   = V4L2_PIX_FMT_MPEG4,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "MPEG-4 part 2 ES",
+		.fourcc = V4L2_PIX_FMT_MPEG4,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_MPEG4 */
 #ifdef V4L2_PIX_FMT_XVID
-{
-   .name     = "Xvid",
-     .fourcc   = V4L2_PIX_FMT_XVID,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "Xvid",
+		.fourcc = V4L2_PIX_FMT_XVID,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_XVID */
 #ifdef V4L2_PIX_FMT_VC1_ANNEX_G
-{
-   .name     = "SMPTE 421M Annex G compliant stream",
-     .fourcc   = V4L2_PIX_FMT_VC1_ANNEX_G,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "SMPTE 421M Annex G compliant stream",
+		.fourcc = V4L2_PIX_FMT_VC1_ANNEX_G,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_VC1_ANNEX_G */
 #ifdef V4L2_PIX_FMT_VC1_ANNEX_L
-{
-   .name     = "SMPTE 421M Annex L compliant stream",
-     .fourcc   = V4L2_PIX_FMT_VC1_ANNEX_L,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "SMPTE 421M Annex L compliant stream",
+		.fourcc = V4L2_PIX_FMT_VC1_ANNEX_L,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_VC1_ANNEX_L */
 #ifdef V4L2_PIX_FMT_VP8
-{
-   .name     = "VP8",
-     .fourcc   = V4L2_PIX_FMT_VP8,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "VP8",
+		.fourcc = V4L2_PIX_FMT_VP8,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_VP8 */
 #ifdef V4L2_PIX_FMT_VP9
-{
-   .name     = "VP9",
-     .fourcc   = V4L2_PIX_FMT_VP9,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "VP9",
+		.fourcc = V4L2_PIX_FMT_VP9,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_VP9 */
 #ifdef V4L2_PIX_FMT_HEVC
-{
-   .name     = "HEVC",
-     .fourcc   = V4L2_PIX_FMT_HEVC,
-     .depth    = 32,
-     .flags    = FORMAT_FLAGS_COMPRESSED,
-     },
+	{
+		.name = "HEVC",
+		.fourcc = V4L2_PIX_FMT_HEVC,
+		.depth = 32,
+		.flags = FORMAT_FLAGS_COMPRESSED,
+	},
 #endif /* V4L2_PIX_FMT_HEVC */
+};
diff -uprN v4l2loopback-0.12.5.orig/v4l2loopback.h v4l2loopback-0.12.5/v4l2loopback.h
--- v4l2loopback-0.12.5.orig/v4l2loopback.h	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/v4l2loopback.h	2024-04-24 13:36:28.418461359 +0800
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * v4l2loopback.h
+ *
+ * Written by IOhannes m zmölnig, 7/1/20.
+ *
+ * Copyright 2020 by IOhannes m zmölnig.  Redistribution of this file is
+ * permitted under the GNU General Public License.
+ */
+#ifndef _V4L2LOOPBACK_H
+#define _V4L2LOOPBACK_H
+
+#define V4L2LOOPBACK_VERSION_MAJOR 0
+#define V4L2LOOPBACK_VERSION_MINOR 12
+#define V4L2LOOPBACK_VERSION_BUGFIX 5
+
+/* /dev/v4l2loopback interface */
+
+struct v4l2_loopback_config {
+	/**
+         * the device-number (/dev/video<nr>)
+         * V4L2LOOPBACK_CTL_ADD:
+         * setting this to a value<0, will allocate an available one
+         * if nr>=0 and the device already exists, the ioctl will EEXIST
+         * if output_nr and capture_nr are the same, only a single device will be created
+         *
+         * V4L2LOOPBACK_CTL_QUERY:
+         * either both output_nr and capture_nr must refer to the same loopback,
+         * or one (and only one) of them must be -1
+         *
+         */
+	int output_nr;
+	int capture_nr;
+
+	/**
+         * a nice name for your device
+         * if (*card_label)==0, an automatic name is assigned
+         */
+	char card_label[32];
+
+	/**
+         * maximum allowed frame size
+         * if too low, default values are used
+         */
+	int max_width;
+	int max_height;
+
+	/**
+         * whether to announce OUTPUT/CAPTURE capabilities exclusively
+         * for this device or not
+         * (!exclusive_caps)
+         * FIXXME: this ought to be removed (if superseded by output_nr vs capture_nr)
+         */
+	int announce_all_caps;
+
+	/**
+         * number of buffers to allocate for the queue
+         * if set to <=0, default values are used
+         */
+	int max_buffers;
+
+	/**
+         * how many consumers are allowed to open this device concurrently
+         * if set to <=0, default values are used
+         */
+	int max_openers;
+
+	/**
+         * set the debugging level for this device
+         */
+	int debug;
+};
+
+/* a pointer to a (struct v4l2_loopback_config) that has all values you wish to impose on the
+ * to-be-created device set.
+ * if the ptr is NULL, a new device is created with default values at the driver's discretion.
+ *
+ * returns the device_nr of the OUTPUT device (which can be used with V4L2LOOPBACK_CTL_QUERY,
+ * to get more information on the device)
+ */
+#define V4L2LOOPBACK_CTL_ADD 0x4C80
+
+/* a pointer to a (struct v4l2_loopback_config) that has output_nr and/or capture_nr set
+ * (the two values must either refer to video-devices associated with the same loopback device
+ *  or exactly one of them must be <0
+ */
+#define V4L2LOOPBACK_CTL_QUERY 0x4C82
+
+/* the device-number (either CAPTURE or OUTPUT) associated with the loopback-device */
+#define V4L2LOOPBACK_CTL_REMOVE 0x4C81
+
+#endif /* _V4L2LOOPBACK_H */
diff -uprN v4l2loopback-0.12.5.orig/vagrant/README.md v4l2loopback-0.12.5/vagrant/README.md
--- v4l2loopback-0.12.5.orig/vagrant/README.md	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/vagrant/README.md	2024-04-24 13:36:28.418461359 +0800
@@ -0,0 +1,70 @@
+developing v4l2loopback with a VM
+=================================
+
+Using `Vagrant` with the `VirtualBox` backend
+
+
+# SETUP
+
+Use the [`Vagrantfile`](#Vagrantfile) below for a Debian/testing based VM.
+Replace the host's `/PATH/TO/v4l2loopback` to the full path to the v4l2loopback sources on your disk.
+
+- The VM needs to have all the goodies (VirtualBox extension pack) for sharing host folders and webcams (if you need that).
+
+      apt install virtualbox-guest-dkms virtualbox-guest-dkms virtualbox-guest-utils
+
+- Make the VM up-to-date `apt update && apt dist-upgrade` or similar
+- Drop the unused stuff with `apt autoremove; apt-get clean` or similar
+- Make sure that the `vagrant` user will end up in `/vagrant/v4l2loopback` when logging in.
+  I did so by adding the following 2 lines at the very end of `~vagrant/.bashrc`:
+
+      cd /vagrant
+      test -d v4l2loopback && cd v4l2loopback
+
+
+- Power the VM off, and create an offline snapshot
+
+- Boot the VM (with the share mounted onto `/vagrant/v4l2loopback`):
+
+      vagrant up
+
+- Create an online snapshot of the running VM
+- Leave the VM running
+
+# scripts
+
+You can find a `vbox-restart` script in the `vagrant/` directory of this repository.
+Running it (give the UUID of a running VM) will:
+- do a hard shutdown of the given VM
+- restore the last snapshot of the given VM
+- start the running VM
+- (optionally) attach the host's webcam to VM
+
+# Workflow
+
+- Open `v4l2loopback.c` in your favourite editor and hack away
+- Whenever you feel like testing, do the following in a separate terminal:
+
+~~~
+me@host:~/v4l2loopback$ cd vagrant
+
+me@host:~/v4l2loopback/vagrant$ ./vbox-restart -a
+me@host:~/v4l2loopback/vagrant$ vagrant ssh
+vagrant@/vagrant/v4l2loopback$ make clean
+vagrant@/vagrant/v4l2loopback$ make modprobe
+vagrant@/vagrant/v4l2loopback$ (do some tests)
+~~~
+
+if the machine freezes, or something else really bad happens to it, just
+re-run `./vbox-restart` and start anew.
+
+# Resources
+
+## Vagrantfile
+
+~~~vagrant
+Vagrant.configure("2") do |config|
+  config.vm.box = "debian/contrib-testing64"
+  config.vm.synced_folder "/PATH/TO/v4l2loopback", "/vagrant/v4l2loopback"
+end
+~~~
diff -uprN v4l2loopback-0.12.5.orig/vagrant/Vagrantfile v4l2loopback-0.12.5/vagrant/Vagrantfile
--- v4l2loopback-0.12.5.orig/vagrant/Vagrantfile	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/vagrant/Vagrantfile	2024-04-24 13:36:28.418461359 +0800
@@ -0,0 +1,71 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# All Vagrant configuration is done below. The "2" in Vagrant.configure
+# configures the configuration version (we support older styles for
+# backwards compatibility). Please don't change it unless you know what
+# you're doing.
+Vagrant.configure("2") do |config|
+  # The most common configuration options are documented and commented below.
+  # For a complete reference, please see the online documentation at
+  # https://docs.vagrantup.com.
+
+  # Every Vagrant development environment requires a box. You can search for
+  # boxes at https://vagrantcloud.com/search.
+  config.vm.box = "debian/contrib-testing64"
+
+  # Disable automatic box update checking. If you disable this, then
+  # boxes will only be checked for updates when the user runs
+  # `vagrant box outdated`. This is not recommended.
+  # config.vm.box_check_update = false
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine. In the example below,
+  # accessing "localhost:8080" will access port 80 on the guest machine.
+  # NOTE: This will enable public access to the opened port
+  # config.vm.network "forwarded_port", guest: 80, host: 8080
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine and only allow access
+  # via 127.0.0.1 to disable public access
+  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  # config.vm.network "private_network", ip: "192.168.33.10"
+
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network "public_network"
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+  config.vm.synced_folder "../", "/vagrant/v4l2loopback"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  # config.vm.provider "virtualbox" do |vb|
+  #   # Display the VirtualBox GUI when booting the machine
+  #   vb.gui = true
+  #
+  #   # Customize the amount of memory on the VM:
+  #   vb.memory = "1024"
+  # end
+  #
+  # View the documentation for the provider you are using for more
+  # information on available options.
+
+  # Enable provisioning with a shell script. Additional provisioners such as
+  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
+  # documentation for more information about their specific syntax and use.
+  # config.vm.provision "shell", inline: <<-SHELL
+  #   apt-get update
+  #   apt-get install -y apache2
+  # SHELL
+end
diff -uprN v4l2loopback-0.12.5.orig/vagrant/vbox-restart v4l2loopback-0.12.5/vagrant/vbox-restart
--- v4l2loopback-0.12.5.orig/vagrant/vbox-restart	1970-01-01 08:00:00.000000000 +0800
+++ v4l2loopback-0.12.5/vagrant/vbox-restart	2024-04-24 13:36:28.418461359 +0800
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+usage() {
+cat 1>&2 <<EOL
+usage: $0 [ -w ] [ -a ] <vm>
+   stops the given VM, restores the current snapshot and restarts it again
+
+OPTIONS
+   -w         attach host's webcam to VM
+   -a         if no <vm> is given, try to automatically guess it
+              (using the currently running one, if this is unambiguous)
+EOL
+
+if [ "x${1}" != "x" ]; then
+    cat 1>&2 <<EOL
+
+ here's a list of running VMs:
+EOL
+    vboxmanage list runningvms
+    echo "" 1>&2
+fi
+}
+
+
+webcam=no
+auto_vm=no
+
+while getopts "h?wa" opt; do
+    case $opt in
+        w)
+            webcam=yes
+            ;;
+        a)
+            auto_vm=yes
+            ;;
+        h|\?)
+            usage
+            exit 0
+            ;;
+        :)
+            usage
+            exit 0
+            ;;
+    esac
+done
+shift $((OPTIND-1))
+
+vm=$1
+
+if test "x${auto_vm}" = "xyes" && test "x${vm}" = "x"; then
+    vm=$(vboxmanage list runningvms | grep -c .)
+    if [ "${vm}" -eq 1 ]; then
+        vboxmanage list runningvms
+        vm=$(vboxmanage list runningvms | awk '{print $2}' | tail -1)
+    else
+        cat 1>&2 <<EOL
+unable to unambiguously determine VM
+
+EOL
+        usage vms
+        exit 1
+    fi
+fi
+
+
+if [ "x${vm}" = "x" ]; then
+    usage 1
+    exit 1
+fi
+
+restore() {
+    vboxmanage controlvm "${vm}" poweroff \
+        && vboxmanage snapshot "${vm}" restorecurrent
+}
+restart() {
+    # don't know why, but if we start the VM immediately after restoring the
+    # current snapshot, it fails...
+    sleep 2
+    vboxmanage startvm "${vm}"
+}
+configure() {
+    if [ "x${webcam}" = "xyes" ]; then
+       vboxmanage controlvm "${vm}" webcam attach /dev/video0
+    fi
+}
+restore && restart && configure
diff -uprN v4l2loopback-0.12.5.orig/v4l2loopback.c v4l2loopback-0.12.5/v4l2loopback.c
--- v4l2loopback-0.12.5.orig/v4l2loopback.c	2026-03-03 18:07:05.510127932 +0530
+++ v4l2loopback-0.12.5/v4l2loopback.c	2026-03-03 18:30:48.332824886 +0530
@@ -43,11 +43,15 @@
 #define strscpy strlcpy
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 17, 0)
+#define timer_delete_sync del_timer_sync
+#endif
+
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 1)
 #define kstrtoul strict_strtoul
 #endif
 
-#if defined(timer_setup) && defined(from_timer)
+#if defined(timer_setup)
 #define HAVE_TIMER_SETUP
 #endif
 
@@ -1591,8 +1595,8 @@ static int vidioc_querybuf(struct file *
 static void buffer_written(struct v4l2_loopback_device *dev,
 			   struct v4l2l_buffer *buf)
 {
-	del_timer_sync(&dev->sustain_timer);
-	del_timer_sync(&dev->timeout_timer);
+	timer_delete_sync(&dev->sustain_timer);
+	timer_delete_sync(&dev->timeout_timer);
 	spin_lock_bh(&dev->lock);
 
 	dev->bufpos2index[dev->write_position % dev->used_buffers] =
@@ -2045,8 +2049,8 @@ static int v4l2_loopback_close(struct fi
 
 	atomic_dec(&dev->open_count);
 	if (dev->open_count.counter == 0) {
-		del_timer_sync(&dev->sustain_timer);
-		del_timer_sync(&dev->timeout_timer);
+		timer_delete_sync(&dev->sustain_timer);
+		timer_delete_sync(&dev->timeout_timer);
 	}
 	try_free_buffers(dev);
 
@@ -2314,7 +2318,8 @@ static void check_timers(struct v4l2_loo
 #ifdef HAVE_TIMER_SETUP
 static void sustain_timer_clb(struct timer_list *t)
 {
-	struct v4l2_loopback_device *dev = from_timer(dev, t, sustain_timer);
+	struct v4l2_loopback_device *dev =
+		container_of(t, struct v4l2_loopback_device, sustain_timer);
 #else
 static void sustain_timer_clb(unsigned long nr)
 {
@@ -2339,7 +2344,8 @@ static void sustain_timer_clb(unsigned l
 #ifdef HAVE_TIMER_SETUP
 static void timeout_timer_clb(struct timer_list *t)
 {
-	struct v4l2_loopback_device *dev = from_timer(dev, t, timeout_timer);
+	struct v4l2_loopback_device *dev =
+                container_of(t, struct v4l2_loopback_device, timeout_timer);	
 #else
 static void timeout_timer_clb(unsigned long nr)
 {
