View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0001607 | channel: elrepo/el9 | kmod-omn-v4l2loopback | public | 2026-09-21 15:49 | 2026-10-02 09:58 |
| Reporter | tqhoang | Assigned To | tqhoang | ||
| Priority | normal | Severity | major | Reproducibility | N/A |
| Status | assigned | Resolution | open | ||
| Platform | x86_64 | OS | RHEL | OS Version | 9.8 |
| Summary | 0001607: V4L2Loopback for Omnissa Horizon 8 Agent for Linux | ||||
| Description | Note: The excerpt below was via email. -- I started looking at the Omnissa Horizon 8 Agent for Linux. [snipped] This is a product we need to get working on RHEL 9. It requires VHCI-HCD and V4L2Loopback with patches and kernel modules build on each system for our use case. DKMS implementation is left to the user. There are weekly kernel updates from Red Hat, so automating kernel module rebuild is a must have. I see that ElRepo provides both solutions, but neither can be used because of the patching required by Omnissa. I am wondering if there is any way of making the ElRepo provided packages working for the Omnissa Horizon 8 Agent and if that would possibly break other use cases of those packages. Have you seen any requests for compatibility of the ElRepo provided packages for the Horizon Agent? Here are a couple of links to the Omnissa documentation for Omnissa Horizon 8 2603, the latest Extended Service Branch (ESB) with Horizon Agent for 64-bit Linux version 8.18.0: System Requirements for Horizon Agent for Linux - VHCI Driver for USB Redirection: https://docs.omnissa.com/Desktops-and-Applications-in-Horizon-V2603/SystemRequirementsforHorizonAgentforLinux (vhci-hcd-1.15.tar.gz from https://sourceforge.net/projects/usb-vhci/files/linux%20kernel%20module/) Install the V4L2Loopback Driver on a Linux Machine https://docs.omnissa.com/Desktops-and-Applications-in-Horizon-V2603/InstalltheV4L2LoopbackDriveronaLinuxMachine v0.12.5 Source code from https://github.com/umlaeute/v4l2loopback/tags (Omnissa documentation requires patching for this one, but the patch does not seem to apply.) I would like to hear your take on this situation. I hope to be able to connect with the Omnissa people in the next couple of weeks to raise all my concerns about the packaging of this solution and the fact that I feel I cannot deploy it on all the systems I support in the current state. Being able to use the packages from ElRepo would definitely help. | ||||
| Tags | No tags attached. | ||||
| Attached Files | v4l2loopback.patch (195,542 bytes)
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)
{
| ||||
|
|
The patches were from Horizon 8 version 2603 with Agent for Linux 8.18.0. Omnissa-horizonagent-linux-2603-8.18.0-24278394073.el.x86_64.rpm https://customerconnect.omnissa.com/downloads/info/slug/virtual_desktop_and_apps/omnissa_horizon/2603 https://customerconnect.omnissa.com/downloads/details?downloadGroup=HZN-2603-LINUX&productId=1715&rPId=119330 |
|
|
The following packages have been pushed to testing: kmod-omn-v4l2loopback-0.12.5-1.el9_8.elrepo.src.rpm kmod-omn-v4l2loopback-0.12.5-1.el9_8.elrepo.x86_64.rpm omn-v4l2loopback-utils-0.12.5-1.el9_8.elrepo.x86_64.rpm |
|
|
The following packages have been pushed to testing: kmod-omn-v4l2loopback-0.12.5-1.el8_10.elrepo.src.rpm kmod-omn-v4l2loopback-0.12.5-1.el8_10.elrepo.x86_64.rpm omn-v4l2loopback-utils-0.12.5-1.el8_10.elrepo.x86_64.rpm |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-09-21 15:49 | tqhoang | New Issue | |
| 2026-09-21 15:49 | tqhoang | Status | new => assigned |
| 2026-09-21 15:49 | tqhoang | Assigned To | => tqhoang |
| 2026-09-21 15:49 | tqhoang | File Added: v4l2loopback.patch | |
| 2026-09-21 15:52 | tqhoang | Relationship added | related to 0001608 |
| 2026-09-21 16:53 | tqhoang | Description Updated | |
| 2026-09-21 16:53 | tqhoang | Note Added: 0010970 | |
| 2026-09-21 16:55 | tqhoang | Note Edited: 0010970 | |
| 2026-09-22 22:03 | tqhoang | Note Edited: 0010970 | |
| 2026-09-23 17:11 | tqhoang | Note Added: 0010974 | |
| 2026-09-29 16:23 | tqhoang | Category | kmod-v4l2loopback => kmod-omn-v4l2loopback |
| 2026-10-02 09:58 | tqhoang | Note Added: 0010987 | |
| 2026-10-02 09:58 | tqhoang | Description Updated |