pax_global_header00006660000000000000000000000064147603022420014512gustar00rootroot0000000000000052 comment=60a0315c6db154597dc733ea42139cf159644a7a v4l2loopback-0.14.0/000077500000000000000000000000001476030224200140765ustar00rootroot00000000000000v4l2loopback-0.14.0/.editorconfig000066400000000000000000000003451476030224200165550ustar00rootroot00000000000000root = 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 v4l2loopback-0.14.0/AUTHORS000066400000000000000000000033751476030224200151560ustar00rootroot00000000000000Aidan Thornton Alex Hu Alienmaster Anatolij Gutschin Andrii Danyleiko Angus McInnes Anton Novikov Attila Tőkés Benny Baumann Dmitry Eremin Gavin Qiu George Chriss Gorinich Zmey IOhannes m zmoelnig Jan-Ralf Meier Javier Infante Jon Morley Joan Bruguera Kai Kang Kurt Kiefer Michel Promonet Nick Sarnie Oleksandr Natalenko Paul Brook Ricardo Ribalda Delgado Scott Maines Stefan Diewald stephematician Tasos Sahanidis Ted Mielczarek Theodore Cipicchio Thomas Hutterer Tim Gardner Tobias Stoeckmann Todor Minchev tongdaxu tz Vasily Levin WaleedTageldeen Wren Turkal wuweixin You-Sheng Yang Yusuke Ohshima v4l2loopback-0.14.0/COPYING000066400000000000000000000432541476030224200151410ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. v4l2loopback-0.14.0/ChangeLog000066400000000000000000001216211476030224200156530ustar00rootroot00000000000000v4l2loopback (0.14.0) unstable; urgency=medium [ IOhannes m zmölnig ] * Fix automatic retrieval of signing keys from DKMS>=3.0.4 * Document building for a different kernel * Minor code cleanups * GitHub + Improve github-issue template + Exclude old Ubuntu LTS releases that cannot be used in GH actions [ MrGamy ] * Update README.md [ stephematician ] * Refactor poll() + Fixes poll() for readers failing if first call has readable state + Tidy up some whitespace * Fix FIFO management * V4L2 UAPI compliance in format negotation and stream activation + Safer list access in DQBUF + Add guard to list ops in REQBUFS + Simplify close and free + Fix v4l2-compliance result for button ctrl + Fix default configuration of exclusive_caps + Set valid pix format in add() + Enforce limits on time-per-frame + Enumerate all formats except when fixed + Add resize outbufs_list helper function + Add file-io initialisation function + Use STREAMOFF in REQBUFS when count is zero + Use REQBUFS to free buffers in close() + Add function to check buffer-set capability + Consolidate S_FMT across reader and writer + Set "I/O method" on first read/write or REQBUF + Remove timeout allocate from allocate_buffers + Add mutex for format and buffer changes + QBUF/DQBUF ensure buffer type is MMAP + QBUF/DQBUF set flags when using timeout I/O + QUERYBUF guess capture buffer flags + QUERYBUF set timestamp if buffer dequeued + DQBUF/STREAMON fail if opener has not alloc'd + Use u32 type for buffer counts + Refactor control of logical streams + Set unique timestamp flags * v4l2loopback-ctl: Fix for block when use set-timeout-image verb + Update README accordingly * Do not discard return value of vm_alloc_page * Use atomic operations to count vm_open/close * Simplify loop preparation cases in mmap() * Code cleanup + Reorder code + Use macros + Remove unreachable/unused code + Tidy up debugging printout -- IOhannes m zmölnig (Debian/GNU) Fri, 28 Feb 2025 09:48:06 +0100 v4l2loopback (0.13.2) unstable; urgency=medium * Fix compilation on 32bit architectures (i386, armhf,...) * Replace (s64 % u32) with our own v4l2l_mod64() wrapper * Fix mod64 warnings on arm * utils buildsystem + Don't fail 'clean' target if 'make -C utils clean' fails + clean objectfiles * v4l2loopback-ctl + mention the special caps 'any' in help + improve error-reporting and return errcodes where appropriate + avoid unused variable warning + explicitly return the fd from open_controldevice() -- IOhannes m zmölnig (Debian/GNU) Fri, 24 May 2024 11:30:51 +0200 v4l2loopback (0.13.1) unstable; urgency=medium [ Max Harmathy ] * Avoid building utils with dkms [ IOhannes m zmölnig ] * 'release.sh' script + shellchecked + write back new version to v4l2loopback.h (if necessary) -- IOhannes m zmölnig (Debian/GNU) Tue, 19 Mar 2024 22:15:34 +0100 v4l2loopback (0.13.0) unstable; urgency=medium [ IOhannes m zmölnig ] * Set device_caps in init_vdev() for all kernel-versions (not just >4.7.0) * Don't set the V4L2_CAP_DEVICE_CAPS on device_caps * only set (struct video_device).device_caps on linux>=4.7.0 * pre-processor directives should be left-aligned * tabify * Drop obsolete ISSUE_TEMPLATE * Mention that people should do research before creating a new ticket * document that we expect people to do some research before creating a ticket * document that people must have a proper bulid-environment [ mpromonet ] * reset ready_for_capture flag processing VIDIOC_STREAMOFF [ IOhannes m zmölnig ] * redirect people for StackOverflow for general help. * rule to get the latest&greatest .clang-format for the linux-kernel * clang-format -i *.c *.h * fixed whitespace in comments and strings * use $() rather than `` * use more frames for setting caps. * document that GStreamer-1.0 is required (rather than GStreamer-0.10) * rename _SIZE_MAX_WIDTH to _SIZE_DEFAULT_MAX_WIDTH (same for HEIGHT) * Add clang-format target * comment formatting * switched v4l2_loopback_init() for a more complete v4l2_loopback_add() * per-device max_width/max_height * store card-label per device * copy newly created video-device back * indentation * use per-device name rather than the global ones * switching free_devices() to idr-iterator * getting rid of static number of devices... * limit card_label to 32 chars (like v4l2_capability->card) * make sure to neither exceed size of cap->card nor dev->card_label * prevent out-of-bounds access to exclusive_caps[] * less ambiguous wording when MAX_DEVICES is exceeded * allow device_nr > MAX_DEVICES * less verbose output * use video_nr for initial devices * macro for the default value of EXLCUSIVE_CAPS * reordered card_label calculation * fill default-values * return the used device number * move v4l2_loopback_config to a separate header * move IDR declaration to a better place. * drop devptr from v4l2_loopback_add * use "int" instead of "bool" for the public interface * now that exclusive_caps are int and can be negative, use that to ask for the default * use "err" instead of "ret" * implement /dev/v4l2loopback to dynamically create/remove devices * use a mutex to prevent concurrent access to the /dev/v4l2loopback device * check whether the device is currently open before closing it * v4l2_loopback_config.card_label now has a fixed size (32 bytes) * only return the device-number if device_add was successfull * added QUERY ioctl * sample application to add/delete/query devices on the fly * indentation * moved DEFINE_IDR/MUTEX *again* * "every" -> "each" * show default values in "modinfo" * Makefile for utils/ * don't assume that IDR matches the device-number * less verbose * print perror() if query fails * explicitely return ENOSYS for invalid ioctls * use "break" instead of "goto" * return EINVAL by default for valid ioctls * copy data from userspace instead of casting away * note that we need two device numbers * Add example udev rules. Thanks to Norman Rasmussen * formatting * perror() if query-after-add fails * only set device is the pointer is valid * (struct v4l2_loopback_config) now has both output_nr and capture_nr * adjust utility to (currently defunct) split CAPTURE/OUTPUT devices * document how to call /dev/v4l2loopback ioctls * gitignore .clang-format * don't export repo-configuration * mention that v4l2_loopback_config.announce_all_caps is to be going away * more specific rule for creating persistent names * rule to allow members of 'video' to change device-properties via sysfs * restructed usage() a bit * implement "set-fps" * also format code in utils/ * moved v4l2loopback version to header file * capitalize command enums * added "-h" and "-v" commands * SETFPS -> SET_FPS * dropped unused "fd" from set_fps call [ You-Sheng Yang ] * utils: respect CPPFLAGS from env vars [ Sakari Tanskanen ] * Allow reading buffered frames [ You-Sheng Yang ] * dev: initialize per opener v4l2_fh * event: install event (un)subscribe hook * event: support polling for events [ IOhannes m zmölnig ] * control more thingies * whitespace * Add documentation about using dkms. Thanks to Toon Verstraelen [ Toon Verstraelen ] * Document how to load the module at boot [ IOhannes m zmölnig ] * lower-case readme * Usde "If" rather than "When" [ IOhannes m zmoelnig ] * note that you cannot just load an old module into a new kernel (even if you built the module yourself). [ ilsi ] * Fix DKMS typos in README [ IOhannes m zmölnig ] * use a helper for showing short descriptions of commands * announce 'set-caps' and 'set-timeout-image' * re-formatted * allow some commands without a /dev/v4l2loopback device * "not implemented" is not the same as "unknown command" * announce the not-yet-implemented "get-fps" and "get-caps" commands * make commands static when possible * helper-function to get sysfs files * specify mode when opening a sysfs-file * close fd on error * implement get_fps * basic get_caps implementation * store fourcc as uint32_t * fixed help-display * rename get_ to open_ * properly display FOURCC when printing caps * re-factored caps-parsing into separate function * implement set_caps * less verbose * query default values before setting those we want to * clang-format * reverse arguments for set-* commands * draft implementation of set-timeout-image * proper ordering of arguments in help * remove printout * search for executable in PATH * comments * clang-format * v4l2loopback-ctl: support for adding split-devices * fixed spelling errors * v4l2loopback_private.devicenr -> .device_nr * remove debugging output * function to check for deprecated calls * wrap launching of external programs into separate function * TERM the child-process on receiving SIGINT; wait for child-process to cleanup * move helpers to top of file * TODO: we want to be able to set the timeout directly * helpers for getting/setting controls by name * have "set-timeout-image" set the 'timeout' and 'timeout_image_io' controls * allow setting of the timeout (TODO: parse args for timeout) * fix(?) GStreamer-pipeline for setting the timeout image * added 'default' clause to switch-statement * clang-format * build utils from master-makefile * factored device-opening into helper-function * handle "any" caps and set "keep_format" and "sustain_framerate" * resolve timeout-image to full path (and allow 4096 chars in paths) * track how many producers have opened the device * reformatting * close video-device before trying to set timeout-image [ You-Sheng Yang ] * event: fix backward compatibility to 3.16 * add GitHub Actions yaml * utils: use VIDIOC_QUERYCTRL for control name resolution * utils: fix error: 'for' loop initial declarations are only allowed in C99 mode [ IOhannes m zmölnig ] * improve parse-device to deal with /dev/v4l/by-*/* symlinks * keep alignment when running clang-format * moved fallback defines for V4L2_PIX_FMT_* to v4l2loopback_formats.h * modular help * set-timeout-image subcommand now takes a "-t " flag * less verbose * optional argument-check for called_deprecated() * check framerate-argument for "set_fps" * return success of subcommands * catch empty /sysfs/.../format (but don't complain loudly) * simplified caps-parsing * get-fps now falls back to querying the video-device's PARMs * use C-style comments for documentation * print error when GStreamer-style caps are detected * renamed "utils/v4l2loopback" to "utils/v4l2loopback-ctl" * replaced placeholder file with .gitignore * fixed help2man invocation * clean-target for utils * call utils' "clean" target when running "make clean" * use /dev/video0 throughough * update to new v4l2loopback-ctl syntax * Fixed upper-casing to standard English * note on changing module options * note on dynamic device management * make "changing options" a sub-item in the "options" chapter * grouping into chapters * more upper-casing [ You-Sheng Yang ] * ci: match known failures to kernel versions [ IOhannes m zmölnig ] * comment fixes * ctrl_handler_init/setup * note on why try_free_buffers() doesn't do harm if keep_format is true * dropped double "sudo" [ You-Sheng Yang ] * gitignore: add utils/v4l2loopback-ctl and v4l2loopback.mod [ IOhannes m zmölnig ] * automatically assign new-bugreports a default label/assignee * disable blank issues and redirect people to U&L resp the wiki * stackoverflow -> unix.stackexchange * also mention stackoverflow * use an imperative rather than a noun as the bug-report name. * pass number of args to help-functions * print known fourcc flags when calling 'set-caps' without arguments * clang-format * editorconfig [ Luigi Baldoni ] * Include header outside of struct definition [ Oleksandr Natalenko ] * confine v4l2loopback_cleanup_module [ IOhannes m zmölnig ] * fixed building of v4l2loopback-ctl in the aftermath of #389 * doc on how to develop with vagrant * slight re-ordering of struct-init * use module_init()/module_exit() rather than '#ifdef MODULE' * note on what to do if the VM freezes * drop MODULE_ALIAS("devname") * add args to vbox-restart * [ci] don't bail out on the first kernel that unexpectedly fails * [ci] if a build failed, accept that 'make clean' might fail as well... * [ci] mark all trusty kernels as known-bad [ Piotr Orzechowski ] * Fix the dkms install instructions in README.md [ IOhannes m zmölnig ] * [ci] renamed 'master' branch to 'main' * embrace Kbuild * fixed typo * use KBUILD_MODULES to detect Kbuild * [gh] fixes errors in the issue template * bumped copyright-dates in README * mention "SSL" in the troubleshooting section * fixed typos * more english style fixes * Fix scanf/printf formatting [ Tadayuki Okada ] * try to set requested colorspace [ You-Sheng Yang ] * coverity: fix unchecked return value * coverity: fix null pointer dereference [ IOhannes m zmölnig ] * don't fail if allocating 0-sized buffers [ a1346054 ] * use apt-get instead of aptitude * master branch was renamed to main * fix shellcheck warnings * unify codestyle * fix spelling * add missing final newline * trim excess whitespace * trim trailing whitespace [ Emil Velikov ] * dkms.conf: remove REMAKE_INITRD option [ bakedpotato191 ] * Add module_version macro [ Bechir Mghirbi ] * Adding support for RGBA32 (AB24) [ Jim Scarborough ] * Fix currentversion.sh to return the value [ IOhannes m zmölnig ] * made "currentversion.sh" script POSIX conformant * [ci] drop 'groovy'; add 'hirsuite' and 'impish' * [ci] dynamically generate Debian/Ubuntu releases to test * on Debian kernel-header packages have a different suffix... * guard V4L2_PIX_FMT_RGBA32 definition... * [ci] better check for installed kernel headers * clang format * [ci] show successfull builds [ Anatoli Babenia ] * README.md Add info how to check the device number [ IOhannes m zmölnig ] * inject snapshot-version in banner [ Benny Baumann ] * Limit v4l2_loopback_write calls to (streaming) writers * Simplify code by using upstream video_drvdata helper * Free module data on error * Initialize max_width/max_height relative to defaults * Off-by-one condition when retrieving defaults [ IOhannes m zmölnig ] * Make sure that only the boolean state of announce_all_caps is used. [ He1nMueck ] * Make free_buffers() return void. [ Benny Baumann ] * Reintroduce previous behavior for write call [ Jan-Ralf Meier ] * Check for negative index before proceeding [ Benny Baumann ] * Free module data on error [ Robert Geislinger ] * fixed possible bufferoverflow [ Alienmaster ] * simplify bufferoverflow checks [ IOhannes m zmölnig ] * indentation [ Alienmaster ] * added explicit cleanup on error [ IOhannes m zmölnig ] * removed superfluous check * only allocate_buffers() if buffer_size>0 [ Benny Baumann ] * Release memory on error in v4l2_loopback_open * Only reset timeout_image_io when open succeeds * Avoid redundant/conflicting casts [ Tobias Stoeckmann ] * attr_store_maxopener: Handle integer truncation [ Benny Baumann ] * Properly snprintf the device card label string into the capability query buffer * Avoid format string issue [ umläute ] * Create SECURITY.md [ IOhannes m zmölnig ] * add explicit format specifier to printf() invocations [ Sludge ] * Support `V4L2_PIX_FMT_ABGR32` format [ Tobias Stoeckmann ] * Fix warning with gcc 12 [ Jan-Ralf Meier ] * Fix return condition, due to EINVAL memory offset. [ IOhannes m zmölnig ] * Update release-script to work with any branch * Exclude repository config in source-tarballs * Backported dkms-patch from Ubuntu [ Oleksandr Natalenko ] * v4l2loopback: un-confine v4l2loopback_cleanup_module() [ IOhannes m zmölnig ] * quote FOURCC-codes to show the space [ Jianhui Dai ] * Reset V4L2_BUF_FLAG_MAPPED if use_count <= 0 * log pid for debug [ tudor ] * DKMS section clarification * too many empty lines [ You-Sheng Yang ] * Track active readers * Support V4L2_EVENT_PRI_CLIENT_USAGE [ Tobias Stoeckmann ] * Fix signed integer overflows in increments. [ Kate Hsuan ] * v4l2loopback: a new offset for V4L2_EVENT_PRI_CLIENT_USAGE [ IOhannes m zmölnig ] * Allow a minimum of 0 fps * clang-format * Propagate snapshot version to v4l2loopback-ctl * strip leading 'v' from git snapshot version * v4l2loopback-ctl: compat flag "--version" * calculate MODULE_VERSION from V4L2LOOPBACK_VERSION * use linux' __stringify * re-ordered 'struct v4l2_loopback_config' so "announce_all_caps" comes last * logging: move "pid(%d)" after the v4l2-loopback identifier * [ci] use Environment-File rather than set-output * Drop support for Linux<2.6.37 * v4l2loopback-ctl: print module version as well * [ci] Job for code-linting * [ci] Fixup lint-job * Re-formatted code with updated .clang-format * [ci] hardcode Debian's supported releases to their rolling names [ sanbrother ] * Fix compilation issues under AOSP [ mzihlmann ] * mutex lock access to outbufs_list in vidioc_dqbuf [ Asahi Lina ] * Revert "Fix signed integer overflows in increments." * Change the type of read_position and write_position to 64-bit [ IOhannes m zmölnig ] * Use separate spinlocks for protecting list access * Make VIDIOC_ENUMINPUT return V4L2_IN_ST_NO_SIGNAL if there's no producer * code formatting * Always protect access to dev->outbufs_list with the list_lock mutex * v4l2loopback-ctl help: use 'detail' level rather than 'brief' flag * help: reverse general form and example * codespell fixes * fix typo * add 'install' target for utils * clang-format * more gitignores * Bump copyright dates * simple test application for producing/consuming buffers * more debugging and a global buffer * set bufsize/bytesused when initializing buffers for MMAP * fix random; optionally set timestamp * try more... * stuff... * use DEFAULT colorspace * set V4L2_BUF_FLAG_TIMESTAMP_COPY flag when copying the buffer timestamp * more diagnostic output * refactor the TRY/S_FMT code * fix formatting warnings when printing timestamps * compat for older kernels * more clang-format * reversed compat logic * yikes, yet another typo * tests/consumer: make S_FMT errors non-fatal * prevent multiple output streams * set TIMESTAMP flags * G_FMT_CAP: only report failure if the format has not been fixated * script to check the output/capture formats of a device * V4L2LOOBACK_IS_FIXED_FMT() to check if the format is changeable * unify the output of vidioc_enum_fmt_* * set default framesize * allow setting of minimum width/height as well * v4l2loopback-ctl: allow setting of minimum framesize * clang-format * tests/producer: fix description of "-c" flag and linefeed * long-options for v4l2loopback-ctl * indentation * unset the timeout_image_io flag if allocating the timeout-image fails * only unset the timeout_image_io flag when requesting buffers for the timeout image * turn the "timeout_image_io" ctrl into a button * whitespace * fixate format with "keep_format" * report format via /sys if it is somehow FIXED * run timeout-image gst-pipeline through "tee" * add "--verbose" flag to "set-timeout-image" * prevent multiple readers to start streaming * indentation... * add /sys/devices/virtual/video4linux/video*/type interface * fix device_nr checks in V4L2LOOPBACK_CTL_QUERY * v4l2loopback-ctl: add "list" verb * v4l2loopback-ctl: escape special chars in device-names * refactored raw-string printout into helper function * v4l2loopback-ctl: align help * v4l2loopback-ctl: more escaping for device-name * v4l2loopback-ctl: add flags to "query" verb * v4l2loopback-ctl: streamline help [ Hans de Goede ] * v4l2loopback: Fixup bytesused field when writer sends a too large value [ IOhannes m zmölnig ] * fallback to dprintkrw() if dev_warn_ratelimited() is not available * rename sysfs-attribute "type" to "state" * make the code less-dependant on the "capture_nr" member of the config-struct * swap output/capture device when adding new devices * Remove the 'capture_nr' member from the v4l2_looback_config struct * build-fixes: install and utils * force timestamp.tv_sec to (long long int) [ Wren Turkal ] * Remove support for pre-3.6.1 Linux kernels. * Remove support for pre-4.0.0 kernels. [ IOhannes m zmölnig ] * [github] Mention "discussions" in the issue landing-page [ rdbo ] * fixed utils build for musl (missing GLOB_ONLYDIR) [ IOhannes m zmölnig ] * note that GLOB_ONLYDIR is indeed not requried by POSIX [ rdbo ] * added v4l2loopback-ctl.o to gitignore [ IOhannes m zmölnig ] * Fix formatting [ Fufu Fang ] * Update README.md [ IOhannes m zmölnig ] * [ci] skip failures on kernels without v4l2 support * [ci] install all available kernel headers * [ci] show skipped builds * [ci] Fix testing for v4l2 capabilities * [ci] bump actions/checkout to v4 * [ci] lower actions/checkout to v3 * [ci] only install latest packageversion of each kernel-flavour * [ci] install 'dkms' to get some more building prerequisites * some minor typos * use 'sudo' to change the permissions of the module * use $< instead of hardcoding the module name * 'sign' target to sign the generated module for use with secure boot [ Joaquim Monteiro ] * Use strscpy instead of strlcpy if available [ IOhannes m zmölnig ] * redefine strscpy() as strlcpy() if needed * Lower minimum width/height to extreme values * allocate_buffers: fix check whether we can re-allocate -- IOhannes m zmölnig (Debian/GNU) Tue, 19 Mar 2024 17:11:08 +0100 v4l2loopback (0.12.7) unstable; urgency=medium [ IOhannes m zmölnig ] * Add explicit format specifier to printf() invocations [ Andreas Beckmann ] * REMAKE_INITRD is deprecated in dkms 3 * Do not attempt to build modules for kernels without CONFIG_VIDEO_V4L2 [ Dimitri John Ledkov ] * Fixup obsolete module init/exit [ You-Sheng Yang ] * dev: initialize per opener v4l2_fh * event: install event (un)subscribe hook * compliance: fix enum frame sizes/intervals errors * compliance: fix "fmtdesc.type was modified" error * UBUNTU: SAUCE: coverity: fix null pointer dereference [ Tim Gardner ] * Fix unchecked return value in vidioc_s_fmt_out() * Fix resource leak in v4l2_loopback_open() * Fix NULL dereference in free_buffers() [ Erich Eickmeyer ] * Don't fail if allocating 0-sized buffers -- IOhannes m zmölnig (Debian/GNU) Wed, 05 Aug 2022 00:24:03 +0200 v4l2loopback (0.12.6) unstable; urgency=medium * Add explicit format specifier to printf() invocations -- IOhannes m zmölnig (Debian/GNU) Sun, 03 Aug 2020 13:15:02 +0200 v4l2loopback (0.12.5) unstable; urgency=medium [ Joan Bruguera ] * Fix build in Linux 5.7-rc1 due to symbol name changes [ IOhannes m zmölnig ] * Simplify set/clear of V4L2_CAP_DEVICE_CAPS in querycaps * Use temp-var for capabilities * Also set vdev->device_caps -- IOhannes m zmölnig (Debian/GNU) Sun, 19 Apr 2020 19:06:09 +0200 v4l2loopback (0.12.4) unstable; urgency=medium [ Alex Xu (Hello71) ] * Use v4l2_buffer ptr instead of timeval (Compat with linux-5.6) [ tongdaxu ] * Add example that loops over YUV frames infinitely [ Thomas Hutterer ] * Document 'exclusive_caps' mode option (and some markdown fixes) [ IOhannes m zmölnig ] * Set the default number of buffers to 2 * Print "SUCCESS" message on installation success * Drop cast to (time_t) * Document 'exclusive_caps' mode option (and some more markdown fixes) -- IOhannes m zmölnig (Debian/GNU) Thu, 09 Apr 2020 22:09:28 +0200 v4l2loopback (0.12.3) unstable; urgency=medium [ Ricardo Ribalda Delgado ] * v4l2lookback: Port to kernel 5.4+ [ IOhannes m zmölnig ] * Set video_device->device_caps for linux>4.7.0 * Set some more device_caps * Update issue templates -- IOhannes m zmölnig (Debian/GNU) Fri, 06 Dec 2019 18:27:25 +0100 v4l2loopback (0.12.2) unstable; urgency=medium [ wuweixin ] * Update README.md [ Theodore Cipicchio ] * Replace v4l2_get_timestamp with ktime_get_ts(64) [ IOhannes m zmölnig ] * Mention support for 5.0.0 * Fix typo -- IOhannes m zmölnig (Debian/GNU) Mon, 27 May 2019 20:32:08 +0200 v4l2loopback (0.12.1) unstable; urgency=medium [ IOhannes m zmölnig ] * Fix permission of source code files * Initialize variables * Use %u to print size_t * Improve coding style by removing unused variables * More coding style fixes * Use GStreamer-1.0 caps in the documentation * Gst1.0 compat for example-script * Protect VP9 and HEVC by #ifdef guards [ Andrii Danyleiko ] * Fix typo [ Kai Kang ] * Replace do_gettimeofday with v4l2_get_timestamp for linux-5 compat -- IOhannes m zmölnig (Debian/GNU) Wed, 23 Jan 2019 21:59:29 +0100 v4l2loopback (0.12.0) unstable; urgency=medium [ WaleedTageldeen ] * Adding support for NV12 as per umlaeute/v4l2loopback#169 [ Jon Morley ] * v4l2loopback.c: Update error message in buf read to reflect actual copy call. [ IOhannes m zmölnig ] * Use kernel-version to determine whether we should set vfl_dir * sign releases and add a message * Support for 8bit bayer * moved bayer-formats into "packed formats" section -- IOhannes m zmölnig (Debian/GNU) Mon, 02 Jul 2018 12:27:29 +0200 v4l2loopback (0.11.0) unstable; urgency=medium [ Nick Sarnie ] * Adapted to new kernel timer API [ Attila Tőkés ] * Avoid setting dev->ready_for_output and opener->type on get/try calls * Allow input enumeration, even when exclusive_caps=1 and no input provided yet [ Todor Minchev ] * Makefile: remove depmod call in modules_install target [ Michel Promonet ] * Added format VP9 & HEVC [ IOhannes m zmölnig ] * Simplified HAVE_TIMER_SETUP clauses * Fixed format output to sysfs * Removed trailing whitespace * Updated README * Added `depmod -a` calls to the documentation * Fixed omitted word * [github] Added issue template for new reports * please don't post images in the issue-tracker -- IOhannes m zmölnig Tue, 06 Mar 2018 10:05:11 +0100 v4l2loopback (0.10.0) unstable; urgency=medium [ Paul Brook ] * Use consistent device names [ Michel Promonet ] * Initialize bytesused in buffer processing write and use it processing read [ Kurt Kiefer ] * Initialize bytesused also on output VIDIOC_QBUF. * Preserve output VIDIOC_QBUF timestamp if present [ IOhannes m zmölnig ] * switch ctl-script to GStreamer-1.0 * move braces into #ifdef block * use late_initcall() when not built as module * Disable exclusive_caps by *default* * Removed deprecated current_norm [ George Chriss ] * Directly set v4l2 buffer flags as v4l2 documentation (note difference vs. internal v4l2l buffers) Hopefully closes: https://github.com/umlaeute/v4l2loopback/issues/60 * Build typo: change b->flags to buf->flags [ Gavin.Qiu ] * Fix bug that return wrong buffer index when dequeue [ IOhannes m zmölnig ] * Added more AUTHORS * Updated README (compat, copyright and cosmetics) -- IOhannes m zmölnig (Debian/GNU) Fri, 02 Dec 2016 22:00:27 +0100 v4l2loopback (0.9.1) unstable; urgency=medium * Fixed module version -- IOhannes m zmölnig (Debian/GNU) Wed, 03 Jun 2015 19:47:23 +0200 v4l2loopback (0.9.0) unstable; urgency=medium [ IOhannes m zmölnig ] * formats * support more formats * support compressed formats * move formats-enumeration to separate file * tools to implement missing formats * controls * disable deprecated vidioc_*ctrl callbacks * register custom-controls * use ctrl_config information in (deprecated) queryctrl * fixed bugs * used static code analysis to find more bugs * more error checking * check timeperframe before setting it (Closes: #61) * make MAX_DEVICES/TIMEOUT/BUFFERS settable during build-process (Closes: #55) * check for errors returned by get_capture_buffer() * check whether there is at least 1 requestbuffer * unsigned comparision against <0 * avoid setting b->count to negative/null * ... * fixed typos * code formatting * standards compliancy * standard-conformant bus_info * pretend to not support {G,S,ENUM}_{IN,OUT}PUT depending on state * only pretend to not support IN/OUTPUT enumeration in exclusive-caps mode * test programs * for (de)queuing buffers * for writing interlaced video * compatibility with newer kernels * compatibility with older kernels * Updated documentation * Removed GFDL document * note where to get API documentation [ tatokis ] * Updated v4l2loopback.c to compile on >= 3.18 kernel [ tz ] * add ondemandcam [ Yusuke Ohshima ] * Fix issue #79 [ Tasos Sahanidis ] * Fix for kernel 4.0 -- IOhannes m zmölnig Tue, 02 Jun 2015 19:58:39 +0200 v4l2loopback (0.8.0) unstable; urgency=medium [ Dmitry Eremin ] * Add DKMS support. [ Angus McInnes ] * Make vidioc_g_fmt_out not change the format * Set correct output buffer type in vidioc_dqbuf [ Javier Infante ] * Added card_labels option when loading module. [ IOhannes m zmölnig ] * renamed 'card_labels' to 'card_label' * removed '-e' flag from call to 'depmod' (needs '-E' or '-F') * auto-detect new version * auto-update dkms.conf to new version -- IOhannes m zmölnig Tue, 10 Dec 2013 18:12:15 +0100 v4l2loopback (0.7.1) unstable; urgency=low [ Aidan Thornton ] * Linux 3.11 compatibility fix [ IOhannes m zmölnig ] * trying to keep pre-2.6.29 compatibility -- IOhannes m zmoelnig (gpg-key at iem) Mon, 16 Sep 2013 09:55:51 +0200 v4l2loopback (0.7.0) unstable; urgency=low [ IOhannes m zmölnig ] * don't implement STD-ioctls * Revert "dummy audio ioctl's that return EINVAL" * disable more STD-stuff based on V4L2LOOPBACK_WITH_STD * don't announce all caps capabilities * only announce capture/output capabilities if possible * 'exclusive_caps' parameter to control caps announcment * avoid duplicate setting of cardname * break lines * remove commented out code * updated AUTHORS information * fixed ChangeLog for 0.6.1 * updated NEWS for last releases [ Anatolij Gustschin ] * fix missing spin lock init * add newlines to debug statements [ Hans Verkuil ] * reformatting to kernel-standards -- IOhannes m zmoelnig (gpg-key at iem) Fri, 07 Jun 2013 11:24:34 +0200 v4l2loopback (0.6.3) unstable; urgency=low [ Ted Mielczarek ] * Fill in the "v4l2_capability::bus_info" field (Closes: #30) [ IOhannes m zmölnig ] * make "v4l2_capability::card" unique per device (Closes: #37) * fill in "video_device::vfl_dir" field on newer kernels (Closes: #35) * always provide format-string when using printf() * fixing update-changelog script -- IOhannes m zmoelnig (gpg-key at iem) Tue, 05 Feb 2013 10:03:28 +0100 v4l2loopback (0.6.2) unstable; urgency=low [ IOhannes m zmölnig ] * provide our own v4l2l_vzalloc * added missing includes * create unique names for the various devices * more verbose debugging output when capture DQBUF fails [ Anton Novikov ] * make v4l2loopback.ko a PHONY target * restarting-writer.sh runs on Ubuntu 11.10 * warning about disabled timeout when setting image * readpos2index -> bufpos2index * test different queue-sizes in restarting-writer.sh * fix buffer indices before dev->used_buffers update * fix ctl script (was hardcoded /dev/video0) [ yukkeorg ] * Fix error on compile in Linux kernel 3.6.1. -- IOhannes m zmoelnig Tue, 23 Oct 2012 14:38:02 +0200 v4l2loopback (0.6.1) UNRELEASED; urgency=low [ IOhannes m zmoelnig ] * Makefile fixes for debian -- IOhannes m zmoelnig (gpg-key at iem) Fri, 27 Apr 2012 17:22:25 +0200 v4l2loopback (0.6.0) UNRELEASED; urgency=low [ IOhannes m zmölnig ] * added direct link to wiki * fixed typos * check for (devices<0) rather than (devices==-1) [ Anton Novikov ] * add .gitignore files * add 'format' sysfs attr * remove 'fourcc' sysfs attr * 'keep_format' ctrl * set_timeperframe(), dev->frame_jiffies * 'sustain_framerate' ctrl * add examples/restarting-writer.sh * reset write_position only when !ready_for_capture * handle arbitrary output QBUF index order * 'timeout' ctrl * add ability to do i/o on placeholder picture buf * add v4l2loopback-ctl script * installing v4l2loopback-ctl * fix dequeuing unused buffers * timeout_image_io cleaner memory handling * some documentation on controls * some v4l2loopback-ctl syntax&doc tweaks [ IOhannes m zmölnig ] * moved utility into utils/ * Updated copyright notice * use max image size to prevent insane allocations * in-code documentation of the format string * fixed description of 'debug' option * fixed closing comment * allow to set the max.framesize via module parameters * renamed 'v4l2loopback' target to 'v4l2loopback.ko' * added install-utils target [ Anton Novikov ] * script bugfix * v4l2loopback-ctl set-fps * more README [ IOhannes m zmölnig ] * initialize list in all cases * notes on how to do kernel-debugging * when dying, write to stderr * check for applications before using them * fix usage/version to make it fit for help2man * manpage for v4l2loopback-ctl * placeholder * simplified description * build and install manpages * deleted manage (it's generated automatically) * updated in-module AUTHORs * debugging printout * don't try to force a given format * clarify README about default device in ./test -- IOhannes m zmoelnig (gpg-key at iem) Fri, 27 Apr 2012 09:29:52 +0200 v4l2loopback (0.5.0) UNRELEASED; urgency=low [ IOhannes m zmölnig ] * more (and better) debugging output * stefan diewald's ENUM_FRAMESIZES fix * simplifified framesize enumeration * stefan diewald's ENUM_FRAMEINTERVAL implementations * stefan diewald's buffer request logic * added Stefan Diewald to the authors * use sudo to rmmod/insmod kernel modules in Makefile * use unlocked_ioctl as suggested by salsaman * provide macros to simplify sysfs attrfile creation * added deviceattributes * implemented "video_nr" parameter to manually force device IDs * dummy audio ioctl's that return EINVAL * better output enumeration/format handling * trying to improve handling of std's * improve readability of vidioc_g_output * added note about video_nr param * fixed memleaks * allow per-device "max_openers" settings * warn if illegal number of max_openers is requested * prefix posts with "v4l2loopback" [ IOhannes m zmoelnig ] * simplistic ChangeLog generator -- zmoelnig Tue, 27 Dec 2011 19:01:25 +0100 v4l2loopback (0.4.1) UNRELEASED; urgency=low [ IOhannes m zmölnig ] * yuv4mpeg producer to be used in conjunction with mplayer * added yuv4mpeg_to_v4l2 to the build targets * simplified Makefile; added clean target * protect newer pixel formats * fixed preprocessor expansion on linux<2.6.32 * made it compile on 2.6.28 and 2.6.27 * <=2.6.27 definitely won't work * allow S_PARM for fps * renamed opener->position to opener->read_position * added dummy VIDIOC_QUERYCTRL implementation (fix for linux-3.1) -- IOhannes m zmoelnig (gpg-key at iem) Thu, 24 Nov 2011 18:11:01 +0100 v4l2loopback (0.4.0) UNRELEASED; urgency=low [ IOhannes m zmölnig ] * default debug-level should be 0 * cleanup version confusion * changed version to 0.3.0 * updated README to mention Debian packages * better internal format representation (as found in bttv-drivers) - still unused * trying to support I420 --- might be very unstable right now * dummy Makefile to allow "make" * allow to set device from cmdline * en/disable the readback test using defines * use FRAME_SIZE throughout * more experiments * more debugging messages * added rule to autoload the new v4l2loopback device * rewrote most of the mmap part in order to support I420 * cleanup to make it C90 compliant again * reordered formats a bit to make better default choices... * replace vloopback by v4l2loopback to avoid confusion * cleaned up code * properly initialize the timestampe in order to guarantee a monotic series * updated copyright * bumped to version 0.4.0 -- IOhannes m zmoelnig (gpg-key at iem) Tue, 29 Mar 2011 12:54:23 +0200 v4l2loopback (0.3) UNRELEASED; urgency=low [ IOhannes m zmölnig ] * note on why gstreamer v4l2sink fails to write to such a device * enum_framesizes and enum_fmt_caps * hmm, this makes it more crashy than better * enable additional ioctls (eg. enum_output) * fixed typo: USAGE instead of USEAGE * remove stray #error * gcode reorganization; uniform comments * experiments with returning 0-size * offline documentation for v4l2 * allow all kinds of formats during negotiation * comment on which fields to set * better support for setting formats * add note about using application's bytesperline * set type to WRITER when caller calls enum_fmt_out * removed TODO as it has been done already * indentation * hopefully a bit more intelligent buffer-reallocation strategy * extra safety checks * print fourcc in fmt-enum * fallback formats for try_fmt_out * nicer format descriptions * use defines for size-limits * return EBUSY when trying to set fmt in CAPTURE mode when not ready * properly implement querycap * bytes_used in the mmap may be smaller than the page-size * some dummy functions for video-std settings * debug-level: 1 * terminate function call with ";" * getting rid of MEMLEAK warning (should be fixed now) * calculate bytesperline * only return dummy-format with G_FMT[out] when none has been set * nicer debugging * disable OVERLAY dummy * return 0-sized image by default * default max_buffers_number is 8 * getting rid of my prefix * pushed to version 0.0.2 * pumped to version 0.3 -- IOhannes m zmoelnig (gpg-key at iem) Sun, 10 Oct 2010 21:12:38 +0200 v4l2loopback (0.2) UNRELEASED; urgency=low [ IOhannes m zmölnig ] * acces /dev/video0 * variable number of pipes * nicer printout * proper cleanup * renamed "pipes" to "devices" * bumped version; added meself as co-author * removed files removed by "debian/rules clean" * removed examples * fixed debian/control debian/rules * postinst stuff * moved example into separate folder * MakefileS need not be executable * README, COPYING, AUTHORS * re-version to 0.2 * updated README * added Vasily Levin to the authors * removed debian stuff * added a README for the test * added vasily.levin to the authors * included linux/slab.h * license issues: this module is GPLv2 * added meself into the copyright header -- IOhannes m zmoelnig (gpg-key at iem) Sun, 10 Oct 2010 21:09:43 +0200 v4l2loopback (0.1) UNRELEASED; urgency=low [ gorinich.zmey ] * initial * first approach * removed autogenerated file * temproraly removed fps control and input from stdin handling * removed irrelevant changelog, changed readme * forgotten changes applued * modules.order delete * cleaned the mess with git-svn * added test file * added VIDIOC_G_PARM call * format handling improvment, current solution is a stub * temporarly removed mmap to keep code simple * compile fix * poll added, streaming started * small test refine * enum_input added * basic streaming, polish needed * first streaming working, mplayer gots a picture, yet crappy * readme add * readme rewrite * readme additions * mutex add * skype working * queue introduction, next step queue remove * first run is OK already * queues debugged * halfway of massive inner structure changes * compiles * pre multireader * style for linux kernel * indent * 80 width * module name changed and debianize start * debian * 2.6.28 support * almost works, just one bug left * debian * bug with two and more openers fixed * redebianized * removed files * license header add * freeing of unitialized pointer fixed, added nonblocking IO * sync with v4l-dvb tree * review responce * hans review * test improvments by Antonio Ospite * removed header * more small fixes [ Scott Maines ] * missing header for Fedora -- IOhannes m zmoelnig (gpg-key at iem) Sun, 10 Oct 2010 21:01:50 +0200 v4l2loopback-0.14.0/Kbuild000066400000000000000000000000311476030224200152250ustar00rootroot00000000000000obj-m := v4l2loopback.o v4l2loopback-0.14.0/Makefile000066400000000000000000000111021476030224200155310ustar00rootroot00000000000000ifneq ($(wildcard .gitversion),) # building a snapshot version V4L2LOOPBACK_SNAPSHOT_VERSION=$(patsubst v%,%,$(shell git describe --always --dirty 2>/dev/null || shell git describe --always 2>/dev/null || echo snapshot)) override KCPPFLAGS += -DSNAPSHOT_VERSION='"$(V4L2LOOPBACK_SNAPSHOT_VERSION)"' endif include Kbuild ifeq ($(KBUILD_MODULES),) KERNELRELEASE ?= `uname -r` KERNEL_DIR ?= /lib/modules/$(KERNELRELEASE)/build PWD := $(shell pwd) PREFIX ?= /usr/local BINDIR = $(PREFIX)/bin INCLUDEDIR = $(PREFIX)/include MANDIR = $(PREFIX)/share/man MAN1DIR = $(MANDIR)/man1 INSTALL = install INSTALL_PROGRAM = $(INSTALL) -p -m 755 INSTALL_DIR = $(INSTALL) -p -m 755 -d INSTALL_DATA = $(INSTALL) -m 644 MODULE_OPTIONS = devices=2 ########################################## # note on build targets # # module-assistant makes some assumptions about targets, namely # : must be present and build the module # .ko is not enough # install: must be present (and should only install the module) # # we therefore make a .PHONY alias to .ko # and remove utils-installation from 'install' # call 'make install-all' if you want to install everything ########################################## .PHONY: all install clean distclean .PHONY: install-all install-extra install-utils install-man install-headers .PHONY: modprobe v4l2loopback # 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 utils all: v4l2loopback.ko utils v4l2loopback: v4l2loopback.ko v4l2loopback.ko: @echo "Building v4l2-loopback driver..." $(MAKE) -C $(KERNEL_DIR) M=$(PWD) KCPPFLAGS="$(KCPPFLAGS)" modules install-all: install install-extra install: $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install @echo "" @echo "SUCCESS (if you got 'SSL errors' above, you can safely ignore them)" @echo "" install-extra: install-utils install-man install-headers install-utils: utils/v4l2loopback-ctl $(INSTALL_DIR) "$(DESTDIR)$(BINDIR)" $(INSTALL_PROGRAM) $< "$(DESTDIR)$(BINDIR)" install-man: man/v4l2loopback-ctl.1 $(INSTALL_DIR) "$(DESTDIR)$(MAN1DIR)" $(INSTALL_DATA) $< "$(DESTDIR)$(MAN1DIR)" install-headers: v4l2loopback.h $(INSTALL_DIR) "$(DESTDIR)$(INCLUDEDIR)/linux" $(INSTALL_DATA) $< "$(DESTDIR)$(INCLUDEDIR)/linux" 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 modprobe: v4l2loopback.ko -sudo chmod a+r $< -sudo modprobe videodev -sudo rmmod $< sudo insmod ./$< $(MODULE_OPTIONS) man/v4l2loopback-ctl.1: utils/v4l2loopback-ctl 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 v4l2loopback.h $(MAKE) -C utils V4L2LOOPBACK_SNAPSHOT_VERSION=$(V4L2LOOPBACK_SNAPSHOT_VERSION) .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 .PHONY: sign # try to read the default certificate/key from the dkms config dkms_framework=/etc/dkms/framework.conf /etc/dkms/framework.conf.d/*.conf KBUILD_SIGN_KEY=$(patsubst mok_signing_key=%,%,$(lastword $(shell grep -h -E "^[[:space:]]*mok_signing_key=" $(dkms_framework)))) KBUILD_SIGN_CERT=$(patsubst mok_certificate=%,%,$(lastword $(shell grep -h -E "^[[:space:]]*mok_certificate=" $(dkms_framework)))) ifeq ($(KBUILD_SIGN_PIN),) define usage_kbuildsignpin $(info ) $(info ++++++ If your certificate requires a password, pass it via the KBUILD_SIGN_PIN env-var!) $(info ++++++ E.g. using 'export KBUILD_SIGN_PIN; read -s -p "Passphrase for signing key $(KBUILD_SIGN_KEY): " KBUILD_SIGN_PIN; sudo --preserve-env=KBUILD_SIGN_PIN make sign') $(info ) endef endif define usage_kbuildsign sign: v4l2loopback.ko $(info ) $(info ++++++ To sign the $< module, you must set KBUILD_SIGN_KEY/KBUILD_SIGN_CERT to point to the signing key/certificate!) $(info ++++++ For your convenience, we try to read these variables as 'mok_signing_key' resp. 'mok_certificate' from $(dkms_framework)) $(call usage_kbuildsignpin) endef ifeq ($(wildcard $(KBUILD_SIGN_KEY)),) $(call usage_kbuildsign) else ifeq ($(wildcard $(KBUILD_SIGN_CERT)),) $(call usage_kbuildsign) else sign: v4l2loopback.ko $(call usage_kbuildsignpin) "$(KERNEL_DIR)"/scripts/sign-file sha256 $(KBUILD_SIGN_KEY) $(KBUILD_SIGN_CERT) $< endif endif # !kbuild v4l2loopback-0.14.0/Makefile.manual000066400000000000000000000042041476030224200170120ustar00rootroot00000000000000## DO NOT USE THIS MAKEFILE! ### this is created based on `make V=1 > make.log` and is used solely ### for creating build for static code analysis .PHONY: default KERNELRELEASE ?= `uname -r` KERNEL_SOURCE ?= /lib/modules/$(KERNELRELEASE)/build KERNEL_SOURCE_COMMON ?= /lib/modules/$(KERNELRELEASE)/source CFLAGS=-nostdinc -isystem /usr/lib/gcc/x86_64-linux-gnu/4.8/include -I$(KERNEL_SOURCE_COMMON)/arch/x86/include -I$(KERNEL_SOURCE)/arch/x86/include/generated -I$(KERNEL_SOURCE_COMMON)/include -I$(KERNEL_SOURCE)/include -I$(KERNEL_SOURCE_COMMON)/arch/x86/include/uapi -I$(KERNEL_SOURCE)/arch/x86/include/generated/uapi -I$(KERNEL_SOURCE_COMMON)/include/uapi -I$(KERNEL_SOURCE)/include/generated/uapi -include $(KERNEL_SOURCE_COMMON)/include/linux/kconfig.h -I. -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m64 -mno-mmx -mno-sse -mpreferred-stack-boundary=3 -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -maccumulate-outgoing-args -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_FXSAVEQ=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -Wframe-larger-than=2048 -Wno-unused-but-set-variable -fomit-frame-pointer -g -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -DCC_HAVE_ASM_GOTO default: $(CC) -Wp,-MD,./.v4l2loopback.o.d $(CFLAGS) -DMODULE -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(v4l2loopback)" -D"KBUILD_MODNAME=KBUILD_STR(v4l2loopback)" -c -o ./.tmp_v4l2loopback.o ./v4l2loopback.c $(CC) -Wp,-MD,./.v4l2loopback.mod.o.d $(CFLAGS) -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(v4l2loopback.mod)" -D"KBUILD_MODNAME=KBUILD_STR(v4l2loopback)" -DMODULE -c -o ./v4l2loopback.mod.o ./v4l2loopback.mod.c $(LD) -r -m elf_x86_64 -T $(KERNEL_SOURCE_COMMON)/scripts/module-common.lds --build-id -o ./v4l2loopback.ko ./v4l2loopback.o ./v4l2loopback.mod.o v4l2loopback-0.14.0/NEWS000066400000000000000000000137061476030224200146040ustar00rootroot00000000000000v4l2loopback-0.13.2 * V4L2 UAPI compliance in format negotation and stream activation * Fix FIFO management * Fix poll() -- IOhannes m zmölnig (Debian/GNU) Fri, 28 Feb 2025 09:48:06 +0100 v4l2loopback-0.13.2 * Fix compilation on 32bit architectures (i386, armhf,...) -- IOhannes m zmölnig (Debian/GNU) Fri, 24 May 2024 11:30:51 +0200 v4l2loopback-0.13.1 * Avoid building utils with dkms * Fix version number in module -- IOhannes m zmölnig (Debian/GNU) Tue, 19 Mar 2024 22:15:34 +0100 v4l2loopback-0.13.0 * Dynamic device management -- IOhannes m zmölnig (Debian/GNU) Tue, 19 Mar 2024 17:11:08 +0100 v4l2loopback-0.12.3 * Fixed compat with kernel 5.4 -- IOhannes m zmölnig Fri, 06 Dec 2019 18:28:29 +0100 v4l2loopback-0.12.1 * Fixed compat with kernel 5.0 * Replace v4l2_get_timestamp with ktime_get_ts(64) for linux-5.1 compat -- IOhannes m zmölnig Mon, 02 Jul 2018 12:27:29 +0200 v4l2loopback-0.12.0 * Fixed compat with kernel 4.16 * NV12 support * 8bit Bayer support -- IOhannes m zmölnig Mon, 02 Jul 2018 12:27:29 +0200 v4l2loopback-0.11.0 * Adapted to new kernel timer API * VP9 & HEVC support * Hacks to work around issues with Google Chrome and GStreamer-0.10 -- IOhannes m zmölnig Tue, 06 Mar 2018 10:05:11 +0100 v4l2loopback-0.10.0 * More consistent device names * Disabled exclusive_caps by *default* * Removed deprecated current_norm * Fixed a number of bugs (esp. when working with GStreamer-1.0) -- IOhannes m zmölnig Fri, 02 Dec 2016 22:00:27 +0100 v4l2loopback-0.9.1 - Fixed module version -- IOhannes m zmölnig Wed, 03 Jun 2015 19:47:23 +0200 v4l2loopback-0.9.0 - more formats - kernel compatibility fixed issues with kernel up to 4.0 -- IOhannes m zmölnig Tue, 02 Jun 2015 19:58:39 +0200 v4l2loopback-0.8.0 - DKMS config - 'card_label' option to manually set device names - fixes in format handling -- IOhannes m zmölnig Tue, 10 Dec 2013 18:12:15 +0100 v4l2loopback-0.7.1 - kernel compatibility fixed issues with kernel-3.11 fixed regression with kernel<2.6.29 -- IOhannes m zmoelnig Mon, 16 Sep 2013 09:55:51 +0200 v4l2loopback-0.7.0 - experimental'exclusive_caps' mode that only reports CAPTURE/OUTPUT capabilities exclusively (support for Chromium/WebRTC) - disabled fake STDs (e.g. ffmpeg support) -- IOhannes m zmoelnig Fri, 07 Jun 2013 11:08:10 +0200 v4l2loopback-0.6.3 - kernel compatibility fixed issues with kernel-3.7 - unique "Card Type" Names (fixes broken clients like flash) - create unique ID in "bus_info" field (fixes Firefox/WebRTC support) -- IOhannes m zmoelnig 2013-02-05 v4l2loopback-0.6.2 - kernel compatibility fixed issues with older kernels (<2.6.37) fixed issues with kernel-3.6.1 - distinct device names (fixes broken clients like google+) -- IOhannes m zmoelnig 2012-10-23 v4l2loopback-0.6.1 - Debian specific build-fixes -- IOhannes m zmoelnig 2012-04-27 v4l2loopback-0.6.0 - support fallback images (in case there is no live-stream) - utilities to interact with v4l2loopback devices -- IOhannes m zmoelnig 2012-04-27 v4l2loopback-0.5.0 - module parameters 'video_nr' manually sets device id(s) - device attributes via sysfs: /sys/devices/virtual/video4linux/video*/ 'max_openers' per device 'fourcc' queries currently selected format - ioctl fixes avoid the BIG KERNEL LOCK ENUM_FRAMEINTERVAL implementation fixed ENUM_FRAMESIZES (fixes skype compatibilitiy) fixes to buffer queue with multiple consumers minor fixes to handling of standards, output enumeration and output formats - more (and better) debugging output - fixed memleaks in the examples -- IOhannes m zmoelnig Tue, 27 Dec 2011 19:01:25 +0100 v4l2loopback-0.4.1 - yuv4mpeg producer example - kernel compatibility fixed issues with kernels<2.6.32 fixed issues with kernel-3.1 -- IOhannes m zmoelnig Thu, 24 Nov 2011 18:11:01 +0100 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 this allows playback with players that need monotonous timestamps (e.g. ffmpeg) - cleaned up code -- IOhannes m zmoelnig Tue, 29 Mar 2011 14:26:10 +0200 v4l2loopback-0.3 - tested writers: GStreamer's normal "v4l2sink" element (from plugins-good) GStreamer's "v4l2loopback" (deprecated by v4l2sink) pd/Gem(0.93svn) - tested readers: GStreamer's "v4l2src" pd/Gem(0.92) vlc xawtv (depending on image format) mplayer (with correct image format, e.g. rgb32) - code documentation - added v4l2-documentation for easier offline programming - added a lot of ioctls to meet the v4l2 standard -- IOhannes m zmoelnig Sun, 10 Oct 2010 21:18:22 +0200 v4l2loopback-0.2 - Linux 2.6.32 & 2.6.35 - tested with pd/Gem(0.93svn) and GStreamer's "v4l2loopback" - add support for multiple video devices - README, COPYING, AUTHORS - re-organized file layout -- IOhannes m zmoelnig Tue Sep 28 09:46:47 CEST 2010 v4l2loopback-0.1 - Linux 2.6.28 - Skype support - support for GStreamer's "v4l2loopback" element - test application - README - dev: nonblocking I/O - dev: VIDIOC_G_PARM -- Gorinich Zmey Wed Jun 16 12:19:59 CEST 2010 v4l2loopback-0.0 - initial -- Vasily Levin Tue Feb 3 10:56:28 CET 2009 v4l2loopback-0.14.0/README.md000066400000000000000000000356421476030224200153670ustar00rootroot00000000000000v4l2loopback - 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 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 Skype video... 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 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. 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 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 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: $ make 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 Afterwards re-run `make` to do the actual build. ## Build for a different kernel By default a simple `make` will (try to) build the module for the currently active kernel (as determined by `uname -r`). If you want to build for a different kernel, youcan do so by providing the kernel version via the `KERNELRELEASE` variable: $ make KERNELRELEASE=6.11.7-amd64 (Of course you must have the kernel-headers for the specified kernel available in the `/lib/modules/${KERNELRELEASE}/build/` directory.) # INSTALL 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: $ make && sudo make install $ sudo depmod -a If your system lacks "sudo", do: $ make $ su (enter root password) # make install # depmod -a # exit (The `depmod -a` call will re-calculate module dependencies, in order to 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 : # modprobe v4l2loopback Using `sudo` use: $ sudo modprobe v4l2loopback 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 application. 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" option, when loading the module; e.g. # modprobe v4l2loopback devices=4 Will give you 4 loopback devices (e.g. `/dev/video1` ... `/dev/video5`) 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`) # 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: - `/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: # modprobe v4l2loopback exclusive_caps=1 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*/` also there are some V4L2 controls that you can list with $ 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 - `sustain_framerate(0/1)`: if set to 1, nominal device fps will be ensured by means of frame duplication when needed - `timeout(integer)`: if >0, will cause a timeout picture (a null frame, by default) to be displayed after (value) msecs of missing input - `timeout_image_io(0/1)`: if set to 1, the next opener will write to timeout frame buffer # CHANGING THE RUNTIME BEHAVIOUR ## FORCING FPS $ v4l2loopback-ctl set-fps /dev/video0 25 or $ echo '@100' | sudo tee /sys/devices/virtual/video4linux/video0/format ## FORCING FORMAT $ 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`) are 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. ~~~ $ v4l2-ctl -d /dev/video0 -c timeout=3000 ~~~ _Requires GStreamer 1.0, version >= 1.16_: You can 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 (in the following example; 3000ms): ~~~ $ v4l2loopback-ctl set-timeout-image -t 3000 /dev/video0 service-unavailable.png ~~~ ## 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 it still works. 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: - >= 5.0.0 should work - >= 4.0.0 should work - >= 3.0.0 might work - << 3.0.0 may work (has not been tested in ages) - <= 2.6.37 will definitely NOT work # DISTRIBUTIONS 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 Ubuntu (starting with natty). The most convenient way is to install the package "v4l2loopback-dkms": # apt-get install v4l2loopback-dkms 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): # 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 | _Note_: Using this method will _NOT_ install the `v4l2loopback-ctl` tool; do so manually via `cd utils && make && sudo make install`. # 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. This is especially convenient when `v4l2loopback` is installed with DKMS or with a package provided by your Linux distribution: ~~~ v4l2loopback ~~~ If needed, one can specify default module options by creating `/etc/modprobe.d/v4l2loopback.conf` in the following form instead: ~~~ options v4l2loopback video_nr=3,4,7 card_label="device number 3,the number four,the last one" ~~~ 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 http://github.com/umlaeute/v4l2loopback/. # LICENSE/COPYING - Copyright (c) 2010-2023 IOhannes m zmoelnig - Copyright (c) 2016 Gavin Qiu - Copyright (c) 2016 George Chriss - Copyright (c) 2014-2015 Tasos Sahanidis - Copyright (c) 2012-2015 Yusuke Ohshima - Copyright (c) 2015 Kurt Kiefer - Copyright (c) 2015 Michel Promonet - Copyright (c) 2015 Paul Brook - Copyright (c) 2015 Tom Zerucha - Copyright (c) 2013 Aidan Thornton - Copyright (c) 2013 Anatolij Gustschin - Copyright (c) 2012 Ted Mielczarek - Copyright (c) 2012 Anton Novikov - Copyright (c) 2011 Stefan Diewald - Copyright (c) 2010 Scott Maines - Copyright (c) 2009 Gorinich Zmey - Copyright (c) 2005-2009 Vasily Levin This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . v4l2loopback-0.14.0/SECURITY.md000066400000000000000000000002441476030224200156670ustar00rootroot00000000000000# Security Policy ## Reporting a Vulnerability You can report vulnerability issues by opening a *confidential issue* on https://git.iem.at/zmoelnig/v4l2loopback/ v4l2loopback-0.14.0/TODO000066400000000000000000000013521476030224200145670ustar00rootroot00000000000000TODO for v4l2loopback in no specific order - fix all bugs :-) - improve buffering (salsaman) - allow USERPTR buffers - allow to use the device without streaming i/o - pass 'v4l2-compliance' tests currently failing are: VIDIOC_G/S_PRIORITY VIDIOC_LOG_STATUS VIDIOC_ENUMAUDIO VIDIOC_G/S_AUDIO VIDIOC_ENUMAUDOUT VIDIOC_G/S/ENUMOUTPUT VIDIOC_G/S_CTRL VIDIOC_G/S/TRY_EXT_CTRLS VIDIOC_ENUM/G/S/QUERY_STD VIDIOC_ENUM/G/S/QUERY_DV_PRESETS VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS VIDIOC_G_FBUF VIDIOC_G_FMT VIDIOC_G_SLICED_VBI_CAP - it would be nice to have a way to communicate format requests from the consumer to the producer (though i see no way how to do that) - provide more producers for more colorspaces in the examples v4l2loopback-0.14.0/currentversion.sh000077500000000000000000000002171476030224200175250ustar00rootroot00000000000000#!/bin/sh num() { grep "^#define V4L2LOOPBACK_VERSION_$1 " v4l2loopback.h | awk '{print $3}' } echo $(num MAJOR).$(num MINOR).$(num BUGFIX) v4l2loopback-0.14.0/dkms.conf000066400000000000000000000014231476030224200157030ustar00rootroot00000000000000PACKAGE_NAME="v4l2loopback" PACKAGE_VERSION="0.14.0" if [ -f $kernel_source_dir/.config ]; then . $kernel_source_dir/.config if ! { echo "$kernelver"; echo 5.18; } | sort -V -C; then # for linux>=5.18, CONFIG_VIDEO_V4L2 has been renamed to CONFIG_VIDEO_DEV if [ "${CONFIG_VIDEO_DEV:-n}" = "n" ]; then BUILD_EXCLUSIVE_KERNEL="REQUIRES CONFIG_VIDEO_DEV" fi else if [ "${CONFIG_VIDEO_V4L2:-n}" = "n" ]; then BUILD_EXCLUSIVE_KERNEL="REQUIRES CONFIG_VIDEO_V4L2" fi fi fi # Items below here should not have to change with each driver version MAKE[0]="make KERNEL_DIR=${kernel_source_dir} v4l2loopback" CLEAN="make clean" BUILT_MODULE_NAME[0]="$PACKAGE_NAME" DEST_MODULE_LOCATION[0]="/extra" AUTOINSTALL="yes" v4l2loopback-0.14.0/doc/000077500000000000000000000000001476030224200146435ustar00rootroot00000000000000v4l2loopback-0.14.0/doc/docs.txt000066400000000000000000000004551476030224200163400ustar00rootroot00000000000000obsolete V4L2-API: http://linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/spec-single/v4l2.html (an even more obsolete version 0.24 of this document used to be included here, but is no longer) current V4L2-API: http://linuxtv.org/downloads/v4l-dvb-apis/ (unfortunately this is multi-page) v4l2loopback-0.14.0/doc/kernel_debugging.txt000066400000000000000000000011171476030224200206770ustar00rootroot00000000000000some hints how to debug kernel panics https://wiki.ubuntu.com/Kernel/KernelDebuggingTricks basically it is: - run tests in a virtual machine (i use VirtualBox) - configure the vm to have a serial output VirtualBox-Settings/Serial Ports enable serial port COM1 port mode: Raw File file path: /tmp/vbox_serial.log - configure the vm's kernel to log to the kernel: add "console=tty console=ttyS0,9600" to the kernel-parms (i put that into grub) - reboot - raise the vm's kernel console verbosity: echo 7 > /proc/sys/kernel/printk - run - examine /tmp/vbox_serial.log on the host v4l2loopback-0.14.0/doc/makeformats.sh000077500000000000000000000010531476030224200175120ustar00rootroot00000000000000#!/bin/sh ## usage: # echo "V4L2_PIX_FMT_MPEG4 MPEG-4 part 2 ES" | $0 ## normally it's more like: # cat /usr/include/linux/videodev2.h \ # | grep "define V4L2_PIX_FMT" \ # | sed -e "s|^#define ||" -e "s|v4l2_fourcc('.', '.', '.', '.')||" -e 's|/\*||' -e 's|\*/||' \ # | $0 DEPTH=0 FLAGS=0 while read FOURCC NAME do echo "#ifdef ${FOURCC}" echo "{" echo " .name = \"${NAME}\"," echo " .fourcc = ${FOURCC}," echo " .depth = ${DEPTH}," echo " .flags = ${FLAGS}," echo " }," echo "#endif /* ${FOURCC} */" done v4l2loopback-0.14.0/doc/missingformats.h000066400000000000000000000160501476030224200200630ustar00rootroot00000000000000#ifdef V4L2_PIX_FMT_Y10BPACK }, { .name = "10 bpp Greyscale bit-packed", .fourcc = V4L2_PIX_FMT_Y10BPACK, .depth = 10, .flags = 0, #endif /* V4L2_PIX_FMT_Y10BPACK */ #ifdef V4L2_PIX_FMT_PAL8 }, { .name = "8 bpp 8-bit palette", .fourcc = V4L2_PIX_FMT_PAL8, .depth = 8, .flags = 0, #endif /* V4L2_PIX_FMT_PAL8 */ #ifdef V4L2_PIX_FMT_UV8 }, { .name = "8 bpp UV 4:4", .fourcc = V4L2_PIX_FMT_UV8, .depth = 8, .flags = 0, #endif /* V4L2_PIX_FMT_UV8 */ #ifdef V4L2_PIX_FMT_HI240 }, { .name = "8 bpp 8-bit color ", .fourcc = V4L2_PIX_FMT_HI240, .depth = 8, .flags = FORMAT_FLAGS_PLANAR, #endif /* V4L2_PIX_FMT_HI240 */ #ifdef V4L2_PIX_FMT_HM12 }, { .name = "8 bpp YUV 4:2:0 16x16 macroblocks", .fourcc = V4L2_PIX_FMT_HM12, .depth = 8, .flags = FORMAT_FLAGS_PLANAR, #endif /* V4L2_PIX_FMT_HM12 */ #ifdef V4L2_PIX_FMT_M420 }, { .name = "12 bpp YUV 4:2:0 2 lines y, 1 line uv interleaved", .fourcc = V4L2_PIX_FMT_M420, .depth = 12, .flags = FORMAT_FLAGS_PLANAR, #endif /* V4L2_PIX_FMT_M420 */ #ifdef V4L2_PIX_FMT_NV12 }, { .name = "12 bpp Y/CbCr 4:2:0 ", .fourcc = V4L2_PIX_FMT_NV12, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_NV12 */ #ifdef V4L2_PIX_FMT_NV21 }, { .name = "12 bpp Y/CrCb 4:2:0 ", .fourcc = V4L2_PIX_FMT_NV21, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_NV21 */ #ifdef V4L2_PIX_FMT_NV16 }, { .name = "16 bpp Y/CbCr 4:2:2 ", .fourcc = V4L2_PIX_FMT_NV16, .depth = 16, .flags = 0, #endif /* V4L2_PIX_FMT_NV16 */ #ifdef V4L2_PIX_FMT_NV61 }, { .name = "16 bpp Y/CrCb 4:2:2 ", .fourcc = V4L2_PIX_FMT_NV61, .depth = 16, .flags = 0, #endif /* V4L2_PIX_FMT_NV61 */ #ifdef V4L2_PIX_FMT_NV24 }, { .name = "24 bpp Y/CbCr 4:4:4 ", .fourcc = V4L2_PIX_FMT_NV24, .depth = 24, .flags = 0, #endif /* V4L2_PIX_FMT_NV24 */ #ifdef V4L2_PIX_FMT_NV42 }, { .name = "24 bpp Y/CrCb 4:4:4 ", .fourcc = V4L2_PIX_FMT_NV42, .depth = 24, .flags = 0, #endif /* V4L2_PIX_FMT_NV42 */ #ifdef V4L2_PIX_FMT_NV12M }, { .name = "12 bpp Y/CbCr 4:2:0 ", .fourcc = V4L2_PIX_FMT_NV12M, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_NV12M */ #ifdef V4L2_PIX_FMT_NV21M }, { .name = "21 bpp Y/CrCb 4:2:0 ", .fourcc = V4L2_PIX_FMT_NV21M, .depth = 21, .flags = 0, #endif /* V4L2_PIX_FMT_NV21M */ #ifdef V4L2_PIX_FMT_NV16M }, { .name = "16 bpp Y/CbCr 4:2:2 ", .fourcc = V4L2_PIX_FMT_NV16M, .depth = 16, .flags = 0, #endif /* V4L2_PIX_FMT_NV16M */ #ifdef V4L2_PIX_FMT_NV61M }, { .name = "16 bpp Y/CrCb 4:2:2 ", .fourcc = V4L2_PIX_FMT_NV61M, .depth = 16, .flags = 0, #endif /* V4L2_PIX_FMT_NV61M */ #ifdef V4L2_PIX_FMT_NV12MT }, { .name = "12 bpp Y/CbCr 4:2:0 64x32 macroblocks", .fourcc = V4L2_PIX_FMT_NV12MT, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_NV12MT */ #ifdef V4L2_PIX_FMT_NV12MT_16X16 }, { .name = "12 bpp Y/CbCr 4:2:0 16x16 macroblocks", .fourcc = V4L2_PIX_FMT_NV12MT_16X16, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_NV12MT_16X16 */ #ifdef V4L2_PIX_FMT_YUV420M }, { .name = "12 bpp YUV420 planar", .fourcc = V4L2_PIX_FMT_YUV420M, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_YUV420M */ #ifdef V4L2_PIX_FMT_YVU420M }, { .name = "12 bpp YVU420 planar", .fourcc = V4L2_PIX_FMT_YVU420M, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_YVU420M */ #ifdef V4L2_PIX_FMT_SBGGR8 }, { .name = "8 bpp BGBG.. GRGR..", .fourcc = V4L2_PIX_FMT_SBGGR8, .depth = 8, .flags = 0, #endif /* V4L2_PIX_FMT_SBGGR8 */ #ifdef V4L2_PIX_FMT_SGBRG8 }, { .name = "8 bpp GBGB.. RGRG..", .fourcc = V4L2_PIX_FMT_SGBRG8, .depth = 8, .flags = 0, #endif /* V4L2_PIX_FMT_SGBRG8 */ #ifdef V4L2_PIX_FMT_SGRBG8 }, { .name = "8 bpp GRGR.. BGBG..", .fourcc = V4L2_PIX_FMT_SGRBG8, .depth = 8, .flags = 0, #endif /* V4L2_PIX_FMT_SGRBG8 */ #ifdef V4L2_PIX_FMT_SRGGB8 }, { .name = "8 bpp RGRG.. GBGB..", .fourcc = V4L2_PIX_FMT_SRGGB8, .depth = 8, .flags = 0, #endif /* V4L2_PIX_FMT_SRGGB8 */ #ifdef V4L2_PIX_FMT_SBGGR10 }, { .name = "10 bpp BGBG.. GRGR..", .fourcc = V4L2_PIX_FMT_SBGGR10, .depth = 10, .flags = 0, #endif /* V4L2_PIX_FMT_SBGGR10 */ #ifdef V4L2_PIX_FMT_SGBRG10 }, { .name = "10 bpp GBGB.. RGRG..", .fourcc = V4L2_PIX_FMT_SGBRG10, .depth = 10, .flags = 0, #endif /* V4L2_PIX_FMT_SGBRG10 */ #ifdef V4L2_PIX_FMT_SGRBG10 }, { .name = "10 bpp GRGR.. BGBG..", .fourcc = V4L2_PIX_FMT_SGRBG10, .depth = 10, .flags = 0, #endif /* V4L2_PIX_FMT_SGRBG10 */ #ifdef V4L2_PIX_FMT_SRGGB10 }, { .name = "10 bpp RGRG.. GBGB..", .fourcc = V4L2_PIX_FMT_SRGGB10, .depth = 10, .flags = 0, #endif /* V4L2_PIX_FMT_SRGGB10 */ #ifdef V4L2_PIX_FMT_SBGGR12 }, { .name = "12 bpp BGBG.. GRGR..", .fourcc = V4L2_PIX_FMT_SBGGR12, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_SBGGR12 */ #ifdef V4L2_PIX_FMT_SGBRG12 }, { .name = "12 bpp GBGB.. RGRG..", .fourcc = V4L2_PIX_FMT_SGBRG12, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_SGBRG12 */ #ifdef V4L2_PIX_FMT_SGRBG12 }, { .name = "12 bpp GRGR.. BGBG..", .fourcc = V4L2_PIX_FMT_SGRBG12, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_SGRBG12 */ #ifdef V4L2_PIX_FMT_SRGGB12 }, { .name = "12 bpp RGRG.. GBGB..", .fourcc = V4L2_PIX_FMT_SRGGB12, .depth = 12, .flags = 0, #endif /* V4L2_PIX_FMT_SRGGB12 */ #ifdef V4L2_PIX_FMT_SBGGR10ALAW8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SBGGR10ALAW8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SBGGR10ALAW8 */ #ifdef V4L2_PIX_FMT_SGBRG10ALAW8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SGBRG10ALAW8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SGBRG10ALAW8 */ #ifdef V4L2_PIX_FMT_SGRBG10ALAW8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SGRBG10ALAW8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SGRBG10ALAW8 */ #ifdef V4L2_PIX_FMT_SRGGB10ALAW8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SRGGB10ALAW8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SRGGB10ALAW8 */ #ifdef V4L2_PIX_FMT_SBGGR10DPCM8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SBGGR10DPCM8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SBGGR10DPCM8 */ #ifdef V4L2_PIX_FMT_SGBRG10DPCM8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SGBRG10DPCM8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SGBRG10DPCM8 */ #ifdef V4L2_PIX_FMT_SGRBG10DPCM8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SGRBG10DPCM8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SGRBG10DPCM8 */ #ifdef V4L2_PIX_FMT_SRGGB10DPCM8 }, { .name = "", .fourcc = V4L2_PIX_FMT_SRGGB10DPCM8, .depth = 0, .flags = 0, #endif /* V4L2_PIX_FMT_SRGGB10DPCM8 */ #ifdef V4L2_PIX_FMT_SBGGR16 }, { .name = "16 bpp BGBG.. GRGR..", .fourcc = V4L2_PIX_FMT_SBGGR16, .depth = 16, .flags = 0, #endif /* V4L2_PIX_FMT_SBGGR16 */ v4l2loopback-0.14.0/doc/v4l2_formats.txt000066400000000000000000000111111476030224200177210ustar00rootroot00000000000000/* RGB formats */ V4L2_PIX_FMT_RGB332 /* 8 RGB-3-3-2 */ V4L2_PIX_FMT_RGB444 /* 16 xxxxrrrr ggggbbbb */ V4L2_PIX_FMT_RGB555 /* 16 RGB-5-5-5 */ V4L2_PIX_FMT_RGB565 /* 16 RGB-5-6-5 */ V4L2_PIX_FMT_RGB555X /* 16 RGB-5-5-5 BE */ V4L2_PIX_FMT_RGB565X /* 16 RGB-5-6-5 BE */ V4L2_PIX_FMT_BGR666 /* 18 BGR-6-6-6 */ V4L2_PIX_FMT_BGR24 /* 24 BGR-8-8-8 */ V4L2_PIX_FMT_RGB24 /* 24 RGB-8-8-8 */ V4L2_PIX_FMT_BGR32 /* 32 BGR-8-8-8-8 */ V4L2_PIX_FMT_RGB32 /* 32 RGB-8-8-8-8 */ /* Grey formats */ V4L2_PIX_FMT_GREY /* 8 Greyscale */ V4L2_PIX_FMT_Y4 /* 4 Greyscale */ V4L2_PIX_FMT_Y6 /* 6 Greyscale */ V4L2_PIX_FMT_Y10 /* 10 Greyscale */ V4L2_PIX_FMT_Y12 /* 12 Greyscale */ V4L2_PIX_FMT_Y16 /* 16 Greyscale */ /* Grey bit-packed formats */ V4L2_PIX_FMT_Y10BPACK /* 10 Greyscale bit-packed */ /* Palette formats */ V4L2_PIX_FMT_PAL8 /* 8 8-bit palette */ /* Chrominance formats */ V4L2_PIX_FMT_UV8 /* 8 UV 4:4 */ /* Luminance+Chrominance formats */ V4L2_PIX_FMT_YVU410 /* 9 YVU 4:1:0 */ V4L2_PIX_FMT_YVU420 /* 12 YVU 4:2:0 */ V4L2_PIX_FMT_YUYV /* 16 YUV 4:2:2 */ V4L2_PIX_FMT_YYUV /* 16 YUV 4:2:2 */ V4L2_PIX_FMT_YVYU /* 16 YVU 4:2:2 */ V4L2_PIX_FMT_UYVY /* 16 YUV 4:2:2 */ V4L2_PIX_FMT_VYUY /* 16 YUV 4:2:2 */ V4L2_PIX_FMT_YUV422P /* 16 YVU422 planar */ V4L2_PIX_FMT_YUV411P /* 16 YVU411 planar */ V4L2_PIX_FMT_Y41P /* 12 YUV 4:1:1 */ V4L2_PIX_FMT_YUV444 /* 16 xxxxyyyy uuuuvvvv */ V4L2_PIX_FMT_YUV555 /* 16 YUV-5-5-5 */ V4L2_PIX_FMT_YUV565 /* 16 YUV-5-6-5 */ V4L2_PIX_FMT_YUV32 /* 32 YUV-8-8-8-8 */ V4L2_PIX_FMT_YUV410 /* 9 YUV 4:1:0 */ V4L2_PIX_FMT_YUV420 /* 12 YUV 4:2:0 */ V4L2_PIX_FMT_HI240 /* 8 8-bit color */ V4L2_PIX_FMT_HM12 /* 8 YUV 4:2:0 16x16 macroblocks */ V4L2_PIX_FMT_M420 /* 12 YUV 4:2:0 2 lines y, 1 line uv interleaved */ /* two planes -- one Y, one Cr + Cb interleaved */ V4L2_PIX_FMT_NV12 /* 12 Y/CbCr 4:2:0 */ V4L2_PIX_FMT_NV21 /* 12 Y/CrCb 4:2:0 */ V4L2_PIX_FMT_NV16 /* 16 Y/CbCr 4:2:2 */ V4L2_PIX_FMT_NV61 /* 16 Y/CrCb 4:2:2 */ V4L2_PIX_FMT_NV24 /* 24 Y/CbCr 4:4:4 */ V4L2_PIX_FMT_NV42 /* 24 Y/CrCb 4:4:4 */ /* two non contiguous planes - one Y, one Cr + Cb interleaved */ V4L2_PIX_FMT_NV12M /* 12 Y/CbCr 4:2:0 */ V4L2_PIX_FMT_NV21M /* 21 Y/CrCb 4:2:0 */ V4L2_PIX_FMT_NV16M /* 16 Y/CbCr 4:2:2 */ V4L2_PIX_FMT_NV61M /* 16 Y/CrCb 4:2:2 */ V4L2_PIX_FMT_NV12MT /* 12 Y/CbCr 4:2:0 64x32 macroblocks */ V4L2_PIX_FMT_NV12MT_16X16 /* 12 Y/CbCr 4:2:0 16x16 macroblocks */ /* three non contiguous planes - Y, Cb, Cr */ V4L2_PIX_FMT_YUV420M /* 12 YUV420 planar */ V4L2_PIX_FMT_YVU420M /* 12 YVU420 planar */ /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */ V4L2_PIX_FMT_SBGGR8 /* 8 BGBG.. GRGR.. */ V4L2_PIX_FMT_SGBRG8 /* 8 GBGB.. RGRG.. */ V4L2_PIX_FMT_SGRBG8 /* 8 GRGR.. BGBG.. */ V4L2_PIX_FMT_SRGGB8 /* 8 RGRG.. GBGB.. */ V4L2_PIX_FMT_SBGGR10 /* 10 BGBG.. GRGR.. */ V4L2_PIX_FMT_SGBRG10 /* 10 GBGB.. RGRG.. */ V4L2_PIX_FMT_SGRBG10 /* 10 GRGR.. BGBG.. */ V4L2_PIX_FMT_SRGGB10 /* 10 RGRG.. GBGB.. */ V4L2_PIX_FMT_SBGGR12 /* 12 BGBG.. GRGR.. */ 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 /* 10bit raw bayer DPCM compressed to 8 bits */ 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... */ V4L2_PIX_FMT_SBGGR16 /* 16 BGBG.. GRGR.. */ /* compressed formats */ V4L2_PIX_FMT_MJPEG /* Motion-JPEG */ V4L2_PIX_FMT_JPEG /* JFIF JPEG */ V4L2_PIX_FMT_DV /* 1394 */ V4L2_PIX_FMT_MPEG /* MPEG-1/2/4 Multiplexed */ V4L2_PIX_FMT_H264 /* H264 with start codes */ V4L2_PIX_FMT_H264_NO_SC /* H264 without start codes */ V4L2_PIX_FMT_H264_MVC /* H264 MVC */ V4L2_PIX_FMT_H263 /* H263 */ V4L2_PIX_FMT_MPEG1 /* MPEG-1 ES */ V4L2_PIX_FMT_MPEG2 /* MPEG-2 ES */ V4L2_PIX_FMT_MPEG4 /* MPEG-4 part 2 ES */ V4L2_PIX_FMT_XVID /* Xvid */ V4L2_PIX_FMT_VC1_ANNEX_G /* SMPTE 421M Annex G compliant stream */ V4L2_PIX_FMT_VC1_ANNEX_L /* SMPTE 421M Annex L compliant stream */ V4L2_PIX_FMT_VP8 /* VP8 */ v4l2loopback-0.14.0/examples/000077500000000000000000000000001476030224200157145ustar00rootroot00000000000000v4l2loopback-0.14.0/examples/Makefile000066400000000000000000000003071476030224200173540ustar00rootroot00000000000000TARGETS=test yuv4mpeg_to_v4l2 ondemandcam yuv420_infiniteloop .PHONY: all clean all: $(TARGETS) ondemandcam: ondemandcam.c gcc -o ondemandcam ondemandcam.c -lrt -lpthread clean: -rm $(TARGETS) v4l2loopback-0.14.0/examples/README000066400000000000000000000041711476030224200165770ustar00rootroot00000000000000v4l2loopback tests ================== test ---- this small sample application will write an image into a v4l2loopback device. the image will be 640x480 pixels in UYVY colorspace (and since all pixels are set to "0" it will be green). the video-device defaults to /dev/video1 but you can specify another device on the commandline USAGE: $ make test $ ./test /dev/video2 & $ xawtv -c /dev/video2 if you want to use another device you need to modify the VIDEO_DEVICE define at the beginning of the code and recompile. yuv4mpeg_to_v4l2 ---------------- Copyright (C) 2011 Eric C. Cooper Example using mplayer as a producer for the v4l2loopback driver: $ mkfifo /tmp/pipe $ ./yuv4mpeg_to_v4l2 < /tmp/pipe & $ mplayer movie.mp4 -vo yuv4mpeg:file=/tmp/pipe ondemandcam ----------- Copyright 2015, tz@execpc.com, GPLv3. This will wait until something connects to pull frames then sends them. It uses two threads and semaphores to do the testing. It can setup and teardown the frame source. The example just sends a different color each second. It is 80x60 (it is a skeleton for hardware with this resolution) $ make ondemandcam $ ondemandcam /dev/videoX & # where X is the v4l2loopback device It can be viewed with: $ vlc v4l2:///dev/videoX # X is same device as above 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 akiyo_qcif.yuv from [here](http://trace.eas.asu.edu/yuv/akiyo/akiyo_qcif.7z). Usage $ make yuv420_infiniteloop $ ./yuv420_infiniteloop devicename yuvpath width height fps Example with akiyo_qcif.yuv, width 176, height 144 and fps 30: $ make yuv420_infiniteloop $ ./yuv420_infiniteloop /dev/video1 akiyo_qcif.yuv 176 144 30 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 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). v4l2loopback-0.14.0/examples/ondemandcam.c000066400000000000000000000057161476030224200203370ustar00rootroot00000000000000#include #include #include #include /* low-level i/o */ #include #include #include #include #include #include #include #include #include #include #include static char *v4l2dev = "/dev/video1"; static int v4l2sink = -1; static int width = 80; //640; // Default for Flash static int height = 60; //480; // Default for Flash static char *vidsendbuf = NULL; static int vidsendsiz = 0; static void init_device() { } static void grab_frame() { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); memset( vidsendbuf, 0, 3); switch( ts.tv_sec & 3 ) { case 0: vidsendbuf[0] = 255; break; case 1: vidsendbuf[0] = 255; vidsendbuf[1] = 255; break; case 2: vidsendbuf[1] = 255; break; case 3: vidsendbuf[2] = 255; break; } memcpy( vidsendbuf+3, vidsendbuf, vidsendsiz-3 ); } static void stop_device() { } static void open_vpipe() { v4l2sink = open(v4l2dev, O_WRONLY); if (v4l2sink < 0) { fprintf(stderr, "Failed to open v4l2sink device. (%s)\n", strerror(errno)); exit(-2); } // setup video for proper format struct v4l2_format v; int t; v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; t = ioctl(v4l2sink, VIDIOC_G_FMT, &v); if( t < 0 ) exit(t); v.fmt.pix.width = width; v.fmt.pix.height = height; v.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; vidsendsiz = width * height * 3; v.fmt.pix.sizeimage = vidsendsiz; t = ioctl(v4l2sink, VIDIOC_S_FMT, &v); if( t < 0 ) exit(t); vidsendbuf = malloc( vidsendsiz ); } static pthread_t sender; static sem_t lock1,lock2; static void *sendvid(void *v) { for (;;) { sem_wait(&lock1); if (vidsendsiz != write(v4l2sink, vidsendbuf, vidsendsiz)) exit(-1); sem_post(&lock2); } } int main(int argc, char **argv) { struct timespec ts; if( argc == 2 ) v4l2dev = argv[1]; open_vpipe(); // open and lock response if (sem_init(&lock2, 0, 1) == -1) exit(-1); sem_wait(&lock2); if (sem_init(&lock1, 0, 1) == -1) exit(-1); pthread_create(&sender, NULL, sendvid, NULL); for (;;) { // wait until a frame can be written fprintf( stderr, "Waiting for sink\n" ); sem_wait(&lock2); // setup source init_device(); // open and setup SPI for (;;) { grab_frame(); // push it out sem_post(&lock1); clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 2; // wait for it to get written (or is blocking) if (sem_timedwait(&lock2, &ts)) break; } stop_device(); // close SPI } close(v4l2sink); return 0; } v4l2loopback-0.14.0/examples/restarting-writer.sh000077500000000000000000000016251476030224200217530ustar00rootroot00000000000000#!/bin/bash device=${1:-/dev/video0} echo "Using $device" run_writers() { declare -a nbufs nbufs=(10 9 4 5 6) #nbufs=(12 12 12 12 12) #for i in $(seq 0 4); do for i in 1; do sleep 1 gst-launch-1.0 videotestsrc horizontal-speed=1 num-buffers=90 ! v4l2sink device=$device done } #v4l2-ctl -d $device -c keep_format=1 || exit 1 ./utils/v4l2loopback-ctl set-caps "$device" 'UYVY:640x480@25/1' || 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 run_writers sleep 10 run_writers } >/dev/null & gst-launch-1.0 v4l2src device=$device ! timeoverlay ! videoconvert ! autovideosink kill $! 2>/dev/null wait v4l2loopback-0.14.0/examples/test.c000066400000000000000000000113761476030224200170470ustar00rootroot00000000000000/* * How to test v4l2loopback: * 1. launch this test program (even in background), it will initialize the * loopback device and keep it open so it won't loose the settings. * 2. Feed the video device with data according to the settings specified * below: size, pixelformat, etc. * For instance, you can try the default settings with this command: * mencoder video.avi -ovc raw -nosound -vf scale=640:480,format=yuy2 -o /dev/video1 * TODO: a command that limits the fps would be better :) * * Test the video in your favourite viewer, for instance: * luvcview -d /dev/video1 -f yuyv */ #include #include #include #include #include #include #include #include #define ROUND_UP_2(num) (((num)+1)&~1) #define ROUND_UP_4(num) (((num)+3)&~3) #define ROUND_UP_8(num) (((num)+7)&~7) #define ROUND_UP_16(num) (((num)+15)&~15) #define ROUND_UP_32(num) (((num)+31)&~31) #define ROUND_UP_64(num) (((num)+63)&~63) #if 0 # define CHECK_REREAD #endif #define VIDEO_DEVICE "/dev/video0" #if 1 # define FRAME_WIDTH 640 # define FRAME_HEIGHT 480 #else # define FRAME_WIDTH 512 # define FRAME_HEIGHT 512 #endif #if 0 # define FRAME_FORMAT V4L2_PIX_FMT_YUYV #else # define FRAME_FORMAT V4L2_PIX_FMT_YVU420 #endif static int debug = 0; int format_properties(const unsigned int format, const unsigned int width, const unsigned int height, size_t*linewidth, size_t*framewidth) { size_t lw, fw; switch(format) { case V4L2_PIX_FMT_YUV420: case V4L2_PIX_FMT_YVU420: lw = width; /* ??? */ fw = ROUND_UP_4 (width) * ROUND_UP_2 (height); fw += 2 * ((ROUND_UP_8 (width) / 2) * (ROUND_UP_2 (height) / 2)); break; case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_Y41P: case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_YVYU: lw = (ROUND_UP_2 (width) * 2); fw = lw * height; break; default: return 0; } if(linewidth)*linewidth=lw; if(framewidth)*framewidth=fw; return 1; } void print_format(struct v4l2_format*vid_format) { printf(" vid_format->type =%d\n", vid_format->type ); printf(" vid_format->fmt.pix.width =%d\n", vid_format->fmt.pix.width ); printf(" vid_format->fmt.pix.height =%d\n", vid_format->fmt.pix.height ); printf(" vid_format->fmt.pix.pixelformat =%d\n", vid_format->fmt.pix.pixelformat); printf(" vid_format->fmt.pix.sizeimage =%d\n", vid_format->fmt.pix.sizeimage ); printf(" vid_format->fmt.pix.field =%d\n", vid_format->fmt.pix.field ); printf(" vid_format->fmt.pix.bytesperline=%d\n", vid_format->fmt.pix.bytesperline ); printf(" vid_format->fmt.pix.colorspace =%d\n", vid_format->fmt.pix.colorspace ); } int main(int argc, char**argv) { struct v4l2_capability vid_caps; struct v4l2_format vid_format; size_t framesize = 0; size_t linewidth = 0; __u8*buffer; __u8*check_buffer; const char*video_device=VIDEO_DEVICE; int fdwr = 0; int ret_code = 0; int i; if(argc>1) { video_device=argv[1]; printf("using output device: %s\n", video_device); } fdwr = open(video_device, O_RDWR); assert(fdwr >= 0); ret_code = ioctl(fdwr, VIDIOC_QUERYCAP, &vid_caps); assert(ret_code != -1); memset(&vid_format, 0, sizeof(vid_format)); ret_code = ioctl(fdwr, VIDIOC_G_FMT, &vid_format); if(debug)print_format(&vid_format); vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; vid_format.fmt.pix.width = FRAME_WIDTH; vid_format.fmt.pix.height = FRAME_HEIGHT; vid_format.fmt.pix.pixelformat = FRAME_FORMAT; vid_format.fmt.pix.sizeimage = framesize; vid_format.fmt.pix.field = V4L2_FIELD_NONE; vid_format.fmt.pix.bytesperline = linewidth; vid_format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; if(debug)print_format(&vid_format); ret_code = ioctl(fdwr, VIDIOC_S_FMT, &vid_format); assert(ret_code != -1); if(debug)printf("frame: format=%d\tsize=%lu\n", FRAME_FORMAT, framesize); print_format(&vid_format); if(!format_properties(vid_format.fmt.pix.pixelformat, vid_format.fmt.pix.width, vid_format.fmt.pix.height, &linewidth, &framesize)) { printf("unable to guess correct settings for format '%d'\n", FRAME_FORMAT); } buffer=(__u8*)malloc(sizeof(__u8)*framesize); check_buffer=(__u8*)malloc(sizeof(__u8)*framesize); memset(buffer, 0, framesize); memset(check_buffer, 0, framesize); for (i = 0; i < framesize; ++i) { //buffer[i] = i % 2; check_buffer[i] = 0; } write(fdwr, buffer, framesize); #ifdef CHECK_REREAD do { /* check if we get the same data on output */ int fdr = open(video_device, O_RDONLY); read(fdr, check_buffer, framesize); for (i = 0; i < framesize; ++i) { if (buffer[i] != check_buffer[i]) assert(0); } close(fdr); } while(0); #endif pause(); close(fdwr); free(buffer); free(check_buffer); return 0; } v4l2loopback-0.14.0/examples/yuv420_infiniteloop.c000066400000000000000000000050401476030224200217070ustar00rootroot00000000000000/* 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 from yuv4mpeg_to_v4l2 example, * original Copyright (C) 2011 Eric C. Cooper * Released under the GNU General Public License */ #include #include #include #include #include #include #include char *prog; struct yuv_setup { char *device; char *file_name; int frame_width; int frame_height; int frame_bytes; float fps; }; void fail(char *msg) { fprintf(stderr, "%s: %s\n", prog, msg); exit(1); } struct yuv_setup process_args(int argc, char **argv) { prog = argv[0]; struct yuv_setup setup; if (argc != 6) { fail("invalid argument"); } else { setup.device = argv[1]; setup.file_name = argv[2]; setup.frame_width = atoi(argv[3]); setup.frame_height = atoi(argv[4]); setup.frame_bytes = 3 * setup.frame_height * setup.frame_width / 2; setup.fps = atof(argv[5]); } return setup; } void copy_frames(struct yuv_setup setup, int dev_fd) { FILE * yuv_file = fopen (setup.file_name,"rb"); if (yuv_file == NULL) { fail("can not open yuv file"); } char *frame = malloc(setup.frame_bytes); if (frame == NULL) { fail("cannot malloc frame"); } 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) { write(dev_fd, frame, setup.frame_bytes); } else if (read_size == 0) { fclose(yuv_file); yuv_file = fopen (setup.file_name,"rb"); } else { free(frame); fail("invalid frame size or file ending"); } } free(frame); } int open_video(struct yuv_setup setup) { struct v4l2_format v; int dev_fd = open(setup.device, O_RDWR); if (dev_fd == -1) { fail("cannot open video device"); } v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (ioctl(dev_fd, VIDIOC_G_FMT, &v) == -1) { fail("cannot setup video device"); } v.fmt.pix.width = setup.frame_width; v.fmt.pix.height = setup.frame_height; 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) { fail("cannot setup video device"); } return dev_fd; } int main(int argc, char **argv) { struct yuv_setup loc_setup = process_args(argc, argv); int loc_dev = open_video(loc_setup); copy_frames(loc_setup, loc_dev); return 0; } v4l2loopback-0.14.0/examples/yuv4mpeg_to_v4l2.c000066400000000000000000000057611476030224200212220ustar00rootroot00000000000000/* * Copy a YUV4MPEG stream to a v4l2 output device. * The stream is read from standard input. * The device can be specified as argument; it defaults to /dev/video0. * * Example using mplayer as a producer for the v4l2loopback driver: * * $ mkfifo /tmp/pipe * $ ./yuv4mpeg_to_v4l2 < /tmp/pipe & * $ mplayer movie.mp4 -vo yuv4mpeg:file=/tmp/pipe * * Copyright (C) 2011 Eric C. Cooper * Released under the GNU General Public License */ #include #include #include #include #include #include #include char *prog; char *device; int dev_fd; int frame_width; int frame_height; int frame_bytes; void usage(void) { fprintf(stderr, "Usage: %s [/dev/videoN]\n", prog); exit(1); } void process_args(int argc, char **argv) { prog = argv[0]; switch (argc) { case 1: device = "/dev/video0"; break; case 2: device = argv[1]; break; default: usage(); break; } } void sysfail(char *msg) { perror(msg); exit(1); } void fail(char *msg) { fprintf(stderr, "%s: %s\n", prog, msg); exit(1); } void bad_header(char *kind) { char msg[64]; sprintf(msg, "malformed %s header", kind); fail(msg); } void do_tag(char tag, char *value) { switch (tag) { case 'W': frame_width = strtoul(value, NULL, 10); break; case 'H': frame_height = strtoul(value, NULL, 10); break; } } int read_header(char *magic) { char *p, *q, *p0; size_t n; int first, done; p0 = NULL; if (getline(&p0, &n, stdin) == -1) { free(p0); return 0; } q = p = p0; first = 1; done = 0; while (!done) { while (*q != ' ' && *q != '\n') if (*q++ == '\0') bad_header(magic); done = (*q == '\n'); *q = '\0'; if (first) if (strcmp(p, magic) == 0) first = 0; else bad_header(magic); else do_tag(*p, p + 1); p = ++q; } free(p0); return 1; } void process_header(void) { if (!read_header("YUV4MPEG2")) fail("missing YUV4MPEG2 header"); frame_bytes = 3 * frame_width * frame_height / 2; if (frame_bytes == 0) fail("frame width or height is missing"); } void copy_frames(void) { char *frame; frame = malloc(frame_bytes); if (frame == NULL) fail("cannot malloc frame"); while (read_header("FRAME")) { if (fread(frame, 1, frame_bytes, stdin) != frame_bytes) { free(frame); fail("malformed frame"); } else if (write(dev_fd, frame, frame_bytes) != frame_bytes) { free(frame); sysfail("write"); } } free(frame); } #define vidioc(op, arg) \ if (ioctl(dev_fd, VIDIOC_##op, arg) == -1) \ sysfail(#op); \ else void open_video(void) { struct v4l2_format v; dev_fd = open(device, O_RDWR); if (dev_fd == -1) sysfail(device); v.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; vidioc(G_FMT, &v); v.fmt.pix.width = frame_width; v.fmt.pix.height = frame_height; v.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; v.fmt.pix.sizeimage = frame_bytes; vidioc(S_FMT, &v); } int main(int argc, char **argv) { process_args(argc, argv); process_header(); open_video(); copy_frames(); return 0; } v4l2loopback-0.14.0/man/000077500000000000000000000000001476030224200146515ustar00rootroot00000000000000v4l2loopback-0.14.0/release.sh000077500000000000000000000064621476030224200160650ustar00rootroot00000000000000#!/bin/sh #################################### # prepare package for release # DONE: get current version from module source # DONE: ChangeLog generator using git-dch # DONE: update dkms.conf # TODO: automatically update AUTHORS # TODO: automatically prepare NEWS (from ChangeLog) # TODO: automatically launch editors for ChangeLog/NEWS/AUTHORS # TODO: automatically tag (if all went well) CHANGELOG=ChangeLog AUTHORS=AUTHORS NEWS=NEWS : "${mainbranch:=main}" error() { echo "$@" 1>&2 } fatal() { error "$@" exit 1 } usage() { fatal "usage: $0 [] " 1>&2 } getoldversion() { dpkg-parsechangelog --count 1 -l${CHANGELOG} | grep -E "^Version:" | head -1 | cut -f2 -d' ' } getmoduleversion0() { 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' } getmoduleversion_() { grep "^[[:space:]]*#[[:space:]]*define[[:space:]]*V4L2LOOPBACK_VERSION_$1[[:space:]]" v4l2loopback.h | awk '{print $NF}' } getmoduleversion() { echo "$(getmoduleversion_ MAJOR).$(getmoduleversion_ MINOR).$(getmoduleversion_ BUGFIX)" } getgitbranch() { git rev-parse --abbrev-ref HEAD } if [ "$(getgitbranch)" != "${mainbranch}" ]; then fatal "current branch '$(getgitbranch)' is not '${mainbranch}'" fi moduleversion=$(getmoduleversion) if [ -z "$2" ]; then ## guess current version NEWVERSION=$1 OLDVERSION=$(getoldversion) else OLDVERSION=$1 NEWVERSION=$2 fi if [ -z "${NEWVERSION}" ]; then NEWVERSION="${moduleversion}" fi echo "module version: ${moduleversion}" echo "updating from: ${OLDVERSION}" if git tag -l "v${OLDVERSION}" | grep . >/dev/null then : else fatal "it seems like there is no tag 'v${OLDVERSION}'" fi if [ -z "${OLDVERSION}" ]; then usage fi if [ -z "${NEWVERSION}" ]; then usage fi echo "updating to: ${NEWVERSION}" if dpkg --compare-versions "${OLDVERSION}" ge "${NEWVERSION}" then fatal "version mismatch: ${NEWVERSION} is not newer than ${OLDVERSION}" fi if [ "${NEWVERSION}" != "${moduleversion}" ]; then echo "${NEWVERSION}" | sed -e 's|\.| |g' | while read -r major minor bugfix; do major=$((major+0)) minor=$((minor+0)) bugfix=$((bugfix+0)) sed -e "s|^\([[:space:]]*#[[:space:]]*define[[:space:]]*V4L2LOOPBACK_VERSION_MAJOR[[:space:]]\).*|\1${major}|" -i v4l2loopback.h sed -e "s|^\([[:space:]]*#[[:space:]]*define[[:space:]]*V4L2LOOPBACK_VERSION_MINOR[[:space:]]\).*|\1${minor}|" -i v4l2loopback.h sed -e "s|^\([[:space:]]*#[[:space:]]*define[[:space:]]*V4L2LOOPBACK_VERSION_BUGFIX[[:space:]]\).*|\1${bugfix}|" -i v4l2loopback.h break done fi OK=false mkdir debian cp "${CHANGELOG}" debian/changelog gbp dch -R --since "v${OLDVERSION}" -N "${NEWVERSION}" --debian-branch="${mainbranch}" && cat debian/changelog > "${CHANGELOG}" && OK=true rm -rf debian if [ "${OK}" = "true" ]; then sed -e "s|^PACKAGE_VERSION=\".*\"$|PACKAGE_VERSION=\"${NEWVERSION}\"|" -i dkms.conf fi if [ "${OK}" = "true" ]; then echo "all went well" echo "" echo "- please check your ${CHANGELOG}" echo "- please check&edit your ${NEWS}" echo "- please check&edit your ${AUTHORS}" echo "- and don't forget to git-tag the new version as v${NEWVERSION}" echo " git tag v${NEWVERSION} -s -m \"Released ${NEWVERSION}\"" fi v4l2loopback-0.14.0/tests/000077500000000000000000000000001476030224200152405ustar00rootroot00000000000000v4l2loopback-0.14.0/tests/Makefile000066400000000000000000000000771476030224200167040ustar00rootroot00000000000000all: test_dqbuf consumer producer consumer producer: common.h v4l2loopback-0.14.0/tests/checkformat.sh000077500000000000000000000054431476030224200200730ustar00rootroot00000000000000#!/bin/sh device=${1:-/dev/video0} if [ ! -e "${device}" ]; then echo "Unable to open device ${device}" 1>&2 exit 1 fi parse_output() { # EAGAIN EBUSY EEXIST EFAULT EINVAL EIO ENODEV ENOMEM ENOSPC ENOSYS ENOTTY while read line; do if echo "${line}" | grep -q "Invalid argument"; then echo "EINVAL" return fi if echo "${line}" | grep -q "Device or resource busy"; then echo "EBUSY" return fi if echo "${line}" | grep -q "Width/Height"; then echo "${line}" | awk '{print $3}' return fi done } parse_enum_fmt() { local format local count=0 while read line; do if echo "${line}" | grep -q "\[[0-9]*\]"; then format=$(printf "%-4s" $(echo "${line}" | awk '{print $2}' | sed -e "s|'||g")) fi if echo "${line}" | grep -q "Size: "; then echo "- ${format}:$(echo $line | awk '{$1=""; $2=""; print $0}' | sed -e 's|[[:space:]]||g')" count=$((count+1)) fi done [ ${count} -gt 0 ] || echo "- none" } output() { echo "| ioctl | capture | output |" echo "| ----- | ------- | ------ |" echo "| GET | ${G_FMT_CAP} | ${G_FMT_OUT} |" echo "| TRY0 | ${TRY_FMT_CAP0} | ${TRY_FMT_OUT0} |" echo "| TRY1 | ${TRY_FMT_CAP1:--} | ${TRY_FMT_OUT1:--} |" echo "| SET0 | ${S_FMT_CAP0:--} | ${S_FMT_OUT0:--} |" echo "| SET1 | ${S_FMT_CAP1:--} | ${S_FMT_OUT1:--} |" } G_FMT_CAP=$(v4l2-ctl -d "${device}" -V | parse_output) G_FMT_OUT=$(v4l2-ctl -d "${device}" -X | parse_output) TRY_FMT_CAP0=$(v4l2-ctl -d "${device}" --try-fmt-video "width=640,height=480" | parse_output) TRY_FMT_CAP1=$(v4l2-ctl -d "${device}" --try-fmt-video "width=640,height=4800" | parse_output) TRY_FMT_OUT0=$(v4l2-ctl -d "${device}" --try-fmt-video-out "width=640,height=480" | parse_output) TRY_FMT_OUT1=$(v4l2-ctl -d "${device}" --try-fmt-video-out "width=640,height=4800" | parse_output) S_FMT_CAP0=$(v4l2-ctl -d "${device}" -v "width=640,height=480" | parse_output) [ -n "${S_FMT_CAP0}" ] || S_FMT_CAP0=$(v4l2-ctl -d "${device}" -V | parse_output) S_FMT_CAP1=$(v4l2-ctl -d "${device}" -v "width=640,height=4800" | parse_output) [ -n "${S_FMT_CAP1}" ] || S_FMT_CAP1=$(v4l2-ctl -d "${device}" -V | parse_output) S_FMT_OUT0=$(v4l2-ctl -d "${device}" -x "width=640,height=480" | parse_output) [ -n "${S_FMT_OUT0}" ] || S_FMT_OUT0=$(v4l2-ctl -d "${device}" -X | parse_output) S_FMT_OUT1=$(v4l2-ctl -d "${device}" -x "width=640,height=4800" | parse_output) [ -n "${S_FMT_OUT1}" ] || S_FMT_OUT1=$(v4l2-ctl -d "${device}" -X | parse_output) output | column -t echo echo "OUTPUT formats" v4l2-ctl -d "${device}" --list-formats-out-ext | parse_enum_fmt echo echo "CAPTURE formats" v4l2-ctl -d "${device}" --list-formats-ext | parse_enum_fmt v4l2loopback-0.14.0/tests/common.h000066400000000000000000000076411476030224200167110ustar00rootroot00000000000000/* -*- c-file-style: "linux" -*- */ /* * common.h -- some commong functions * * Copyright (C) 2023 IOhannes m zmoelnig (zmoelnig@iem.at) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * */ #include #include 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; } static const char *field2str(unsigned int field) { switch (field) { case V4L2_FIELD_ANY: return "any"; case V4L2_FIELD_NONE: return "none"; case V4L2_FIELD_TOP: return "top"; case V4L2_FIELD_BOTTOM: return "bottom"; case V4L2_FIELD_INTERLACED: return "interlaced"; case V4L2_FIELD_SEQ_TB: return "seq/topbottom"; case V4L2_FIELD_SEQ_BT: return "seq/bottomtop"; case V4L2_FIELD_ALTERNATE: return "alternate"; case V4L2_FIELD_INTERLACED_TB: return "interlaced/topbottom"; case V4L2_FIELD_INTERLACED_BT: return "interlaced/bottomtop"; default: break; } return "unknown"; } static const char *buftype2str(unsigned int type) { switch (type) { default: break; case V4L2_BUF_TYPE_VIDEO_CAPTURE: return "CAPTURE"; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: return "CAPTURE(planar)"; case V4L2_BUF_TYPE_VIDEO_OUTPUT: return "OUTPUT"; case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: return "OUTPUT(planar)"; case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: return "OUTPUT(overlay)"; case V4L2_BUF_TYPE_VIDEO_OVERLAY: return "OVERLAY"; case V4L2_BUF_TYPE_VBI_CAPTURE: return "VBI(capture)"; case V4L2_BUF_TYPE_VBI_OUTPUT: return "VBI(output)"; case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE: return "SlicedVBI(capture)"; case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT: return "SlicedVBI(output)"; case V4L2_BUF_TYPE_SDR_CAPTURE: return "SDR(capture)"; case V4L2_BUF_TYPE_SDR_OUTPUT: return "SDR(output)"; case V4L2_BUF_TYPE_META_CAPTURE: return "META(capture)"; case V4L2_BUF_TYPE_META_OUTPUT: return "META(output)"; case V4L2_BUF_TYPE_PRIVATE: return "private"; } return "unknown"; } static const char *bufmemory2str(unsigned int mem) { switch (mem) { case V4L2_MEMORY_MMAP: return "MMAP"; case V4L2_MEMORY_USERPTR: return "USERPTR"; case V4L2_MEMORY_OVERLAY: return "OVERLAY"; case V4L2_MEMORY_DMABUF: return "DMABUF"; default: break; } return "unknown"; } static const char *snprintf_format(char *buf, size_t size, struct v4l2_format *fmt) { char fourcc[5]; fourcc[4] = 0; switch (fmt->type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT: case V4L2_BUF_TYPE_VIDEO_CAPTURE: snprintf(buf, size, "%s:%dx%d:%s bytes/line=%u sizeimage=%u field=%s", buftype2str(fmt->type), fmt->fmt.pix.width, fmt->fmt.pix.height, fourcc2str(fmt->fmt.pix.pixelformat, fourcc), fmt->fmt.pix.bytesperline, fmt->fmt.pix.sizeimage, field2str(fmt->fmt.pix.field)); break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: snprintf(buf, size, "%s:%dx%d:%s (%d planes) field=%s", buftype2str(fmt->type), fmt->fmt.pix_mp.width, fmt->fmt.pix_mp.height, fourcc2str(fmt->fmt.pix_mp.pixelformat, fourcc), fmt->fmt.pix_mp.num_planes, field2str(fmt->fmt.pix_mp.field)); default: snprintf(buf, size, "TODO: %s(type=%d)", __FUNCTION__, (fmt->type)); } return buf; } static const char *snprintf_buffer(char *strbuf, size_t size, struct v4l2_buffer *buf) { snprintf( strbuf, size, "buffer#%d @ %p %s bytesused=%d, length=%d flags=0x%08X field=%s timestamp=%ld.%06ld memory=%s (offset=%d)", buf->index, buf, buftype2str(buf->type), buf->bytesused, buf->length, buf->flags, field2str(buf->field), buf->timestamp.tv_sec, buf->timestamp.tv_usec, bufmemory2str(buf->memory), buf->m.offset); return strbuf; } v4l2loopback-0.14.0/tests/consumer.c000066400000000000000000000301561476030224200172440ustar00rootroot00000000000000/* * V4L2 video capture example * * This program can be used and distributed without restrictions. * * This program is provided with the V4L2 API * see http://linuxtv.org/docs.php for more information */ #include #include #include #include #include /* getopt_long() */ #include /* low-level i/o */ #include #include #include #include #include #include #include #include #include "common.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) enum io_method { IO_METHOD_READ, IO_METHOD_MMAP, IO_METHOD_USERPTR, }; struct buffer { void *start; size_t length; }; static char *dev_name; static enum io_method io = IO_METHOD_MMAP; static int fd = -1; struct buffer *buffers; static unsigned int n_buffers; static int frame_count = 70; static void errno_exit(const char *s) { fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); exit(EXIT_FAILURE); } static int xioctl(int fh, unsigned long int request, void *arg) { int r; do { r = ioctl(fh, request, arg); } while (-1 == r && EINTR == errno); return r; } static int read_frame(void) { char strbuf[1024]; struct v4l2_buffer buf; unsigned int i; switch (io) { case IO_METHOD_READ: if (-1 == read(fd, buffers[0].start, buffers[0].length)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("read"); } } printf("READ\t%lu@%p\n", buffers[0].length, buffers[0].start); break; case IO_METHOD_MMAP: CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("VIDIOC_DQBUF"); } } printf("MMAP\t%s\n", snprintf_buffer(strbuf, sizeof(strbuf), &buf)); assert(buf.index < n_buffers); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); break; case IO_METHOD_USERPTR: CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("VIDIOC_DQBUF"); } } for (i = 0; i < n_buffers; ++i) if (buf.m.userptr == (unsigned long)buffers[i].start && buf.length == buffers[i].length) break; printf("USERPTR\t%s\n", snprintf_buffer(strbuf, sizeof(strbuf), &buf)); assert(i < n_buffers); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); break; } return 1; } static void mainloop(void) { unsigned int count; int keep_running = 1; count = frame_count; while (1) { if (count < 1) break; if (frame_count >= 0) { count--; } for (;;) { fd_set fds; struct timeval tv; int r; FD_ZERO(&fds); FD_SET(fd, &fds); /* Timeout. */ tv.tv_sec = 2; tv.tv_usec = 0; r = select(fd + 1, &fds, NULL, NULL, &tv); if (-1 == r) { if (EINTR == errno) continue; errno_exit("select"); } if (0 == r) { fprintf(stderr, "select timeout\n"); exit(EXIT_FAILURE); } if (read_frame()) break; /* EAGAIN - continue select loop. */ } } } static void stop_capturing(void) { enum v4l2_buf_type type; switch (io) { case IO_METHOD_READ: /* Nothing to do. */ break; case IO_METHOD_MMAP: case IO_METHOD_USERPTR: type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) errno_exit("VIDIOC_STREAMOFF"); break; } } static void start_capturing(void) { unsigned int i; enum v4l2_buf_type type; switch (io) { case IO_METHOD_READ: /* Nothing to do. */ break; case IO_METHOD_MMAP: for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) errno_exit("VIDIOC_STREAMON"); break; case IO_METHOD_USERPTR: for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_USERPTR; buf.index = i; buf.m.userptr = (unsigned long)buffers[i].start; buf.length = buffers[i].length; if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) errno_exit("VIDIOC_STREAMON"); break; } } static void uninit_device(void) { unsigned int i; switch (io) { case IO_METHOD_READ: free(buffers[0].start); break; case IO_METHOD_MMAP: for (i = 0; i < n_buffers; ++i) if (-1 == munmap(buffers[i].start, buffers[i].length)) errno_exit("munmap"); break; case IO_METHOD_USERPTR: for (i = 0; i < n_buffers; ++i) free(buffers[i].start); break; } free(buffers); } static void init_read(unsigned int buffer_size) { buffers = calloc(1, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } buffers[0].length = buffer_size; buffers[0].start = malloc(buffer_size); if (!buffers[0].start) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } } static void init_mmap(void) { char strbuf[1024]; const int count = 4; struct v4l2_requestbuffers req; CLEAR(req); req.count = count; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { fprintf(stderr, "%s does not support " "memory mapping\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); } } printf("requested %d buffers, got %d\n", count, req.count); if (req.count < 2) { fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); exit(EXIT_FAILURE); } buffers = calloc(req.count, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) errno_exit("VIDIOC_QUERYBUF"); printf("requested buffer %d/%d: %s\n", n_buffers, count, snprintf_buffer(strbuf, sizeof(strbuf), &buf)); buffers[n_buffers].length = buf.length; buffers[n_buffers].start = mmap(NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset); if (MAP_FAILED == buffers[n_buffers].start) errno_exit("mmap"); } } static void init_userp(unsigned int buffer_size) { struct v4l2_requestbuffers req; CLEAR(req); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_USERPTR; if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { fprintf(stderr, "%s does not support " "user pointer i/o\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); } } buffers = calloc(4, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } for (n_buffers = 0; n_buffers < 4; ++n_buffers) { buffers[n_buffers].length = buffer_size; buffers[n_buffers].start = malloc(buffer_size); if (!buffers[n_buffers].start) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } } } static void init_device(void) { char strbuf[1024]; struct v4l2_capability cap; struct v4l2_cropcap cropcap; struct v4l2_crop crop; struct v4l2_format fmt; if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf(stderr, "%s is no V4L2 device\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_QUERYCAP"); } } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { fprintf(stderr, "%s is no video capture device\n", dev_name); exit(EXIT_FAILURE); } switch (io) { case IO_METHOD_READ: if (!(cap.capabilities & V4L2_CAP_READWRITE)) { fprintf(stderr, "%s does not support read i/o\n", dev_name); exit(EXIT_FAILURE); } break; case IO_METHOD_MMAP: case IO_METHOD_USERPTR: if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support streaming i/o\n", dev_name); exit(EXIT_FAILURE); } break; } /* Select video input, video standard and tune here. */ CLEAR(cropcap); cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = cropcap.defrect; /* reset to default */ if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { switch (errno) { case EINVAL: /* Cropping not supported. */ break; default: /* Errors ignored. */ break; } } } else { /* Errors ignored. */ } CLEAR(fmt); /* get the current format */ fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) errno_exit("VIDIOC_G_FMT"); printf("got format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); /* try to set the current format (no-change should always succeed) */ if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { const char *s = "VIDIOC_S_FMT"; fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); //errno_exit("VIDIOC_S_FMT"); } printf("set format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); switch (io) { case IO_METHOD_READ: init_read(fmt.fmt.pix.sizeimage); break; case IO_METHOD_MMAP: init_mmap(); break; case IO_METHOD_USERPTR: init_userp(fmt.fmt.pix.sizeimage); break; } } static void close_device(void) { if (-1 == close(fd)) errno_exit("close"); fd = -1; } static void open_device(void) { struct stat st; if (-1 == stat(dev_name, &st)) { fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, errno, strerror(errno)); exit(EXIT_FAILURE); } if (!S_ISCHR(st.st_mode)) { fprintf(stderr, "%s is no device\n", dev_name); exit(EXIT_FAILURE); } fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); if (-1 == fd) { fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno, strerror(errno)); exit(EXIT_FAILURE); } } static void usage(FILE *fp, int argc, char **argv) { fprintf(fp, "Usage: %s [options]\n\n" "Version 1.3\n" "Options:\n" "-d | --device name Video device name [%s]\n" "-h | --help Print this message\n" "-m | --mmap Use memory mapped buffers [default]\n" "-r | --read Use read() calls\n" "-u | --userp Use application allocated buffers\n" "-c | --count Number of frames to grab [%i] (negative numbers: no limit)\n" "", argv[0], dev_name, frame_count); } static const char short_options[] = "d:hmruofc:"; static const struct option long_options[] = { { "device", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "mmap", no_argument, NULL, 'm' }, { "read", no_argument, NULL, 'r' }, { "userp", no_argument, NULL, 'u' }, { "count", required_argument, NULL, 'c' }, { 0, 0, 0, 0 } }; int main(int argc, char **argv) { dev_name = "/dev/video0"; for (;;) { int idx; int c; c = getopt_long(argc, argv, short_options, long_options, &idx); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'd': dev_name = optarg; break; case 'h': usage(stdout, argc, argv); exit(EXIT_SUCCESS); case 'm': io = IO_METHOD_MMAP; break; case 'r': io = IO_METHOD_READ; break; case 'u': io = IO_METHOD_USERPTR; break; case 'c': errno = 0; frame_count = strtol(optarg, NULL, 0); if (errno) errno_exit(optarg); break; default: usage(stderr, argc, argv); exit(EXIT_FAILURE); } } open_device(); init_device(); start_capturing(); mainloop(); stop_capturing(); uninit_device(); close_device(); fprintf(stderr, "\n"); return 0; } v4l2loopback-0.14.0/tests/interlaced_w000077500000000000000000000013461476030224200176320ustar00rootroot00000000000000#!/bin/sh DEVICE=$1 WIDTH=320 HEIGHT=240 #WIDTH=1920 #HEIGHT=1080 ## uncomment the 'progressive' line to switch to non-interlaced INTERLACED=interlaced #INTERLACED=progressive INTERLACED=mixed error() { echo "$@" 1>&2 } debug() { error "$@" $@ } if [ "x${GSTLAUNCH}" = "x" ]; then GSTLAUNCH=$(which gst-launch-1.0) fi if [ "x${GSTLAUNCH}" = "x" ]; then error "need gst-launch-1.0" exit 1 fi if [ "x${DEVICE}" = "x" ]; then DEVICE=/dev/video0 fi if [ -c "${DEVICE}" ]; then : else error "illegal device ${DEVICE}" exit 1 fi debug ${GSTLAUNCH} videotestsrc horizontal-speed=16 \ ! video/x-raw, format=UYVY, width=${WIDTH}, height=${HEIGHT}, frame-rate=30000/1001, interlace-mode=${INTERLACED} \ ! v4l2sink device=${DEVICE} v4l2loopback-0.14.0/tests/producer.c000066400000000000000000000363561476030224200172440ustar00rootroot00000000000000/* * V4L2 video output example * * This program can be used and distributed without restrictions. * * This program is provided with the V4L2 API * see http://linuxtv.org/docs.php for more information */ #include #include #include #include #include /* clock_gettime() */ #include /* getopt_long() */ #include /* low-level i/o */ #include #include #include #include #include #include #include #include #include "common.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) #define SET_QUEUED(buffer) ((buffer).flags |= V4L2_BUF_FLAG_QUEUED) #define IS_QUEUED(buffer) \ ((buffer).flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)) enum io_method { IO_METHOD_WRITE, IO_METHOD_MMAP, IO_METHOD_USERPTR, }; struct buffer { void *start; size_t length; size_t bytesused; }; static char *dev_name; static enum io_method io = IO_METHOD_MMAP; static int fd = -1; struct buffer *buffers; static unsigned int n_buffers; static int frame_count = 70; static unsigned int width = 640; static unsigned int height = 480; static unsigned int pixelformat = V4L2_PIX_FMT_YUYV; static int set_timestamp = 0; static char strbuf[1024]; static void errno_exit(const char *s) { fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno)); exit(EXIT_FAILURE); } static unsigned int str2fourcc(char buf[4]) { return (buf[0]) + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); } static int xioctl(int fh, unsigned long int request, void *arg) { int r; do { r = ioctl(fh, request, arg); } while (-1 == r && EINTR == errno); return r; } static unsigned int random_nextseed = 148985372; static unsigned char randombyte(void) { random_nextseed = (random_nextseed * 472940017) + 832416023; return ((random_nextseed >> 16) & 0xFF); } static void process_image(unsigned char *data, size_t length) { size_t i; for (i = 0; i < length; i++) { data[i] = randombyte(); } } static int framenum = 0; static int write_frame(void) { struct v4l2_buffer buf; unsigned int i; switch (io) { case IO_METHOD_WRITE: process_image(buffers[0].start, buffers[0].bytesused); if (-1 == write(fd, buffers[0].start, buffers[0].length)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("write"); } } printf("WRITE %p: %lu/%lu\n", buffers[0].start, buffers[0].bytesused, buffers[0].length); break; case IO_METHOD_MMAP: CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("VIDIOC_DQBUF"); } } if (set_timestamp) { struct timespec curTime; clock_gettime(CLOCK_MONOTONIC, &curTime); buf.timestamp.tv_sec = curTime.tv_sec; buf.timestamp.tv_usec = curTime.tv_nsec / 1000ULL; } else { buf.timestamp.tv_sec = 0; buf.timestamp.tv_usec = 0; } printf("MMAP\t%s\n", snprintf_buffer(strbuf, sizeof(strbuf), &buf)); fflush(stdout); assert(buf.index < n_buffers); process_image(buffers[buf.index].start, buf.bytesused); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); if (!IS_QUEUED(buf)) { printf("driver pretends buffer is not queued even if queue succeeded\n"); SET_QUEUED(buf); } break; case IO_METHOD_USERPTR: CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.memory = V4L2_MEMORY_USERPTR; if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) { switch (errno) { case EAGAIN: return 0; case EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("VIDIOC_DQBUF"); } } for (i = 0; i < n_buffers; ++i) if (buf.m.userptr == (unsigned long)buffers[i].start && buf.bytesused == buffers[i].bytesused) break; assert(i < n_buffers); printf("USERPTR\t%s\n", snprintf_buffer(strbuf, sizeof(strbuf), &buf)); process_image(buffers[buf.index].start, buf.bytesused); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); break; } return 1; } static void mainloop(void) { unsigned int count; int keep_running = 1; count = frame_count; while (1) { if (count < 1) break; if (frame_count >= 0) { count--; } for (;;) { if (write_frame()) break; /* EAGAIN - continue select loop. */ } usleep(33000); } } static void stop_capturing(void) { enum v4l2_buf_type type; switch (io) { case IO_METHOD_WRITE: /* Nothing to do. */ break; case IO_METHOD_MMAP: case IO_METHOD_USERPTR: type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) errno_exit("VIDIOC_STREAMOFF"); break; } } static void start_capturing(void) { unsigned int i; enum v4l2_buf_type type; switch (io) { case IO_METHOD_WRITE: /* Nothing to do. */ break; case IO_METHOD_MMAP: for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; buf.length = buffers[i].length; buf.bytesused = buffers[i].bytesused; printf("MMAP init qbuf %d/%d (length=%d): %s\n", i, n_buffers, buffers[i].length, snprintf_buffer(strbuf, sizeof(strbuf), &buf)); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); } type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) errno_exit("VIDIOC_STREAMON"); break; case IO_METHOD_USERPTR: for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.memory = V4L2_MEMORY_USERPTR; buf.index = i; buf.m.userptr = (unsigned long)buffers[i].start; buf.bytesused = buffers[i].bytesused; buf.length = buffers[i].length; printf("USERPTR init qbuf %d/%d: %s\n", i, n_buffers, snprintf_buffer(strbuf, sizeof(strbuf), &buf)); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); } type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) errno_exit("VIDIOC_STREAMON"); break; } } static void uninit_device(void) { unsigned int i; switch (io) { case IO_METHOD_WRITE: free(buffers[0].start); break; case IO_METHOD_MMAP: for (i = 0; i < n_buffers; ++i) if (-1 == munmap(buffers[i].start, buffers[i].length)) errno_exit("munmap"); break; case IO_METHOD_USERPTR: for (i = 0; i < n_buffers; ++i) free(buffers[i].start); break; } free(buffers); } static void init_write(unsigned int buffer_size) { buffers = calloc(1, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } buffers[0].length = buffer_size; buffers[0].bytesused = buffer_size; buffers[0].start = malloc(buffer_size); if (!buffers[0].start) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } } static void init_mmap(void) { const int count = 4; struct v4l2_requestbuffers req; CLEAR(req); req.count = count; req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; req.memory = V4L2_MEMORY_MMAP; if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { fprintf(stderr, "%s does not support " "memory mapping\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); } } printf("requested %d buffers, got %d\n", count, req.count); if (req.count < 2) { fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name); exit(EXIT_FAILURE); } buffers = calloc(req.count, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf)) errno_exit("VIDIOC_QUERYBUF"); printf("requested buffer %d/%d: %s\n", n_buffers, count, snprintf_buffer(strbuf, sizeof(strbuf), &buf)); buffers[n_buffers].length = buf.length; buffers[n_buffers].bytesused = buf.bytesused; buffers[n_buffers].start = mmap(NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset); if (MAP_FAILED == buffers[n_buffers].start) errno_exit("mmap"); printf("buffer#%d @%p of %d bytes\n", n_buffers, buffers[n_buffers].start, buffers[n_buffers].length); } } static void init_userp(unsigned int buffer_size) { struct v4l2_requestbuffers req; CLEAR(req); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; req.memory = V4L2_MEMORY_USERPTR; if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { if (EINVAL == errno) { fprintf(stderr, "%s does not support " "user pointer i/o\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_REQBUFS"); } } buffers = calloc(4, sizeof(*buffers)); if (!buffers) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } for (n_buffers = 0; n_buffers < 4; ++n_buffers) { buffers[n_buffers].length = buffer_size; buffers[n_buffers].start = malloc(buffer_size); buffers[n_buffers].bytesused = buffer_size; if (!buffers[n_buffers].start) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } } } static void init_device(void) { struct v4l2_capability cap; struct v4l2_cropcap cropcap; struct v4l2_crop crop; struct v4l2_format fmt; if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { if (EINVAL == errno) { fprintf(stderr, "%s is no V4L2 device\n", dev_name); exit(EXIT_FAILURE); } else { errno_exit("VIDIOC_QUERYCAP"); } } if (!(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) { fprintf(stderr, "%s is no video output device\n", dev_name); exit(EXIT_FAILURE); } switch (io) { case IO_METHOD_WRITE: if (!(cap.capabilities & V4L2_CAP_READWRITE)) { fprintf(stderr, "%s does not support write i/o\n", dev_name); exit(EXIT_FAILURE); } break; case IO_METHOD_MMAP: case IO_METHOD_USERPTR: if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "%s does not support streaming i/o\n", dev_name); exit(EXIT_FAILURE); } break; } /* Select video input, video standard and tune here. */ CLEAR(cropcap); cropcap.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; crop.c = cropcap.defrect; /* reset to default */ if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { switch (errno) { case EINVAL: /* Cropping not supported. */ break; default: /* Errors ignored. */ break; } } } else { /* Errors ignored. */ } CLEAR(fmt); /* get the current format */ fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) errno_exit("VIDIOC_G_FMT"); printf("get format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); /* try to set the current format (no-change should always succeed) */ if (xioctl(fd, VIDIOC_TRY_FMT, &fmt) < 0) errno_exit("VIDIOC_TRY_FMT"); printf("tried format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); /* and get the format again */ if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) errno_exit("VIDIOC_G_FMT"); printf("got format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); /* try to set the current format (no-change should always succeed) */ if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) errno_exit("VIDIOC_S_FMT"); printf("set format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); switch (fmt.type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT: fmt.fmt.pix.width = width; fmt.fmt.pix.height = height; fmt.fmt.pix.pixelformat = pixelformat; break; default: printf("unable to set format for anything but output/single-plane\n"); break; } printf("finalizing format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { fprintf(stderr, "VIDIOC_S_FMT error %d, %s\n", errno, strerror(errno)); } printf("final format: %s\n", snprintf_format(strbuf, sizeof(strbuf), &fmt)); switch (io) { case IO_METHOD_WRITE: init_write(fmt.fmt.pix.sizeimage); break; case IO_METHOD_MMAP: init_mmap(); break; case IO_METHOD_USERPTR: init_userp(fmt.fmt.pix.sizeimage); break; } } static void close_device(void) { if (-1 == close(fd)) errno_exit("close"); fd = -1; } static void open_device(void) { struct stat st; if (-1 == stat(dev_name, &st)) { fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev_name, errno, strerror(errno)); exit(EXIT_FAILURE); } if (!S_ISCHR(st.st_mode)) { fprintf(stderr, "%s is no device\n", dev_name); exit(EXIT_FAILURE); } fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); if (-1 == fd) { fprintf(stderr, "Cannot open '%s': %d, %s\n", dev_name, errno, strerror(errno)); exit(EXIT_FAILURE); } } static void usage(FILE *fp, int argc, char **argv) { char fourccstr[5]; fourccstr[4] = 0; fprintf(fp, "Usage: %s [options]\n\n" "Version 1.3\n" "Options:\n" "-d | --device name Video device name [%s]\n" "-h | --help Print this message\n" "-m | --mmap Use memory mapped buffers [default]\n" "-w | --write Use write() calls\n" "-u | --userp Use application allocated buffers\n" "-c | --count Number of frames to create [%i] (negative numbers: no limit)\n" "-f | --format Use format [%dx%d@%s]\n" "-t | --timestamp Set timestamp\n" "", argv[0], dev_name, frame_count, width, height, fourcc2str(pixelformat, fourccstr)); } static const char short_options[] = "d:hmwuc:f:t"; static const struct option long_options[] = { { "device", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "mmap", no_argument, NULL, 'm' }, { "write", no_argument, NULL, 'w' }, { "userp", no_argument, NULL, 'u' }, { "count", required_argument, NULL, 'c' }, { "format", required_argument, NULL, 'f' }, { "timestamp", no_argument, NULL, 't' }, { 0, 0, 0, 0 } }; int main(int argc, char **argv) { dev_name = "/dev/video0"; for (;;) { int idx; int c; c = getopt_long(argc, argv, short_options, long_options, &idx); if (-1 == c) break; switch (c) { case 0: /* getopt_long() flag */ break; case 'd': dev_name = optarg; break; case 'h': usage(stdout, argc, argv); exit(EXIT_SUCCESS); case 'm': io = IO_METHOD_MMAP; break; case 'w': io = IO_METHOD_WRITE; break; case 'u': io = IO_METHOD_USERPTR; break; case 'c': errno = 0; frame_count = strtol(optarg, NULL, 0); if (errno) errno_exit(optarg); break; case 'f': { int n; int w = 0, h = 0; char col[5]; n = sscanf(optarg, "%dx%d@%4c", &w, &h, col); if (n == 3) { width = (w > 0) ? w : 0; height = (h > 0) ? h : 0; pixelformat = str2fourcc(col); col[4] = 0; } else { errno_exit(optarg); } break; case 't': set_timestamp = 1; break; } default: usage(stderr, argc, argv); exit(EXIT_FAILURE); } } open_device(); init_device(); start_capturing(); mainloop(); stop_capturing(); uninit_device(); close_device(); fprintf(stderr, "\n"); return 0; } v4l2loopback-0.14.0/tests/test_dqbuf.c000066400000000000000000000057551476030224200175600ustar00rootroot00000000000000/* * v4l2loopback.c -- video4linux2 loopback driver * * Copyright (C) 2014 Nicolas Dufresne */ #include #include #include #include #include #include #include #include #include #include #include #define COUNT 4 #define sysfail(msg) \ { \ printf("%s failed: %s\n", (msg), strerror(errno)); \ return -1; \ } void usage(const char *progname) { printf("usage: %s \n", progname); exit(1); } int main(int argc, char **argv) { struct v4l2_format fmt = { 0 }; struct v4l2_requestbuffers breq = { 0 }; struct v4l2_buffer bufs[COUNT]; void *data[COUNT] = { 0 }; int fd; int i; if (argc < 2) usage(argv[0]); fd = open(argv[1], O_RDWR); if (fd < 0) sysfail("open"); fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; fmt.fmt.pix.width = 320; fmt.fmt.pix.height = 240; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) sysfail("S_FMT"); breq.count = COUNT; breq.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; breq.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, &breq) < 0) sysfail("REQBUFS"); assert(breq.count == COUNT); memset(bufs, 0, sizeof(bufs)); for (i = 0; i < COUNT; i++) { int p; bufs[i].index = i; bufs[i].type = breq.type; bufs[i].memory = breq.memory; if (ioctl(fd, VIDIOC_QUERYBUF, &bufs[i]) < 0) sysfail("QUERYBUF"); data[i] = mmap(NULL, bufs[i].length, PROT_WRITE, MAP_SHARED, fd, bufs[i].m.offset); if (data[i] == MAP_FAILED) sysfail("mmap"); 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"); if ((bufs[0].flags & V4L2_BUF_FLAG_QUEUED) == 0) { printf("BUG #1: Driver should set the QUEUED flag before returning from QBUF\n"); bufs[0].flags |= V4L2_BUF_FLAG_QUEUED; } if (ioctl(fd, VIDIOC_STREAMON, &fmt.type) < 0) sysfail("STREAMON"); i = 1; while (1) { struct v4l2_buffer buf = { 0 }; if (ioctl(fd, VIDIOC_QBUF, &bufs[i]) < 0) sysfail("QBUF"); printf("\tQUEUED=%d\tDONE=%d\n", bufs[i].flags & V4L2_BUF_FLAG_QUEUED, bufs[i].flags & V4L2_BUF_FLAG_DONE); if ((bufs[i].flags & V4L2_BUF_FLAG_QUEUED) == 0) { printf("BUG #1: Driver should set the QUEUED flag before returning from QBUF\n"); bufs[i].flags |= V4L2_BUF_FLAG_QUEUED; } buf.type = breq.type; buf.memory = breq.memory; if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) sysfail("DBBUF"); i = buf.index; if ((bufs[i].flags & V4L2_BUF_FLAG_QUEUED) == 0) { printf("BUG #2: Driver should not dequeue a buffer that was not initially queued\n"); } #if 0 assert (bufs[i].flags & V4L2_BUF_FLAG_QUEUED); assert (!(buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE))); #endif bufs[i] = buf; } return 0; } v4l2loopback-0.14.0/udev/000077500000000000000000000000001476030224200150415ustar00rootroot00000000000000v4l2loopback-0.14.0/udev/60-persistent-v4l2loopback.rules000066400000000000000000000013711476030224200230420ustar00rootroot00000000000000# 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 {} +" v4l2loopback-0.14.0/utils/000077500000000000000000000000001476030224200152365ustar00rootroot00000000000000v4l2loopback-0.14.0/utils/Makefile000066400000000000000000000011251476030224200166750ustar00rootroot00000000000000CPPFLAGS += -I.. ifneq ($(V4L2LOOPBACK_SNAPSHOT_VERSION),) CPPFLAGS += -DSNAPSHOT_VERSION='"$(V4L2LOOPBACK_SNAPSHOT_VERSION)"' endif prefix?=/usr exec_prefix = ${prefix} bindir = ${exec_prefix}/bin INSTALL = /usr/bin/install -c INSTALL_PROGRAM = ${INSTALL} MKDIR_P = /usr/bin/mkdir -p .PHONY: default clean programs = v4l2loopback-ctl default: $(programs) clean: -rm $(programs) -rm $(programs:%=%.o) install: $(MKDIR_P) $(DESTDIR)$(bindir) $(INSTALL_PROGRAM) $(programs) $(DESTDIR)$(bindir) v4l2loopback-ctl.o: v4l2loopback-ctl.c ../v4l2loopback.h v4l2loopback-ctl: v4l2loopback-ctl.o v4l2loopback-0.14.0/utils/v4l2loopback-ctl.c000066400000000000000000001144521476030224200204730ustar00rootroot00000000000000/* -*- c-file-style: "linux" -*- */ /* * v4l2loopback-ctl -- An application to control v4l2loopback devices driver * * Copyright (C) 2020-2023 IOhannes m zmoelnig (zmoelnig@iem.at) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2loopback.h" #ifndef GLOB_ONLYDIR /* Fix for musl libc and other libcs missing GLOB_ONLYDIR at glob.h */ /* (GLOB_ONLYDIR is not required by POSIX) */ #define GLOB_ONLYDIR 0 #endif #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]; int res = 0; if (!which(exe, 1024, cmdline[0])) { dprintf(2, "cannot find %s - is it installed???\n", cmdline[0]); return 1; } #if 0 do { char *const *argp = cmdline; dprintf(2, "%s:", exe); while (*argp) { dprintf(2, " %s", *argp++); }; dprintf(2, "\n"); } while(0); #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 void printf_raw(const char *str, int escape_level) { const char *backslash = (escape_level > 1) ? "\\\\" : "\\"; if (escape_level > 0) while (*str) { char c = *str++; switch (c) { case '\"': printf("%s\"", backslash); break; case '\'': printf("%s\'", backslash); break; case '\\': printf("%s\\", backslash); break; case '\a': printf("%sa", backslash); break; case '\b': printf("%sb", backslash); break; case '\n': printf("%sn", backslash); break; case '\t': printf("%st", backslash); break; // and so on default: if (iscntrl(c)) printf("%s%03o", backslash, c); else printf("%c", c); } } else printf("%s", str); } 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, LIST, QUERY, SET_FPS, GET_FPS, SET_CAPS, GET_CAPS, SET_TIMEOUTIMAGE, MOO, _UNKNOWN } t_command; static void _help(int detail, const char *section, const char *program, const char *verb, const char *argstring, const char *description, const char *options) { (void)section; if (!detail) { dprintf(2, "%s%s%s %s\n", program ? program : "", program ? " " : "", verb, argstring); return; } dprintf(2, "\n%s%s%s %s:\n", program ? program : "", program ? " " : "", verb, argstring); dprintf(2, " %s\n ", description); if (options) { dprintf(2, "%s\n", options); //dprintf(2, "\n\n'%s' OPTIONS:%s\n", verb, options); } } static void help_list(const char *program, int detail) { _help(detail, "Listing Devices", program, "list", "[OPTIONS]", "list all available loopback-devices", "\n\t-e, --escape escape control-characters in (device) names" "\n\t-h, --help print this help and exit" ""); } static void help_add(const char *program, int detail) { _help(detail, "Adding Devices", program, "add", "[OPTIONS] [ []]", "create/add a new loopback-device", "\n\t-b , --buffers buffers to queue" "\n\t-h , --max-height maximum allowed frame height" "\n\t-n , --name pretty name for the device" "\n\t-o , --max-openers maximum allowed concurrent openers" "\n\t-v, --verbose verbose mode (print properties of device after successfully creating it)" "\n\t-w , --max-width maximum allowed frame width" "\n\t-x , --exclusive-caps whether to announce OUTPUT/CAPTURE capabilities exclusively" "\n\t--min-width minimum allowed frame width" "\n\t--min-height minimum allowed frame height" "\n\t-?, --help print this help and exit" "\n" "\n \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 \tif given, use separate output & capture devices (otherwise they are the same)."); } static void help_delete(const char *program, int detail) { _help(detail, "Deleting Devices", program, "delete", "", "delete/remove an unused loopback device", "\n \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 detail) { _help(detail, "Querying Devices", program, "query", "[OPTIONS] ", "query information about a loopback device", "\n\t-e, --escape escape control-characters in (device) names" "\n\t-h, --help print this help and exit" "\n" "\n \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 detail) { _help(detail, "Setting Framerate", program, "set-fps", " ", "set the default framerate for a loopback device", "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." "\n \tframes per second, either as integer ('30') or fraction ('50/2')."); } static void help_getfps(const char *program, int detail) { _help(detail, "Getting Framerate", program, "get-fps", "", "query the framerate of a loopback device", 0); } static void help_setcaps(const char *program, int detail) { _help(detail, "Setting Capabilities", program, "set-caps", " ", "set format/dimension/framerate of a loopback device", "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." "\n \tformat specification as ':x@' (e.g. 'UYVY:1024x768@60/1')" "\n \tunset the current caps with the special value 'any'"); if (detail > 1) { dprintf(2, "\nknown fourcc-codes" "\n==================" "\nFOURCC\thex \tdec \tdescription" "\n------\t----------\t------------\t-----------" ""); 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'\t0x%08X\t%12d\t%s\n", fourcc2str(fmt->fourcc, fourcc), fmt->fourcc, fmt->fourcc, fmt->name); } } } static void help_getcaps(const char *program, int detail) { _help(detail, "Getting Capabilities", program, "get-caps", "", "get current format/dimension/framerate of a loopback device", 0); } static void help_settimeoutimage(const char *program, int detail) { _help(detail, "Setting Timeout Image", program, "set-timeout-image", "[OPTIONS] ", "set a fallback image to be used if a video producer does not send new frames in time.", "\n \tany of the following flags may be present" "\n\t-h, --help print this help and exit" "\n\t-t , --timeout timeout (in ms)" "\n\t-v, --verbose raise verbosity (print what is being done)" "\n" "\n \teither specify a device name (e.g. '/dev/video1') or a device number ('1')." "\n \timage file"); } static void help_none(const char *program, int detail) { } typedef void (*t_help)(const char *, int); static t_help get_help(t_command cmd) { switch (cmd) { default: break; case ADD: return help_add; case DELETE: return help_delete; case LIST: return help_list; 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 [OPTIONS]\n", name); for (cmd = ADD; cmd < _UNKNOWN; cmd++) { t_help hlp = get_help(cmd); if (help_none == hlp) continue; dprintf(2, " or: %s ", name); hlp(0, 0); } dprintf(2, "\nManage v4l2 loopback devices."); dprintf(2, "\n" "\nThe general invocation uses a verb (like 'add' or 'delete') that defines" "\nan action to be executed. Each verb has their own options and arguments." "\n" "\nOptions:" "\n\t-h, -?, --help: print this help and exit" "\n\t-v, --version: print version and exit" "\n\n"); /* long helps */ dprintf(2, "*Verbs and their arguments*\n"); for (cmd = ADD; cmd < _UNKNOWN; cmd++) { t_help hlp = get_help(cmd); if (help_none == hlp) continue; hlp("v4l2loopback-ctl", 1); dprintf(2, "\n\n"); } dprintf(2, "*Reporting Bugs*\n" "\nIssue tracker: https://github.com/umlaeute/v4l2loopback/issues" "\nSecurity Issue tracker: https://git.iem.at/zmoelnig/v4l2loopback/-/issues" "\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, 2); 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, int escape_level) { int output_nr, capture_nr; MARK(); if (!cfg) { printf("configuration: %p\n", cfg); return; } output_nr = capture_nr = cfg->output_nr; #ifdef SPLIT_DEVICES capture_nr = cfg->capture_nr; #endif MARK(); printf("\tcapture_device# : %d" "\n\toutput_device# : %d" "\n\tcard_label : ", capture_nr, output_nr); printf_raw(cfg->card_label, escape_level); printf("\n\tmin_width : %d" "\n\tmax_width : %d" "\n\tmin_height : %d" "\n\tmax_height : %d" "\n\tannounce_all_caps: %d" "\n\tmax_buffers : %d" "\n\tmax_openers : %d" "\n\tdebug : %d" "\n", cfg->min_width, cfg->max_width, cfg->min_height, 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 min_width, int max_width, int min_height, int max_height, int exclusive_caps, int buffers, int openers, int capture_device, int output_device) { if (!cfg) return 0; /* check if at least one of the args are non-default */ if (!label && min_width <= 0 && max_width <= 0 && min_height <= 0 && max_height <= 0 && exclusive_caps < 0 && buffers <= 0 && openers <= 0 && capture_device < 0 && output_device < 0) return 0; #ifdef SPLIT_DEVICES cfg->capture_nr = capture_device; #endif cfg->output_nr = output_device; cfg->card_label[0] = 0; if (label) snprintf(cfg->card_label, 32, "%s", label); cfg->min_width = (min_width < 0) ? 0 : min_width; cfg->max_width = (max_width < 0) ? 0 : max_width; cfg->min_height = (min_height < 0) ? 0 : min_height; cfg->max_height = (max_height < 0) ? 0 : max_height; 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) { int err = 0; MARK(); int ret = ioctl(fd, V4L2LOOPBACK_CTL_ADD, cfg); MARK(); if (ret < 0) { err = errno; perror("failed to create device"); return err; } MARK(); printf("/dev/video%d\n", ret); if (verbose > 0) { MARK(); struct v4l2_loopback_config config; memset(&config, 0, sizeof(config)); config.output_nr = ret; #ifdef SPLIT_DEVICES config.capture_nr = ret; #endif ret = ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config); if (ret < 0) { err = errno; perror("failed querying newly added device"); } MARK(); print_conf(&config, 0); MARK(); } return err; } static int delete_device(int fd, const char *devicename) { int err = 0; 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) { err = errno; perror(devicename); } return err; } static int query_device(int fd, const char *devicename, int escape) { 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 = dev; #ifdef SPLIT_DEVICES config.capture_nr = dev; #endif err = ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config); if (err) perror("query failed"); else { printf("%s\n", devicename); print_conf(&config, escape); return 0; } return err; } static int list_devices(int fd, int escape) { struct devnode_ { int output, capture; char name[32]; } *devices = 0; size_t numdevices = 0, i; glob_t globbuf = { 0 }; int output_nr, capture_nr; glob("/sys/devices/virtual/video4linux/video*", GLOB_ONLYDIR, 0, &globbuf); if (globbuf.gl_pathc) { devices = malloc(globbuf.gl_pathc * sizeof(*devices)); } for (i = 0; i < globbuf.gl_pathc; i++) { size_t j; struct v4l2_loopback_config config = { 0 }; int dev = -1; char *endptr; struct stat sb; const char *path = globbuf.gl_pathv[i]; if (lstat(path, &sb)) { //perror("stat"); continue; } if (!S_ISDIR(sb.st_mode)) { //dprintf(2, "not a directory\n"); continue; } dev = strtol(path + 38, &endptr, 10); if (*endptr) { //dprintf(2, "unable to parse device-name\n"); continue; } /* check if this is a loopback device */ config.output_nr = dev; #ifdef SPLIT_DEVICES config.capture_nr = -1; if (ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config)) { memset(&config, 0, sizeof(config)); config.output_nr = -1; config.capture_nr = dev; if (ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config)) { //dprintf(2, "not a loopback device\n"); continue; } } capture_nr = config.capture_nr; #else if (ioctl(fd, V4L2LOOPBACK_CTL_QUERY, &config)) { //dprintf(2, "not a loopback device\n"); continue; } capture_nr = config.output_nr; #endif output_nr = config.output_nr; /* check if we already have this device */ for (j = 0; j < numdevices; j++) { if ((devices[j].output == output_nr) && (devices[j].capture == capture_nr)) { //dprintf(2, "duplicate device\n"); output_nr = capture_nr = -1; break; } } if ((output_nr < 0) || (capture_nr < 0)) continue; devices[numdevices].output = output_nr; devices[numdevices].capture = capture_nr; snprintf(devices[numdevices].name, sizeof(devices[numdevices].name), "%s", config.card_label); numdevices++; } if (numdevices) { dprintf(2, "OUTPUT \tCAPTURE \tNAME\n"); } else { dprintf(2, "no loopback devices found\n"); } for (i = 0; i < numdevices; i++) { const char *str = devices[i].name; printf("/dev/video%-3d\t/dev/video%-3d\t", devices[i].output, devices[i].capture); printf_raw(str, escape); printf("\n"); } globfree(&globbuf); free(devices); return 0; } 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); } return fd; } 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(sysdev); 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 param; 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) { ret = 1; goto done; } memset(¶m, 0, sizeof(param)); param.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; if (ioctl(fd, VIDIOC_G_PARM, ¶m) == 0) { const struct v4l2_fract *tf = ¶m.parm.output.timeperframe; num = tf->numerator; denom = tf->denominator; goto done; } memset(¶m, 0, sizeof(param)); param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_G_PARM, ¶m) == 0) { const struct v4l2_fract *tf = ¶m.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 ':x[@] 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 verbose) { int err = 0; int fd = -1; char imagearg[4096], imagefile2[4096], devicearg[4096]; char *args[] = { "gst-launch-1.0", "uridecodebin", 0, "!", "videoconvert", "!", "videoscale", "!", "imagefreeze", "!", "identity", "eos-after=2", "drop-allocation=1", "!", "v4l2sink", "show-preroll-frame=false", 0, 0 }; if (verbose) printf("set-timeout-image '%s' for '%s' with %dms timeout\n", imagefile, devicename, timeout); snprintf(imagearg, 4096, "uri=file://%s", realpath(imagefile, imagefile2)); snprintf(devicearg, 4096, "device=%s", devicename); imagearg[4095] = devicearg[4095] = 0; args[2] = imagearg; args[16] = devicearg; fd = open_videodevice(devicename, O_RDWR); if (fd >= 0) { dprintf(2, "v4l2-ctl -d %s -c timeout_image_io=1\n", devicename); set_control_i(fd, "timeout_image_io", 1); close(fd); } else { err = errno; } if (verbose > 1) { char **ap = args; while (*ap) { dprintf(2, "%s", *ap); if (*ap++) dprintf(2, " "); else dprintf(2, "\n"); } } dprintf(2, "v======================================================================v\n"); if (my_execv(args)) { dprintf(2, "ERROR: setting time-out image failed\n"); err = 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 { dprintf(2, "v4l2-ctl -d %s -c timeout=%d\n", devicename, timeout); 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); } else { err = errno; } return err; } static t_command get_command(const char *command) { if (!strncmp(command, "-h", 3)) return HELP; if (!strncmp(command, "-?", 3)) return HELP; if (!strncmp(command, "--help", 7)) return HELP; if (!strncmp(command, "-v", 3)) return VERSION; if (!strncmp(command, "--version", 10)) return VERSION; if (!strncmp(command, "list", 5)) return LIST; if (!strncmp(command, "add", 4)) return ADD; if (!strncmp(command, "del", 3)) /* also allow delete */ return DELETE; if (!strncmp(command, "query", 6)) return QUERY; if (!strncmp(command, "set-fps", 8)) return SET_FPS; if (!strncmp(command, "get-fps", 8)) return GET_FPS; if (!strncmp(command, "set-caps", 9)) return SET_CAPS; if (!strncmp(command, "get-caps", 9)) return GET_CAPS; if (!strncmp(command, "set-timeout-image", 18)) return SET_TIMEOUTIMAGE; if (!strncmp(command, "moo", 10)) return MOO; 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 does not look like a device, but does * if so, assume that the user swapped the two */ /* if the looks about right, optionally do some extra * -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> ' is deprecated!\n", programname, cmdname, argname); dprintf(2, "WARNING: use '%s %s <%s>' instead.\n", programname, cmdname, argname); return 1; } return 0; } static int do_defaultargs(const char *progname, t_command cmd, int argc, char **argv) { static const char options_short[] = "?h"; static const struct option options_long[] = { { "help", no_argument, NULL, 'h' }, { 0, 0, 0, 0 } }; for (;;) { int c; int idx; c = getopt_long(argc - 1, argv + 1, options_short, options_long, &idx); if (-1 == c) break; switch (c) { case 'h': usage_topic(argv[0], cmd, argc - 1, argv + 1); exit(0); default: usage_topic(argv[0], cmd, argc - 1, argv + 1); exit(1); } } return optind; } int main(int argc, char **argv) { const char *progname = argv[0]; int i; int fd = -1; int verbose = 0; t_command cmd; char *label = 0; int min_width = -1; int max_width = -1; int min_height = -1; int max_height = -1; int exclusive_caps = -1; int buffers = -1; int openers = -1; int escape_strings = 0; int ret = 0; static const char add_options_short[] = "?vn:w:h:x:b:o:"; static const struct option add_options_long[] = { { "help", no_argument, NULL, '?' }, { "verbose", no_argument, NULL, 'v' }, { "name", required_argument, NULL, 'n' }, { "min-width", required_argument, NULL, 'w' + 0xFFFF }, { "max-width", required_argument, NULL, 'w' }, { "min-height", required_argument, NULL, 'h' + 0xFFFF }, { "max-height", required_argument, NULL, 'h' }, { "exclusive-caps", required_argument, NULL, 'x' }, { "buffers", required_argument, NULL, 'b' }, { "max-openers", required_argument, NULL, 'o' }, { 0, 0, 0, 0 } }; static const char list_options_short[] = "?he"; static const struct option list_options_long[] = { { "help", no_argument, NULL, 'h' }, { "escape", no_argument, NULL, 'e' }, { 0, 0, 0, 0 } }; static const char timeoutimg_options_short[] = "?ht:v"; static const struct option timeoutimg_options_long[] = { { "help", no_argument, NULL, 'h' }, { "timeout", required_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { 0, 0, 0, 0 } }; if (argc < 2) usage(progname); cmd = get_command(argv[1]); if (_UNKNOWN == cmd) { dprintf(2, "unknown command '%s'\n\n", argv[1]); usage(progname); return 1; } argc--; argv++; switch (cmd) { case HELP: help(progname, 0); break; case LIST: for (;;) { int c; int idx; c = getopt_long(argc, argv, list_options_short, list_options_long, &idx); if (-1 == c) break; switch (c) { case 'e': escape_strings++; break; default: usage_topic(progname, cmd, argc - 1, argv + 1); return 1; } } argc -= optind; argv += optind; if (argc) { dprintf(2, "'list' does not take any arguments\n"); return 1; } fd = open_controldevice(); if (fd >= 0) list_devices(fd, escape_strings); else return 1; break; case ADD: for (;;) { int c; int idx; c = getopt_long(argc, argv, add_options_short, add_options_long, &idx); if (-1 == c) break; switch (c) { case 'v': verbose++; break; case 'n': label = optarg; break; case 'w' + 0xFFFF: min_width = my_atoi("min_width", optarg); break; case 'h' + 0xFFFF: min_height = my_atoi("min_height", 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(progname, cmd, argc - 1, argv + 1); return 1; } } argc -= optind; argv += optind; fd = open_controldevice(); if (min_width > max_width && max_width > 0) { dprintf(2, "min_width (%d) must not be greater than max_width (%d)\n", min_width, max_width); return 1; } if (min_height > max_height && max_height > 0) { dprintf(2, "min_height (%d) must not be greater than max_height (%d)\n", min_height, max_height); return 1; } do { struct v4l2_loopback_config cfg; int capture_nr = -1, output_nr = -1; switch (argc) { case 0: /* no device given: pick some */ break; case 2: /* two devices given: capture_device and output_device */ output_nr = parse_device(argv[0]); capture_nr = parse_device(argv[1]); #ifndef SPLIT_DEVICES if (capture_nr != output_nr) dprintf(2, "split output/capture devices currently not supported...ignoring capture device\n"); capture_nr = output_nr; #endif break; case 1: /* single device given: use it for both input and output */ capture_nr = output_nr = parse_device(argv[0]); break; default: usage_topic(progname, cmd, argc, argv); return 1; } ret = add_device(fd, make_conf(&cfg, label, min_width, max_width, min_height, max_height, exclusive_caps, buffers, openers, capture_nr, output_nr), verbose); } while (0); break; case DELETE: optind = do_defaultargs(progname, cmd, argc, argv); argc -= optind; argv += optind; if (!argc) usage_topic(progname, cmd, argc, argv); fd = open_controldevice(); for (i = 0; i < argc; i++) { int err = delete_device(fd, argv[i]); if (err) ret = err; } break; case QUERY: for (;;) { int c; int idx; c = getopt_long(argc, argv, list_options_short, list_options_long, &idx); if (-1 == c) break; switch (c) { case 'e': escape_strings++; break; default: usage_topic(progname, cmd, argc - 1, argv + 1); return 1; } } argc -= optind; argv += optind; if (!argc) usage_topic(progname, cmd, argc, argv); fd = open_controldevice(); for (i = 0; i < argc; i++) { ret += query_device(fd, argv[i], escape_strings); } ret = (ret > 0); break; case SET_FPS: optind = do_defaultargs(progname, cmd, argc, argv); argc -= optind; argv += optind; if (argc != 2) usage_topic(progname, cmd, argc, argv); if (called_deprecated(argv[0], argv[1], progname, "set-fps", "fps", is_fps)) { ret = set_fps(argv[1], argv[0]); } else ret = set_fps(argv[0], argv[1]); break; case GET_FPS: optind = do_defaultargs(progname, cmd, argc, argv); argc -= optind; argv += optind; if (argc != 1) usage_topic(progname, cmd, argc, argv); ret = get_fps(argv[0]); break; case SET_CAPS: optind = do_defaultargs(progname, cmd, argc, argv); argc -= optind; argv += optind; if (argc != 2) usage_topic(progname, cmd, argc, argv); if (called_deprecated(argv[0], argv[1], progname, "set-caps", "caps", 0)) { ret = set_caps(argv[1], argv[0]); } else { ret = set_caps(argv[0], argv[1]); } break; case GET_CAPS: optind = do_defaultargs(progname, cmd, argc, argv); argc -= optind; argv += optind; if (argc != 1) usage_topic(progname, cmd, argc, argv); ret = get_caps(argv[0]); break; case SET_TIMEOUTIMAGE: if ((3 == argc) && (strncmp("-t", argv[1], 3)) && (strncmp("--timeout", argv[1], 10)) && (called_deprecated(argv[1], argv[2], progname, "set-timeout-image", "image", 0))) { ret = set_timeoutimage(argv[2], argv[1], -1, verbose); } else { int timeout = -1; for (;;) { int c, idx; c = getopt_long(argc, argv, timeoutimg_options_short, timeoutimg_options_long, &idx); if (-1 == c) break; switch (c) { case 't': timeout = my_atoi("timeout", optarg); break; case 'v': verbose++; break; default: usage_topic(progname, cmd, argc, argv); } } argc -= optind; argv += optind; if (argc != 2) usage_topic(progname, cmd, argc, argv); ret = set_timeoutimage(argv[0], argv[1], timeout, verbose); } break; case VERSION: #ifdef SNAPSHOT_VERSION printf("%s v%s\n", progname, SNAPSHOT_VERSION); #else printf("%s v%d.%d.%d\n", progname, V4L2LOOPBACK_VERSION_MAJOR, V4L2LOOPBACK_VERSION_MINOR, V4L2LOOPBACK_VERSION_BUGFIX); #endif fd = open("/sys/module/v4l2loopback/version", O_RDONLY); if (fd >= 0) { char buf[1024]; int len = read(fd, buf, sizeof(buf)); if (len > 0) { if (len < sizeof(buf)) buf[len] = 0; printf("v4l2loopback module v%s", buf); } } break; default: dprintf(2, "not implemented: '%s'\n", argv[0]); break; } if (fd >= 0) close(fd); return ret; } v4l2loopback-0.14.0/v4l2loopback.c000066400000000000000000002725431476030224200165610ustar00rootroot00000000000000/* -*- c-file-style: "linux" -*- */ /* * v4l2loopback.c -- video4linux2 loopback driver * * Copyright (C) 2005-2009 Vasily Levin (vasaka@gmail.com) * Copyright (C) 2010-2023 IOhannes m zmoelnig (zmoelnig@iem.at) * Copyright (C) 2011 Stefan Diewald (stefan.diewald@mytum.de) * Copyright (C) 2012 Anton Novikov (random.plant@gmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2loopback.h" #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) #error This module is not supported on kernels before 4.0.0. #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) #define strscpy strlcpy #endif #if defined(timer_setup) && defined(from_timer) #define HAVE_TIMER_SETUP #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0) #define VFL_TYPE_VIDEO VFL_TYPE_GRABBER #endif #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 ," "Stefan Diewald," "Anton Novikov" "et al."); #ifdef SNAPSHOT_VERSION MODULE_VERSION(__stringify(SNAPSHOT_VERSION)); #else MODULE_VERSION("" __stringify(V4L2LOOPBACK_VERSION_MAJOR) "." __stringify( V4L2LOOPBACK_VERSION_MINOR) "." __stringify(V4L2LOOPBACK_VERSION_BUGFIX)); #endif MODULE_LICENSE("GPL"); /* * helpers */ #define dprintk(fmt, args...) \ do { \ if (debug > 0) { \ printk(KERN_INFO "v4l2-loopback[" __stringify( \ __LINE__) "], pid(%d): " fmt, \ task_pid_nr(current), ##args); \ } \ } while (0) #define MARK() \ do { \ if (debug > 1) { \ printk(KERN_INFO "%s:%d[%s], pid(%d)\n", __FILE__, \ __LINE__, __func__, task_pid_nr(current)); \ } \ } while (0) #define dprintkrw(fmt, args...) \ do { \ if (debug > 2) { \ printk(KERN_INFO "v4l2-loopback[" __stringify( \ __LINE__) "], pid(%d): " fmt, \ task_pid_nr(current), ##args); \ } \ } while (0) static inline void v4l2l_get_timestamp(struct v4l2_buffer *b) { struct timespec64 ts; ktime_get_ts64(&ts); b->timestamp.tv_sec = ts.tv_sec; b->timestamp.tv_usec = (ts.tv_nsec / NSEC_PER_USEC); b->flags |= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; b->flags &= ~V4L2_BUF_FLAG_TIMESTAMP_COPY; } #if BITS_PER_LONG == 32 #include /* do_div() for 64bit division */ static inline int v4l2l_mod64(const s64 A, const u32 B) { u64 a = (u64)A; u32 b = B; if (A > 0) return do_div(a, b); a = -A; return -do_div(a, b); } #else static inline int v4l2l_mod64(const s64 A, const u32 B) { return A % B; } #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0) typedef unsigned __poll_t; #endif /* module constants * can be overridden during he build process using something like * make KCPPFLAGS="-DMAX_DEVICES=100" */ /* maximum number of v4l2loopback devices that can be created */ #ifndef MAX_DEVICES #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 */ #endif /* max buffers that can be mapped, actually they * are all mapped to max_buffers buffers */ #ifndef MAX_BUFFERS #define MAX_BUFFERS 32 #endif /* module parameters */ static int debug = 0; module_param(debug, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(debug, "debugging level (higher values == more verbose)"); #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 [DEFAULT: " __stringify( 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 * the /sys/devices interface * * note that max_openers should be at least 2 in order to get a working system: * one opener for the producer and one opener for the consumer * however, we leave that to the user */ #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 the loopback device [DEFAULT: " __stringify( 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 }; module_param_array(video_nr, int, NULL, 0444); 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 each device"); 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 [DEFAULT: " __stringify( V4L2LOOPBACK_DEFAULT_EXCLUSIVECAPS) "]"); /* format specifications */ #define V4L2LOOPBACK_SIZE_MIN_WIDTH 2 #define V4L2LOOPBACK_SIZE_MIN_HEIGHT 1 #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 static int max_width = V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH; module_param(max_width, int, S_IRUGO); MODULE_PARM_DESC(max_width, "maximum allowed frame width [DEFAULT: " __stringify( 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 allowed frame height [DEFAULT: " __stringify( V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT) "]"); static DEFINE_IDR(v4l2loopback_index_idr); static DEFINE_MUTEX(v4l2loopback_ctl_mutex); /* frame intervals */ #define V4L2LOOPBACK_FRAME_INTERVAL_MAX __UINT32_MAX__ #define V4L2LOOPBACK_FPS_DEFAULT 30 #define V4L2LOOPBACK_FPS_MAX 1000 /* control IDs */ #define V4L2LOOPBACK_CID_BASE (V4L2_CID_USER_BASE | 0xf000) #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 = { // 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 = { // 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 = { // 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 = { // clang-format off .ops = &v4l2loopback_ctrl_ops, .id = CID_TIMEOUT_IMAGE_IO, .name = "timeout_image_io", .type = V4L2_CTRL_TYPE_BUTTON, .min = 0, .max = 0, .step = 0, .def = 0, // clang-format on }; /* module structures */ struct v4l2loopback_private { int device_nr; }; /* TODO(vasaka) use typenames which are common to kernel, but first find out if * it is needed */ /* struct keeping state and settings of loopback device */ struct v4l2l_buffer { struct v4l2_buffer buffer; struct list_head list_head; atomic_t use_count; }; struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; struct video_device *vdev; /* loopback device-specific parameters */ char card_label[32]; bool announce_all_caps; /* announce both OUTPUT and CAPTURE capabilities * when true; else announce OUTPUT when no * writer is streaming, otherwise CAPTURE. */ int max_openers; /* how many times can this device be opened */ int min_width, max_width; int min_height, max_height; /* pixel and stream format */ struct v4l2_pix_format pix_format; bool pix_format_has_valid_sizeimage; struct v4l2_captureparm capture_param; unsigned long frame_jiffies; /* ctrls */ int keep_format; /* CID_KEEP_FORMAT; lock the format, do not free * on close(), and when `!announce_all_caps` do NOT * fall back to OUTPUT when no writers attached (clear * `keep_format` to attach a new writer) */ int sustain_framerate; /* CID_SUSTAIN_FRAMERATE; duplicate frames to maintain (close to) nominal framerate */ unsigned long timeout_jiffies; /* CID_TIMEOUT; 0 means disabled */ int timeout_image_io; /* CID_TIMEOUT_IMAGE_IO; next opener will * queue/dequeue the timeout image buffer */ /* buffers for OUTPUT and CAPTURE */ u8 *image; /* pointer to actual buffers data */ unsigned long image_size; /* number of bytes alloc'd for all buffers */ struct v4l2l_buffer buffers[MAX_BUFFERS]; /* inner driver buffers */ u32 buffer_count; /* should not be big, 4 is a good choice */ u32 buffer_size; /* number of bytes alloc'd per buffer */ u32 used_buffer_count; /* number of buffers allocated to openers */ struct list_head outbufs_list; /* FIFO queue for OUTPUT buffers */ u32 bufpos2index[MAX_BUFFERS]; /* mapping of `(position % used_buffers)` * to `buffers[index]` */ s64 write_position; /* sequence number of last 'displayed' buffer plus * one */ /* synchronization between openers */ atomic_t open_count; struct mutex image_mutex; /* mutex for allocating image(s) and * exchanging format tokens */ spinlock_t lock; /* lock for the timeout and framerate timers */ spinlock_t list_lock; /* lock for the OUTPUT buffer queue */ wait_queue_head_t read_event; u32 format_tokens; /* tokens to 'set format' for OUTPUT, CAPTURE, or * timeout buffers */ u32 stream_tokens; /* tokens to 'start' OUTPUT, CAPTURE, or timeout * stream */ /* sustain framerate */ struct timer_list sustain_timer; unsigned int reread_count; /* timeout */ u8 *timeout_image; /* copied to outgoing buffers when timeout passes */ struct v4l2l_buffer timeout_buffer; u32 timeout_buffer_size; /* number bytes alloc'd for timeout buffer */ struct timer_list timeout_timer; int timeout_happened; }; enum v4l2l_io_method { V4L2L_IO_NONE = 0, V4L2L_IO_MMAP = 1, V4L2L_IO_FILE = 2, V4L2L_IO_TIMEOUT = 3, }; /* struct keeping state and type of opener */ struct v4l2_loopback_opener { u32 format_token; /* token (if any) for type used in call to S_FMT or * REQBUFS */ u32 stream_token; /* token (if any) for type used in call to STREAMON */ u32 buffer_count; /* number of buffers (if any) that opener acquired via * REQBUFS */ s64 read_position; /* sequence number of the next 'captured' frame */ unsigned int reread_count; enum v4l2l_io_method io_method; 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; }; /* set the v4l2l_format.flags to PLANAR for non-packed formats */ #define FORMAT_FLAGS_PLANAR 0x01 #define FORMAT_FLAGS_COMPRESSED 0x02 #include "v4l2loopback_formats.h" #ifndef V4L2_TYPE_IS_CAPTURE #define V4L2_TYPE_IS_CAPTURE(type) \ ((type) == V4L2_BUF_TYPE_VIDEO_CAPTURE || \ (type) == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) #endif /* V4L2_TYPE_IS_CAPTURE */ #ifndef V4L2_TYPE_IS_OUTPUT #define V4L2_TYPE_IS_OUTPUT(type) \ ((type) == V4L2_BUF_TYPE_VIDEO_OUTPUT || \ (type) == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) #endif /* V4L2_TYPE_IS_OUTPUT */ /* token values for privilege to set format or start/stop stream */ #define V4L2L_TOKEN_CAPTURE 0x01 #define V4L2L_TOKEN_OUTPUT 0x02 #define V4L2L_TOKEN_TIMEOUT 0x04 #define V4L2L_TOKEN_MASK \ (V4L2L_TOKEN_CAPTURE | V4L2L_TOKEN_OUTPUT | V4L2L_TOKEN_TIMEOUT) /* helpers for token exchange and token status */ #define token_from_type(type) \ (V4L2_TYPE_IS_CAPTURE(type) ? V4L2L_TOKEN_CAPTURE : V4L2L_TOKEN_OUTPUT) #define acquire_token(dev, opener, label, token) \ do { \ (opener)->label##_token = token; \ (dev)->label##_tokens &= ~token; \ } while (0) #define release_token(dev, opener, label) \ do { \ (dev)->label##_tokens |= (opener)->label##_token; \ (opener)->label##_token = 0; \ } while (0) #define has_output_token(token) (token & V4L2L_TOKEN_OUTPUT) #define has_capture_token(token) (token & V4L2L_TOKEN_CAPTURE) #define has_no_owners(dev) ((~((dev)->format_tokens) & V4L2L_TOKEN_MASK) == 0) #define has_other_owners(opener, dev) \ (~((dev)->format_tokens ^ (opener)->format_token) & V4L2L_TOKEN_MASK) #define need_timeout_buffer(dev, token) \ ((dev)->timeout_jiffies > 0 || (token) & V4L2L_TOKEN_TIMEOUT) static const unsigned int FORMATS = ARRAY_SIZE(formats); static char *fourcc2str(unsigned int fourcc, char buf[5]) { buf[0] = (fourcc >> 0) & 0xFF; buf[1] = (fourcc >> 8) & 0xFF; buf[2] = (fourcc >> 16) & 0xFF; buf[3] = (fourcc >> 24) & 0xFF; buf[4] = 0; return buf; } static const struct v4l2l_format *format_by_fourcc(int fourcc) { unsigned int i; char buf[5]; for (i = 0; i < FORMATS; i++) { if (formats[i].fourcc == fourcc) return formats + i; } dprintk("unsupported format '%4s'\n", fourcc2str(fourcc, buf)); return NULL; } static void pix_format_set_size(struct v4l2_pix_format *f, const struct v4l2l_format *fmt, unsigned int width, unsigned int height) { f->width = width; f->height = height; if (fmt->flags & FORMAT_FLAGS_PLANAR) { 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 */ f->bytesperline = 0; f->sizeimage = (width * height * fmt->depth) >> 3; } else { f->bytesperline = (width * fmt->depth) >> 3; f->sizeimage = height * f->bytesperline; } } static int v4l2l_fill_format(struct v4l2_format *fmt, const u32 minwidth, const u32 maxwidth, const u32 minheight, const u32 maxheight) { u32 width = fmt->fmt.pix.width, height = fmt->fmt.pix.height; u32 pixelformat = fmt->fmt.pix.pixelformat; struct v4l2_format fmt0 = *fmt; u32 bytesperline = 0, sizeimage = 0; if (!width) width = V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; if (!height) height = V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; width = clamp_val(width, minwidth, maxwidth); height = clamp_val(height, minheight, maxheight); /* sets: width,height,pixelformat,bytesperline,sizeimage */ if (!(V4L2_TYPE_IS_MULTIPLANAR(fmt0.type))) { fmt0.fmt.pix.bytesperline = 0; fmt0.fmt.pix.sizeimage = 0; } if (0) { ; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) } else if (!v4l2_fill_pixfmt(&fmt0.fmt.pix, pixelformat, width, height)) { ; } else if (!v4l2_fill_pixfmt_mp(&fmt0.fmt.pix_mp, pixelformat, width, height)) { ; #endif } else { const struct v4l2l_format *format = format_by_fourcc(pixelformat); if (!format) return -EINVAL; pix_format_set_size(&fmt0.fmt.pix, format, width, height); fmt0.fmt.pix.pixelformat = format->fourcc; } if (V4L2_TYPE_IS_MULTIPLANAR(fmt0.type)) { *fmt = fmt0; if ((fmt->fmt.pix_mp.colorspace == V4L2_COLORSPACE_DEFAULT) || (fmt->fmt.pix_mp.colorspace > V4L2_COLORSPACE_DCI_P3)) fmt->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB; if (V4L2_FIELD_ANY == fmt->fmt.pix_mp.field) fmt->fmt.pix_mp.field = V4L2_FIELD_NONE; } else { bytesperline = fmt->fmt.pix.bytesperline; sizeimage = fmt->fmt.pix.sizeimage; *fmt = fmt0; if (!fmt->fmt.pix.bytesperline) fmt->fmt.pix.bytesperline = bytesperline; if (!fmt->fmt.pix.sizeimage) fmt->fmt.pix.sizeimage = sizeimage; if ((fmt->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) || (fmt->fmt.pix.colorspace > V4L2_COLORSPACE_DCI_P3)) fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; if (V4L2_FIELD_ANY == fmt->fmt.pix.field) fmt->fmt.pix.field = V4L2_FIELD_NONE; } return 0; } /* Checks if v4l2l_fill_format() has set a valid, fixed sizeimage val. */ static bool v4l2l_pix_format_has_valid_sizeimage(struct v4l2_format *fmt) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) const struct v4l2_format_info *info; info = v4l2_format_info(fmt->fmt.pix.pixelformat); if (info && info->mem_planes == 1) return true; #endif return false; } static int pix_format_eq(const struct v4l2_pix_format *ref, const struct v4l2_pix_format *tgt, int strict) { /* check if the two formats are equivalent. * ANY fields are handled gracefully */ #define _pix_format_eq0(x) \ if (ref->x != tgt->x) \ result = 0 #define _pix_format_eq1(x, def) \ do { \ if ((def != tgt->x) && (ref->x != tgt->x)) { \ printk(KERN_INFO #x " failed"); \ result = 0; \ } \ } while (0) int result = 1; _pix_format_eq0(width); _pix_format_eq0(height); _pix_format_eq0(pixelformat); if (!strict) return result; _pix_format_eq1(field, V4L2_FIELD_ANY); _pix_format_eq0(bytesperline); _pix_format_eq0(sizeimage); _pix_format_eq1(colorspace, V4L2_COLORSPACE_DEFAULT); return result; } static void set_timeperframe(struct v4l2_loopback_device *dev, struct v4l2_fract *tpf) { if (!tpf->denominator && !tpf->numerator) { tpf->numerator = 1; tpf->denominator = V4L2LOOPBACK_FPS_DEFAULT; } else if (tpf->numerator > V4L2LOOPBACK_FRAME_INTERVAL_MAX * tpf->denominator) { /* divide-by-zero or greater than maximum interval => min FPS */ tpf->numerator = V4L2LOOPBACK_FRAME_INTERVAL_MAX; tpf->denominator = 1; } else if (tpf->numerator * V4L2LOOPBACK_FPS_MAX < tpf->denominator) { /* zero or lower than minimum interval => max FPS */ tpf->numerator = 1; tpf->denominator = V4L2LOOPBACK_FPS_MAX; } dev->capture_param.timeperframe = *tpf; dev->frame_jiffies = max(1UL, (msecs_to_jiffies(1000) * tpf->numerator) / tpf->denominator); } static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd); /* device attributes */ /* available via sysfs: /sys/devices/virtual/video4linux/video* */ static ssize_t attr_show_format(struct device *cd, 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); const struct v4l2_fract *tpf; char buf4cc[5], buf_fps[32]; if (!dev || (has_no_owners(dev) && !dev->keep_format)) return 0; tpf = &dev->capture_param.timeperframe; fourcc2str(dev->pix_format.pixelformat, buf4cc); if (tpf->numerator == 1) snprintf(buf_fps, sizeof(buf_fps), "%u", tpf->denominator); else snprintf(buf_fps, sizeof(buf_fps), "%u/%u", tpf->denominator, tpf->numerator); return sprintf(buf, "%4s:%ux%u@%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 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, "@%u/%u", &fps_num, &fps_den) > 0) { struct v4l2_fract f = { .numerator = fps_den, .denominator = fps_num }; set_timeperframe(dev, &f); return len; } return -EINVAL; } 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 v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); if (!dev) return -ENODEV; return sprintf(buf, "%u\n", dev->used_buffer_count); } 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 v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); if (!dev) return -ENODEV; return sprintf(buf, "%d\n", dev->max_openers); } static ssize_t attr_store_maxopeners(struct device *cd, struct device_attribute *attr, const char *buf, size_t len) { struct v4l2_loopback_device *dev = NULL; unsigned long curr = 0; if (kstrtoul(buf, 0, &curr)) return -EINVAL; dev = v4l2loopback_cd2dev(cd); if (!dev) return -ENODEV; if (dev->max_openers == curr) return len; if (curr > __INT_MAX__ || dev->open_count.counter > curr) { /* request to limit to less openers as are currently attached to us */ return -EINVAL; } dev->max_openers = (int)curr; return len; } static DEVICE_ATTR(max_openers, S_IRUGO | S_IWUSR, attr_show_maxopeners, attr_store_maxopeners); static ssize_t attr_show_state(struct device *cd, struct device_attribute *attr, char *buf) { struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); if (!dev) return -ENODEV; if (!has_output_token(dev->stream_tokens) || dev->keep_format) { return sprintf(buf, "capture\n"); } else return sprintf(buf, "output\n"); return -EAGAIN; } static DEVICE_ATTR(state, S_IRUGO, attr_show_state, NULL); static void v4l2loopback_remove_sysfs(struct video_device *vdev) { #define V4L2_SYSFS_DESTROY(x) device_remove_file(&vdev->dev, &dev_attr_##x) if (vdev) { V4L2_SYSFS_DESTROY(format); V4L2_SYSFS_DESTROY(buffers); V4L2_SYSFS_DESTROY(max_openers); V4L2_SYSFS_DESTROY(state); /* ... */ } } static void v4l2loopback_create_sysfs(struct video_device *vdev) { int res = 0; #define V4L2_SYSFS_CREATE(x) \ res = device_create_file(&vdev->dev, &dev_attr_##x); \ if (res < 0) \ break if (!vdev) return; do { V4L2_SYSFS_CREATE(format); V4L2_SYSFS_CREATE(buffers); V4L2_SYSFS_CREATE(max_openers); V4L2_SYSFS_CREATE(state); /* ... */ } while (0); if (res >= 0) return; dev_err(&vdev->dev, "%s error: %d\n", __func__, res); } /* Event APIs */ #define V4L2LOOPBACK_EVENT_BASE (V4L2_EVENT_PRIVATE_START) #define V4L2LOOPBACK_EVENT_OFFSET 0x08E00000 #define V4L2_EVENT_PRI_CLIENT_USAGE \ (V4L2LOOPBACK_EVENT_BASE + V4L2LOOPBACK_EVENT_OFFSET + 1) struct v4l2_event_client_usage { __u32 count; }; /* global module data */ /* 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; } #define v4l2loopback_get_vdev_nr(vdev) \ ((struct v4l2loopback_private *)video_get_drvdata(vdev))->device_nr static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd) { struct video_device *loopdev = to_video_device(cd); int device_nr = v4l2loopback_get_vdev_nr(loopdev); return idr_find(&v4l2loopback_index_idr, device_nr); } static struct v4l2_loopback_device *v4l2loopback_getdevice(struct file *f) { struct v4l2loopback_private *ptr = video_drvdata(f); int nr = ptr->device_nr; return idr_find(&v4l2loopback_index_idr, nr); } /* forward declarations */ static void client_usage_queue_event(struct video_device *vdev); static bool any_buffers_mapped(struct v4l2_loopback_device *dev); static int allocate_buffers(struct v4l2_loopback_device *dev, struct v4l2_pix_format *pix_format); static void init_buffers(struct v4l2_loopback_device *dev, u32 bytes_used, u32 buffer_size); static void free_buffers(struct v4l2_loopback_device *dev); static int allocate_timeout_buffer(struct v4l2_loopback_device *dev); static void free_timeout_buffer(struct v4l2_loopback_device *dev); static void check_timers(struct v4l2_loopback_device *dev); static const struct v4l2_file_operations v4l2_loopback_fops; static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops; /* V4L2 ioctl caps and params calls */ /* returns device capabilities * called on VIDIOC_QUERYCAP */ static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); int device_nr = v4l2loopback_get_vdev_nr(dev->vdev); __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; strscpy(cap->driver, "v4l2 loopback", sizeof(cap->driver)); snprintf(cap->card, sizeof(cap->card), "%s", dev->card_label); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:v4l2loopback-%03d", device_nr); if (dev->announce_all_caps) { capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; } else { if (opener->io_method == V4L2L_IO_TIMEOUT || (has_output_token(dev->stream_tokens) && !dev->keep_format)) { capabilities |= V4L2_CAP_VIDEO_OUTPUT; } else capabilities |= V4L2_CAP_VIDEO_CAPTURE; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) dev->vdev->device_caps = #endif /* >=linux-4.7.0 */ cap->device_caps = cap->capabilities = capabilities; cap->capabilities |= V4L2_CAP_DEVICE_CAPS; memset(cap->reserved, 0, sizeof(cap->reserved)); return 0; } static int vidioc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *argp) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); /* there can be only one... */ if (argp->index) return -EINVAL; if (dev->keep_format || has_other_owners(opener, dev)) { /* only current frame size supported */ if (argp->pixel_format != dev->pix_format.pixelformat) return -EINVAL; argp->type = V4L2_FRMSIZE_TYPE_DISCRETE; argp->discrete.width = dev->pix_format.width; argp->discrete.height = dev->pix_format.height; } else { /* return continuous sizes if pixel format is supported */ if (NULL == format_by_fourcc(argp->pixel_format)) return -EINVAL; if (dev->min_width == dev->max_width && dev->min_height == dev->max_height) { argp->type = V4L2_FRMSIZE_TYPE_DISCRETE; argp->discrete.width = dev->min_width; argp->discrete.height = dev->min_height; } else { argp->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; argp->stepwise.min_width = dev->min_width; argp->stepwise.min_height = dev->min_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; } } return 0; } /* Test if the device is currently 'capable' of the buffer (stream) type when * the `exclusive_caps` parameter is set. `keep_format` should lock the format * and prevent free of buffers */ static int check_buffer_capability(struct v4l2_loopback_device *dev, struct v4l2_loopback_opener *opener, enum v4l2_buf_type type) { /* short-circuit for (non-compliant) timeout image mode */ if (opener->io_method == V4L2L_IO_TIMEOUT) return 0; if (dev->announce_all_caps) return (type == V4L2_BUF_TYPE_VIDEO_CAPTURE || type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? 0 : -EINVAL; /* CAPTURE if opener has a capture format or a writer is streaming; * else OUTPUT. */ switch (type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (!(has_capture_token(opener->format_token) || !has_output_token(dev->stream_tokens))) return -EINVAL; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (!(has_output_token(opener->format_token) || has_output_token(dev->stream_tokens))) return -EINVAL; break; default: return -EINVAL; } return 0; } /* 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) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); /* there can be only one... */ if (argp->index) return -EINVAL; if (dev->keep_format || has_other_owners(opener, dev)) { /* keep_format also locks the frame rate */ if (argp->width != dev->pix_format.width || argp->height != dev->pix_format.height || argp->pixel_format != dev->pix_format.pixelformat) return -EINVAL; argp->type = V4L2_FRMIVAL_TYPE_DISCRETE; argp->discrete = dev->capture_param.timeperframe; } else { if (argp->width < dev->min_width || argp->width > dev->max_width || argp->height < dev->min_height || argp->height > dev->max_height || !format_by_fourcc(argp->pixel_format)) return -EINVAL; argp->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; argp->stepwise.min.numerator = 1; argp->stepwise.min.denominator = V4L2LOOPBACK_FPS_MAX; argp->stepwise.max.numerator = V4L2LOOPBACK_FRAME_INTERVAL_MAX; argp->stepwise.max.denominator = 1; argp->stepwise.step.numerator = 1; argp->stepwise.step.denominator = 1; } return 0; } /* Enumerate device formats * Returns: * - EINVAL the index is out of bounds; or if non-zero when format is fixed * - EFAULT unexpected null pointer */ static int vidioc_enum_fmt_vid(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); int fixed = dev->keep_format || has_other_owners(opener, dev); const struct v4l2l_format *fmt; if (check_buffer_capability(dev, opener, f->type) < 0) return -EINVAL; if (!(f->index < FORMATS)) return -EINVAL; /* TODO: Support 6.14 V4L2_FMTDESC_FLAG_ENUM_ALL */ if (fixed && f->index) return -EINVAL; fmt = fixed ? format_by_fourcc(dev->pix_format.pixelformat) : &formats[f->index]; if (!fmt) return -EFAULT; f->flags = 0; if (fmt->flags & FORMAT_FLAGS_COMPRESSED) f->flags |= V4L2_FMT_FLAG_COMPRESSED; snprintf(f->description, sizeof(f->description), fmt->name); f->pixelformat = fmt->fourcc; return 0; } /* Tests (or tries) the format. * Returns: * - EINVAL if the buffer type or format is not supported */ static int vidioc_try_fmt_vid(struct file *file, void *fh, struct v4l2_format *f) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, f->type) < 0) return -EINVAL; if (v4l2l_fill_format(f, dev->min_width, dev->max_width, dev->min_height, dev->max_height) != 0) return -EINVAL; if (dev->keep_format || has_other_owners(opener, dev)) /* use existing format - including colorspace info */ f->fmt.pix = dev->pix_format; return 0; } /* Sets new format. Fills 'f' argument with the requested or existing format. * Side-effect: buffers are allocated for the (returned) format. * Returns: * - EINVAL if the type is not supported * - EBUSY if buffers are already allocated * TODO: (vasaka) set subregions of input */ static int vidioc_s_fmt_vid(struct file *file, void *fh, struct v4l2_format *f) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); u32 token = opener->io_method == V4L2L_IO_TIMEOUT ? V4L2L_TOKEN_TIMEOUT : token_from_type(f->type); int changed, result; char buf[5]; result = vidioc_try_fmt_vid(file, fh, f); if (result < 0) return result; if (opener->buffer_count > 0) /* must free buffers before format can be set */ return -EBUSY; result = mutex_lock_killable(&dev->image_mutex); if (result < 0) return result; if (opener->format_token) release_token(dev, opener, format); if (!(dev->format_tokens & token)) { result = -EBUSY; goto exit_s_fmt_unlock; } dprintk("S_FMT[%s] %4s:%ux%u size=%u\n", V4L2_TYPE_IS_CAPTURE(f->type) ? "CAPTURE" : "OUTPUT", fourcc2str(f->fmt.pix.pixelformat, buf), f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.sizeimage); changed = !pix_format_eq(&dev->pix_format, &f->fmt.pix, 0); if (changed || has_no_owners(dev)) { result = allocate_buffers(dev, &f->fmt.pix); if (result < 0) goto exit_s_fmt_unlock; } if ((dev->timeout_image && changed) || (!dev->timeout_image && need_timeout_buffer(dev, token))) { result = allocate_timeout_buffer(dev); if (result < 0) goto exit_s_fmt_free; } if (changed) { dev->pix_format = f->fmt.pix; dev->pix_format_has_valid_sizeimage = v4l2l_pix_format_has_valid_sizeimage(f); } acquire_token(dev, opener, format, token); if (opener->io_method == V4L2L_IO_TIMEOUT) dev->timeout_image_io = 0; goto exit_s_fmt_unlock; exit_s_fmt_free: free_buffers(dev); exit_s_fmt_unlock: mutex_unlock(&dev->image_mutex); return result; } /* ------------------ CAPTURE ----------------------- */ /* ioctl for VIDIOC_ENUM_FMT, _G_FMT, _S_FMT, and _TRY_FMT when buffer type * is V4L2_BUF_TYPE_VIDEO_CAPTURE */ static int vidioc_enum_fmt_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) { return vidioc_enum_fmt_vid(file, fh, f); } static int vidioc_g_fmt_cap(struct file *file, void *fh, struct v4l2_format *f) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, f->type) < 0) return -EINVAL; f->fmt.pix = dev->pix_format; return 0; } static int vidioc_try_fmt_cap(struct file *file, void *fh, struct v4l2_format *f) { return vidioc_try_fmt_vid(file, fh, f); } static int vidioc_s_fmt_cap(struct file *file, void *fh, struct v4l2_format *f) { return vidioc_s_fmt_vid(file, fh, f); } /* ------------------ OUTPUT ----------------------- */ /* ioctl for VIDIOC_ENUM_FMT, _G_FMT, _S_FMT, and _TRY_FMT when buffer type * is V4L2_BUF_TYPE_VIDEO_OUTPUT */ static int vidioc_enum_fmt_out(struct file *file, void *fh, struct v4l2_fmtdesc *f) { return vidioc_enum_fmt_vid(file, fh, f); } static int vidioc_g_fmt_out(struct file *file, void *fh, struct v4l2_format *f) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, f->type) < 0) return -EINVAL; /* * LATER: this should return the currently valid format * gstreamer doesn't like it, if this returns -EINVAL, as it * then concludes that there is _no_ valid format * CHECK whether this assumption is wrong, * or whether we have to always provide a valid format */ f->fmt.pix = dev->pix_format; return 0; } static int vidioc_try_fmt_out(struct file *file, void *fh, struct v4l2_format *f) { return vidioc_try_fmt_vid(file, fh, f); } static int vidioc_s_fmt_out(struct file *file, void *fh, struct v4l2_format *f) { return vidioc_s_fmt_vid(file, fh, f); } // #define V4L2L_OVERLAY #ifdef V4L2L_OVERLAY /* ------------------ OVERLAY ----------------------- */ /* currently unsupported */ /* GSTreamer's v4l2sink is buggy, as it requires the overlay to work * while it should only require it, if overlay is requested * 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) { return 0; } 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 *fh, struct v4l2_streamparm *parm) { /* do not care about type of opener, hope these enums would always be * compatible */ struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, parm->type) < 0) return -EINVAL; parm->parm.capture = dev->capture_param; return 0; } /* get some data flow parameters, only capability, fps and readbuffers has * effect on this driver * called on VIDIOC_S_PARM */ static int vidioc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *parm) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); dprintk("S_PARM(frame-time=%u/%u)\n", parm->parm.capture.timeperframe.numerator, parm->parm.capture.timeperframe.denominator); if (check_buffer_capability(dev, opener, parm->type) < 0) return -EINVAL; switch (parm->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: set_timeperframe(dev, &parm->parm.capture.timeperframe); break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: set_timeperframe(dev, &parm->parm.output.timeperframe); break; default: return -EINVAL; } parm->parm.capture = dev->capture_param; return 0; } #ifdef V4L2LOOPBACK_WITH_STD /* sets a tv standard, actually we do not need to handle this any special way * added to support effecttv * called on VIDIOC_S_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; if (_std) { req_std = *_std; *_std = all_std; } /* we support everything in V4L2_STD_ALL, but not more... */ supported_std = (all_std & req_std); if (no_std == supported_std) return -EINVAL; return 0; } /* gets a fake video standard * called on VIDIOC_G_STD */ static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) { if (norm) *norm = V4L2_STD_ALL; return 0; } /* gets a fake video standard * called on VIDIOC_QUERYSTD */ static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm) { if (norm) *norm = V4L2_STD_ALL; return 0; } #endif /* V4L2LOOPBACK_WITH_STD */ static int v4l2loopback_set_ctrl(struct v4l2_loopback_device *dev, u32 id, s64 val) { int result = 0; switch (id) { case CID_KEEP_FORMAT: if (val < 0 || val > 1) return -EINVAL; dev->keep_format = val; result = mutex_lock_killable(&dev->image_mutex); if (result < 0) return result; if (!dev->keep_format) { if (has_no_owners(dev) && !any_buffers_mapped(dev)) free_buffers(dev); } mutex_unlock(&dev->image_mutex); break; case CID_SUSTAIN_FRAMERATE: if (val < 0 || val > 1) return -EINVAL; spin_lock_bh(&dev->lock); dev->sustain_framerate = val; check_timers(dev); spin_unlock_bh(&dev->lock); break; case CID_TIMEOUT: if (val < 0 || val > MAX_TIMEOUT) return -EINVAL; if (val > 0) { result = mutex_lock_killable(&dev->image_mutex); if (result < 0) return result; /* on-the-fly allocate if device is owned; else * allocate occurs on next S_FMT or REQBUFS */ if (!has_no_owners(dev)) result = allocate_timeout_buffer(dev); mutex_unlock(&dev->image_mutex); if (result < 0) { /* disable timeout as buffer not alloc'd */ spin_lock_bh(&dev->lock); dev->timeout_jiffies = 0; spin_unlock_bh(&dev->lock); return result; } } spin_lock_bh(&dev->lock); dev->timeout_jiffies = msecs_to_jiffies(val); check_timers(dev); spin_unlock_bh(&dev->lock); break; case CID_TIMEOUT_IMAGE_IO: dev->timeout_image_io = 1; break; default: return -EINVAL; } return 0; } static int v4l2loopback_s_ctrl(struct v4l2_ctrl *ctrl) { struct v4l2_loopback_device *dev = container_of( ctrl->handler, struct v4l2_loopback_device, ctrl_handler); return v4l2loopback_set_ctrl(dev, ctrl->id, ctrl->val); } /* 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) { __u32 index = outp->index; struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -ENOTTY; if (index) return -EINVAL; /* clear all data (including the reserved fields) */ memset(outp, 0, sizeof(*outp)); outp->index = index; 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 outp->capabilities |= V4L2_OUT_CAP_STD; #endif /* V4L2_OUT_CAP_STD */ #endif /* V4L2LOOPBACK_WITH_STD */ return 0; } /* which output is currently active, * called on VIDIOC_G_OUTPUT */ static int vidioc_g_output(struct file *file, void *fh, unsigned int *index) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -ENOTTY; if (index) *index = 0; return 0; } /* set output, can make sense if we have more than one video src, * called on VIDIOC_S_OUTPUT */ static int vidioc_s_output(struct file *file, void *fh, unsigned int index) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -ENOTTY; return index == 0 ? index : -EINVAL; } /* 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) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); __u32 index = inp->index; if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_CAPTURE)) return -ENOTTY; if (index) return -EINVAL; /* clear all data (including the reserved fields) */ memset(inp, 0, sizeof(*inp)); inp->index = index; strscpy(inp->name, "loopback", sizeof(inp->name)); inp->type = V4L2_INPUT_TYPE_CAMERA; inp->audioset = 0; inp->tuner = 0; inp->status = 0; #ifdef V4L2LOOPBACK_WITH_STD inp->std = V4L2_STD_ALL; #ifdef V4L2_IN_CAP_STD inp->capabilities |= V4L2_IN_CAP_STD; #endif #endif /* V4L2LOOPBACK_WITH_STD */ if (has_output_token(dev->stream_tokens) && !dev->keep_format) /* if no outputs attached; pretend device is powered off */ inp->status |= V4L2_IN_ST_NO_SIGNAL; return 0; } /* which input is currently active, * called on VIDIOC_G_INPUT */ static int vidioc_g_input(struct file *file, void *fh, unsigned int *index) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_CAPTURE)) return -ENOTTY; /* NOTE: -EAGAIN might be more informative */ if (index) *index = 0; return 0; } /* set input, can make sense if we have more than one video src, * called on VIDIOC_S_INPUT */ static int vidioc_s_input(struct file *file, void *fh, unsigned int index) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); if (index != 0) return -EINVAL; if (check_buffer_capability(dev, opener, V4L2_BUF_TYPE_VIDEO_CAPTURE)) return -ENOTTY; /* NOTE: -EAGAIN might be more informative */ return 0; } /* --------------- V4L2 ioctl buffer related calls ----------------- */ #define is_allocated(opener, type, index) \ (opener->format_token & (opener->io_method == V4L2L_IO_TIMEOUT ? \ V4L2L_TOKEN_TIMEOUT : \ token_from_type(type)) && \ (index) < (opener)->buffer_count) #define BUFFER_DEBUG_FMT_STR \ "buffer#%u @ %p type=%u bytesused=%u length=%u flags=%x " \ "field=%u timestamp= %lld.%06lldsequence=%u\n" #define BUFFER_DEBUG_FMT_ARGS(buf) \ (buf)->index, (buf), (buf)->type, (buf)->bytesused, (buf)->length, \ (buf)->flags, (buf)->field, \ (long long)(buf)->timestamp.tv_sec, \ (long long)(buf)->timestamp.tv_usec, (buf)->sequence /* Buffer flag helpers */ #define unset_flags(flags) \ do { \ flags &= ~V4L2_BUF_FLAG_QUEUED; \ flags &= ~V4L2_BUF_FLAG_DONE; \ } while (0) #define set_queued(flags) \ do { \ flags |= V4L2_BUF_FLAG_QUEUED; \ flags &= ~V4L2_BUF_FLAG_DONE; \ } while (0) #define set_done(flags) \ do { \ flags &= ~V4L2_BUF_FLAG_QUEUED; \ flags |= V4L2_BUF_FLAG_DONE; \ } while (0) static bool any_buffers_mapped(struct v4l2_loopback_device *dev) { u32 index; for (index = 0; index < dev->buffer_count; ++index) if (dev->buffers[index].buffer.flags & V4L2_BUF_FLAG_MAPPED) return true; return false; } static void prepare_buffer_queue(struct v4l2_loopback_device *dev, int count) { struct v4l2l_buffer *bufd, *n; u32 pos; spin_lock_bh(&dev->list_lock); /* ensure sufficient number of buffers in queue */ for (pos = 0; pos < count; ++pos) { bufd = &dev->buffers[pos]; if (list_empty(&bufd->list_head)) list_add_tail(&bufd->list_head, &dev->outbufs_list); } if (list_empty(&dev->outbufs_list)) goto exit_prepare_queue_unlock; /* remove any excess buffers */ list_for_each_entry_safe(bufd, n, &dev->outbufs_list, list_head) { if (bufd->buffer.index >= count) list_del_init(&bufd->list_head); } /* buffers are no longer queued; and `write_position` will correspond * to the first item of `outbufs_list`. */ pos = v4l2l_mod64(dev->write_position, count); list_for_each_entry(bufd, &dev->outbufs_list, list_head) { unset_flags(bufd->buffer.flags); dev->bufpos2index[pos % count] = bufd->buffer.index; ++pos; } exit_prepare_queue_unlock: spin_unlock_bh(&dev->list_lock); } /* forward declaration */ static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type type); /* negotiate buffer type * only mmap streaming supported * called on VIDIOC_REQBUFS */ static int vidioc_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *reqbuf) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); u32 token = opener->io_method == V4L2L_IO_TIMEOUT ? V4L2L_TOKEN_TIMEOUT : token_from_type(reqbuf->type); u32 req_count = reqbuf->count; int result = 0; dprintk("REQBUFS(memory=%u, req_count=%u) and device-bufs=%u/%u " "[used/max]\n", reqbuf->memory, req_count, dev->used_buffer_count, dev->buffer_count); switch (reqbuf->memory) { case V4L2_MEMORY_MMAP: #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) reqbuf->capabilities = 0; /* only guarantee MMAP support */ #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0) reqbuf->flags = 0; /* no memory consistency support */ #endif break; default: return -EINVAL; } if (opener->format_token & ~token) /* different (buffer) type already assigned to descriptor by * S_FMT or REQBUFS */ return -EINVAL; MARK(); result = mutex_lock_killable(&dev->image_mutex); if (result < 0) return result; /* -EINTR */ /* CASE queue/dequeue timeout-buffer only: */ if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { opener->buffer_count = req_count; if (req_count == 0) release_token(dev, opener, format); goto exit_reqbufs_unlock; } MARK(); /* CASE count is zero: streamoff, free buffers, release their token */ if (req_count == 0) { if (dev->format_tokens & token) { acquire_token(dev, opener, format, token); opener->io_method = V4L2L_IO_MMAP; } result = vidioc_streamoff(file, fh, reqbuf->type); opener->buffer_count = 0; /* undocumented requirement - REQBUFS with count zero should * ALSO release lock on logical stream */ if (opener->format_token) release_token(dev, opener, format); if (has_no_owners(dev)) dev->used_buffer_count = 0; goto exit_reqbufs_unlock; } /* CASE count non-zero: allocate buffers and acquire token for them */ MARK(); switch (reqbuf->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (!(dev->format_tokens & token || opener->format_token & token)) /* only exclusive ownership for each stream */ result = -EBUSY; break; default: result = -EINVAL; } if (result < 0) goto exit_reqbufs_unlock; if (has_other_owners(opener, dev) && dev->used_buffer_count > 0) { /* allow 'allocation' of existing number of buffers */ req_count = dev->used_buffer_count; } else if (any_buffers_mapped(dev)) { /* do not allow re-allocation if buffers are mapped */ result = -EBUSY; goto exit_reqbufs_unlock; } MARK(); opener->buffer_count = 0; if (req_count > dev->buffer_count) req_count = dev->buffer_count; if (has_no_owners(dev)) { result = allocate_buffers(dev, &dev->pix_format); if (result < 0) goto exit_reqbufs_unlock; } if (!dev->timeout_image && need_timeout_buffer(dev, token)) { result = allocate_timeout_buffer(dev); if (result < 0) goto exit_reqbufs_unlock; } acquire_token(dev, opener, format, token); MARK(); switch (opener->io_method) { case V4L2L_IO_TIMEOUT: dev->timeout_image_io = 0; opener->buffer_count = req_count; break; default: opener->io_method = V4L2L_IO_MMAP; prepare_buffer_queue(dev, req_count); dev->used_buffer_count = opener->buffer_count = req_count; } exit_reqbufs_unlock: mutex_unlock(&dev->image_mutex); reqbuf->count = opener->buffer_count; return result; } /* returns buffer asked for; * give app as many buffers as it wants, if it less than MAX, * but map them in our inner buffers * called on VIDIOC_QUERYBUF */ static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *buf) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); u32 type = buf->type; u32 index = buf->index; if ((type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && (type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -EINVAL; if (!is_allocated(opener, type, index)) return -EINVAL; if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { *buf = dev->timeout_buffer.buffer; buf->index = index; } else *buf = dev->buffers[index].buffer; buf->type = type; if (!(buf->flags & (V4L2_BUF_FLAG_DONE | V4L2_BUF_FLAG_QUEUED))) { /* v4l2-compliance requires these to be zero */ buf->sequence = 0; buf->timestamp.tv_sec = buf->timestamp.tv_usec = 0; } else if (V4L2_TYPE_IS_CAPTURE(type)) { /* guess flags based on sequence values */ if (buf->sequence >= opener->read_position) { set_done(buf->flags); } else if (buf->flags & V4L2_BUF_FLAG_DONE) { set_queued(buf->flags); } } dprintkrw("QUERYBUF(%s, index=%u) -> " BUFFER_DEBUG_FMT_STR, V4L2_TYPE_IS_CAPTURE(type) ? "CAPTURE" : "OUTPUT", index, BUFFER_DEBUG_FMT_ARGS(buf)); return 0; } 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->list_lock); list_move_tail(&buf->list_head, &dev->outbufs_list); spin_unlock_bh(&dev->list_lock); spin_lock_bh(&dev->lock); dev->bufpos2index[v4l2l_mod64(dev->write_position, dev->used_buffer_count)] = buf->buffer.index; ++dev->write_position; dev->reread_count = 0; check_timers(dev); spin_unlock_bh(&dev->lock); } /* put buffer to queue * called on VIDIOC_QBUF */ static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); struct v4l2l_buffer *bufd; u32 index = buf->index; u32 type = buf->type; if (!is_allocated(opener, type, index)) return -EINVAL; bufd = &dev->buffers[index]; switch (buf->memory) { case V4L2_MEMORY_MMAP: if (!(bufd->buffer.flags & V4L2_BUF_FLAG_MAPPED)) dprintkrw("QBUF() unmapped buffer [index=%u]\n", index); break; default: return -EINVAL; } if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { set_queued(buf->flags); return 0; } switch (type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: dprintkrw("QBUF(CAPTURE, index=%u) -> " BUFFER_DEBUG_FMT_STR, index, BUFFER_DEBUG_FMT_ARGS(buf)); set_queued(buf->flags); break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: dprintkrw("QBUF(OUTPUT, index=%u) -> " BUFFER_DEBUG_FMT_STR, index, BUFFER_DEBUG_FMT_ARGS(buf)); if (!(bufd->buffer.flags & V4L2_BUF_FLAG_TIMESTAMP_COPY) && (buf->timestamp.tv_sec == 0 && buf->timestamp.tv_usec == 0)) { v4l2l_get_timestamp(&bufd->buffer); } else { bufd->buffer.timestamp = buf->timestamp; bufd->buffer.flags |= V4L2_BUF_FLAG_TIMESTAMP_COPY; bufd->buffer.flags &= ~V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; } if (dev->pix_format_has_valid_sizeimage) { if (buf->bytesused >= dev->pix_format.sizeimage) { bufd->buffer.bytesused = dev->pix_format.sizeimage; } else { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) dev_warn_ratelimited( &dev->vdev->dev, #else dprintkrw( #endif "warning queued output buffer bytesused too small %u < %u\n", buf->bytesused, dev->pix_format.sizeimage); bufd->buffer.bytesused = buf->bytesused; } } else { bufd->buffer.bytesused = buf->bytesused; } bufd->buffer.sequence = dev->write_position; set_queued(bufd->buffer.flags); *buf = bufd->buffer; buffer_written(dev, bufd); set_done(bufd->buffer.flags); wake_up_all(&dev->read_event); break; default: return -EINVAL; } buf->type = type; return 0; } 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; spin_unlock_bh(&dev->lock); return ret; } static int get_capture_buffer(struct file *file) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data); int pos, timeout_happened; u32 index; if ((file->f_flags & O_NONBLOCK) && (dev->write_position <= opener->read_position && dev->reread_count <= opener->reread_count && !dev->timeout_happened)) return -EAGAIN; wait_event_interruptible(dev->read_event, can_read(dev, opener)); spin_lock_bh(&dev->lock); if (dev->write_position == opener->read_position) { if (dev->reread_count > opener->reread_count + 2) opener->reread_count = dev->reread_count - 1; ++opener->reread_count; pos = v4l2l_mod64(opener->read_position + dev->used_buffer_count - 1, dev->used_buffer_count); } else { opener->reread_count = 0; if (dev->write_position > opener->read_position + dev->used_buffer_count) opener->read_position = dev->write_position - 1; pos = v4l2l_mod64(opener->read_position, dev->used_buffer_count); ++opener->read_position; } timeout_happened = dev->timeout_happened && (dev->timeout_jiffies > 0); dev->timeout_happened = 0; spin_unlock_bh(&dev->lock); index = dev->bufpos2index[pos]; if (timeout_happened) { if (index >= dev->used_buffer_count) { dprintkrw("get_capture_buffer() read position is at " "an unallocated buffer [index=%u]\n", index); return -EFAULT; } /* although allocated on-demand, timeout_image is freed only * in free_buffers(), so we don't need to worry about it being * deallocated suddenly */ memcpy(dev->image + dev->buffers[index].buffer.m.offset, dev->timeout_image, dev->buffer_size); } return (int)index; } /* put buffer to dequeue * called on VIDIOC_DQBUF */ static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); u32 type = buf->type; int index; struct v4l2l_buffer *bufd; if (buf->memory != V4L2_MEMORY_MMAP) return -EINVAL; if (opener->format_token & V4L2L_TOKEN_TIMEOUT) { *buf = dev->timeout_buffer.buffer; buf->type = type; unset_flags(buf->flags); return 0; } if ((opener->buffer_count == 0) || !(opener->format_token & token_from_type(type))) return -EINVAL; switch (type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: index = get_capture_buffer(file); if (index < 0) return index; *buf = dev->buffers[index].buffer; unset_flags(buf->flags); break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: spin_lock_bh(&dev->list_lock); bufd = list_first_entry_or_null(&dev->outbufs_list, struct v4l2l_buffer, list_head); if (bufd) list_move_tail(&bufd->list_head, &dev->outbufs_list); spin_unlock_bh(&dev->list_lock); if (!bufd) return -EFAULT; unset_flags(bufd->buffer.flags); *buf = bufd->buffer; break; default: return -EINVAL; } buf->type = type; dprintkrw("DQBUF(%s, index=%u) -> " BUFFER_DEBUG_FMT_STR, V4L2_TYPE_IS_CAPTURE(type) ? "CAPTURE" : "OUTPUT", index, BUFFER_DEBUG_FMT_ARGS(buf)); return 0; } /* ------------- STREAMING ------------------- */ /* start streaming * called on VIDIOC_STREAMON */ static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type type) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); u32 token = token_from_type(type); /* short-circuit when using timeout buffer set */ if (opener->format_token & V4L2L_TOKEN_TIMEOUT) return 0; /* opener must have claimed (same) buffer set via REQBUFS */ if (!opener->buffer_count || !(opener->format_token & token)) return -EINVAL; switch (type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (has_output_token(dev->stream_tokens) && !dev->keep_format) return -EIO; if (dev->stream_tokens & token) { acquire_token(dev, opener, stream, token); client_usage_queue_event(dev->vdev); } return 0; case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (dev->stream_tokens & token) acquire_token(dev, opener, stream, token); return 0; default: return -EINVAL; } } /* stop streaming * called on VIDIOC_STREAMOFF */ static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); u32 token = token_from_type(type); /* short-circuit when using timeout buffer set */ if (opener->format_token & V4L2L_TOKEN_TIMEOUT) return 0; /* short-circuit when buffer set has no owner */ if (dev->format_tokens & token) return 0; /* opener needs a claim to buffer set */ if (!opener->format_token) return -EBUSY; if (opener->format_token & ~token) return -EINVAL; switch (type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT: if (opener->stream_token & token) release_token(dev, opener, stream); /* reset output queue */ if (dev->used_buffer_count > 0) prepare_buffer_queue(dev, dev->used_buffer_count); return 0; case V4L2_BUF_TYPE_VIDEO_CAPTURE: if (opener->stream_token & token) { release_token(dev, opener, stream); client_usage_queue_event(dev->vdev); } return 0; default: return -EINVAL; } } #ifdef CONFIG_VIDEO_V4L1_COMPAT static int vidiocgmbuf(struct file *file, void *fh, struct video_mbuf *p) { struct v4l2_loopback_device *dev; MARK(); dev = v4l2loopback_getdevice(file); p->frames = dev->buffer_count; p->offsets[0] = 0; p->offsets[1] = 0; p->size = dev->buffer_size; return 0; } #endif static void client_usage_queue_event(struct video_device *vdev) { struct v4l2_event ev; struct v4l2_loopback_device *dev; dev = container_of(vdev->v4l2_dev, struct v4l2_loopback_device, v4l2_dev); memset(&ev, 0, sizeof(ev)); ev.type = V4L2_EVENT_PRI_CLIENT_USAGE; ((struct v4l2_event_client_usage *)&ev.u)->count = !has_capture_token(dev->stream_tokens); v4l2_event_queue(vdev, &ev); } static int client_usage_ops_add(struct v4l2_subscribed_event *sev, unsigned elems) { if (!(sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL)) return 0; client_usage_queue_event(sev->fh->vdev); return 0; } static void client_usage_ops_replace(struct v4l2_event *old, const struct v4l2_event *new) { *((struct v4l2_event_client_usage *)&old->u) = *((struct v4l2_event_client_usage *)&new->u); } static void client_usage_ops_merge(const struct v4l2_event *old, struct v4l2_event *new) { *((struct v4l2_event_client_usage *)&new->u) = *((struct v4l2_event_client_usage *)&old->u); } const struct v4l2_subscribed_event_ops client_usage_ops = { .add = client_usage_ops_add, .replace = client_usage_ops_replace, .merge = client_usage_ops_merge, }; 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); case V4L2_EVENT_PRI_CLIENT_USAGE: return v4l2_event_subscribe(fh, sub, 0, &client_usage_ops); } return -EINVAL; } /* file operations */ static void vm_open(struct vm_area_struct *vma) { struct v4l2l_buffer *buf; MARK(); buf = vma->vm_private_data; atomic_inc(&buf->use_count); buf->buffer.flags |= V4L2_BUF_FLAG_MAPPED; } static void vm_close(struct vm_area_struct *vma) { struct v4l2l_buffer *buf; MARK(); buf = vma->vm_private_data; if (atomic_dec_and_test(&buf->use_count)) buf->buffer.flags &= ~V4L2_BUF_FLAG_MAPPED; } static struct vm_operations_struct vm_ops = { .open = vm_open, .close = vm_close, }; static int v4l2_loopback_mmap(struct file *file, struct vm_area_struct *vma) { u8 *addr; unsigned long start, size, offset; struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data); struct v4l2l_buffer *buffer = NULL; int result = 0; MARK(); offset = (unsigned long)vma->vm_pgoff << PAGE_SHIFT; start = (unsigned long)vma->vm_start; size = (unsigned long)(vma->vm_end - vma->vm_start); /* always != 0 */ /* ensure buffer size, count, and allocated image(s) are not altered by * other file descriptors */ result = mutex_lock_killable(&dev->image_mutex); if (result < 0) return result; if (size > dev->buffer_size) { dprintk("mmap() attempt to map %lubytes when %ubytes are " "allocated to buffers\n", size, dev->buffer_size); result = -EINVAL; goto exit_mmap_unlock; } if (offset % dev->buffer_size != 0) { dprintk("mmap() offset does not match start of any buffer\n"); result = -EINVAL; goto exit_mmap_unlock; } switch (opener->format_token) { case V4L2L_TOKEN_TIMEOUT: if (offset != (unsigned long)dev->buffer_size * MAX_BUFFERS) { dprintk("mmap() incorrect offset for timeout image\n"); result = -EINVAL; goto exit_mmap_unlock; } buffer = &dev->timeout_buffer; addr = dev->timeout_image; break; default: if (offset >= dev->image_size) { dprintk("mmap() attempt to map beyond all buffers\n"); result = -EINVAL; goto exit_mmap_unlock; } u32 index = offset / dev->buffer_size; buffer = &dev->buffers[index]; addr = dev->image + offset; break; } while (size > 0) { struct page *page = vmalloc_to_page(addr); result = vm_insert_page(vma, start, page); if (result < 0) goto exit_mmap_unlock; start += PAGE_SIZE; addr += PAGE_SIZE; size -= PAGE_SIZE; } vma->vm_ops = &vm_ops; vma->vm_private_data = buffer; vm_open(vma); exit_mmap_unlock: mutex_unlock(&dev->image_mutex); return result; } static unsigned int v4l2_loopback_poll(struct file *file, struct poll_table_struct *pts) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data); __poll_t req_events = poll_requested_events(pts); int ret_mask = 0; /* call poll_wait in first call, regardless, to ensure that the * wait-queue is not null */ poll_wait(file, &dev->read_event, pts); poll_wait(file, &opener->fh.wait, pts); if (req_events & POLLPRI) { if (v4l2_event_pending(&opener->fh)) { ret_mask |= POLLPRI; if (!(req_events & DEFAULT_POLLMASK)) return ret_mask; } } switch (opener->format_token) { case V4L2L_TOKEN_OUTPUT: if (opener->stream_token != 0 || opener->io_method == V4L2L_IO_NONE) ret_mask |= POLLOUT | POLLWRNORM; break; case V4L2L_TOKEN_CAPTURE: if ((opener->io_method == V4L2L_IO_NONE || opener->stream_token != 0) && can_read(dev, opener)) ret_mask |= POLLIN | POLLWRNORM; break; case V4L2L_TOKEN_TIMEOUT: ret_mask |= POLLOUT | POLLWRNORM; break; default: break; } return ret_mask; } /* do not want to limit device opens, it can be as many readers as user want, * writers are limited by means of setting writer field */ static int v4l2_loopback_open(struct file *file) { struct v4l2_loopback_device *dev; struct v4l2_loopback_opener *opener; 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; atomic_inc(&dev->open_count); if (dev->timeout_image_io && dev->format_tokens & V4L2L_TOKEN_TIMEOUT) /* will clear timeout_image_io once buffer set acquired */ opener->io_method = V4L2L_IO_TIMEOUT; v4l2_fh_init(&opener->fh, video_devdata(file)); file->private_data = &opener->fh; v4l2_fh_add(&opener->fh); dprintk("open() -> dev@%p with image@%p\n", dev, dev ? dev->image : NULL); return 0; } static int v4l2_loopback_close(struct file *file) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data); int result = 0; dprintk("close() -> dev@%p with image@%p\n", dev, dev ? dev->image : NULL); if (opener->format_token) { struct v4l2_requestbuffers reqbuf = { .count = 0, .memory = V4L2_MEMORY_MMAP, .type = 0 }; switch (opener->format_token) { case V4L2L_TOKEN_CAPTURE: reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; break; case V4L2L_TOKEN_OUTPUT: case V4L2L_TOKEN_TIMEOUT: reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; break; } if (reqbuf.type) result = vidioc_reqbufs(file, file->private_data, &reqbuf); if (result < 0) dprintk("failed to free buffers REQBUFS(count=0) " " returned %d\n", result); mutex_lock(&dev->image_mutex); release_token(dev, opener, format); mutex_unlock(&dev->image_mutex); } if (atomic_dec_and_test(&dev->open_count)) { del_timer_sync(&dev->sustain_timer); del_timer_sync(&dev->timeout_timer); if (!dev->keep_format) { mutex_lock(&dev->image_mutex); free_buffers(dev); mutex_unlock(&dev->image_mutex); } } v4l2_fh_del(&opener->fh); v4l2_fh_exit(&opener->fh); kfree(opener); return 0; } static int start_fileio(struct file *file, void *fh, enum v4l2_buf_type type) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_loopback_opener *opener = fh_to_opener(fh); struct v4l2_requestbuffers reqbuf = { .count = dev->buffer_count, .memory = V4L2_MEMORY_MMAP, .type = type }; int token = token_from_type(type); int result; if (opener->format_token & V4L2L_TOKEN_TIMEOUT || opener->format_token & ~token) return -EBUSY; /* NOTE: -EBADF might be more informative */ /* short-circuit if already have stream token */ if (opener->stream_token && opener->io_method == V4L2L_IO_FILE) return 0; /* otherwise attempt to acquire stream token and assign IO method */ if (!(dev->stream_tokens & token) || opener->io_method != V4L2L_IO_NONE) return -EBUSY; result = vidioc_reqbufs(file, fh, &reqbuf); if (result < 0) return result; result = vidioc_streamon(file, fh, type); if (result < 0) return result; opener->io_method = V4L2L_IO_FILE; return 0; } static ssize_t v4l2_loopback_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_buffer *b; int index, result; dprintkrw("read() %zu bytes\n", count); result = start_fileio(file, file->private_data, V4L2_BUF_TYPE_VIDEO_CAPTURE); if (result < 0) return result; index = get_capture_buffer(file); if (index < 0) return index; b = &dev->buffers[index].buffer; if (count > b->bytesused) count = b->bytesused; if (copy_to_user((void *)buf, (void *)(dev->image + b->m.offset), count)) { printk(KERN_ERR "v4l2-loopback read() failed copy_to_user()\n"); return -EFAULT; } return count; } static ssize_t v4l2_loopback_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); struct v4l2_buffer *b; int index, result; dprintkrw("write() %zu bytes\n", count); result = start_fileio(file, file->private_data, V4L2_BUF_TYPE_VIDEO_OUTPUT); if (result < 0) return result; if (count > dev->buffer_size) count = dev->buffer_size; index = v4l2l_mod64(dev->write_position, dev->used_buffer_count); b = &dev->buffers[index].buffer; if (copy_from_user((void *)(dev->image + b->m.offset), (void *)buf, count)) { printk(KERN_ERR "v4l2-loopback write() failed copy_from_user()\n"); return -EFAULT; } b->bytesused = count; v4l2l_get_timestamp(b); b->sequence = dev->write_position; set_queued(b->flags); buffer_written(dev, &dev->buffers[index]); set_done(b->flags); wake_up_all(&dev->read_event); return count; } /* init functions */ /* frees buffers, if allocated */ static void free_buffers(struct v4l2_loopback_device *dev) { dprintk("free_buffers() with image@%p\n", dev->image); if (!dev->image) return; if (!has_no_owners(dev) || any_buffers_mapped(dev)) /* maybe an opener snuck in before image_mutex was acquired */ printk(KERN_WARNING "v4l2-loopback free_buffers() buffers of video device " "#%u freed while still mapped to userspace\n", dev->vdev->num); vfree(dev->image); dev->image = NULL; dev->image_size = 0; dev->buffer_size = 0; } static void free_timeout_buffer(struct v4l2_loopback_device *dev) { dprintk("free_timeout_buffer() with timeout_image@%p\n", dev->timeout_image); if (!dev->timeout_image) return; if ((dev->timeout_jiffies > 0 && !has_no_owners(dev)) || dev->timeout_buffer.buffer.flags & V4L2_BUF_FLAG_MAPPED) printk(KERN_WARNING "v4l2-loopback free_timeout_buffer() timeout image " "of device #%u freed while still mapped to userspace\n", dev->vdev->num); vfree(dev->timeout_image); dev->timeout_image = NULL; dev->timeout_buffer_size = 0; } /* allocates buffers if no (other) openers are already using them */ static int allocate_buffers(struct v4l2_loopback_device *dev, struct v4l2_pix_format *pix_format) { u32 buffer_size = PAGE_ALIGN(pix_format->sizeimage); unsigned long image_size = (unsigned long)buffer_size * (unsigned long)dev->buffer_count; /* vfree on close file operation in case no open handles left */ if (buffer_size == 0 || dev->buffer_count == 0 || buffer_size < pix_format->sizeimage) return -EINVAL; if ((__LONG_MAX__ / buffer_size) < dev->buffer_count) return -ENOSPC; dprintk("allocate_buffers() size %lubytes = %ubytes x %ubuffers\n", image_size, buffer_size, dev->buffer_count); if (dev->image) { /* check that no buffers are expected in user-space */ if (!has_no_owners(dev) || any_buffers_mapped(dev)) return -EBUSY; dprintk("allocate_buffers() existing size=%lubytes\n", dev->image_size); /* FIXME: prevent double allocation more intelligently! */ if (image_size == dev->image_size) { dprintk("allocate_buffers() keep existing\n"); return 0; } free_buffers(dev); } /* FIXME: set buffers to 0 */ dev->image = vmalloc(image_size); if (dev->image == NULL) { dev->buffer_size = dev->image_size = 0; return -ENOMEM; } init_buffers(dev, pix_format->sizeimage, buffer_size); dev->buffer_size = buffer_size; dev->image_size = image_size; dprintk("allocate_buffers() -> vmalloc'd %lubytes\n", dev->image_size); return 0; } static int allocate_timeout_buffer(struct v4l2_loopback_device *dev) { /* device's `buffer_size` and `buffers` must be initialised in * allocate_buffers() */ dprintk("allocate_timeout_buffer() size %ubytes\n", dev->buffer_size); if (dev->buffer_size == 0) return -EINVAL; if (dev->timeout_image) { if (dev->timeout_buffer.buffer.flags & V4L2_BUF_FLAG_MAPPED) return -EBUSY; if (dev->buffer_size == dev->timeout_buffer_size) return 0; free_timeout_buffer(dev); } dev->timeout_image = vzalloc(dev->buffer_size); if (!dev->timeout_image) { dev->timeout_buffer_size = 0; return -ENOMEM; } dev->timeout_buffer_size = dev->buffer_size; return 0; } /* init inner buffers, they are capture mode and flags are set as for capture * mode buffers */ static void init_buffers(struct v4l2_loopback_device *dev, u32 bytes_used, u32 buffer_size) { u32 i; for (i = 0; i < dev->buffer_count; ++i) { struct v4l2_buffer *b = &dev->buffers[i].buffer; b->index = i; b->bytesused = bytes_used; b->length = buffer_size; b->field = V4L2_FIELD_NONE; b->flags = 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; v4l2l_get_timestamp(b); } dev->timeout_buffer = dev->buffers[0]; dev->timeout_buffer.buffer.m.offset = MAX_BUFFERS * buffer_size; } /* fills and register video device */ static void init_vdev(struct video_device *vdev, int nr) { #ifdef V4L2LOOPBACK_WITH_STD 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; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) vdev->device_caps = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; #endif if (debug > 1) vdev->dev_debug = V4L2_DEV_DEBUG_IOCTL | V4L2_DEV_DEBUG_IOCTL_ARG; vdev->vfl_dir = VFL_DIR_M2M; } /* init default capture parameters, only fps may be changed in future */ static void init_capture_param(struct v4l2_captureparm *capture_param) { capture_param->capability = V4L2_CAP_TIMEPERFRAME; /* since 2.16 */ capture_param->capturemode = 0; capture_param->extendedmode = 0; capture_param->readbuffers = max_buffers; capture_param->timeperframe.numerator = 1; capture_param->timeperframe.denominator = V4L2LOOPBACK_FPS_DEFAULT; } static void check_timers(struct v4l2_loopback_device *dev) { if (has_output_token(dev->stream_tokens)) return; 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); } #ifdef HAVE_TIMER_SETUP static void sustain_timer_clb(struct timer_list *t) { 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 = idr_find(&v4l2loopback_index_idr, nr); #endif spin_lock(&dev->lock); if (dev->sustain_framerate) { dev->reread_count++; dprintkrw("sustain_timer_clb() write_pos=%lld reread=%u\n", (long long)dev->write_position, dev->reread_count); if (dev->reread_count == 1) mod_timer(&dev->sustain_timer, jiffies + max(1UL, dev->frame_jiffies / 2)); else mod_timer(&dev->sustain_timer, jiffies + dev->frame_jiffies); wake_up_all(&dev->read_event); } spin_unlock(&dev->lock); } #ifdef HAVE_TIMER_SETUP static void timeout_timer_clb(struct timer_list *t) { 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 = idr_find(&v4l2loopback_index_idr, nr); #endif spin_lock(&dev->lock); if (dev->timeout_jiffies > 0) { dev->timeout_happened = 1; mod_timer(&dev->timeout_timer, jiffies + dev->timeout_jiffies); wake_up_all(&dev->read_event); } spin_unlock(&dev->lock); } /* init loopback main structure */ #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; struct v4l2loopback_private *vdev_priv = NULL; int err; u32 _width = V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; u32 _height = V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; u32 _min_width = DEFAULT_FROM_CONF(min_width, < V4L2LOOPBACK_SIZE_MIN_WIDTH, V4L2LOOPBACK_SIZE_MIN_WIDTH); u32 _min_height = DEFAULT_FROM_CONF(min_height, < V4L2LOOPBACK_SIZE_MIN_HEIGHT, V4L2LOOPBACK_SIZE_MIN_HEIGHT); u32 _max_width = DEFAULT_FROM_CONF(max_width, < _min_width, max_width); u32 _max_height = DEFAULT_FROM_CONF(max_height, < _min_height, max_height); bool _announce_all_caps = (conf && conf->announce_all_caps >= 0) ? (bool)(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); struct v4l2_format _fmt; int nr = -1; if (conf) { const int output_nr = conf->output_nr; #ifdef SPLIT_DEVICES const int capture_nr = conf->capture_nr; #else const int capture_nr = output_nr; #endif if (capture_nr >= 0 && output_nr == capture_nr) { nr = output_nr; } else if (capture_nr < 0 && output_nr < 0) { nr = -1; } else if (capture_nr < 0) { nr = output_nr; } else if (output_nr < 0) { nr = capture_nr; } else { printk(KERN_ERR "v4l2-loopback add() split OUTPUT and CAPTURE " "devices not yet supported.\n"); printk(KERN_INFO "v4l2-loopback add() both devices must have the " "same number (%d != %d).\n", output_nr, capture_nr); return -EINVAL; } } if (idr_find(&v4l2loopback_index_idr, nr)) return -EEXIST; /* initialisation of a new device */ dprintk("add() creating 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; /* register new device */ MARK(); nr = err; if (conf && conf->card_label[0]) { 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); err = v4l2_device_register(NULL, &dev->v4l2_dev); if (err) goto out_free_idr; /* initialise the _video_ device */ MARK(); err = -ENOMEM; dev->vdev = video_device_alloc(); if (dev->vdev == NULL) goto out_unregister; vdev_priv = kzalloc(sizeof(struct v4l2loopback_private), GFP_KERNEL); if (vdev_priv == NULL) goto out_unregister; video_set_drvdata(dev->vdev, vdev_priv); if (video_get_drvdata(dev->vdev) == NULL) goto out_unregister; snprintf(dev->vdev->name, sizeof(dev->vdev->name), "%s", dev->card_label); vdev_priv->device_nr = nr; init_vdev(dev->vdev, nr); dev->vdev->v4l2_dev = &dev->v4l2_dev; /* initialise v4l2-loopback specific parameters */ MARK(); dev->announce_all_caps = _announce_all_caps; dev->min_width = _min_width; dev->min_height = _min_height; dev->max_width = _max_width; dev->max_height = _max_height; dev->max_openers = _max_openers; /* set (initial) pixel and stream format */ _width = clamp_val(_width, _min_width, _max_width); _height = clamp_val(_height, _min_height, _max_height); _fmt = (struct v4l2_format){ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .fmt.pix = { .width = _width, .height = _height, .pixelformat = formats[0].fourcc, .colorspace = V4L2_COLORSPACE_DEFAULT, .field = V4L2_FIELD_NONE } }; err = v4l2l_fill_format(&_fmt, _min_width, _max_width, _min_height, _max_height); if (err) /* highly unexpected failure to assign default format */ goto out_unregister; dev->pix_format = _fmt.fmt.pix; init_capture_param(&dev->capture_param); set_timeperframe(dev, &dev->capture_param.timeperframe); /* ctrls parameters */ dev->keep_format = 0; dev->sustain_framerate = 0; dev->timeout_jiffies = 0; dev->timeout_image_io = 0; /* initialise OUTPUT and CAPTURE buffer values */ dev->image = NULL; dev->image_size = 0; dev->buffer_count = _max_buffers; dev->buffer_size = 0; dev->used_buffer_count = 0; INIT_LIST_HEAD(&dev->outbufs_list); do { u32 index; for (index = 0; index < dev->buffer_count; ++index) INIT_LIST_HEAD(&dev->buffers[index].list_head); } while (0); memset(dev->bufpos2index, 0, sizeof(dev->bufpos2index)); dev->write_position = 0; /* initialise synchronisation data */ atomic_set(&dev->open_count, 0); mutex_init(&dev->image_mutex); spin_lock_init(&dev->lock); spin_lock_init(&dev->list_lock); init_waitqueue_head(&dev->read_event); dev->format_tokens = V4L2L_TOKEN_MASK; dev->stream_tokens = V4L2L_TOKEN_MASK; /* initialise sustain frame rate and timeout parameters, and timers */ dev->reread_count = 0; dev->timeout_image = NULL; dev->timeout_happened = 0; #ifdef HAVE_TIMER_SETUP timer_setup(&dev->sustain_timer, sustain_timer_clb, 0); timer_setup(&dev->timeout_timer, timeout_timer_clb, 0); #else setup_timer(&dev->sustain_timer, sustain_timer_clb, nr); setup_timer(&dev->timeout_timer, timeout_timer_clb, nr); #endif /* initialise the control handler and add controls */ MARK(); 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) { 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; /* register the device (creates /dev/video*) */ MARK(); if (video_register_device(dev->vdev, VFL_TYPE_VIDEO, nr) < 0) { printk(KERN_ERR "v4l2-loopback add() failed video_register_device()\n"); err = -EFAULT; goto out_free_device; } v4l2loopback_create_sysfs(dev->vdev); /* NOTE: ambivalent if sysfs entries fail */ if (ret_nr) *ret_nr = dev->vdev->num; return 0; out_free_device: video_device_release(dev->vdev); out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); out_unregister: video_set_drvdata(dev->vdev, NULL); if (vdev_priv != NULL) kfree(vdev_priv); 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) { int device_nr = v4l2loopback_get_vdev_nr(dev->vdev); mutex_lock(&dev->image_mutex); free_buffers(dev); free_timeout_buffer(dev); mutex_unlock(&dev->image_mutex); v4l2loopback_remove_sysfs(dev->vdev); v4l2_ctrl_handler_free(&dev->ctrl_handler); kfree(video_get_drvdata(dev->vdev)); video_unregister_device(dev->vdev); v4l2_device_unregister(&dev->v4l2_dev); idr_remove(&v4l2loopback_index_idr, device_nr); 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, capture_nr, output_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) { ret = -EBUSY; if (dev->open_count.counter > 0) break; 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; capture_nr = output_nr = conf.output_nr; #ifdef SPLIT_DEVICES capture_nr = conf.capture_nr; #endif device_nr = (output_nr < 0) ? capture_nr : 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 != capture_nr) && (capture_nr >= 0) && ((ret = v4l2loopback_lookup(capture_nr, 0)) < 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 != output_nr) && (output_nr >= 0) && ((ret = v4l2loopback_lookup(output_nr, 0)) < 0)) break; /* v4l2_loopback_config identified a single device, so fetch the data */ snprintf(conf.card_label, sizeof(conf.card_label), "%s", dev->card_label); conf.output_nr = dev->vdev->num; #ifdef SPLIT_DEVICES conf.capture_nr = dev->vdev->num; #endif conf.min_width = dev->min_width; conf.min_height = dev->min_height; conf.max_width = dev->max_width; conf.max_height = dev->max_height; conf.announce_all_caps = dev->announce_all_caps; conf.max_buffers = dev->buffer_count; conf.max_openers = dev->max_openers; conf.debug = debug; MARK(); if (copy_to_user((void *)parm, &conf, sizeof(conf))) { ret = -EFAULT; break; } ret = 0; break; } 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 = { // 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 = { // clang-format off .vidioc_querycap = &vidioc_querycap, .vidioc_enum_framesizes = &vidioc_enum_framesizes, .vidioc_enum_frameintervals = &vidioc_enum_frameintervals, .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, #endif #ifdef V4L2LOOPBACK_WITH_STD .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_reqbufs = &vidioc_reqbufs, .vidioc_querybuf = &vidioc_querybuf, .vidioc_qbuf = &vidioc_qbuf, .vidioc_dqbuf = &vidioc_dqbuf, .vidioc_streamon = &vidioc_streamon, .vidioc_streamoff = &vidioc_streamoff, #ifdef CONFIG_VIDEO_V4L1_COMPAT .vidiocgmbuf = &vidiocgmbuf, #endif .vidioc_subscribe_event = &vidioc_subscribe_event, .vidioc_unsubscribe_event = &v4l2_event_unsubscribe, // clang-format on }; static int free_device_cb(int id, void *ptr, void *data) { struct v4l2_loopback_device *dev = ptr; v4l2_loopback_remove(dev); return 0; } static void free_devices(void) { idr_for_each(&v4l2loopback_index_idr, &free_device_cb, NULL); idr_destroy(&v4l2loopback_index_idr); } static int __init v4l2loopback_init_module(void) { const u32 min_width = V4L2LOOPBACK_SIZE_MIN_WIDTH; const u32 min_height = V4L2LOOPBACK_SIZE_MIN_HEIGHT; int err; int i; MARK(); err = misc_register(&v4l2loopback_misc); if (err < 0) return err; if (devices < 0) { devices = 1; /* try guessing the devices from the "video_nr" parameter */ for (i = MAX_DEVICES - 1; i >= 0; i--) { if (video_nr[i] >= 0) { devices = i + 1; break; } } } if (devices > MAX_DEVICES) { devices = MAX_DEVICES; printk(KERN_INFO "v4l2-loopback init() number of initial devices is " "limited to: %d\n", MAX_DEVICES); } if (max_buffers > MAX_BUFFERS) { max_buffers = MAX_BUFFERS; printk(KERN_INFO "v4l2-loopback init() number of buffers is limited " "to: %d\n", MAX_BUFFERS); } if (max_openers < 0) { printk(KERN_INFO "v4l2-loopback init() allowing %d openers rather " "than %d\n", 2, max_openers); max_openers = 2; } if (max_width < min_width) { max_width = V4L2LOOPBACK_SIZE_DEFAULT_MAX_WIDTH; printk(KERN_INFO "v4l2-loopback init() using max_width %d\n", max_width); } if (max_height < min_height) { max_height = V4L2LOOPBACK_SIZE_DEFAULT_MAX_HEIGHT; printk(KERN_INFO "v4l2-loopback init() using max_height %d\n", max_height); } for (i = 0; i < devices; i++) { struct v4l2_loopback_config cfg = { // clang-format off .output_nr = video_nr[i], #ifdef SPLIT_DEVICES .capture_nr = video_nr[i], #endif .min_width = min_width, .min_height = min_height, .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(); goto error; } } dprintk("module installed\n"); printk(KERN_INFO "v4l2-loopback 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 " (" __stringify(SNAPSHOT_VERSION) ")" #else "" #endif ); // clang-format on return 0; error: misc_deregister(&v4l2loopback_misc); return err; } 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"); } MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); module_init(v4l2loopback_init_module); module_exit(v4l2loopback_cleanup_module); v4l2loopback-0.14.0/v4l2loopback.h000066400000000000000000000057551476030224200165650ustar00rootroot00000000000000/* 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 14 #define V4L2LOOPBACK_VERSION_BUGFIX 0 /* /dev/v4l2loopback interface */ struct v4l2_loopback_config { /** * the device-number (/dev/video) * 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 * NOTE: currently split-devices (where output_nr and capture_nr differ) * are not implemented yet. * until then, requesting different device-IDs will result in EINVAL. * * 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 unused; /*capture_nr;*/ /** * a nice name for your device * if (*card_label)==0, an automatic name is assigned */ char card_label[32]; /** * allowed frame size * if too low, default values are used */ unsigned int min_width; unsigned int max_width; unsigned int min_height; unsigned int max_height; /** * 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; /** * whether to announce OUTPUT/CAPTURE capabilities exclusively * for this device or not * (!exclusive_caps) * NOTE: this is going to be removed once separate output/capture * devices are implemented */ int announce_all_caps; }; /* 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 */ v4l2loopback-0.14.0/v4l2loopback_formats.h000066400000000000000000000213031476030224200203030ustar00rootroot00000000000000static 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_ABGR32 { .name = "32 bpp RGBA, le", .fourcc = V4L2_PIX_FMT_ABGR32, .depth = 32, .flags = 0, }, #endif #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, }, #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, }, #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, }, #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, }, #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, }, #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, }, #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, }, #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, }, #ifdef V4L2_PIX_FMT_YVYU { .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, }, #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, }, #ifdef V4L2_PIX_FMT_Y4 { .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, }, #endif /* V4L2_PIX_FMT_Y6 */ #ifdef V4L2_PIX_FMT_Y10 { .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, }, #endif /* V4L2_PIX_FMT_Y12 */ { .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, }, #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, }, #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, }, #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, }, #endif /* V4L2_PIX_FMT_SRGGB8 */ #ifdef V4L2_PIX_FMT_SGRBG8 { .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, }, #endif /* V4L2_PIX_FMT_SGBRG8 */ #ifdef V4L2_PIX_FMT_SBGGR8 { .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, }, #ifdef V4L2_PIX_FMT_YUV422P { .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, }, #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, }, #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, }, #endif /* V4L2_PIX_FMT_NV12 */ /* here come the compressed formats */ #ifdef V4L2_PIX_FMT_MJPEG { .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, }, #endif /* V4L2_PIX_FMT_JPEG */ #ifdef V4L2_PIX_FMT_DV { .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, }, #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, }, #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, }, #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, }, #endif /* V4L2_PIX_FMT_H264_MVC */ #ifdef V4L2_PIX_FMT_H263 { .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, }, #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, }, #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, }, #endif /* V4L2_PIX_FMT_MPEG4 */ #ifdef V4L2_PIX_FMT_XVID { .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, }, #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, }, #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, }, #endif /* V4L2_PIX_FMT_VP8 */ #ifdef V4L2_PIX_FMT_VP9 { .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, }, #endif /* V4L2_PIX_FMT_HEVC */ }; v4l2loopback-0.14.0/vagrant/000077500000000000000000000000001476030224200155405ustar00rootroot00000000000000v4l2loopback-0.14.0/vagrant/README.md000066400000000000000000000041401476030224200170160ustar00rootroot00000000000000developing 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 ~~~ v4l2loopback-0.14.0/vagrant/Vagrantfile000066400000000000000000000060171476030224200177310ustar00rootroot00000000000000# -*- 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 v4l2loopback-0.14.0/vagrant/vbox-restart000077500000000000000000000032011476030224200201220ustar00rootroot00000000000000#!/bin/sh usage() { cat 1>&2 < stops the given VM, restores the current snapshot and restarts it again OPTIONS -w attach host's webcam to VM -a if no 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 <&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 <