dvswitch-0.8.3.6/000077500000000000000000000000001161012451100135115ustar00rootroot00000000000000dvswitch-0.8.3.6/CMakeLists.txt000066400000000000000000000041101161012451100162450ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.4) if(COMMAND cmake_policy) cmake_policy(SET CMP0005 OLD) endif(COMMAND cmake_policy) project(DVSWITCH CXX C) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) include(FindPkgConfig) pkg_check_modules(ALSA REQUIRED alsa) pkg_check_modules(GTKMM REQUIRED gtkmm-2.4) pkg_check_modules(LIBAVCODEC REQUIRED libavcodec) pkg_check_modules(LIBAVUTIL REQUIRED libavutil) find_library(BOOST_THREAD_LIBRARIES NAMES boost_thread-mt boost_thread NO_SYSTEM_ENVIRONMENT_PATH) if(NOT BOOST_THREAD_LIBRARIES) message(FATAL_ERROR "Could not find Boost.Thread library") endif(NOT BOOST_THREAD_LIBRARIES) # Cope with recent move of ffmpeg headers into subdirectories set(LIBAVCODEC_INCLUDE_DIRS ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVCODEC_INCLUDEDIR}/libavcodec) set(prefix /usr/local CACHE STRING "Installation prefix") set(bindir ${prefix}/bin CACHE STRING "Executable installation directory") set(sharedir ${prefix}/share CACHE STRING "Shared data installation directory") set(docdir ${sharedir}/doc CACHE STRING "Documentation installation directory") set(mandir ${sharedir}/man CACHE STRING "Manual page installation directory") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98 -Wall -Wextra") add_definitions(-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_POSIX_C_SOURCE) include_directories(src) # Suppress bogus warnings (GCC PR7302) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor") # Suppress deprecation warnings from libavcodec headers about themselves if(LIBAVCODEC_VERSION MATCHES "^51\\.") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") endif(LIBAVCODEC_VERSION MATCHES "^51\\.") # Remove bogus -fPIC (Debian bug #478404) set(CMAKE_SHARED_LIBRARY_C_FLAGS "") set(CMAKE_SHARED_LIBRARY_CXX_FLAGS "") add_subdirectory(data) add_subdirectory(doc) add_subdirectory(src) add_subdirectory(tests) install(FILES ChangeLog DESTINATION ${docdir}/dvswitch) dvswitch-0.8.3.6/COPYING000066400000000000000000000435721161012451100145570ustar00rootroot00000000000000DVswitch 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. ------------------------------------------------------------------------ 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. dvswitch-0.8.3.6/ChangeLog000066400000000000000000000201471161012451100152670ustar00rootroot00000000000000dvswitch (0.8.3.6) unstable; urgency=low * Fix build failure with ffmpeg 0.7 -- Ben Hutchings Fri, 15 Jul 2011 21:54:19 +0100 dvswitch (0.8.3.5) unstable; urgency=low * List all needed libraries to the linker. We were accidentally depending on the behaviour of GNU ld, which treats 'needed' as transitive by default - Debian bug #554310 -- Ben Hutchings Thu, 24 Feb 2011 04:07:10 +0000 dvswitch (0.8.3.4) unstable; urgency=low * Fix build failure with ffmpeg 0.6 * Fix crash at startup with ffmpeg 0.6 due to a new parameter check -- Ben Hutchings Fri, 25 Jun 2010 12:09:12 +0100 dvswitch (0.8.3.3) unstable; urgency=low * Override source frame aspect ratio in case of a mismatch, rather then warning about it * Fix APTs and validity flags in header block - Alioth bug #311836 -- Ben Hutchings Fri, 05 Feb 2010 13:40:10 +0100 dvswitch (0.8.3.2) unstable; urgency=low * Fix crash when selecting/deselecting a source that has disconnected -- Ben Hutchings Thu, 23 Jul 2009 15:12:33 +0200 dvswitch (0.8.3.1) unstable; urgency=low * Disable buffering of activation (tally light) notifications -- Ben Hutchings Thu, 09 Jul 2009 21:59:33 +0100 dvswitch (0.8.3) unstable; urgency=low * Add status overlay to make it more clear whether recording is enabled * Set display to black before a source is connected * Add support for tally lights in dvsource-firewire/dvsource-v4l2-dv, thanks to Wouter Verhelst -- Ben Hutchings Sun, 28 Jun 2009 20:50:02 +0100 dvswitch (0.8.2) unstable; urgency=low * Create one encoding thread per processor, up to a maximum of 8 * Increase mixer queue length to allow for scheduler silliness * Improve robustness of dvsource-alsa * Fix audio encoding bug that resulted in crackling audio when using dvsource-alsa with NTSC -- Ben Hutchings Thu, 26 Mar 2009 22:45:15 -0500 dvswitch (0.8.1) unstable; urgency=low * Added dvsource-alsa as an audio-only source -- Ben Hutchings Sun, 01 Mar 2009 03:16:30 +0000 dvswitch (0.8) unstable; urgency=low * Added ticks and labels to the VU-meter * Bug fixes: - Removed use of function not present in Gtkmm 2.8 - Fixed warnings from CMake 2.6 - Fixed warnings from GCC - Fixed drag-selection when the pointer moves beyond the left or top edge of the display - Fixed memory corruption/crash in a selection corner-case - Fixed synchronisation of display buffers with the X server - Fixed repainting of main display before any frames have been received -- Ben Hutchings Wed, 04 Feb 2009 23:16:13 +0000 dvswitch (0.8~rc1) unstable; urgency=low * Disabled recording at startup * Added menu, currently with Quit command * Added buttons to GUI for effect selection * Changed key commands slightly * Allowed changing of primary and secondary video sources without cancelling the effect * Made dvsink-files create directories as necessary * Made dvsource-file reject non-DV files * Added support for USB DV sources through dvgrab and the uvcvideo driver * Moved source selector buttons to keep window within 1024x768 (depending on font selection and window decoration) * Added VU-meter for basic audio monitoring * Added warning indicator for sources which don't match the selected video and audio format * Documentation changes: - Documented all key commands - Changed contact address to dvswitch-devel@lists.alioth.debian.org - Added notes to (maybe) avoid user mistakes * Build system changes: - Changed to use cmake - Fixed building with current ffmpeg libavcodec library * Bug fixes: - Changed picture-in-picture selection to maintain frame aspect ratio - Restricted picture-in-picture to use the active area of the reduced frame - Slightly improved colour accuracy of picture-in-picture - Fixed cancellation of picture-in-picture selection - Fixed crash when user tries to apply picture-in-picture with an empty selection - Fixed crash when display depth is not 24 or 32 - Fixed crash when users click in the dvswitch window without any source enabled - Made dvsource-file work with non-seekable files such as pipes -- Ben Hutchings Tue, 27 Jan 2009 01:04:43 +0000 dvswitch (0.7) unstable; urgency=low * Made keyboard shortcut for cut button work * Moved video/DV system parameters into a common structure * Replaced libdv codec with DV codec in libavcodec (ffmpeg), which is significantly faster * Added UI for picture-in-picture mixing -- Ben Hutchings Sun, 20 Apr 2008 22:18:37 +0100 dvswitch (0.6.2) unstable; urgency=low * Fixed bug in thumbnail image size initialisation that could result in a fatal BadValue error from X * Fixed minor bug in thumbnail image cleanup * Fixed bug that caused all sinks to be disabled when not recording -- Ben Hutchings Sun, 24 Feb 2008 10:55:41 +0100 dvswitch (0.6.1) unstable; urgency=low * Improved X driver compatibility: - Fixed calculation of Xv port numbers - Changed selection of visual class and depth for images to match the window - Added error messages and later checks for failure of image creation, where previously dvswitch could crash -- Ben Hutchings Thu, 21 Feb 2008 22:17:32 +0000 dvswitch (0.6) unstable; urgency=low * Added some infrastructure for video effects including picture-in-picture mixing * Switched from gtkmm 2.0/2.2 to 2.4+ * Corrected small error in video display width * Changed the server to indicate overflow in output to sinks rather than disconnecting sinks that have overflowed; the file sink will create a new file after each overflow * Implemented dynamic display sizing, adding support for 16:9 frames * Turned source selection images into radio buttons * Fixed labelling of sources numbered above 9 * Added support for starting and stopping recording while leaving any stream running * Added control buttons for recording and changed to default mnemonic for Cut -- Ben Hutchings Tue, 19 Feb 2008 09:37:22 +0000 dvswitch (0.5.1) unstable; urgency=low * Replaced FIREWIRE_DEVICE and --device option to dvsource-firewire with FIREWIRE_CARD and --card option * Corrected arguments to dvgrab -- Ben Hutchings Sun, 10 Jun 2007 21:41:17 +0100 dvswitch (0.5.0) unstable; urgency=low * Added indicator of title-safe area -- Ben Hutchings Mon, 30 Apr 2007 02:23:08 +0100 dvswitch (0.4) unstable; urgency=low * Implemented a command-spawning sink * Improved error handling * Added ability to switch between audio sources * Added indicators of selected video and audio sources -- Ben Hutchings Sun, 15 Apr 2007 16:27:47 +0100 dvswitch (0.3) unstable; urgency=low * Implemented dubbing of audio source onto video source (and silencing if audio source is unavailable) * Implemented reuse of source slots, so a disconnected source that reconnects will normally reappear in the same place * Added support for digit keys on the numeric keypad * Fixed priority of display updates, so the user interfaceshould remain responsive even with silly numbers of sources * Implemented synchronisation of mixer clock with audio source clock * Implemented sink serving and a DIF file sink * Fixed various bugs -- Ben Hutchings Tue, 10 Apr 2007 01:54:32 +0100 dvswitch (0.2) unstable; urgency=low * Fixed hang on exit * Implemented thumbnail displays of all sources * Switched to 1-based source selection in order to match keyboard layout * Reduced display lag * Reduced processor load of display updates by using Xv and Xshm -- Ben Hutchings Fri, 6 Apr 2007 00:54:22 +0100 dvswitch (0.1) unstable; urgency=low * Initial release -- Ben Hutchings Mon, 2 Apr 2007 03:20:34 +0100 dvswitch-0.8.3.6/cmake/000077500000000000000000000000001161012451100145715ustar00rootroot00000000000000dvswitch-0.8.3.6/cmake/FindPkgConfig.cmake000066400000000000000000000361631161012451100202540ustar00rootroot00000000000000# - a pkg-config module for CMake # # Usage: # pkg_check_modules( [REQUIRED] []*) # checks for all the given modules # # pkg_search_module( [REQUIRED] []*) # checks for given modules and uses the first working one # # When the 'REQUIRED' argument was set, macros will fail with an error # when module(s) could not be found # # It sets the following variables: # PKG_CONFIG_FOUND ... true iff pkg-config works on the system # PKG_CONFIG_EXECUTABLE ... pathname of the pkg-config program # _FOUND ... set to 1 iff module(s) exist # # For the following variables two sets of values exist; first one is the # common one and has the given PREFIX. The second set contains flags # which are given out when pkgconfig was called with the '--static' # option. # _LIBRARIES ... only the libraries (w/o the '-l') # _LIBRARY_DIRS ... the paths of the libraries (w/o the '-L') # _LDFLAGS ... all required linker flags # _LDFLAGS_OTHER ... all other linker flags # _INCLUDE_DIRS ... the '-I' preprocessor flags (w/o the '-I') # _CFLAGS ... all required cflags # _CFLAGS_OTHER ... the other compiler flags # # = for common case # = _STATIC for static linking # # There are some special variables whose prefix depends on the count # of given modules. When there is only one module, stays # unchanged. When there are multiple modules, the prefix will be # changed to _: # _VERSION ... version of the module # _PREFIX ... prefix-directory of the module # _INCLUDEDIR ... include-dir of the module # _LIBDIR ... lib-dir of the module # # = when |MODULES| == 1, else # = _ # # A parameter can have the following formats: # {MODNAME} ... matches any version # {MODNAME}>={VERSION} ... at least version is required # {MODNAME}={VERSION} ... exactly version is required # {MODNAME}<={VERSION} ... modules must not be newer than # # Examples # pkg_check_modules (GLIB2 glib-2.0) # # pkg_check_modules (GLIB2 glib-2.0>=2.10) # requires at least version 2.10 of glib2 and defines e.g. # GLIB2_VERSION=2.10.3 # # pkg_check_modules (FOO glib-2.0>=2.10 gtk+-2.0) # requires both glib2 and gtk2, and defines e.g. # FOO_glib-2.0_VERSION=2.10.3 # FOO_gtk+-2.0_VERSION=2.8.20 # # pkg_check_modules (XRENDER REQUIRED xrender) # defines e.g.: # XRENDER_LIBRARIES=Xrender;X11 # XRENDER_STATIC_LIBRARIES=Xrender;X11;pthread;Xau;Xdmcp # # pkg_search_module (BAR libxml-2.0 libxml2 libxml>=2) # Copyright (C) 2006 Enrico Scholz # # Redistribution and use, with or without modification, are permitted # provided that the following conditions are met: # # 1. Redistributions must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. The name of the author may not be used to endorse or promote # products derived from this software without specific prior # written permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### Common stuff #### set(PKG_CONFIG_VERSION 1) set(PKG_CONFIG_FOUND 0) find_program(PKG_CONFIG_EXECUTABLE NAMES pkg-config DOC "pkg-config executable") mark_as_advanced(PKG_CONFIG_EXECUTABLE) if(PKG_CONFIG_EXECUTABLE) set(PKG_CONFIG_FOUND 1) endif(PKG_CONFIG_EXECUTABLE) # Unsets the given variables macro(_pkgconfig_unset var) set(${var} "" CACHE INTERNAL "") endmacro(_pkgconfig_unset) macro(_pkgconfig_set var value) set(${var} ${value} CACHE INTERNAL "") endmacro(_pkgconfig_set) # Invokes pkgconfig, cleans up the result and sets variables macro(_pkgconfig_invoke _pkglist _prefix _varname _regexp) set(_pkgconfig_invoke_result) execute_process( COMMAND ${PKG_CONFIG_EXECUTABLE} ${ARGN} ${_pkglist} OUTPUT_VARIABLE _pkgconfig_invoke_result RESULT_VARIABLE _pkgconfig_failed) if (_pkgconfig_failed) set(_pkgconfig_${_varname} "") _pkgconfig_unset(${_prefix}_${_varname}) else(_pkgconfig_failed) string(REGEX REPLACE "[\r\n]" " " _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") string(REGEX REPLACE " +$" "" _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") if (NOT ${_regexp} STREQUAL "") string(REGEX REPLACE "${_regexp}" " " _pkgconfig_invoke_result "${_pkgconfig_invoke_result}") endif(NOT ${_regexp} STREQUAL "") separate_arguments(_pkgconfig_invoke_result) #message(STATUS " ${_varname} ... ${_pkgconfig_invoke_result}") set(_pkgconfig_${_varname} ${_pkgconfig_invoke_result}) _pkgconfig_set(${_prefix}_${_varname} "${_pkgconfig_invoke_result}") endif(_pkgconfig_failed) endmacro(_pkgconfig_invoke) # Invokes pkgconfig two times; once without '--static' and once with # '--static' macro(_pkgconfig_invoke_dyn _pkglist _prefix _varname cleanup_regexp) _pkgconfig_invoke("${_pkglist}" ${_prefix} ${_varname} "${cleanup_regexp}" ${ARGN}) _pkgconfig_invoke("${_pkglist}" ${_prefix} STATIC_${_varname} "${cleanup_regexp}" --static ${ARGN}) endmacro(_pkgconfig_invoke_dyn) # Splits given arguments into options and a package list macro(_pkgconfig_parse_options _result _is_req) set(${_is_req} 0) foreach(_pkg ${ARGN}) if (_pkg STREQUAL "REQUIRED") set(${_is_req} 1) endif (_pkg STREQUAL "REQUIRED") endforeach(_pkg ${ARGN}) set(${_result} ${ARGN}) list(REMOVE_ITEM ${_result} "REQUIRED") endmacro(_pkgconfig_parse_options) ### macro(_pkg_check_modules_internal _is_required _is_silent _prefix) _pkgconfig_unset(${_prefix}_FOUND) _pkgconfig_unset(${_prefix}_VERSION) _pkgconfig_unset(${_prefix}_PREFIX) _pkgconfig_unset(${_prefix}_INCLUDEDIR) _pkgconfig_unset(${_prefix}_LIBDIR) _pkgconfig_unset(${_prefix}_LIBS) _pkgconfig_unset(${_prefix}_LIBS_L) _pkgconfig_unset(${_prefix}_LIBS_PATHS) _pkgconfig_unset(${_prefix}_LIBS_OTHER) _pkgconfig_unset(${_prefix}_CFLAGS) _pkgconfig_unset(${_prefix}_CFLAGS_I) _pkgconfig_unset(${_prefix}_CFLAGS_OTHER) _pkgconfig_unset(${_prefix}_STATIC_LIBDIR) _pkgconfig_unset(${_prefix}_STATIC_LIBS) _pkgconfig_unset(${_prefix}_STATIC_LIBS_L) _pkgconfig_unset(${_prefix}_STATIC_LIBS_PATHS) _pkgconfig_unset(${_prefix}_STATIC_LIBS_OTHER) _pkgconfig_unset(${_prefix}_STATIC_CFLAGS) _pkgconfig_unset(${_prefix}_STATIC_CFLAGS_I) _pkgconfig_unset(${_prefix}_STATIC_CFLAGS_OTHER) # create a better addressable variable of the modules and calculate its size set(_pkg_check_modules_list ${ARGN}) list(LENGTH _pkg_check_modules_list _pkg_check_modules_cnt) if(PKG_CONFIG_EXECUTABLE) # give out status message telling checked module if (NOT ${_is_silent}) if (_pkg_check_modules_cnt EQUAL 1) message(STATUS "checking for module '${_pkg_check_modules_list}'") else(_pkg_check_modules_cnt EQUAL 1) message(STATUS "checking for modules '${_pkg_check_modules_list}'") endif(_pkg_check_modules_cnt EQUAL 1) endif(NOT ${_is_silent}) set(_pkg_check_modules_packages) set(_pkg_check_modules_failed) # iterate through module list and check whether they exist and match the required version foreach (_pkg_check_modules_pkg ${_pkg_check_modules_list}) set(_pkg_check_modules_exist_query) # check whether version is given if (_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\1" _pkg_check_modules_pkg_name "${_pkg_check_modules_pkg}") string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\2" _pkg_check_modules_pkg_op "${_pkg_check_modules_pkg}") string(REGEX REPLACE "(.*[^><])(>=|=|<=)(.*)" "\\3" _pkg_check_modules_pkg_ver "${_pkg_check_modules_pkg}") else(_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") set(_pkg_check_modules_pkg_name "${_pkg_check_modules_pkg}") set(_pkg_check_modules_pkg_op) set(_pkg_check_modules_pkg_ver) endif(_pkg_check_modules_pkg MATCHES ".*(>=|=|<=).*") # handle the operands if (_pkg_check_modules_pkg_op STREQUAL ">=") list(APPEND _pkg_check_modules_exist_query --atleast-version) endif(_pkg_check_modules_pkg_op STREQUAL ">=") if (_pkg_check_modules_pkg_op STREQUAL "=") list(APPEND _pkg_check_modules_exist_query --exact-version) endif(_pkg_check_modules_pkg_op STREQUAL "=") if (_pkg_check_modules_pkg_op STREQUAL "<=") list(APPEND _pkg_check_modules_exist_query --max-version) endif(_pkg_check_modules_pkg_op STREQUAL "<=") # create the final query which is of the format: # * --atleast-version # * --exact-version # * --max-version # * --exists if (_pkg_check_modules_pkg_op) list(APPEND _pkg_check_modules_exist_query "${_pkg_check_modules_pkg_ver}") else(_pkg_check_modules_pkg_op) list(APPEND _pkg_check_modules_exist_query --exists) endif(_pkg_check_modules_pkg_op) _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_VERSION) _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_PREFIX) _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_INCLUDEDIR) _pkgconfig_unset(${_prefix}_${_pkg_check_modules_pkg_name}_LIBDIR) list(APPEND _pkg_check_modules_exist_query "${_pkg_check_modules_pkg_name}") list(APPEND _pkg_check_modules_packages "${_pkg_check_modules_pkg_name}") # execute the query execute_process( COMMAND ${PKG_CONFIG_EXECUTABLE} ${_pkg_check_modules_exist_query} RESULT_VARIABLE _pkgconfig_retval) # evaluate result and tell failures if (_pkgconfig_retval) if(NOT ${_is_silent}) message(STATUS " package '${_pkg_check_modules_pkg}' not found") endif(NOT ${_is_silent}) set(_pkg_check_modules_failed 1) endif(_pkgconfig_retval) endforeach(_pkg_check_modules_pkg) if(_pkg_check_modules_failed) # fail when requested if (${_is_required}) message(SEND_ERROR "A required package was not found") endif (${_is_required}) else(_pkg_check_modules_failed) # when we are here, we checked whether requested modules # exist. Now, go through them and set variables _pkgconfig_set(${_prefix}_FOUND 1) list(LENGTH _pkg_check_modules_packages pkg_count) # iterate through all modules again and set individual variables foreach (_pkg_check_modules_pkg ${_pkg_check_modules_packages}) # handle case when there is only one package required if (pkg_count EQUAL 1) set(_pkg_check_prefix "${_prefix}") else(pkg_count EQUAL 1) set(_pkg_check_prefix "${_prefix}_${_pkg_check_modules_pkg}") endif(pkg_count EQUAL 1) _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" VERSION "" --modversion ) _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" PREFIX "" --variable=prefix ) _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" INCLUDEDIR "" --variable=includedir ) _pkgconfig_invoke(${_pkg_check_modules_pkg} "${_pkg_check_prefix}" LIBDIR "" --variable=libdir ) message(STATUS " found ${_pkg_check_modules_pkg}, version ${_pkgconfig_VERSION}") endforeach(_pkg_check_modules_pkg) # set variables which are combined for multiple modules _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LIBRARIES "(^| )-l" --libs-only-l ) _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LIBRARY_DIRS "(^| )-L" --libs-only-L ) _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LDFLAGS "" --libs ) _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" LDFLAGS_OTHER "" --libs-only-other ) _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" INCLUDE_DIRS "(^| )-I" --cflags-only-I ) _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" CFLAGS "" --cflags ) _pkgconfig_invoke_dyn("${_pkg_check_modules_packages}" "${_prefix}" CFLAGS_OTHER "" --cflags-only-other ) endif(_pkg_check_modules_failed) else(PKG_CONFIG_EXECUTABLE) if (${_is_required}) message(SEND_ERROR "pkg-config tool not found") endif (${_is_required}) endif(PKG_CONFIG_EXECUTABLE) endmacro(_pkg_check_modules_internal) ### ### User visible macros start here ### ### macro(pkg_check_modules _prefix _module0) # check cached value if (NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION}) _pkgconfig_parse_options (_pkg_modules _pkg_is_required "${_module0}" ${ARGN}) _pkg_check_modules_internal("${_pkg_is_required}" 0 "${_prefix}" ${_pkg_modules}) _pkgconfig_set(__pkg_config_checked_${_prefix} ${PKG_CONFIG_VERSION}) endif(NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION}) endmacro(pkg_check_modules) ### macro(pkg_search_module _prefix _module0) # check cached value if (NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) set(_pkg_modules_found 0) _pkgconfig_parse_options(_pkg_modules_alt _pkg_is_required "${_module0}" ${ARGN}) message(STATUS "checking for one of the modules '${_pkg_modules_alt}'") # iterate through all modules and stop at the first working one. foreach(_pkg_alt ${_pkg_modules_alt}) if(NOT _pkg_modules_found) _pkg_check_modules_internal(0 1 "${_prefix}" "${_pkg_alt}") endif(NOT _pkg_modules_found) if (${_prefix}_FOUND) set(_pkg_modules_found 1) endif(${_prefix}_FOUND) endforeach(_pkg_alt) if (NOT ${_prefix}_FOUND) if(${_pkg_is_required}) message(SEND_ERROR "None of the required '${_pkg_modules_alt}' found") endif(${_pkg_is_required}) endif(NOT ${_prefix}_FOUND) _pkgconfig_set(__pkg_config_checked_${_prefix} ${PKG_CONFIG_VERSION}) endif(NOT DEFINED __pkg_config_checked_${_prefix} OR __pkg_config_checked_${_prefix} LESS ${PKG_CONFIG_VERSION} OR NOT ${_prefix}_FOUND) endmacro(pkg_search_module) ### Local Variables: ### mode: cmake ### End: dvswitch-0.8.3.6/cmake/Symlink.cmake000066400000000000000000000011341161012451100172200ustar00rootroot00000000000000# Symbol link module for CMake # # Usage: # symlink( ) # install_symlink( ) macro(symlink _dest _source) set(_ln_failed) message(STATUS "Installing link: ${_source} -> ${_dest}") execute_process( COMMAND ln -sf ${_dest} ${_source} RESULT_VARIABLE _ln_failed) if(_ln_failed) message(FATAL_ERROR "ln failed") endif(_ln_failed) endmacro(symlink) macro(install_symlink _dest _source) install(CODE "include(\"${CMAKE_HOME_DIRECTORY}/cmake/Symlink.cmake\") symlink(\"${_dest}\" \"\$ENV{DESTDIR}${_source}\")") endmacro(install_symlink) dvswitch-0.8.3.6/data/000077500000000000000000000000001161012451100144225ustar00rootroot00000000000000dvswitch-0.8.3.6/data/CMakeLists.txt000066400000000000000000000001451161012451100171620ustar00rootroot00000000000000file(GLOB image_files *.png) install(FILES ${image_files} DESTINATION ${sharedir}/dvswitch) dvswitch-0.8.3.6/data/audio-source.png000066400000000000000000000002041161012451100175230ustar00rootroot00000000000000‰PNG  IHDRóÿaKIDAT8Ëc`ìà?!LdjþOÈ€ÿÄal&"5ch„"ý ÓÈHJÞ$? DFb]ÀˆG §A¸âü?¹)”d…f©qô ÅIEND®B`‚dvswitch-0.8.3.6/data/pri-video-source.png000066400000000000000000000002441161012451100203240ustar00rootroot00000000000000‰PNG  IHDR $¿•sRGB®Îé^IDAT8ËÕ“Á C5úÿ_^—ˆ 5 =8AGòUÏ ¡§Éã¥æ1ÿx%ÊXÇ"Ë ë‰<Úå|ÏÒ3ˆ"À¶ô ˆ$Àv–z`,ÍÙ èA€«Š¡×ÿÉõ5¶„bIEND®B`‚dvswitch-0.8.3.6/data/sec-video-source.png000066400000000000000000000002421161012451100203020ustar00rootroot00000000000000‰PNG  IHDR $¿•sRGB®Îé\IDAT8ËÕSË ²èÿÙ.Õ!ì±X‡  AÔDGj7ÅÛ .²…üÂUðCˆ!ÅåtÊ"[zPnØçïx:Øn—GR,;:8F”Ôl:àMDñr|T¨ã Q-FlIEND®B`‚dvswitch-0.8.3.6/doc/000077500000000000000000000000001161012451100142565ustar00rootroot00000000000000dvswitch-0.8.3.6/doc/CMakeLists.txt000066400000000000000000000004651161012451100170230ustar00rootroot00000000000000include(Symlink) file(GLOB manpages *.1) install(FILES README DESTINATION ${docdir}/dvswitch) install(FILES ${manpages} DESTINATION ${mandir}/man1) install_symlink(dvsource-dvgrab.1 "${mandir}/man1/dvsource-firewire.1") install_symlink(dvsource-dvgrab.1 "${mandir}/man1/dvsource-v4l2-dv.1") dvswitch-0.8.3.6/doc/README000066400000000000000000000164361161012451100151500ustar00rootroot00000000000000dvswitch: a basic video mixer for live DV streams ================================================= Introduction ------------ dvswitch is a basic video mixer designed for the needs of DebConf. It may suit the needs of other conferences that require live mixing for streams and/or recording. Currently dvswitch can combine audio from one source with video from another and can combine two video streams with a picture-in-picture effect. Further mixing effects are planned. It works solely with the DV format since almost all camcorders support this and it allows for low latency display and cutting. Since DV cameras must be connected to computers via Firewire cables of limited length, dvswitch is divided into several different programs that can run on different computers on a TCP/IP network: - dvswitch: the mixer GUI and network server - dvsource-firewire: source that connects to a DV camera via Firewire - dvsource-v4l2-dv: source that connect to a DV camera via V4L2, useful for USB - dvsource-file: source that reads a raw DV (DIF) file - dvsource-alsa: source that captures audio through ALSA - dvsink-files: sink that writes the mixed stream to raw DV files - dvsink-command: sink that runs a command with the mixed stream as its standard input It is important to make sure all DV sources use the same video system (PAL or NTSC), aspect ratio (16:9 or 4:3) and audio sample rate (32, 48 or 44.1 kHz). Mixing input format will produce bogus output. DVswitch automatically detects the format used by its first source and will show warnings for any sources that don't match it. Home page --------- The project home page is . This has links to the download page, bug tracker, source repository, and mailing list. System requirements ------------------- dvswitch is developed and tested under Linux 2.6, and may not work on earlier versions of Linux or other Unix-like systems. In particular, dvsource-firewire uses dvgrab which only runs on Linux. The sources and sinks have low processor and memory requirements. The dvswitch mixer should be able to run on an x86 or x86-64 processor with a 1 GHz or faster clock, but will need a somewhat faster processor to update the display at full frame rate. On other architectures it is less efficient as the DV decoder only has optimisations for these two architectures. It currently requires about 40 MB memory in addition to whatever the underlying operating system requires. The dvswitch mixer requires an X display of at least 1024x768 pixels that supports the X video extension (aka XVideo or Xv). XVideo is available in most modern X display drivers but was not implemented in older versions of fglrx. A DV stream has a fixed bandwidth of about 36 Mbit/s and it is doubtful that current wireless networks can reliably provide this bandwidth. We recommend use of a 100 or 1000 Mbit/s switched Ethernet network between the hosts running the dvswitch system. Operating system configuration ------------------------------ CPU frequency scaling should be disabled on the host running the mixer because it seems to make the mixer's timing irregular, leading to dropped and duplicated frames. Socket buffers should be allowed to grow to at least one DV frame size (144000 or 120000 bytes depending on the video system). On Linux 2.6 this happens automatically. Configuration ------------- All programs read the configuration files /etc/dvswitchrc and ~/.dvswitchrc. These files can specify the following options: MIXER_HOST - the hostname (or IP address) on which the mixer listens (no default) MIXER_PORT - the port on which the mixer listens (no default) FIREWIRE_CARD - number of the Firewire card that dvsource-firewire should read through (default: use first which appears to have a camera attached) The mixer hostname and port can also be specified using the -h and -p command-line options. A Firewire card number for dvsource-firewire can be specified using the -c option. Starting the mixer ------------------ Run dvswitch, and you will initially see an empty window. You then need to connect some sources to it (see below). Once sources are connected, you will see the mixed output at full resolution (slightly higher, in fact, since the video pixels are not square). Below it, you will see all the sources at lower resolution in greyscale. (This allows dvswitch to display many sources without requiring a very fast processor.) Each is numbered and the active sources for video and audio are indicated with a film strip and speaker symbol respectively. In the main display, a 10% border on each side of the frame is shaded. This indicates approximately the area of the picture that may not be visible on a TV. Everything that viewers need to see should be framed within these borders (the "title-safe" area). There are currently a small number of commands, available through keystrokes: 1 to 9 select the video source Alt-1 to Alt-9 select the audio source P picture-in-picture R start/stop recording (in English locales) T cut recording (in English locales) Return/Enter apply effect Escape cancel area selection Ctrl-Q quit Video and audio sources can also be selected using the buttons beneath their thumbnails. Connecting sources and sinks ---------------------------- Run dvsource-file to stream a DV file to the mixer. It takes a single filename. Normally it plays the file once and then exits. You can enable looping of the file with the -l option. Run dvsource-firewire to stream from a DV device (camera or VCR) connected by Firewire. Run dvsource-v4l2-dv to stream from a DV device that has a Video4Linux2 driver. This includes most USB DV cameras if you have the uvcvideo driver installed. Run dvsink-files to save the mixer's output to files. It takes a filename pattern which may include formatting sequences as used by strftime(3) so that files are named according to the time when they are created. It will open a new file every time you start recording and every time you cut in the mixing window. If the name pattern does not end with the suffix ".dv", this will be added. Finally, a hyphen and a number will be added before the ".dv" if necessary to avoid filename collisions. Run dvsink-command to send the mixer's output to the standard input of a command. For example, to send a downscaled Theora stream over Icecast, run: dvsink-command -- \ ffmpeg2theora -o - -f dv -x 320 -y 240 --aspect 4:3 --deinterlace - \ | oggfwd dvsink-command provides a continuous stream which is not affected by the recording commands. Applying effects ---------------- Currently the only mixing effect is picture-in-picture. To apply this effect: 1. Select the primary video source by pressing its number key or clicking the 'A' button under its thumbnail 2. Select the secondary video source by pressing Alt and its number key or clicking the 'B' button under its thumbnail 3. Press P 4. Drag to mark the area the secondary video source should overlay in the main display, using mouse button 1 (normally left). You can also extend the selection using mouse button 2 (normally middle). You can also cancel selection by pressing Escape. 5. Press Return/Enter To cancel the effect, press Escape. dvswitch-0.8.3.6/doc/design-notes000066400000000000000000000026251161012451100166050ustar00rootroot00000000000000Clock One of the trickiest things is clocking and synchronisation. Since the system clock and the source clocks will not be perfectly synchronised with each other, we need to pick a master clock. The master clock should match the clock of whichever source we are taking audio from since audio discontinuities are so undesirable. Latency between the source and the mixer is variable so we will need to generate a clock indepdently and adjust the rate to match the rolling average frame rate from that source. We should use a high-priority thread to implement the clock. At each frame interval it will take a video frame off each input queue that has one available and add an item to the output queue specifying the input frames and mixer settings for the output frame. Where no new input frame is available it should repeat the previous frame. (Note this should only ever happen for video.) Frame storage For each input frame we keep both the original DV data and the decoded data, since we can usually copy across the DV data rather than reencoding it. The thread(s) servicing sources will parse the DIF blocks and assemble complete frames. (So long as we keep using TCP then this is trivial.) Decoding and encoding Initially no encoding will be needed. The thread servicing the output queue will copy encoded audio and video data from sources. Q: At what point and in what thread do we decode frames for display? dvswitch-0.8.3.6/doc/dvsink-command.1000066400000000000000000000012261161012451100172530ustar00rootroot00000000000000.\" dvsink-command.1 written by Ben Hutchings .TH DVSINK-COMMAND 1 "18 February 2009" .SH NAME dvsink-command \- command sink for DVswitch .SH SYNOPSIS .HP .B dvsink-command .RI [ OPTIONS "] " COMMAND ... .SH DESCRIPTION .LP Pass the output from DVswitch to another command. The output will appear as its standard input. .SH OPTIONS \fB\-h\fR, \fB\-\-host=\fIHOST\fR .TP \fB\-p\fR, \fB\-\-port=\fIPORT\fR .RS Specify the network address on which DVswitch is listening. The host address may be specified by name or as an IPv4 or IPv6 literal. .RE .SH AUTHOR Ben Hutchings . .SH SEE ALSO /usr/share/doc/dvswitch/README dvswitch-0.8.3.6/doc/dvsink-files.1000066400000000000000000000020141161012451100167330ustar00rootroot00000000000000.\" dvsink-files.1 written by Ben Hutchings .TH DVSINK-FILES 1 "18 February 2009" .SH NAME dvsink-files \- file sink for DVswitch .SH SYNOPSIS .HP .B dvsink-files .RI [ OPTIONS "] " NAME-FORMAT .SH DESCRIPTION .LP Record the output from DVswitch. This will open a new file whenever recording starts and whenever the Cut command is used in DVswitch. The filename format may include formatting sequences as used by \fBstrftime\fR(3) so that files are named according to the time when they are created. If the name format does not end with the suffix ".dv", this will be added. Finally, a hyphen and a number will be added before the ".dv" if necessary to avoid filename collisions. .SH OPTIONS \fB\-h\fR, \fB\-\-host=\fIHOST\fR .TP \fB\-p\fR, \fB\-\-port=\fIPORT\fR .RS Specify the network address on which DVswitch is listening. The host address may be specified by name or as an IPv4 or IPv6 literal. .RE .SH AUTHOR Ben Hutchings . .SH SEE ALSO strftime(3), /usr/share/doc/dvswitch/README dvswitch-0.8.3.6/doc/dvsource-alsa.1000066400000000000000000000030161161012451100171100ustar00rootroot00000000000000.\" dvsource-alsa.1 written by Ben Hutchings .mso www.tmac .TH DVSOURCE-DVGRAB 1 "11 March 2009" .SH NAME dvsource-alsa \- audio source for DVswitch .SH SYNOPSIS .HP .B dvsource-alsa .RI [ OPTIONS ] .RB [ \-s " ntsc|pal] .RB [ \-r " 48000|32000|44100] .RI [ DEVICE ] .SH DESCRIPTION .LP \fBdvsource-alsa\fR uses ALSA to capture audio and sends it to DVswitch together with a dummy video stream. .LP The optional \fIDEVICE\fR argument is a PCM device name. See .URL http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html#pcm_dev_names "the ALSA documentation" for an explanation of these names. .SH OPTIONS \fB\-h\fR, \fB\-\-host=\fIHOST\fR .TP \fB\-p\fR, \fB\-\-port=\fIPORT\fR .RS Specify the network address on which DVswitch is listening. The host address may be specified by name or as an IPv4 or IPv6 literal. .RE .TP \fB\-s\fR, \fB\-\-system=\fRntsc|pal .RS Specify the video system to use. This must match the system used by DVswitch. The default is "pal". .RE .TP \fB\-d\fR, \fB\-\-rate=\fR48000|32000|44100 .RS Specify the sample rate, in Hz. The default is 48000. .RE .TP \fB-d\fR, \fB--delay\fR=\fIDELAY\fR .RS Specify the time by which audio should be delayed, in seconds. The delays in live audio and video sources need to be closely matched in order that the audio and video signals will be synchronised in the output of DVswitch. The default is 0.2. .RE .SH AUTHOR Ben Hutchings . .SH SEE ALSO http://www\.alsa\-project\.org/alsa\-doc/alsa\-lib/pcm\.html, /usr/share/doc/dvswitch/README dvswitch-0.8.3.6/doc/dvsource-dvgrab.1000066400000000000000000000023751161012451100174440ustar00rootroot00000000000000.\" dvsource-dvgrab.1 written by Ben Hutchings .TH DVSOURCE-DVGRAB 1 "24 May 2009" .SH NAME dvsource-dvgrab \- Firewire and Video4Linux2 sources for DVswitch .SH SYNOPSIS .HP .B dvsource-firewire .RI [ OPTIONS ] .RB [ \-c .IR CARD-NUMBER " | " DEVICE ] .HP .B dvsource-v4l2-dv .RI [ OPTIONS "] [" DEVICE ] .SH DESCRIPTION .LP \fBdvsource-firewire\fR uses \fBdvgrab\fR(1) to stream from a DV device (camera or VCR) connected by Firewire. .LP \fBdvsource-v4l2-dv\fR uses \fBdvgrab\fR(1) to stream from a DV device that has a Video4Linux2 driver. This includes most USB DV cameras if you have the uvcvideo driver installed. .SH OPTIONS \fB\-t\fR, \fB\-\-tally\fR .RS Print notices of activation and deactivation in the mixer, for use with a tally light. These will take the form "TALLY: on" or "TALLY: off". .RE .TP \fB\-h\fR, \fB\-\-host=\fIHOST\fR .TP \fB\-p\fR, \fB\-\-port=\fIPORT\fR .RS Specify the network address on which DVswitch is listening. The host address may be specified by name or as an IPv4 or IPv6 literal. .RE .TP \fB\-c\fR, \fB\-\-card=\fICARD-NUMBER\fR .RS Specify the index of the Firewire card the DV device is connected to. .RE .SH AUTHOR Ben Hutchings . .SH SEE ALSO dvgrab(1), /usr/share/doc/dvswitch/README dvswitch-0.8.3.6/doc/dvsource-file.1000066400000000000000000000014141161012451100171070ustar00rootroot00000000000000.\" dvsource-file.1 written by Ben Hutchings .TH DVSOURCE-FILE 1 "18 February 2009" .SH NAME dvsource-file \- file source for DVswitch .SH SYNOPSIS .HP .B dvsource-file .RI [ OPTIONS ] .RB [ \-l ] .I FILE .SH DESCRIPTION .LP Stream a DV file to the mixer. By default this plays the file once and then exits. This is mostly useful for development and testing purposes. .SH OPTIONS \fB\-h\fR, \fB\-\-host=\fIHOST\fR .TP \fB\-p\fR, \fB\-\-port=\fIPORT\fR .RS Specify the network address on which DVswitch is listening. The host address may be specified by name or as an IPv4 or IPv6 literal. .RE .TP .BR \-l , " \-\-loop" .RS Play the file repeatedly in a loop. .RE .SH AUTHOR Ben Hutchings . .SH SEE ALSO /usr/share/doc/dvswitch/README dvswitch-0.8.3.6/doc/dvswitch.1000066400000000000000000000015251161012451100161760ustar00rootroot00000000000000.\" dvswitch.1 written by Ben Hutchings .TH DVSWITCH 1 "18 February 2009" .SH NAME dvswitch \- mixes and distributes DV streams .SH SYNOPSIS .HP .B dvswitch .RI [ OPTIONS ] .SH DESCRIPTION .LP DVswitch is a basic video mixer designed for use at conferences that require live mixing for streams and/or recording. It presents a GUI for interactive control and runs a network server for connection of sources and sinks. .LP The GUI is documented in the \fBREADME\fR file. .SH OPTIONS \fB\-h\fR, \fB\-\-host=\fIHOST\fR .TP \fB\-p\fR, \fB\-\-port=\fIPORT\fR .RS Specify the network address on which to listen and accept source and sink connections. The host address may be specified by name or as an IPv4 or IPv6 literal. .RE .SH AUTHOR Ben Hutchings . .SH SEE ALSO gtk-options(7), /usr/share/doc/dvswitch/README dvswitch-0.8.3.6/doc/inefficiencies000066400000000000000000000003411161012451100171420ustar00rootroot00000000000000We should decode in multiple threads if there are multiple processors available. dv_decode_full_frame currently prevents this with a mutex, though there's no obvious shared state that would make it unsafe without the mutex. dvswitch-0.8.3.6/src/000077500000000000000000000000001161012451100143005ustar00rootroot00000000000000dvswitch-0.8.3.6/src/CMakeLists.txt000066400000000000000000000030351161012451100170410ustar00rootroot00000000000000include(Symlink) add_definitions(${ALSA_CFLAGS_OTHER} ${GTKMM_CFLAGS_OTHER} ${LIBAVCODEC_CFLAGS_OTHER}) include_directories(${ALSA_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS} ${LIBAVCODEC_INCLUDE_DIRS}) link_directories(${ALSA_LIBRARY_DIRS} ${GTKMM_LIBRARY_DIRS} ${LIBAVCODEC_LIBRARY_DIRS}) add_definitions(-DSHAREDIR="\\"${sharedir}\\"") set(common_sources config.c dif.c socket.c) add_executable(dvsink-command dvsink-command.c ${common_sources}) add_executable(dvsink-files dvsink-files.c ${common_sources}) add_executable(dvsource-file dvsource-file.c frame_timer.c ${common_sources}) target_link_libraries(dvsource-file pthread rt) add_executable(dvsource-dvgrab dvsource-dvgrab.c ${common_sources}) add_executable(dvsource-alsa dvsource-alsa.c dif_audio.c ${common_sources}) target_link_libraries(dvsource-alsa m ${ALSA_LIBRARIES}) add_executable(dvswitch dvswitch.cpp mixer.cpp frame_timer.c mixer_window.cpp dv_display_widget.cpp dv_selector_widget.cpp server.cpp auto_pipe.cpp os_error.cpp video_effect.c frame_pool.cpp frame.c auto_codec.cpp format_dialog.cpp dif_audio.c vu_meter.cpp status_overlay.cpp ${common_sources}) target_link_libraries(dvswitch m pthread rt X11 Xext Xv ${BOOST_THREAD_LIBRARIES} ${GTKMM_LIBRARIES} ${LIBAVCODEC_LIBRARIES} ${LIBAVUTIL_LIBRARIES}) install(TARGETS dvsink-command dvsink-files dvsource-file dvsource-dvgrab dvsource-alsa dvswitch DESTINATION ${bindir}) install_symlink(dvsource-dvgrab "${bindir}/dvsource-firewire") install_symlink(dvsource-dvgrab "${bindir}/dvsource-v4l2-dv") dvswitch-0.8.3.6/src/auto_codec.cpp000066400000000000000000000032501161012451100171110ustar00rootroot00000000000000// Copyright 2008 Ben Hutchings . // See the file "COPYING" for licence details. #include #include #include "auto_codec.hpp" #include "os_error.hpp" namespace { boost::mutex avcodec_mutex; struct avcodec_initialiser { avcodec_initialiser() { avcodec_init(); avcodec_register_all(); } } initialiser; } auto_codec auto_codec_open_decoder(CodecID codec_id) { auto_codec result(avcodec_alloc_context()); if (!result.get()) throw std::bad_alloc(); auto_codec_open_decoder(result, codec_id); return result; } void auto_codec_open_decoder(const auto_codec & context, CodecID codec_id) { boost::mutex::scoped_lock lock(avcodec_mutex); AVCodec * codec = avcodec_find_decoder(codec_id); if (!codec) throw os_error("avcodec_find_decoder", ENOENT); os_check_error("avcodec_open", -avcodec_open(context.get(), codec)); } auto_codec auto_codec_open_encoder(CodecID codec_id) { auto_codec result(avcodec_alloc_context()); if (!result.get()) throw std::bad_alloc(); auto_codec_open_encoder(result, codec_id); return result; } void auto_codec_open_encoder(const auto_codec & context, CodecID codec_id) { boost::mutex::scoped_lock lock(avcodec_mutex); AVCodec * codec = avcodec_find_encoder(codec_id); if (!codec) throw os_error("avcodec_find_encoder", ENOENT); os_check_error("avcodec_open", -avcodec_open(context.get(), codec)); } void auto_codec_closer::operator()(AVCodecContext * context) const { if (context) { if (context->codec) { boost::mutex::scoped_lock lock(avcodec_mutex); avcodec_close(context); } av_free(context); } } dvswitch-0.8.3.6/src/auto_codec.hpp000066400000000000000000000013061161012451100171160ustar00rootroot00000000000000// Copyright 2008 Ben Hutchings . // See the file "COPYING" for licence details. #ifndef INC_AUTO_CODEC_HPP #define INC_AUTO_CODEC_HPP #include "auto_handle.hpp" #include "avcodec_wrap.h" struct auto_codec_closer { void operator()(AVCodecContext * context) const; }; struct auto_codec_factory { AVCodecContext * operator()() const { return 0; } }; typedef auto_handle auto_codec; auto_codec auto_codec_open_decoder(CodecID); void auto_codec_open_decoder(const auto_codec &, CodecID); auto_codec auto_codec_open_encoder(CodecID); void auto_codec_open_encoder(const auto_codec &, CodecID); #endif // !INC_AUTO_CODEC_HPP dvswitch-0.8.3.6/src/auto_fd.hpp000066400000000000000000000010261161012451100164310ustar00rootroot00000000000000// Copyright 2005 Ben Hutchings . // See the file "COPYING" for licence details. #ifndef INC_AUTO_FD_HPP #define INC_AUTO_FD_HPP #include "auto_handle.hpp" #include #include struct auto_fd_closer { void operator()(int fd) const { if (fd >= 0) { int result = close(fd); assert(result == 0); } } }; struct auto_fd_factory { int operator()() const { return -1; } }; typedef auto_handle auto_fd; #endif // !INC_AUTO_FD_HPP dvswitch-0.8.3.6/src/auto_handle.hpp000066400000000000000000000043051161012451100172760ustar00rootroot00000000000000// Copyright 2005 Ben Hutchings . // See the file "COPYING" for licence details. #ifndef INC_AUTO_HANDLE_HPP #define INC_AUTO_HANDLE_HPP // Like auto_ptr, but for arbitrary "handle" types. // The parameters are: // - handle_type: the type of the raw handle to be wrapped // - closer_type: a function object type whose operator() takes a raw handle // and closes it (or does nothing if it is a null handle) // - factory_type: a function object type whose operator() returns a null // handle template class auto_handle_ref; template class auto_handle // Use inheritance so we can benefit from the empty base optimisation : private closer_type, private factory_type { typedef auto_handle_ref ref_type; public: auto_handle() : handle_(factory_type::operator()()) {} explicit auto_handle(handle_type handle) : handle_(handle) {} auto_handle(auto_handle & other) : handle_(other.release()) {} auto_handle(ref_type other) : handle_(other.release()) {} auto_handle & operator=(auto_handle & other) { reset(other.release()); } ~auto_handle() { reset(); } handle_type get() const { return handle_; } handle_type release() { handle_type handle(handle_); handle_ = factory_type::operator()(); return handle; } void reset() { closer_type::operator()(handle_); handle_ = factory_type::operator()(); } void reset(handle_type handle) { closer_type::operator()(handle_); handle_ = handle; } operator ref_type() { return ref_type(*this); } private: handle_type handle_; }; template class auto_handle_ref { typedef auto_handle target_type; public: explicit auto_handle_ref(target_type & target) : target_(target) {} handle_type release() { return target_.release(); } private: target_type & target_; }; #endif // !defined(INC_AUTO_HANDLE_HPP) dvswitch-0.8.3.6/src/auto_pipe.cpp000066400000000000000000000010721161012451100167710ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings . // See the file "COPYING" for licence details. #include "auto_pipe.hpp" #include "os_error.hpp" #include #include auto_pipe::auto_pipe(int reader_flags, int writer_flags) { int pipe_ends[2]; os_check_zero("pipe", pipe(pipe_ends)); reader.reset(pipe_ends[0]); writer.reset(pipe_ends[1]); if (reader_flags) os_check_nonneg("fcntl", fcntl(reader.get(), F_SETFL, reader_flags)); if (writer_flags) os_check_nonneg("fcntl", fcntl(writer.get(), F_SETFL, writer_flags)); } dvswitch-0.8.3.6/src/auto_pipe.hpp000066400000000000000000000005211161012451100167740ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings . // See the file "COPYING" for licence details. #ifndef INC_AUTO_PIPE_HPP #define INC_AUTO_PIPE_HPP #include "auto_fd.hpp" struct auto_pipe { explicit auto_pipe(int reader_flags = 0, int writer_flags = 0); auto_fd reader, writer; }; #endif // !defined(INC_AUTO_PIPE_HPP) dvswitch-0.8.3.6/src/avcodec_wrap.h000066400000000000000000000017221161012451100171100ustar00rootroot00000000000000// Copyright 2009-2010 Ben Hutchings. // See the file "COPYING" for licence details. // Some versions of ffmpeg define an ABS macro, which glib also does. // The two definitions are equivalent but the duplicate definitions // provoke a warning. #undef ABS // may need UINT64_C while may or may not // define that for C++. Therefore, include here and then // define UINT64_C if it didn't get defined. #include #ifndef UINT64_C #define UINT64_C(n) n ## ULL #endif // These guards were removed from ... what were they thinking? #ifdef __cplusplus extern "C" { #endif #include #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 26, 0) static inline int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, AVPacket *avpkt) { return avcodec_decode_video(avctx, picture, got_picture_ptr, avpkt->data, avpkt->size); } #endif #ifdef __cplusplus } #endif #undef ABS dvswitch-0.8.3.6/src/config.c000066400000000000000000000067471161012451100157270ustar00rootroot00000000000000/* Copyright 2007 Ben Hutchings. * See the file "COPYING" for licence details. */ #include #include #include #include #include #include static char * string_join(const char * left, const char * right) { size_t left_len = strlen(left), right_len = strlen(right); char * result; if ((result = malloc(left_len + right_len + 1)) == NULL) { perror("ERROR: malloc"); exit(1); } memcpy(result, left, left_len); memcpy(result + left_len, right, right_len); result[left_len + right_len] = 0; return result; } /* Read configuration file. Exit if it is unreadable or invalid. * The configuration format consists of Bourne shell variable assignments * and comments, with no variable references or escaped line breaks * allowed. */ static void read_config(const char * path, void (*item_handler)(const char *, const char *)) { FILE * file; unsigned line_no = 0; char line_buf[1000]; /* this is just going to have to be long enough */ if ((file = fopen(path, "r")) == NULL) { /* Configuration files are optional so this is only an error if * the file exists yet is unreadable. */ if (errno == ENOENT) return; perror("ERROR: fopen"); exit(1); } while (fgets(line_buf, sizeof(line_buf), file)) { const char * name, * value; char * p; int ch; bool valid = true; ++line_no; /* Find first non-space. */ p = line_buf; while ((ch = (unsigned char)*p) && isspace(ch)) ++p; if (ch == 0 || ch == '#') { /* This is a blank or comment line. */ continue; } else if (isalpha(ch) || ch == '_') { /* Find end of name. */ name = p++; while ((ch = (unsigned char)*p) && (isalnum(ch) || ch == '_')) ++p; *p = 0; /* ensure name is terminated */ if (ch != '=') { valid = false; } else { char * out; ++p; value = out = p; while ((ch = (unsigned char)*p) && !isspace(ch)) { int quote = (ch == '\'' || ch == '"') ? ch : 0; if (quote) ++p; while ((ch = (unsigned char)*p) && !(quote ? ch == quote : (isspace(ch) || ch == '\'' || ch == '"'))) { ++p; if (quote != '\'') { if (ch == '$') { /* We're not going to do $-expansion. */ valid = false; } else if (ch == '\\') { /* Check whether it's a valid escape. */ ch = (unsigned char)*p; switch (ch) { case '$': case '\'': case '\"': case '\\': ++p; break; case ' ': if (quote) valid = false; else ++p; break; default: valid = false; break; } } } *out++ = ch; } if (quote && ch) ++p; } while ((ch = (unsigned char)*p) && isspace(ch) && ch != '\n') ++p; if (ch != '\n') valid = false; /* new-line was quoted or missing */ *out = 0; /* terminate value */ } } else { valid = false; } if (valid) { item_handler(name, value); } else { /* XXX This could be more informative! */ fprintf(stderr, "ERROR: syntax error at %s:%d\n", path, line_no); exit(2); } } fclose(file); } void dvswitch_read_config(void (*item_handler)(const char *, const char *)) { read_config("/etc/dvswitchrc", item_handler); const char * home = getenv("HOME"); if (home) { char * home_dvswitchrc = string_join(home, "/.dvswitchrc"); read_config(home_dvswitchrc, item_handler); free(home_dvswitchrc); } } dvswitch-0.8.3.6/src/config.h000066400000000000000000000011031161012451100157110ustar00rootroot00000000000000/* Copyright 2007 Ben Hutchings. * See the file "COPYING" for licence details. */ #ifndef DVSWITCH_CONFIG_H #define DVSWITCH_CONFIG_H #ifdef __cplusplus extern "C" { #endif /* Read configuration files. Exit if they are unreadable or invalid. * Call the item_handler function for each configuration item found. * There may be multiple items with the same name; the last should * take precedence. */ void dvswitch_read_config(void (*item_handler)(const char * name, const char * value)); #ifdef __cplusplus } #endif #endif /* !defined(DVSWITCH_CONFIG_H) */ dvswitch-0.8.3.6/src/dif.c000066400000000000000000000121241161012451100152060ustar00rootroot00000000000000// Copyright 2008-2009 Ben Hutchings. // See the file "COPYING" for licence details. #include "dif.h" static const uint8_t dv_audio_shuffle_625_50[12][9] = { { 0, 36, 72, 26, 62, 98, 16, 52, 88}, /* 1st channel */ { 6, 42, 78, 32, 68, 104, 22, 58, 94}, { 12, 48, 84, 2, 38, 74, 28, 64, 100}, { 18, 54, 90, 8, 44, 80, 34, 70, 106}, { 24, 60, 96, 14, 50, 86, 4, 40, 76}, { 30, 66, 102, 20, 56, 92, 10, 46, 82}, { 1, 37, 73, 27, 63, 99, 17, 53, 89}, /* 2nd channel */ { 7, 43, 79, 33, 69, 105, 23, 59, 95}, { 13, 49, 85, 3, 39, 75, 29, 65, 101}, { 19, 55, 91, 9, 45, 81, 35, 71, 107}, { 25, 61, 97, 15, 51, 87, 5, 41, 77}, { 31, 67, 103, 21, 57, 93, 11, 47, 83}, }; const struct dv_system dv_system_625_50 = { .common_name = "pal", .frame_width = 720, .frame_height = 576, .active_region = { .left = 9, .top = 0, .right = 711, .bottom = 576 }, .frame_rate_numer = 25, .frame_rate_denom = 1, .pixel_aspect = { [dv_frame_aspect_normal] = { .width = 59, .height = 54 }, [dv_frame_aspect_wide] = { .width = 118, .height = 81 } }, .seq_count = 12, .size = 12 * DIF_SEQUENCE_SIZE, .sample_counts = { [dv_sample_rate_48k] = { .min = 1896, .max = 1944, .std_cycle_len = 1, .std_cycle = { 1920 } }, [dv_sample_rate_44k1] = { .min = 1742, .max = 1786, .std_cycle_len = 1, .std_cycle = { 1764 } }, [dv_sample_rate_32k] = { .min = 1264, .max = 1296, .std_cycle_len = 1, .std_cycle = { 1280 } } }, .audio_shuffle = dv_audio_shuffle_625_50, }; static const uint8_t dv_audio_shuffle_525_60[10][9] = { { 0, 30, 60, 20, 50, 80, 10, 40, 70 }, /* 1st channel */ { 6, 36, 66, 26, 56, 86, 16, 46, 76 }, { 12, 42, 72, 2, 32, 62, 22, 52, 82 }, { 18, 48, 78, 8, 38, 68, 28, 58, 88 }, { 24, 54, 84, 14, 44, 74, 4, 34, 64 }, { 1, 31, 61, 21, 51, 81, 11, 41, 71 }, /* 2nd channel */ { 7, 37, 67, 27, 57, 87, 17, 47, 77 }, { 13, 43, 73, 3, 33, 63, 23, 53, 83 }, { 19, 49, 79, 9, 39, 69, 29, 59, 89 }, { 25, 55, 85, 15, 45, 75, 5, 35, 65 }, }; const struct dv_system dv_system_525_60 = { .common_name = "ntsc", .frame_width = 720, .frame_height = 480, .active_region = { .left = 4, .top = 0, .right = 716, .bottom = 480 }, .frame_rate_numer = 30000, .frame_rate_denom = 1001, .pixel_aspect = { [dv_frame_aspect_normal] = { .width = 10, .height = 11 }, [dv_frame_aspect_wide] = { .width = 40, .height = 33 } }, .seq_count = 10, .size = 10 * DIF_SEQUENCE_SIZE, .sample_counts = { [dv_sample_rate_48k] = { .min = 1580, .max = 1620, .std_cycle_len = 5, .std_cycle = { 1602, 1601, 1602, 1601, 1602 } }, [dv_sample_rate_44k1] = { .min = 1452, .max = 1489, .std_cycle_len = 100, .std_cycle = { 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471, 1472, 1471 } }, [dv_sample_rate_32k] = { .min = 1053, .max = 1080, .std_cycle_len = 15, .std_cycle = { 1068, 1067, 1068, 1068, 1068, 1067, 1068, 1068, 1068, 1067, 1068, 1068, 1068, 1067, 1068 } }, }, .audio_shuffle = dv_audio_shuffle_525_60, }; enum dv_frame_aspect dv_buffer_get_aspect(const uint8_t * buffer) { const uint8_t * vsc_pack = buffer + 5 * DIF_BLOCK_SIZE + 53; // If no VSC pack present, assume normal (4:3) aspect if (vsc_pack[0] != 0x61) return dv_frame_aspect_normal; // Check the aspect code (depends partly on the DV variant) int aspect = vsc_pack[2] & 7; int apt = buffer[4] & 7; if (aspect == 2 || (apt == 0 && aspect == 7)) return dv_frame_aspect_wide; else return dv_frame_aspect_normal; } void dv_buffer_set_aspect(uint8_t * buffer, enum dv_frame_aspect aspect) { const struct dv_system * system = dv_buffer_system(buffer); unsigned seq; for (seq = 0; seq != system->seq_count; ++seq) { uint8_t * vsc_pack = buffer + seq * DIF_SEQUENCE_SIZE + ((seq & 1) ? 3 * DIF_BLOCK_SIZE + 8 : 5 * DIF_BLOCK_SIZE + 53); vsc_pack[2] = ((vsc_pack[2] & 0xf8) | ((aspect == dv_frame_aspect_wide) ? 2 : 0)); } } enum dv_sample_rate dv_buffer_get_sample_rate(const uint8_t * buffer) { const uint8_t * as_pack = buffer + (6 + 3 * 16) * DIF_BLOCK_SIZE + 3; if (as_pack[0] == 0x50) { unsigned sample_rate = (as_pack[4] >> 3) & 7; if (sample_rate < dv_sample_rate_count) return sample_rate; } // If no AS pack present or sample rate is unrecognised, assume 48 kHz. // XXX Does this make any sense? return dv_sample_rate_48k; } dvswitch-0.8.3.6/src/dif.h000066400000000000000000000066231161012451100152220ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_DIF_H #define DVSWITCH_DIF_H #include #include #include "geometry.h" #ifdef __cplusplus extern "C" { #endif #define DIF_BLOCK_SIZE 80 #define DIF_BLOCKS_PER_SEQUENCE 150 #define DIF_SEQUENCE_SIZE (DIF_BLOCK_SIZE * DIF_BLOCKS_PER_SEQUENCE) #define DIF_MAX_FRAME_SIZE (DIF_SEQUENCE_SIZE * 12) #define DIF_BLOCK_ID_SIZE 3 #define DIF_PACK_SIZE 5 // Block id for first block of a sequence #define DIF_SIGNATURE_SIZE DIF_BLOCK_ID_SIZE #define DIF_SIGNATURE "\x1f\x07\x00" enum dv_sample_rate { dv_sample_rate_auto = -1, dv_sample_rate_48k, dv_sample_rate_44k1, dv_sample_rate_32k, dv_sample_rate_count }; extern enum dv_sample_rate dv_buffer_get_sample_rate(const uint8_t * frame); enum dv_frame_aspect { dv_frame_aspect_auto = -1, dv_frame_aspect_normal, // 4:3 dv_frame_aspect_wide, // 16:9 dv_frame_aspect_count }; extern enum dv_frame_aspect dv_buffer_get_aspect(const uint8_t * frame); extern void dv_buffer_set_aspect(uint8_t * buffer, enum dv_frame_aspect aspect); struct dv_system { const char * common_name; unsigned frame_width, frame_height; struct rectangle active_region; unsigned frame_rate_numer, frame_rate_denom; struct { unsigned width, height; } pixel_aspect[dv_frame_aspect_count]; unsigned seq_count; size_t size; // The number of samples per frame may vary, for two reasons. Firstly, // consumer gear is not required to have synchronised audio and video // clocks. Secondly, the frame rate 30000/1001 does not divide evenly // into any of the supported audio sample rates. struct { // Minimum and maximum sample counts allowed. The actual sample // count is encoded in the AS pack relative to the minimum. unsigned min, max; // A cycle of sample counts which will result in perfect // synchronisation ("locked audio" for 32k and 48k). unsigned std_cycle_len, std_cycle[100]; } sample_counts[dv_sample_rate_count]; const uint8_t (*audio_shuffle)[9]; }; extern const struct dv_system dv_system_625_50, dv_system_525_60; static inline unsigned dv_buffer_system_code(const uint8_t * buffer) { return buffer[3] >> 7; } static inline const struct dv_system * dv_buffer_system(const uint8_t * buffer) { return dv_buffer_system_code(buffer) ? &dv_system_625_50 : &dv_system_525_60; } // Get audio data from buffer. Copy the first 2 channels to the buffer // as interleaved signed 16-bit PCM samples. Return the number of // samples from each channel. Caller must ensure the buffer is large // enough! unsigned dv_buffer_get_audio(const uint8_t * buffer, int16_t * samples); // Set sample rate and audio data in buffer. Copy interleaved signed // 16-bit PCM samples to the first 2 channels to the buffer // (companding to 12-bit if necessary). Caller must ensure the number // of samples is valid for the selected video system and sample rate. void dv_buffer_set_audio(uint8_t * buffer, enum dv_sample_rate sample_rate_code, unsigned sample_count, const int16_t * samples); void dv_buffer_get_audio_levels(const uint8_t * frame, int * levels); void dv_buffer_dub_audio(uint8_t * dest, const uint8_t * source); void dv_buffer_silence_audio(uint8_t * buffer, enum dv_sample_rate sample_rate_code, unsigned serial_num); #ifdef __cplusplus } #endif #endif // !defined(DVSWITCH_DIF_H) dvswitch-0.8.3.6/src/dif_audio.c000066400000000000000000000232711161012451100163740ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include "dif.h" // Samples may be encoded as either 16-bit LPCM or 12-bit companded PCM. // The companding mapping is: // // 16-bit sample 12-bit code Scale // ------------------------------------ // 0x4000..0x7fff 0x700..0x7ff 6 // 0x2000..0x4000 0x600..0x700 5 // 0x1000..0x2000 0x500..0x600 4 // 0x0800..0x1000 0x400..0x500 3 // 0x0400..0x0800 0x300..0x400 2 // 0x0200..0x0400 0x200..0x300 1 // 0x0000..0x0200 0x000..0x200 0 // 0xfe00..0xffff 0xe00..0xfff 0 // 0xfc00..0xfe00 0xd00..0xe00 1 // 0xf800..0xfc00 0xc00..0xd00 2 // 0xf000..0xf800 0xb00..0xc00 3 // 0xe000..0xf000 0xa00..0xb00 4 // 0xc000..0xe000 0x900..0xa00 5 // 0x8001..0xc000 0x801..0x900 6 // // (The ranges overlap because both scale values work at the boundaries.) static unsigned get_12bit_scale(uint16_t sample) { unsigned result = 0; if (sample & 0x7000) { sample >>= 4; result += 4; } if (sample & 0x0c00) { sample >>= 2; result += 2; } if (sample & 0x0200) result += 1; return result; } static int16_t decode_12bit(unsigned code) { if (code < 0x200) { return code; } else if (code < 0x800) { unsigned scale = (code >> 8) - 1; return ((code & 0xff) + 0x100) << scale; } else if (code == 0x800) { return 0; } else if (code < 0xe00) { unsigned scale = 14 - (code >> 8); return ((int)(code & 0xff) - 0x200) << scale; } else { return (int)code - 0x1000; } } unsigned dv_buffer_get_audio(const uint8_t * buffer, int16_t * samples) { const struct dv_system * system = dv_buffer_system(buffer); const uint8_t * as_pack = buffer + (6 + 3 * 16) * DIF_BLOCK_SIZE + 3; if (as_pack[0] != 0x50) return 0; enum dv_sample_rate sample_rate_code = (as_pack[4] >> 3) & 7; if (sample_rate_code >= dv_sample_rate_count) return 0; unsigned quant = as_pack[4] & 7; if (quant > 1) return 0; unsigned sample_count = 2 * (system->sample_counts[sample_rate_code].min + (as_pack[1] & 0x3f)); for (unsigned seq = 0; seq != (quant ? system->seq_count / 2 : system->seq_count); ++seq) { for (unsigned block_n = 0; block_n != 9; ++block_n) { const uint8_t * block = &buffer[seq * DIF_SEQUENCE_SIZE + (6 + 16 * block_n) * DIF_BLOCK_SIZE]; if (quant) // 12-bit { for (unsigned i = 0; i != 24; ++i) { unsigned pos = (system->audio_shuffle[seq][block_n] + i * system->seq_count * 9); if (pos < sample_count) { unsigned code = ((block[8 + 3 * i] << 4) + (block[8 + 3 * i + 2] >> 4)); samples[pos] = decode_12bit(code); } pos = (system->audio_shuffle[ seq + system->seq_count / 2][block_n] + i * system->seq_count * 9); if (pos < sample_count) { unsigned code = ((block[8 + 3 * i + 1] << 4) + (block[8 + 3 * i + 2] & 0xf)); samples[pos] = decode_12bit(code); } } } else // 16-bit { for (unsigned i = 0; i != 36; ++i) { unsigned pos = (system->audio_shuffle[seq][block_n] + i * system->seq_count * 9); if (pos < sample_count) { int16_t sample = (block[8 + 2 * i + 1] + (block[8 + 2 * i] << 8)); if (sample == -0x8000) sample = 0; samples[pos] = sample; } } } } } return sample_count / 2; } void dv_buffer_get_audio_levels(const uint8_t * buffer, int * levels) { int16_t samples[2 * 2000]; unsigned sample_count = dv_buffer_get_audio(buffer, samples); assert(2 * sample_count * sizeof(int16_t) <= sizeof(samples)); // Total of squares of samples, so we can calculate average power. We shift // right to avoid overflow. static const unsigned total_shift = 9; unsigned total_l = 0, total_r = 0; for (unsigned i = 0; i != sample_count; ++i) { int16_t sample = samples[2 * i]; total_l += ((unsigned)(sample * sample)) >> total_shift; sample = samples[2 * i + 1]; total_r += ((unsigned)(sample * sample)) >> total_shift; } // Calculate average power and convert to dB levels[0] = (total_l == 0 ? INT_MIN : (int)(log10((double)total_l * (1 << total_shift) / ((double)sample_count * (0x7fff * 0x7fff))) * 10.0)); levels[1] = (total_r == 0 ? INT_MIN : (int)(log10((double)total_r * (1 << total_shift) / ((double)sample_count * (0x7fff * 0x7fff))) * 10.0)); } static unsigned encode_12bit(int16_t sample) { if (sample >= -0x200 && sample <= 0x200) { return (unsigned)sample & 0xfff; } else if (sample > 0) { unsigned scale = get_12bit_scale(sample); return ((scale + 1) << 8) | ((sample >> scale) & 0xff); } else { unsigned scale = get_12bit_scale(~sample); return ((14 - scale) << 8) | ((((sample - 1) >> scale) + 1) & 0xff); } } void dv_buffer_dub_audio(uint8_t * dest, const uint8_t * source) { const struct dv_system * system = dv_buffer_system(dest); assert(dv_buffer_system(source) == system); for (unsigned seq_num = 0; seq_num != system->seq_count; ++seq_num) { for (unsigned block_num = 0; block_num != 9; ++block_num) { ptrdiff_t block_pos = (DIF_SEQUENCE_SIZE * seq_num + DIF_BLOCK_SIZE * (6 + block_num * 16)); memcpy(dest + block_pos, source + block_pos, DIF_BLOCK_SIZE); } } } void dv_buffer_set_audio(uint8_t * buffer, enum dv_sample_rate sample_rate_code, unsigned sample_count, const int16_t * samples) { const struct dv_system * system = dv_buffer_system(buffer); assert(sample_rate_code >= 0 && sample_rate_code < dv_sample_rate_count); assert(sample_count >= system->sample_counts[sample_rate_code].min && sample_count <= system->sample_counts[sample_rate_code].max); bool use_12bit = sample_rate_code == dv_sample_rate_32k; // Each audio block has a 3-byte block id, a 5-byte AAUX // pack, and 72 bytes of samples. Audio block 3 in each // sequence has an AS (audio source) pack, audio block 4 // has an ASC (audio source control) pack, and the other // packs seem to be optional. static const uint8_t aaux_blank_pack[DIF_PACK_SIZE] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; uint8_t aaux_as_pack[DIF_PACK_SIZE] = { // pack id; 0x50 for AAUX source 0x50, // bits 0-5: number of samples in frame minus minimum value // bit 6: flag "should be 1" // bit 7: flag for unlocked audio sampling (sample_count - system->sample_counts[sample_rate_code].min) | (1 << 6) | (1 << 7), // bits 0-3: audio mode // bit 4: flag for independent channels // bit 5: flag for "lumped" stereo (?) // bits 6-7: number of audio channels per block minus 1 use_12bit << 6, // bits 0-4: system type; 0x0 for DV // bit 5: frame rate; 0 for 29.97 fps, 1 for 25 fps // bit 6: flag for multi-language audio // bit 7: ? dv_buffer_system_code(buffer) << 5, // bits 0-2: quantisation; 0 for 16-bit LPCM, 1 for 12-bit // bits 3-5: sample rate code // bit 6: time constant of emphasis; must be 1 // bit 7: flag for no emphasis use_12bit | (sample_rate_code << 3) | (1 << 6) | (1 << 7) }; static const uint8_t aaux_asc_pack[DIF_PACK_SIZE] = { // pack id; 0x51 for AAUX source control 0x51, // bits 0-1: emphasis flag and ? // bits 2-3: compression level; 0 for once // bits 4-5: input type; 1 for digital // bits 6-7: copy generation management system; 0 for unrestricted (1 << 4), // bits 0-2: ? // bits 3-5: recording mode; 1 for original (XXX should indicate dub) // bit 6: recording end flag, inverted // bit 7: recording start flag, inverted (1 << 3) | (1 << 6) | (1 << 7), // bits 0-6: speed; 0x20 seems to be normal // bit 7: direction: 1 for forward 0x20 | (1 << 7), // bits 0-6: genre; 0x7F seems to be unknown // bit 7: reserved 0x7F }; sample_count *= 2; // stereo for (unsigned seq = 0; seq != system->seq_count; ++seq) { if (use_12bit && seq == system->seq_count / 2) samples = NULL; // silence extra 2 channels for (unsigned block_n = 0; block_n != 9; ++block_n) { uint8_t * out = (buffer + seq * DIF_SEQUENCE_SIZE + (6 + 16 * block_n) * DIF_BLOCK_SIZE + DIF_BLOCK_ID_SIZE); memcpy(out, block_n == 3 ? aaux_as_pack : block_n == 4 ? aaux_asc_pack : aaux_blank_pack, DIF_PACK_SIZE); out += DIF_PACK_SIZE; if (samples == NULL) { memset(out, 0, DIF_BLOCK_SIZE - DIF_BLOCK_ID_SIZE - DIF_PACK_SIZE); } else if (use_12bit) { for (unsigned i = 0; i != 24; ++i) { unsigned pos = (system->audio_shuffle[seq][block_n] + i * system->seq_count * 9); unsigned code1 = (pos < sample_count) ? encode_12bit(samples[pos]) : 0; pos = (system->audio_shuffle[ seq + system->seq_count / 2][block_n] + i * system->seq_count * 9); unsigned code2 = (pos < sample_count) ? encode_12bit(samples[pos]) : 0; *out++ = code1 >> 4; *out++ = code2 >> 4; *out++ = (code1 << 4) | (code2 & 0xf); } } else // 16-bit { for (unsigned i = 0; i != 36; ++i) { unsigned pos = (system->audio_shuffle[seq][block_n] + i * system->seq_count * 9); int16_t sample = (pos < sample_count) ? samples[pos] : 0; *out++ = sample >> 8; *out++ = sample & 0xff; } } } } } void dv_buffer_silence_audio(uint8_t * buffer, enum dv_sample_rate sample_rate_code, unsigned serial_num) { const struct dv_system * system = dv_buffer_system(buffer); unsigned sample_count = system->sample_counts[sample_rate_code].std_cycle[ serial_num % system->sample_counts[sample_rate_code].std_cycle_len]; dv_buffer_set_audio(buffer, sample_rate_code, sample_count, NULL); } dvswitch-0.8.3.6/src/dv_display_widget.cpp000066400000000000000000000614001161012451100205060ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // Copyright 2008 Petter Reinholdtsen. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include "dv_display_widget.hpp" #include "frame.h" #include "gui.hpp" #include "video_effect.h" // X headers come last due to egregious macro pollution. #include #include #include namespace { template T div_round_nearest(T numer, T denom) { return (numer + denom / 2) / denom; } const unsigned thumb_scale_denom = 4; const uint32_t invalid_xv_port = uint32_t(-1); Display * get_x_display(const Glib::RefPtr & drawable) { return gdk_x11_drawable_get_xdisplay(drawable->gobj()); } Display * get_x_display(Gtk::Widget & widget) { Glib::RefPtr window(widget.get_window()); assert(window); return get_x_display(window); } Window get_x_window(const Glib::RefPtr & drawable) { return gdk_x11_drawable_get_xid(drawable->gobj()); } Window get_x_window(Gtk::Widget & widget) { Glib::RefPtr window(widget.get_window()); assert(window); return get_x_window(window); } char * allocate_x_shm(Display * x_display, XShmSegmentInfo * info, std::size_t size) { char * result = 0; if ((info->shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0777)) != -1) { info->shmaddr = static_cast(shmat(info->shmid, 0, 0)); if (info->shmaddr != reinterpret_cast(-1) && XShmAttach(x_display, info)) result = info->shmaddr; shmctl(info->shmid, IPC_RMID, 0); } return result; } void free_x_shm(XShmSegmentInfo * info) { shmdt(info->shmaddr); } } // dv_display_widget dv_display_widget::dv_display_widget(int lowres) : decoder_(avcodec_alloc_context()), decoded_serial_num_(-1), shm_busy_(false) { AVCodecContext * decoder = decoder_.get(); if (!decoder) throw std::bad_alloc(); decoder->lowres = lowres; auto_codec_open_decoder(decoder_, CODEC_ID_DVVIDEO); decoder->opaque = this; decoder->get_buffer = get_buffer; decoder->release_buffer = release_buffer; decoder->reget_buffer = reget_buffer; set_app_paintable(true); set_double_buffered(false); } dv_display_widget::~dv_display_widget() { } bool dv_display_widget::init_x_shm_events() { Glib::RefPtr display(get_window()->get_display()); int major_opcode, first_error; if (!XQueryExtension(gdk_x11_display_get_xdisplay(display->gobj()), "MIT-SHM", &major_opcode, &x_shm_first_event_, &first_error)) return false; gdk_x11_register_standard_event_type(display->gobj(), x_shm_first_event_, ShmNumberEvents); get_window()->add_filter(filter_x_shm_event, this); return true; } void dv_display_widget::fini_x_shm_events() { get_window()->remove_filter(filter_x_shm_event, this); } GdkFilterReturn dv_display_widget::filter_x_shm_event(void * void_event, GdkEvent *, void * data) { dv_display_widget * widget = static_cast(data); XEvent * x_event = static_cast(void_event); if (x_event->type == widget->x_shm_first_event_ + ShmCompletion) { widget->shm_busy_ = false; return GDK_FILTER_REMOVE; } return GDK_FILTER_CONTINUE; } dv_display_widget::display_region dv_display_widget::get_display_region(const dv_system * system, dv_frame_aspect frame_aspect) { display_region result; static_cast(result) = system->active_region; result.pixel_width = system->pixel_aspect[frame_aspect].width; result.pixel_height = system->pixel_aspect[frame_aspect].height; return result; } void dv_display_widget::put_frame(const dv_frame_ptr & dv_frame) { if (!is_realized()) return; if (dv_frame->serial_num != decoded_serial_num_ && !shm_busy_) { const struct dv_system * system = dv_frame_system(dv_frame.get()); AVCodecContext * decoder = decoder_.get(); AVFrame * header = get_frame_header(); if (!header) return; AVPacket packet; av_init_packet(&packet); packet.data = dv_frame->buffer; packet.size = system->size; int got_frame; int used_size = avcodec_decode_video2(decoder, header, &got_frame, &packet); if (used_size <= 0) return; assert(got_frame && size_t(used_size) == system->size); header->opaque = const_cast(static_cast(system)); decoded_serial_num_ = dv_frame->serial_num; put_frame_buffer( get_display_region(system, dv_frame_get_aspect(dv_frame.get()))); set_error(dv_frame->format_error); queue_draw(); } } void dv_display_widget::put_frame(const raw_frame_ptr & raw_frame) { if (!is_realized()) return; if (raw_frame->header.pts != decoded_serial_num_ && !shm_busy_) { const struct dv_system * system = raw_frame_system(raw_frame.get()); AVFrame * header = get_frame_buffer(get_frame_header(), raw_frame->pix_fmt, system->frame_height); if (!header) return; raw_frame_ref dest, source; for (int plane = 0; plane != 4; ++plane) { dest.planes.data[plane] = header->data[plane]; dest.planes.linesize[plane] = header->linesize[plane]; source.planes.data[plane] = raw_frame->header.data[plane]; source.planes.linesize[plane] = raw_frame->header.linesize[plane]; } dest.pix_fmt = source.pix_fmt = raw_frame->pix_fmt; dest.height = source.height = system->frame_height; copy_raw_frame(dest, source); decoded_serial_num_ = raw_frame->header.pts; put_frame_buffer(get_display_region(system, raw_frame->aspect)); queue_draw(); } } void dv_display_widget::set_error(bool) { } int dv_display_widget::get_buffer(AVCodecContext * context, AVFrame * header) { dv_display_widget * widget = static_cast(context->opaque); return widget->get_frame_buffer(header, context->pix_fmt, context->height) ? 0 : -1; } void dv_display_widget::release_buffer(AVCodecContext *, AVFrame * header) { for (int i = 0; i != 4; ++i) header->data[i] = 0; } int dv_display_widget::reget_buffer(AVCodecContext *, AVFrame *) { return 0; } // dv_full_display_widget dv_full_display_widget::dv_full_display_widget() : pix_fmt_(PIX_FMT_NONE), height_(0), xv_port_(invalid_xv_port), xv_image_(0), xv_shm_info_(0), // We don't know what the frame format will be, but assume "PAL" // 4:3 frames and therefore an active image size of 702x576 and // pixel aspect ratio of 59:54. dest_width_(767), dest_height_(576), sel_enabled_(false), sel_in_progress_(false) { std::memset(&source_region_, 0, sizeof(source_region_)); std::memset(&selection_, 0, sizeof(selection_)); set_size_request(dest_width_, dest_height_); add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON2_MOTION_MASK); } void dv_full_display_widget::set_selection_enabled(bool flag) { Glib::RefPtr window(get_window()); sel_enabled_ = flag; if (sel_enabled_) { window->set_cursor(Gdk::Cursor(Gdk::CROSSHAIR)); } else { window->set_cursor(); // inherit from parent if (sel_in_progress_) { sel_in_progress_ = false; remove_modal_grab(); } } queue_draw(); } rectangle dv_full_display_widget::get_selection() { return selection_; } bool dv_full_display_widget::try_init_xvideo(PixelFormat pix_fmt, unsigned height) throw() { if (pix_fmt == pix_fmt_ && height == height_) return xv_image_; fini_xvideo(); if (!init_x_shm_events()) return false; int xv_pix_fmt; switch (pix_fmt) { case PIX_FMT_YUV420P: // Use I420, which is an exact match and widely supported. xv_pix_fmt = 0x30323449; break; case PIX_FMT_YUV411P: // There is no common match for this, so use YUY2 and convert. xv_pix_fmt = 0x32595559; break; default: assert(!"Unexpected pixel format"); } Display * x_display = get_x_display(*this); unsigned adaptor_count; XvAdaptorInfo * adaptor_info; if (XvQueryAdaptors(x_display, get_x_window(*this), &adaptor_count, &adaptor_info) != Success) { std::cerr << "ERROR: XvQueryAdaptors() failed\n"; return false; } // Search for a suitable adaptor. unsigned i; for (i = 0; i != adaptor_count; ++i) { if (!(adaptor_info[i].type & XvImageMask)) continue; int format_count; XvImageFormatValues * format_info = XvListImageFormats(x_display, adaptor_info[i].base_id, &format_count); if (!format_info) continue; for (int j = 0; j != format_count; ++j) if (format_info[j].id == xv_pix_fmt) goto end_adaptor_loop; } end_adaptor_loop: if (i == adaptor_count) { std::cerr << "ERROR: No Xv adaptor for this display supports " << char(xv_pix_fmt & 0xFF) << char((xv_pix_fmt >> 8) & 0xFF) << char((xv_pix_fmt >> 16) & 0xFF) << char(xv_pix_fmt >> 24) << " format\n"; } else { // Try to allocate a port. unsigned j; for (j = 0; j != adaptor_info[i].num_ports; ++j) { XvPortID port = adaptor_info[i].base_id + j; if (XvGrabPort(x_display, port, CurrentTime) == Success) { xv_port_ = port; break; } } if (j == adaptor_info[i].num_ports) std::cerr << "ERROR: Could not grab an Xv port\n"; } XvFreeAdaptorInfo(adaptor_info); if (xv_port_ == invalid_xv_port) return false; if (XShmSegmentInfo * xv_shm_info = new (std::nothrow) XShmSegmentInfo) { // Allocate frame buffer in shared memory. Note we allocate an // extra row to allow space for in-place conversion. if (XvImage * xv_image = XvShmCreateImage(x_display, xv_port_, xv_pix_fmt, 0, FRAME_WIDTH, height + 1, xv_shm_info)) { if ((xv_image->data = allocate_x_shm(x_display, xv_shm_info, xv_image->data_size))) { pix_fmt_ = pix_fmt; height_ = height; xv_image_ = xv_image; xv_shm_info_ = xv_shm_info; } else { free(xv_image); delete xv_shm_info; } } else { delete xv_shm_info; } } if (!xv_image_) std::cerr << "ERROR: Could not create Xv image\n"; return xv_image_; } void dv_full_display_widget::fini_xvideo() throw() { if (xv_port_ != invalid_xv_port) { Display * x_display = get_x_display(*this); XvStopVideo(x_display, xv_port_, get_x_window(*this)); if (XvImage * xv_image = static_cast(xv_image_)) { xv_image_ = 0; free(xv_image); XShmSegmentInfo * xv_shm_info = static_cast(xv_shm_info_); xv_shm_info_ = 0; free_x_shm(xv_shm_info); delete xv_shm_info; } XvUngrabPort(x_display, xv_port_, CurrentTime); xv_port_ = invalid_xv_port; fini_x_shm_events(); } pix_fmt_ = PIX_FMT_NONE; height_ = 0; } AVFrame * dv_full_display_widget::get_frame_header() { return &frame_header_; } AVFrame * dv_full_display_widget::get_frame_buffer(AVFrame * header, PixelFormat pix_fmt, unsigned height) { if (!try_init_xvideo(pix_fmt, height)) return 0; XvImage * xv_image = static_cast(xv_image_); if (pix_fmt == PIX_FMT_YUV420P) { for (int plane = 0; plane != 3; ++plane) { header->data[plane] = reinterpret_cast(xv_image->data + xv_image->offsets[plane]); header->linesize[plane] = xv_image->pitches[plane]; } } else if (pix_fmt == PIX_FMT_YUV411P) { uint8_t * data = reinterpret_cast(xv_image->data + xv_image->offsets[0]); unsigned linesize = xv_image->pitches[0]; // Interleave the lines in the buffer so we can convert it to // 4:2:2 in-place. header->data[0] = data + linesize; header->linesize[0] = linesize; header->data[1] = data + linesize + linesize / 2; header->linesize[1] = linesize; header->data[2] = data + linesize + linesize * 3 / 4; header->linesize[2] = linesize; } else { assert(!"unknown pixel format"); } header->data[3] = 0; header->linesize[3] = 0; header->type = FF_BUFFER_TYPE_USER; return &frame_header_; } void dv_full_display_widget::put_frame_buffer( const display_region & source_region) { raw_frame_ref frame_ref; for (int plane = 0; plane != 4; ++plane) { frame_ref.planes.data[plane] = frame_header_.data[plane]; frame_ref.planes.linesize[plane] = frame_header_.linesize[plane]; } frame_ref.pix_fmt = pix_fmt_; frame_ref.height = height_; video_effect_show_title_safe(frame_ref); if (sel_enabled_) { selection_ &= source_region; video_effect_brighten(frame_ref, selection_); } if (pix_fmt_ == PIX_FMT_YUV411P) { // Lines are interleaved in the buffer; convert them in-place. XvImage * xv_image = static_cast(xv_image_); uint8_t * data = reinterpret_cast(xv_image->data + xv_image->offsets[0]); unsigned linesize = xv_image->pitches[0]; for (unsigned y = 0; y != height_; ++y) { uint8_t * out = data + y * linesize; uint8_t * end = out + FRAME_WIDTH * 2; const uint8_t * in_y = out + linesize; const uint8_t * in_u = in_y + linesize / 2; const uint8_t * in_v = in_u + linesize / 4; do { *out++ = *in_y++; *out++ = *in_u; *out++ = *in_y++; *out++ = *in_v; *out++ = *in_y++; *out++ = *in_u++; *out++ = *in_y++; *out++ = *in_v++; } while (out != end); } } if (source_region.pixel_width > source_region.pixel_height) { dest_height_ = source_region.bottom - source_region.top; dest_width_ = div_round_nearest((source_region.right - source_region.left) * source_region.pixel_width, source_region.pixel_height); } else { dest_width_ = source_region.right - source_region.left; dest_height_ = div_round_nearest((source_region.bottom - source_region.top) * source_region.pixel_height, source_region.pixel_width); } source_region_ = source_region; set_size_request(dest_width_, dest_height_); } void dv_full_display_widget::window_to_frame_coords( int & frame_x, int & frame_y, int window_x, int window_y) throw() { frame_x = (source_region_.left + div_round_nearest(window_x * (source_region_.right - source_region_.left), dest_width_)); frame_y = (source_region_.top + div_round_nearest(window_y * (source_region_.bottom - source_region_.top), dest_height_)); } void dv_full_display_widget::update_selection(int x2, int y2) { if (source_region_.empty()) return; int frame_width = source_region_.right - source_region_.left; int frame_height = source_region_.bottom - source_region_.top; int dir_x, x1, scale_x_max; if (x2 < sel_start_x_) { dir_x = -1; x1 = sel_start_x_ + 1; scale_x_max = (x1 - source_region_.left) * frame_height; } else { dir_x = 1; x1 = sel_start_x_; x2 += 1; scale_x_max = (source_region_.right - x1) * frame_height; } int scale_x = (x2 - x1) * dir_x * frame_height; int dir_y, y1, scale_y_max; if (y2 < sel_start_y_) { dir_y = -1; y1 = sel_start_y_ + 1; scale_y_max = (y1 - source_region_.top) * frame_width; } else { dir_y = 1; y1 = sel_start_y_; y2 += 1; scale_y_max = (source_region_.bottom - y1) * frame_width; } int scale_y = (y2 - y1) * dir_y * frame_width; // Expand to maintain aspect ratio and shrink to fit the display region int scale = std::min(std::max(scale_x, scale_y), std::min(scale_x_max, scale_y_max)); x2 = x1 + dir_x * scale / frame_height; y2 = y1 + dir_y * scale / frame_width; selection_.left = dir_x < 0 ? x2 : x1; selection_.right = dir_x < 0 ? x1 : x2; selection_.top = dir_y < 0 ? y2 : y1; selection_.bottom = dir_y < 0 ? y1 : y2; queue_draw(); } bool dv_full_display_widget::on_button_press_event(GdkEventButton * event) throw() { if (sel_enabled_ && (event->button == 1 || event->button == 2)) { sel_in_progress_ = true; add_modal_grab(); int x, y; window_to_frame_coords(x, y, int(event->x), int(event->y)); if (event->button == 1) { sel_start_x_ = x; sel_start_y_ = y; } update_selection(x, y); return true; } return false; } bool dv_full_display_widget::on_button_release_event(GdkEventButton * event) throw() { if (sel_in_progress_ && (event->button == 1 || event->button == 2)) { sel_in_progress_ = false; remove_modal_grab(); return true; } return false; } bool dv_full_display_widget::on_motion_notify_event(GdkEventMotion * event) throw() { int x, y; window_to_frame_coords(x, y, int(event->x), int(event->y)); update_selection(x, y); return true; } bool dv_full_display_widget::on_expose_event(GdkEventExpose *) throw() { Glib::RefPtr drawable; int dest_x, dest_y; get_window()->get_internal_paint_info(drawable, dest_x, dest_y); drawable->reference(); // get_internal_paint_info() doesn't do this! if (Glib::RefPtr gc = Gdk::GC::create(drawable)) { if (xv_image_) { XvShmPutImage(get_x_display(drawable), xv_port_, get_x_window(drawable), gdk_x11_gc_get_xgc(gc->gobj()), static_cast(xv_image_), source_region_.left, source_region_.top, source_region_.right - source_region_.left, source_region_.bottom - source_region_.top, dest_x, dest_y, dest_width_, dest_height_, /*send_event=*/ True); set_shm_busy(); } else { Gdk::Color colour; colour.set_grey(0); // black gc->set_rgb_fg_color(colour); drawable->draw_rectangle(gc, true, dest_x, dest_y, dest_width_, dest_height_); } } return true; } void dv_full_display_widget::on_unrealize() throw() { fini_xvideo(); dv_display_widget::on_unrealize(); } // dv_thumb_display_widget namespace { const unsigned dv_block_size_log2 = 3; const unsigned dv_block_size = 1 << dv_block_size_log2; const unsigned frame_thumb_linesize_4 = (FRAME_WIDTH / dv_block_size + 15) & ~15; const unsigned frame_thumb_linesize_2 = (FRAME_WIDTH / 2 / dv_block_size + 15) & ~15; } struct dv_thumb_display_widget::raw_frame_thumb { AVFrame header; enum PixelFormat pix_fmt; dv_frame_aspect aspect; struct { uint8_t y[frame_thumb_linesize_4 * FRAME_HEIGHT_MAX / dv_block_size]; uint8_t c_dummy[frame_thumb_linesize_2]; } buffer __attribute__((aligned(16))); }; dv_thumb_display_widget::dv_thumb_display_widget() : dv_display_widget(dv_block_size_log2), raw_frame_(new raw_frame_thumb), x_image_(0), x_shm_info_(0), dest_width_(0), dest_height_(0), error_pixbuf_(load_icon("gtk-dialog-warning", 64)), error_(false) { // We don't know what the frame format will be, but assume "PAL" // 4:3 frames and therefore an active image size of 702x576 and // pixel aspect ratio of 59:54. set_size_request(192, 144); } dv_thumb_display_widget::~dv_thumb_display_widget() { } bool dv_thumb_display_widget::try_init_xshm(PixelFormat pix_fmt, unsigned height) throw() { assert(pix_fmt == PIX_FMT_YUV420P || pix_fmt == PIX_FMT_YUV422P || pix_fmt == PIX_FMT_YUV410P || pix_fmt == PIX_FMT_YUV411P); assert(height <= FRAME_HEIGHT_MAX / dv_block_size); if (x_image_) { raw_frame_->pix_fmt = pix_fmt; return true; } if (!init_x_shm_events()) return false; Display * x_display = get_x_display(*this); Glib::RefPtr drawable; int dest_x, dest_y; get_window()->get_internal_paint_info(drawable, dest_x, dest_y); drawable->reference(); // get_internal_paint_info() doesn't do this! Visual * visual = gdk_x11_visual_get_xvisual(drawable->get_visual()->gobj()); int depth = drawable->get_depth(); if ((visual->c_class == TrueColor || visual->c_class == DirectColor) && (depth == 24 || depth == 32)) { if (XShmSegmentInfo * x_shm_info = new (std::nothrow) XShmSegmentInfo) { if (XImage * x_image = XShmCreateImage( x_display, visual, depth, ZPixmap, 0, x_shm_info, // Calculate maximum dimensions assuming widest pixel // ratio and full frame (slightly over-conservative). div_round_nearest(FRAME_WIDTH * 118, 81 * thumb_scale_denom), div_round_nearest(FRAME_HEIGHT_MAX, thumb_scale_denom))) { if ((x_image->data = allocate_x_shm( x_display, x_shm_info, x_image->height * x_image->bytes_per_line))) { raw_frame_->pix_fmt = pix_fmt; x_image_ = x_image; x_shm_info_ = x_shm_info; } else { free(x_image); } } if (!x_shm_info_) delete x_shm_info; } if (!x_image_) std::cerr << "ERROR: Could not create Xshm image\n"; } else { std::cerr << "ERROR: Window does not support 24- or 32-bit colour\n"; } return x_image_; } void dv_thumb_display_widget::fini_xshm() throw() { if (XImage * x_image = static_cast(x_image_)) { XShmSegmentInfo * x_shm_info = static_cast(x_shm_info_); free_x_shm(x_shm_info); delete x_shm_info; x_shm_info_ = 0; free(x_image); x_image_ = 0; fini_x_shm_events(); } } void dv_thumb_display_widget::on_unrealize() throw() { fini_xshm(); dv_display_widget::on_unrealize(); } AVFrame * dv_thumb_display_widget::get_frame_header() { return &raw_frame_->header; } AVFrame * dv_thumb_display_widget::get_frame_buffer(AVFrame * header, PixelFormat pix_fmt, unsigned height) { if (!try_init_xshm(pix_fmt, height)) return 0; header->data[0] = raw_frame_->buffer.y; header->linesize[0] = frame_thumb_linesize_4; header->data[1] = raw_frame_->buffer.c_dummy; header->linesize[1] = 0; header->data[2] = raw_frame_->buffer.c_dummy; header->linesize[2] = 0; header->data[3] = 0; header->linesize[3] = 0; header->type = FF_BUFFER_TYPE_USER; return header; } void dv_thumb_display_widget::put_frame_buffer( const display_region & source_region) { XImage * x_image = static_cast(x_image_); dest_width_ = div_round_nearest((source_region.right - source_region.left) * source_region.pixel_width, source_region.pixel_height * thumb_scale_denom); dest_height_ = div_round_nearest(source_region.bottom - source_region.top, thumb_scale_denom); // Scale the image up using Bresenham's algorithm assert(x_image->bits_per_pixel == 24 || x_image->bits_per_pixel == 32); const unsigned source_width = ((source_region.right - source_region.left) / dv_block_size); const unsigned source_height = ((source_region.bottom - source_region.top) / dv_block_size); assert(source_width <= dest_width_); assert(source_height <= dest_height_); unsigned source_y = source_region.top / dv_block_size, dest_y = 0; unsigned error_y = source_height / 2; do { const uint8_t * source = raw_frame_->buffer.y + frame_thumb_linesize_4 * source_y + source_region.left / dv_block_size; uint8_t * dest = reinterpret_cast( x_image->data + x_image->bytes_per_line * dest_y); uint8_t * dest_row_end = dest + x_image->bits_per_pixel / 8 * dest_width_; unsigned error_x = source_width / 2; uint8_t source_value = *source; do { // Write Y component to each byte of the pixel *dest++ = source_value; *dest++ = source_value; *dest++ = source_value; if (x_image->bits_per_pixel == 32) *dest++ = source_value; error_x += source_width; if (error_x >= dest_width_) { source_value = *++source; error_x -= dest_width_; } } while (dest != dest_row_end); error_y += source_height; if (error_y >= dest_height_) { ++source_y; error_y -= dest_height_; } ++dest_y; } while (dest_y != dest_height_); set_size_request(dest_width_, dest_height_); } void dv_thumb_display_widget::set_error(bool error) { error_ = error; } bool dv_thumb_display_widget::on_expose_event(GdkEventExpose *) throw() { if (!x_image_ || !dest_width_ || !dest_height_) return true; Glib::RefPtr drawable; int dest_x, dest_y; get_window()->get_internal_paint_info(drawable, dest_x, dest_y); drawable->reference(); // get_internal_paint_info() doesn't do this! if (Glib::RefPtr gc = Gdk::GC::create(drawable)) { XShmPutImage(get_x_display(drawable), get_x_window(drawable), gdk_x11_gc_get_xgc(gc->gobj()), static_cast(x_image_), 0, 0, dest_x, dest_y, dest_width_, dest_height_, /*send_event=*/ True); set_shm_busy(); if (error_) { drawable->draw_pixbuf( gc, error_pixbuf_, 0, 0, dest_x + (dest_width_ - error_pixbuf_->get_width()) / 2, dest_y + (dest_height_ - error_pixbuf_->get_height()) / 2, -1, -1, Gdk::RGB_DITHER_NORMAL, 0, 0); } } return true; } dvswitch-0.8.3.6/src/dv_display_widget.hpp000066400000000000000000000071621161012451100205200ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_DV_DISPLAY_WIDGET_HPP #define DVSWITCH_DV_DISPLAY_WIDGET_HPP #include #include #include #include #include "auto_codec.hpp" #include "frame.h" #include "frame_pool.hpp" #include "geometry.h" class dv_display_widget : public Gtk::DrawingArea { public: void put_frame(const dv_frame_ptr &); void put_frame(const raw_frame_ptr &); protected: struct display_region : rectangle { unsigned pixel_width, pixel_height; }; explicit dv_display_widget(int lowres = 0); ~dv_display_widget(); bool init_x_shm_events(); void fini_x_shm_events(); void set_shm_busy() { shm_busy_ = true; } auto_codec decoder_; private: display_region get_display_region(const dv_system * system, dv_frame_aspect frame_aspect); virtual AVFrame * get_frame_header() = 0; virtual AVFrame * get_frame_buffer(AVFrame * header, PixelFormat pix_fmt, unsigned height) = 0; virtual void put_frame_buffer(const display_region &) = 0; virtual void set_error(bool); static int get_buffer(AVCodecContext *, AVFrame *); static void release_buffer(AVCodecContext *, AVFrame *); static int reget_buffer(AVCodecContext *, AVFrame *); static GdkFilterReturn filter_x_shm_event(void * void_event, GdkEvent * event, void * data); unsigned decoded_serial_num_; int x_shm_first_event_; bool shm_busy_; }; class dv_full_display_widget : public dv_display_widget { public: dv_full_display_widget(); void set_selection_enabled(bool); rectangle get_selection(); private: bool try_init_xvideo(PixelFormat pix_fmt, unsigned height) throw(); void fini_xvideo() throw(); void window_to_frame_coords(int & frame_x, int & frame_y, int window_x, int window_y) throw(); void update_selection(int x, int y); virtual AVFrame * get_frame_header(); virtual AVFrame * get_frame_buffer(AVFrame * header, PixelFormat pix_fmt, unsigned height); virtual void put_frame_buffer(const display_region &); virtual bool on_button_press_event(GdkEventButton *) throw(); virtual bool on_button_release_event(GdkEventButton *) throw(); virtual bool on_expose_event(GdkEventExpose *) throw(); virtual bool on_motion_notify_event(GdkEventMotion *) throw(); virtual void on_unrealize() throw(); PixelFormat pix_fmt_; unsigned height_; uint32_t xv_port_; void * xv_image_; void * xv_shm_info_; AVFrame frame_header_; display_region source_region_; unsigned dest_width_, dest_height_; bool sel_enabled_; bool sel_in_progress_; int sel_start_x_, sel_start_y_; rectangle selection_; }; class dv_thumb_display_widget : public dv_display_widget { public: dv_thumb_display_widget(); ~dv_thumb_display_widget(); private: struct raw_frame_thumb; bool try_init_xshm(PixelFormat pix_fmt, unsigned height) throw(); void fini_xshm() throw(); virtual AVFrame * get_frame_header(); virtual AVFrame * get_frame_buffer(AVFrame * header, PixelFormat pix_fmt, unsigned height); virtual void put_frame_buffer(const display_region &); virtual void set_error(bool); virtual bool on_expose_event(GdkEventExpose *) throw(); virtual void on_unrealize() throw(); std::auto_ptr raw_frame_; void * x_image_; void * x_shm_info_; unsigned dest_width_, dest_height_; Glib::RefPtr error_pixbuf_; bool error_; }; #endif // !defined(DVSWITCH_DV_DISPLAY_WIDGET_HPP) dvswitch-0.8.3.6/src/dv_selector_widget.cpp000066400000000000000000000156271161012451100206730ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include #include "dv_selector_widget.hpp" #include "gui.hpp" namespace { const unsigned thumbs_per_row = 4; enum { column_labels, column_display, column_separator, column_multiplier }; enum { row_text_label, row_pri_video_button, row_sec_video_button, row_audio_button, row_multiplier }; } dv_selector_widget::dv_selector_widget() : pri_video_source_pixbuf_( Gdk::Pixbuf::create_from_file(SHAREDIR "/dvswitch/pri-video-source.png")), sec_video_source_pixbuf_( Gdk::Pixbuf::create_from_file(SHAREDIR "/dvswitch/sec-video-source.png")), audio_source_pixbuf_( Gdk::Pixbuf::create_from_file(SHAREDIR "/dvswitch/audio-source.png")) { set_col_spacings(gui_standard_spacing); set_row_spacings(gui_standard_spacing); } Gtk::RadioButton * dv_selector_widget::create_radio_button( Gtk::RadioButtonGroup & group, const Glib::RefPtr & pixbuf) { Gtk::Image * image = manage(new Gtk::Image(pixbuf)); image->show(); Gtk::RadioButton * button = manage(new Gtk::RadioButton(group)); button->set_image(*image); button->set_mode(/*draw_indicator=*/false); return button; } void dv_selector_widget::set_accel_group( const Glib::RefPtr & accel_group) { assert(!accel_group_); accel_group_ = accel_group; } void dv_selector_widget::set_source_count(unsigned count) { if (count > thumbnails_.size()) { resize(((count + thumbs_per_row - 1) / thumbs_per_row) * row_multiplier, thumbs_per_row * column_multiplier - 1); mixer::source_id first_new_source_id = thumbnails_.size(); try { thumbnails_.resize(count); for (mixer::source_id i = first_new_source_id; i != count; ++i) { unsigned column = (i % thumbs_per_row) * column_multiplier; unsigned row = (i / thumbs_per_row) * row_multiplier; if (i % thumbs_per_row != 0) { Gtk::VSeparator * sep = manage(new Gtk::VSeparator); sep->show(); attach(*sep, column - 1, column, row, row + row_multiplier, Gtk::FILL, Gtk::FILL, 0, 0); } dv_thumb_display_widget * thumb = manage(new dv_thumb_display_widget); thumb->show(); attach(*thumb, column + column_display, column + column_display + 1, row, row + row_multiplier, Gtk::FILL, Gtk::FILL, 0, 0); thumbnails_[i] = thumb; char label_text[4]; snprintf(label_text, sizeof(label_text), (i < 9) ? "_%u" : "%u", unsigned(1 + i)); Gtk::Label * label = manage(new Gtk::Label(label_text, true)); label->show(); attach(*label, column + column_labels, column + column_labels + 1, row + row_text_label, row + row_text_label + 1, Gtk::FILL, Gtk::FILL, 0, 0); Gtk::RadioButton * pri_video_button = create_radio_button(pri_video_button_group_, pri_video_source_pixbuf_); pri_video_button->signal_pressed().connect( sigc::bind( sigc::mem_fun(*this, &dv_selector_widget::on_pri_video_selected), i)); pri_video_button->show(); attach(*pri_video_button, column + column_labels, column + column_labels + 1, row + row_pri_video_button, row + row_pri_video_button + 1, Gtk::FILL, Gtk::FILL, 0, 0); Gtk::RadioButton * sec_video_button = create_radio_button(sec_video_button_group_, sec_video_source_pixbuf_); sec_video_button->signal_pressed().connect( sigc::bind( sigc::mem_fun(*this, &dv_selector_widget::on_sec_video_selected), i)); sec_video_button->show(); attach(*sec_video_button, column + column_labels, column + column_labels + 1, row + row_sec_video_button, row + row_sec_video_button + 1, Gtk::FILL, Gtk::FILL, 0, 0); Gtk::RadioButton * audio_button = create_radio_button(audio_button_group_, audio_source_pixbuf_); audio_button->signal_pressed().connect( sigc::bind( sigc::mem_fun(*this, &dv_selector_widget::on_audio_selected), i)); audio_button->show(); attach(*audio_button, column + column_labels, column + column_labels + 1, row + row_audio_button, row + row_audio_button + 1, Gtk::FILL, Gtk::FILL, 0, 0); if (i < 9) { // Make the mnemonic on the label work. Also make // the numeric keypad and Alt-keys work. label->set_mnemonic_widget(*pri_video_button); pri_video_button->add_accelerator("activate", accel_group_, GDK_KP_1 + i, Gdk::ModifierType(0), Gtk::AccelFlags(0)); pri_video_button->signal_activate().connect( sigc::bind( sigc::mem_fun( *this, &dv_selector_widget::on_pri_video_selected), i)); sec_video_button->add_accelerator("activate", accel_group_, '1' + i, Gdk::SHIFT_MASK, Gtk::AccelFlags(0)); sec_video_button->add_accelerator("activate", accel_group_, GDK_KP_1 + i, Gdk::SHIFT_MASK, Gtk::AccelFlags(0)); sec_video_button->signal_activate().connect( sigc::bind( sigc::mem_fun( *this, &dv_selector_widget::on_sec_video_selected), i)); audio_button->add_accelerator("activate", accel_group_, '1' + i, Gdk::MOD1_MASK, Gtk::AccelFlags(0)); audio_button->add_accelerator("activate", accel_group_, GDK_KP_1 + i, Gdk::MOD1_MASK, Gtk::AccelFlags(0)); audio_button->signal_activate().connect( sigc::bind( sigc::mem_fun( *this, &dv_selector_widget::on_audio_selected), i)); } } } catch (std::exception & e) { // Roll back size changes thumbnails_.resize(first_new_source_id); std::cerr << "ERROR: Failed to add source display: " << e.what() << "\n"; } } } void dv_selector_widget::put_frame(mixer::source_id source_id, const dv_frame_ptr & source_frame) { if (source_id < thumbnails_.size()) thumbnails_[source_id]->put_frame(source_frame); } sigc::signal1 & dv_selector_widget::signal_pri_video_selected() { return pri_video_selected_signal_; } sigc::signal1 & dv_selector_widget::signal_sec_video_selected() { return sec_video_selected_signal_; } sigc::signal1 & dv_selector_widget::signal_audio_selected() { return audio_selected_signal_; } void dv_selector_widget::on_pri_video_selected(mixer::source_id source_id) { pri_video_selected_signal_(source_id); } void dv_selector_widget::on_sec_video_selected(mixer::source_id source_id) { sec_video_selected_signal_(source_id); } void dv_selector_widget::on_audio_selected(mixer::source_id source_id) { audio_selected_signal_(source_id); } dvswitch-0.8.3.6/src/dv_selector_widget.hpp000066400000000000000000000034221161012451100206660ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_DV_SELECTOR_WIDGET_HPP #define DVSWITCH_DV_SELECTOR_WIDGET_HPP #include #include #include #include #include #include "dv_display_widget.hpp" #include "mixer.hpp" class dv_selector_widget : public Gtk::Table { public: dv_selector_widget(); void set_accel_group(const Glib::RefPtr & accel_group); void set_source_count(unsigned); void put_frame(mixer::source_id source_id, const dv_frame_ptr & source_frame); sigc::signal1 & signal_pri_video_selected(); sigc::signal1 & signal_sec_video_selected(); sigc::signal1 & signal_audio_selected(); private: Gtk::RadioButton * create_radio_button( Gtk::RadioButtonGroup & group, const Glib::RefPtr & pixbuf); void on_pri_video_selected(mixer::source_id); void on_sec_video_selected(mixer::source_id); void on_audio_selected(mixer::source_id); Glib::RefPtr accel_group_; Glib::RefPtr pri_video_source_pixbuf_; Glib::RefPtr sec_video_source_pixbuf_; Gtk::RadioButtonGroup pri_video_button_group_; Gtk::RadioButtonGroup sec_video_button_group_; sigc::signal1 pri_video_selected_signal_; sigc::signal1 sec_video_selected_signal_; Glib::RefPtr audio_source_pixbuf_; Gtk::RadioButtonGroup audio_button_group_; sigc::signal1 audio_selected_signal_; std::vector thumbnails_; }; #endif // !defined(DVSWITCH_DV_SELECTOR_WIDGET_HPP) dvswitch-0.8.3.6/src/dvsink-command.c000066400000000000000000000043631161012451100173640ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include "config.h" #include "protocol.h" #include "socket.h" static struct option options[] = { {"host", 1, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"help", 0, NULL, 'H'}, {NULL, 0, NULL, 0} }; static char * mixer_host = NULL; static char * mixer_port = NULL; static void handle_config(const char * name, const char * value) { if (strcmp(name, "MIXER_HOST") == 0) { free(mixer_host); mixer_host = strdup(value); } else if (strcmp(name, "MIXER_PORT") == 0) { free(mixer_port); mixer_port = strdup(value); } } static void usage(const char * progname) { fprintf(stderr, "\ Usage: %s [-h HOST] [-p PORT] COMMAND...\n", progname); } int main(int argc, char ** argv) { // Initialise settings from configuration files. dvswitch_read_config(handle_config); // Parse arguments. int opt; while ((opt = getopt_long(argc, argv, "h:p:", options, NULL)) != -1) { switch (opt) { case 'h': free(mixer_host); mixer_host = strdup(optarg); break; case 'p': free(mixer_port); mixer_port = strdup(optarg); break; case 'H': // --help usage(argv[0]); return 0; default: usage(argv[0]); return 2; } } if (!mixer_host || !mixer_port) { fprintf(stderr, "%s: mixer hostname and port not defined\n", argv[0]); return 2; } if (optind == argc) { fprintf(stderr, "%s: missing command\n", argv[0]); usage(argv[0]); return 2; } // Connect to the mixer, set that as stdin, and run given command. printf("INFO: Connecting to %s:%s\n", mixer_host, mixer_port); int sock = create_connected_socket(mixer_host, mixer_port); assert(sock >= 0); // create_connected_socket() should handle errors if (write(sock, GREETING_RAW_SINK, GREETING_SIZE) != GREETING_SIZE) { perror("ERROR: write"); exit(1); } if (dup2(sock, STDIN_FILENO) < 0) { perror("ERROR: dup2"); return 1; } close(sock); execvp(argv[optind], argv + optind); perror("ERROR: execvp"); return 1; } dvswitch-0.8.3.6/src/dvsink-files.c000066400000000000000000000150341161012451100170450ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // Copyright 2008 Petter Reinholdtsen. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "dif.h" #include "protocol.h" #include "socket.h" static struct option options[] = { {"host", 1, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"help", 0, NULL, 'H'}, {NULL, 0, NULL, 0} }; static char * mixer_host = NULL; static char * mixer_port = NULL; static char * output_name_format = NULL; static void handle_config(const char * name, const char * value) { if (strcmp(name, "MIXER_HOST") == 0) { free(mixer_host); mixer_host = strdup(value); } else if (strcmp(name, "MIXER_PORT") == 0) { free(mixer_port); mixer_port = strdup(value); } else if (strcmp(name, "OUTPUT_NAME_FORMAT") == 0) { free(output_name_format); output_name_format = strdup(value); } } static void usage(const char * progname) { fprintf(stderr, "\ Usage: %s [-h HOST] [-p PORT] [NAME-FORMAT]\n", progname); } struct transfer_params { int sock; }; static int create_file(const char * format, char ** name) { time_t now; struct tm now_local; size_t name_buf_len = 200, name_len; char * name_buf = 0; int file; now = time(0); localtime_r(&now, &now_local); // Allocate a name buffer and generate the name in it, leaving room // for a suffix. for (;;) { name_buf = realloc(name_buf, name_buf_len); if (!name_buf) { perror("realloc"); exit(1); } name_len = strftime(name_buf, name_buf_len - 20, format, &now_local); if (name_len > 0) break; // Try a bigger buffer. name_buf_len *= 2; } // Add ".dv" extension if missing. Add distinguishing // number before it if necessary to avoid collision. // Create parent directories as necessary. int suffix_num = 0; if (name_len <= 3 || strcmp(name_buf + name_len - 3, ".dv") != 0) strcpy(name_buf + name_len, ".dv"); else name_len -= 3; for (;;) { file = open(name_buf, O_CREAT | O_EXCL | O_WRONLY, 0666); if (file >= 0) { *name = name_buf; return file; } else if (errno == EEXIST) { // Name collision; try changing the suffix sprintf(name_buf + name_len, "-%d.dv", ++suffix_num); } else if (errno == ENOENT) { // Parent directory missing char * p = name_buf + 1; while ((p = strchr(p, '/'))) { *p = 0; if (mkdir(name_buf, 0777) < 0 && errno != EEXIST) { fprintf(stderr, "ERROR: mkdir %s: %s\n", name_buf, strerror(errno)); exit(1); } *p++ = '/'; } } else { fprintf(stderr, "ERROR: open %s: %s\n", name_buf, strerror(errno)); exit(1); } } *name = name_buf; return file; } static ssize_t write_retry(int fd, const void * buf, size_t count) { ssize_t chunk, total = 0; do { chunk = write(fd, buf, count); if (chunk < 0) return chunk; total += chunk; buf = (const char *)buf + chunk; count -= chunk; } while (count); return total; } static void transfer_frames(struct transfer_params * params) { static uint8_t buf[SINK_FRAME_HEADER_SIZE + DIF_MAX_FRAME_SIZE]; const struct dv_system * system; int file = -1; char * name; ssize_t read_size; for (;;) { size_t wanted_size = SINK_FRAME_HEADER_SIZE; size_t buf_pos = 0; do { read_size = read(params->sock, buf + buf_pos, wanted_size - buf_pos); if (read_size <= 0) goto read_failed; buf_pos += read_size; } while (buf_pos != wanted_size); // Open/close files as necessary if (buf[SINK_FRAME_CUT_FLAG_POS] || file < 0) { bool starting = file < 0; if (file >= 0) { close(file); file = -1; } // Check for stop indicator if (buf[SINK_FRAME_CUT_FLAG_POS] == 'S') { printf("INFO: Stopped recording\n"); fflush(stdout); continue; } file = create_file(output_name_format, &name); if (starting) printf("INFO: Started recording\n"); printf("INFO: Created file %s\n", name); fflush(stdout); } wanted_size = SINK_FRAME_HEADER_SIZE + DIF_SEQUENCE_SIZE; do { read_size = read(params->sock, buf + buf_pos, wanted_size - buf_pos); if (read_size <= 0) goto read_failed; buf_pos += read_size; } while (buf_pos != wanted_size); system = dv_buffer_system(buf + SINK_FRAME_HEADER_SIZE); wanted_size = SINK_FRAME_HEADER_SIZE + system->size; do { read_size = read(params->sock, buf + buf_pos, wanted_size - buf_pos); if (read_size <= 0) goto read_failed; buf_pos += read_size; } while (buf_pos != wanted_size); if (write_retry(file, buf + SINK_FRAME_HEADER_SIZE, system->size) != (ssize_t)system->size) { perror("ERROR: write"); exit(1); } } read_failed: if (read_size != 0) { perror("ERROR: read"); exit(1); } if (file >= 0) close(file); } int main(int argc, char ** argv) { // Initialise settings from configuration files. dvswitch_read_config(handle_config); // Parse arguments. int opt; while ((opt = getopt_long(argc, argv, "h:p:", options, NULL)) != -1) { switch (opt) { case 'h': free(mixer_host); mixer_host = strdup(optarg); break; case 'p': free(mixer_port); mixer_port = strdup(optarg); break; case 'H': // --help usage(argv[0]); return 0; default: usage(argv[0]); return 2; } } if (!mixer_host || !mixer_port) { fprintf(stderr, "%s: mixer hostname and port not defined\n", argv[0]); return 2; } if (optind < argc) output_name_format = argv[optind]; if (optind < argc - 1) { fprintf(stderr, "%s: excess argument \"%s\"\n", argv[0], argv[optind + 1]); usage(argv[0]); return 2; } if (!output_name_format || !output_name_format[0]) { fprintf(stderr, "%s: output name format not defined or empty\n", argv[0]); return 2; } struct transfer_params params; printf("INFO: Connecting to %s:%s\n", mixer_host, mixer_port); fflush(stdout); params.sock = create_connected_socket(mixer_host, mixer_port); assert(params.sock >= 0); // create_connected_socket() should handle errors if (write(params.sock, GREETING_REC_SINK, GREETING_SIZE) != GREETING_SIZE) { perror("ERROR: write"); exit(1); } transfer_frames(¶ms); close(params.sock); return 0; } dvswitch-0.8.3.6/src/dvsource-alsa.c000066400000000000000000000232211161012451100172140ustar00rootroot00000000000000/* Copyright 2007-2009 Ben Hutchings. * See the file "COPYING" for licence details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "dif.h" #include "protocol.h" #include "socket.h" static struct option options[] = { {"host", 1, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"system", 1, NULL, 's'}, {"rate", 1, NULL, 'r'}, {"delay", 1, NULL, 'd'}, {"help", 0, NULL, 'H'}, {NULL, 0, NULL, 0} }; static char * mixer_host = NULL; static char * mixer_port = NULL; static void handle_config(const char * name, const char * value) { if (strcmp(name, "MIXER_HOST") == 0) { free(mixer_host); mixer_host = strdup(value); } else if (strcmp(name, "MIXER_PORT") == 0) { free(mixer_port); mixer_port = strdup(value); } } static void usage(const char * progname) { fprintf(stderr, "\ Usage: %s [-h HOST] [-p PORT] [-s ntsc|pal] \\\n\ [-r 48000|32000|44100] [-d DELAY] [DEVICE]\n", progname); } struct transfer_params { snd_pcm_t * pcm; snd_pcm_uframes_t hw_sample_count; const struct dv_system * system; enum dv_sample_rate sample_rate_code; snd_pcm_uframes_t delay_size; int sock; }; static void dv_buffer_fill_dummy(uint8_t * buf, const struct dv_system * system) { unsigned seq_num, block_num; uint8_t * block = buf; for (seq_num = 0; seq_num != system->seq_count; ++seq_num) { for (block_num = 0; block_num != DIF_BLOCKS_PER_SEQUENCE; ++block_num) { block[1] = (seq_num << 4) | 7; if (block_num == 0) { // Header block[0] = 0x1f; block[2] = 0; memset(block + DIF_BLOCK_ID_SIZE, 0xff, DIF_BLOCK_SIZE - DIF_BLOCK_ID_SIZE); // Header pack block[DIF_BLOCK_ID_SIZE] = (system == &dv_system_625_50) ? 0xbf : 0x3f; int apt = 0; // IEC 61834 only for now block[DIF_BLOCK_ID_SIZE + 1] = 0xf8 | apt; block[DIF_BLOCK_ID_SIZE + 2] = 0x78 | apt; // audio valid block[DIF_BLOCK_ID_SIZE + 3] = 0xf8 | apt; // video invalid block[DIF_BLOCK_ID_SIZE + 4] = 0xf8 | apt; // subcode invalid } else if (block_num < 3) { // Subcode block[0] = 0x3f; block[2] = block_num - 1; memset(block + DIF_BLOCK_ID_SIZE, 0xff, DIF_BLOCK_SIZE - DIF_BLOCK_ID_SIZE); } else if (block_num < 6) { // VAUX block[0] = 0x56; block[2] = block_num - 3; memset(block + DIF_BLOCK_ID_SIZE, 0xff, DIF_BLOCK_SIZE - DIF_BLOCK_ID_SIZE); // VS pack int dsf = (system == &dv_system_625_50) ? 1 : 0; block[DIF_BLOCK_ID_SIZE] = 0x60; block[DIF_BLOCK_ID_SIZE + 3] = 0xc0 | (dsf << 5); // VSC pack block[DIF_BLOCK_ID_SIZE + DIF_PACK_SIZE] = 0x61; block[DIF_BLOCK_ID_SIZE + DIF_PACK_SIZE + 1] = 0x3f; block[DIF_BLOCK_ID_SIZE + DIF_PACK_SIZE + 2] = 0xc8; block[DIF_BLOCK_ID_SIZE + DIF_PACK_SIZE + 3] = 0xfc; // ...and again memcpy(block + DIF_BLOCK_ID_SIZE + 9 * DIF_PACK_SIZE, block + DIF_BLOCK_ID_SIZE, 2 * DIF_PACK_SIZE); } else if (block_num % 16 == 6) { // Audio block[0] = 0x76; block[2] = block_num / 16; memset(block + DIF_BLOCK_ID_SIZE, 0xff, DIF_PACK_SIZE); memset(block + DIF_BLOCK_ID_SIZE + DIF_PACK_SIZE, 0, DIF_BLOCK_SIZE - DIF_BLOCK_ID_SIZE - DIF_PACK_SIZE); } else { // Video block[0] = 0x96; block[2] = (block_num - 7) - (block_num - 7) / 16; // A macroblock full of black; no need for overspill block[DIF_BLOCK_ID_SIZE] = 0x0f; int i; // 4 luma blocks of 14 bytes for (i = DIF_BLOCK_ID_SIZE + 1; i != DIF_BLOCK_ID_SIZE + 57; i += 14) { block[i] = 0x90; block[i + 1] = 0x06; memset(block + i + 2, 0, 14 - 2); } // 2 chroma blocks of 10 bytes for (; i != DIF_BLOCK_SIZE; i += 10) { block[i] = 0x00; block[i + 1] = 0x16; memset(block + i + 2, 0, 10 - 2); } } block += DIF_BLOCK_SIZE; } } } static void transfer_frames(struct transfer_params * params) { static uint8_t buf[DIF_MAX_FRAME_SIZE]; static const unsigned channel_count = 2; unsigned avail_count = 0; unsigned serial_num = 0; const snd_pcm_uframes_t buffer_size = (params->delay_size >= 2000 ? params->delay_size : 2000) + params->hw_sample_count - 1; int16_t * samples = malloc(sizeof(int16_t) * channel_count * buffer_size); dv_buffer_fill_dummy(buf, params->system); for (;;) { unsigned sample_count = params->system->sample_counts[params->sample_rate_code].std_cycle[ serial_num % params->system->sample_counts[params->sample_rate_code].std_cycle_len]; while (avail_count < params->delay_size || avail_count < sample_count) { snd_pcm_sframes_t rc = snd_pcm_readi(params->pcm, samples + channel_count * avail_count, params->hw_sample_count); if (rc < 0) { // Recover from buffer underrun if (rc == -EPIPE && snd_pcm_prepare(params->pcm) == 0) { fprintf(stderr, "WARN: Failing to keep up with audio source\n"); continue; } else { fprintf(stderr, "ERROR: snd_pcm_readi: %s\n", snd_strerror(rc)); exit(1); } } avail_count += rc; } dv_buffer_set_audio(buf, params->sample_rate_code, sample_count, samples); if (write(params->sock, buf, params->system->size) != (ssize_t)params->system->size) { perror("ERROR: write"); exit(1); } memmove(samples, samples + channel_count * sample_count, sizeof(int16_t) * channel_count * (avail_count - sample_count)); avail_count -= sample_count; ++serial_num; } } int main(int argc, char ** argv) { /* Initialise settings from configuration files. */ dvswitch_read_config(handle_config); struct transfer_params params; char * system_name = NULL; long sample_rate = 48000; double delay = 0.2; /* Parse arguments. */ int opt; while ((opt = getopt_long(argc, argv, "h:p:s:r:d:", options, NULL)) != -1) { switch (opt) { case 'h': free(mixer_host); mixer_host = strdup(optarg); break; case 'p': free(mixer_port); mixer_port = strdup(optarg); break; case 's': free(system_name); system_name = strdup(optarg); break; case 'r': sample_rate = strtol(optarg, NULL, 10); break; case 'd': delay = strtod(optarg, NULL); break; case 'H': /* --help */ usage(argv[0]); return 0; default: usage(argv[0]); return 2; } } if (!mixer_host || !mixer_port) { fprintf(stderr, "%s: mixer hostname and port not defined\n", argv[0]); return 2; } if (!system_name || !strcasecmp(system_name, "pal")) { params.system = &dv_system_625_50; } else if (!strcasecmp(system_name, "ntsc")) { params.system = &dv_system_525_60; } else { fprintf(stderr, "%s: invalid system name \"%s\"\n", argv[0], system_name); return 2; } if (sample_rate == 32000) { params.sample_rate_code = dv_sample_rate_32k; } else if (sample_rate == 44100) { params.sample_rate_code = dv_sample_rate_44k1; } else if (sample_rate == 48000) { params.sample_rate_code = dv_sample_rate_48k; } else { fprintf(stderr, "%s: invalid sample rate %ld\n", argv[0], sample_rate); return 2; } if (delay >= 0.0) { params.delay_size = delay * sample_rate; } else { fprintf(stderr, "%s: delays do not work that way!\n", argv[0]); return 2; } if (argc > optind + 1) { fprintf(stderr, "%s: excess argument \"%s\"\n", argv[0], argv[optind + 1]); usage(argv[0]); return 2; } const char * device = (argc == optind) ? "default" : argv[optind]; int rc; /* Prepare to capture and connect a socket to the mixer. */ printf("INFO: Capturing from %s\n", device); rc = snd_pcm_open(¶ms.pcm, device, SND_PCM_STREAM_CAPTURE, 0); if (rc < 0) { fprintf(stderr, "ERROR: snd_pcm_open: %s\n", snd_strerror(rc)); return 1; } snd_pcm_hw_params_t * hw_params; snd_pcm_hw_params_alloca(&hw_params); rc = snd_pcm_hw_params_any(params.pcm, hw_params); if (rc < 0) { fprintf(stderr, "ERROR: snd_pcm_hw_params_any: %s\n", snd_strerror(rc)); return 1; } rc = snd_pcm_hw_params_set_access(params.pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (rc >= 0) rc = snd_pcm_hw_params_set_format(params.pcm, hw_params, SND_PCM_FORMAT_S16); if (rc >= 0) snd_pcm_hw_params_set_channels(params.pcm, hw_params, 2); if (rc >= 0) snd_pcm_hw_params_set_rate_resample(params.pcm, hw_params, 1); if (rc >= 0) snd_pcm_hw_params_set_rate(params.pcm, hw_params, sample_rate, 0); if (rc >= 0) { params.hw_sample_count = params.system->sample_counts[params.sample_rate_code].std_cycle[0]; rc = snd_pcm_hw_params_set_period_size_near(params.pcm, hw_params, ¶ms.hw_sample_count, 0); } if (rc >= 0) { unsigned buffer_time = 250000; rc = snd_pcm_hw_params_set_buffer_time_near(params.pcm, hw_params, &buffer_time, 0); } if (rc >= 0) rc = snd_pcm_hw_params(params.pcm, hw_params); if (rc < 0) { fprintf(stderr, "ERROR: snd_pcm_hw_params: %s\n", snd_strerror(rc)); return 1; } printf("INFO: Connecting to %s:%s\n", mixer_host, mixer_port); params.sock = create_connected_socket(mixer_host, mixer_port); assert(params.sock >= 0); /* create_connected_socket() should handle errors */ if (write(params.sock, GREETING_SOURCE, GREETING_SIZE) != GREETING_SIZE) { perror("ERROR: write"); exit(1); } transfer_frames(¶ms); close(params.sock); snd_pcm_close(params.pcm); return 0; } dvswitch-0.8.3.6/src/dvsource-dvgrab.c000066400000000000000000000145131161012451100175450ustar00rootroot00000000000000/* Copyright 2007-2009 Ben Hutchings. * Copyright 2008 Petter Reinholdtsen. * Copyright 2009 Wouter Verhelst. * See the file "COPYING" for licence details. */ #include #include #include #include #include #include #include #include "config.h" #include "protocol.h" #include "socket.h" static struct option options[] = { {"card", 1, NULL, 'c'}, {"firewire", 0, NULL, 'F'}, {"v4l2", 0, NULL, 'V'}, {"host", 1, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"tally", 0, NULL, 't'}, {"help", 0, NULL, 'H'}, {NULL, 0, NULL, 0} }; enum mode { mode_unknown, mode_firewire, mode_v4l2, }; static enum mode mode = mode_unknown; static char * device_name = NULL; static char * firewire_card = NULL; static char * mixer_host = NULL; static char * mixer_port = NULL; static int do_tally = 0; static enum mode program_mode(const char * progname) { const char * slash = strrchr(progname, '/'); if (slash) progname = slash + 1; if (strcmp(progname, "dvsource-firewire") == 0) return mode_firewire; if (strcmp(progname, "dvsource-v4l2-dv") == 0) return mode_v4l2; return mode_unknown; } static void handle_config(const char * name, const char * value) { if (strcmp(name, "FIREWIRE_CARD") == 0) { free(firewire_card); firewire_card = strdup(value); } else if (strcmp(name, "FIREWIRE_DEVICE") == 0 && mode == mode_firewire) { free(device_name); device_name = strdup(value); } else if (strcmp(name, "V4L2_DV_DEVICE") == 0 && mode == mode_v4l2) { free(device_name); device_name = strdup(value); } else if (strcmp(name, "MIXER_HOST") == 0) { free(mixer_host); mixer_host = strdup(value); } else if (strcmp(name, "MIXER_PORT") == 0) { free(mixer_port); mixer_port = strdup(value); } } static void usage(const char * progname) { static const char firewire_args[] = "[-c CARD-NUMBER | DEVICE]"; static const char v4l2_args[] = "[DEVICE]"; static const char other_args[] = "[-t] [-h HOST] [-p PORT]"; switch (program_mode(progname)) { case mode_unknown: fprintf(stderr, "Usage: %s %s \\\n" " --firewire %s\n" " %s %s \\\n" " --v4l2 %s\n", progname, other_args, firewire_args, progname, other_args, v4l2_args); break; case mode_firewire: fprintf(stderr, "Usage: %s %s \\\n" " %s\n", progname, other_args, firewire_args); break; case mode_v4l2: fprintf(stderr, "Usage: %s %s \\\n" " %s\n", progname, other_args, v4l2_args); break; } } static ssize_t read_retry(int fd, void * buf, size_t count) { ssize_t chunk, total = 0; do { chunk = read(fd, buf, count); if (chunk <= 0) { if (total == 0) return chunk; break; } total += chunk; buf = (char *)buf + chunk; count -= chunk; } while (count); return total; } static void tally(int sock) { // Messages should never be buffered setbuf(stdout, NULL); for (;;) { char act_msg[ACT_MSG_SIZE]; ssize_t read_size = read_retry(sock, act_msg, ACT_MSG_SIZE); if (read_size < ACT_MSG_SIZE) { if (read_size < 0) perror("ERROR: read"); break; } if (act_msg[ACT_MSG_VIDEO_POS]) printf("TALLY: on\n"); else printf("TALLY: off\n"); } } int main(int argc, char ** argv) { mode = program_mode(argv[0]); /* Initialise settings from configuration files. */ dvswitch_read_config(handle_config); /* Parse arguments. */ int opt; while ((opt = getopt_long(argc, argv, "c:h:p:t", options, NULL)) != -1) { switch (opt) { case 'c': free(firewire_card); firewire_card = strdup(optarg); break; case 'F': mode = mode_firewire; break; case 'V': mode = mode_v4l2; break; case 'h': free(mixer_host); mixer_host = strdup(optarg); break; case 'p': free(mixer_port); mixer_port = strdup(optarg); break; case 't': do_tally = 1; break; case 'H': /* --help */ usage(argv[0]); return 0; default: usage(argv[0]); return 2; } } if (optind != argc) { free(device_name); device_name = strdup(argv[optind++]); } if (!mixer_host || !mixer_port) { fprintf(stderr, "%s: mixer hostname and port not defined\n", argv[0]); return 2; } if (optind != argc) { fprintf(stderr, "%s: excess argument \"%s\"\n", argv[0], argv[optind]); usage(argv[0]); return 2; } if (mode == mode_firewire) { if (device_name) printf("INFO: Reading from Firewire device %s\n", device_name); else if (firewire_card) printf("INFO: Reading from Firewire card %s\n", firewire_card); else printf("INFO: Reading from first Firewire card with camera\n"); } else if (mode == mode_v4l2) { if (!device_name) device_name = "/dev/video"; printf("INFO: Reading from V4L2 device %s\n", device_name); } else { fprintf(stderr, "%s: mode not defined (Firewire or V4L2)\n", argv[0]); return 2; } /* Connect to the mixer, set that as stdout, and run dvgrab. */ printf("INFO: Connecting to %s:%s\n", mixer_host, mixer_port); int sock = create_connected_socket(mixer_host, mixer_port); assert(sock >= 0); /* create_connected_socket() should handle errors */ if (write(sock, do_tally ? GREETING_ACT_SOURCE : GREETING_SOURCE, GREETING_SIZE) != GREETING_SIZE) { perror("ERROR: write"); exit(1); } if (do_tally) { fflush(NULL); int child_pid = fork(); if (child_pid < 0) { perror("ERROR: fork"); return 1; } if (child_pid == 0) { tally(sock); _exit(0); } } if (dup2(sock, STDOUT_FILENO) < 0) { perror("ERROR: dup2"); return 1; } close(sock); char * dvgrab_argv[7]; char ** argp = dvgrab_argv; *argp++ = "dvgrab"; if (mode == mode_v4l2) *argp++ = "-v4l2"; if (device_name) { *argp++ = "-input"; *argp++ = device_name; } else if (firewire_card) { *argp++ = "-card"; *argp++ = firewire_card; } *argp++ = "-noavc"; *argp++ = "-"; *argp = NULL; assert(argp < dvgrab_argv + sizeof(dvgrab_argv) / sizeof(dvgrab_argv[0])); execvp("dvgrab", dvgrab_argv); perror("ERROR: execvp"); return 1; } dvswitch-0.8.3.6/src/dvsource-file.c000066400000000000000000000123451161012451100172200ustar00rootroot00000000000000/* Copyright 2007-2009 Ben Hutchings. * Copyright 2008 Petter Reinholdtsen. * See the file "COPYING" for licence details. */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "dif.h" #include "frame_timer.h" #include "protocol.h" #include "socket.h" static struct option options[] = { {"host", 1, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"loop", 0, NULL, 'l'}, {"help", 0, NULL, 'H'}, {NULL, 0, NULL, 0} }; static char * mixer_host = NULL; static char * mixer_port = NULL; static void handle_config(const char * name, const char * value) { if (strcmp(name, "MIXER_HOST") == 0) { free(mixer_host); mixer_host = strdup(value); } else if (strcmp(name, "MIXER_PORT") == 0) { free(mixer_port); mixer_port = strdup(value); } } static void usage(const char * progname) { fprintf(stderr, "\ Usage: %s [-h HOST] [-p PORT] [-l] FILE\n", progname); } struct transfer_params { int file; int sock; bool opt_loop; }; static ssize_t read_retry(int fd, void * buf, size_t count) { ssize_t chunk, total = 0; do { chunk = read(fd, buf, count); if (chunk <= 0) { if (total == 0) return chunk; break; } total += chunk; buf = (char *)buf + chunk; count -= chunk; } while (count); return total; } static void transfer_frames(struct transfer_params * params) { const struct dv_system * last_system = 0, * system; static uint8_t buf[DIF_MAX_FRAME_SIZE]; uint64_t frame_timestamp = 0; unsigned int frame_interval = 0; frame_timer_init(); for (;;) { ssize_t size = read_retry(params->file, buf, DIF_SEQUENCE_SIZE); if (size == 0) { // End of file; exit or loop if (!params->opt_loop) return; if (lseek(params->file, 0, 0) == 0) continue; perror("ERROR: lseek"); exit(1); } if (size != (ssize_t)DIF_SEQUENCE_SIZE) { if (size < 0) perror("ERROR: read"); else fputs("ERROR: Failed to read complete frame\n", stderr); exit(1); } system = dv_buffer_system(buf); /* (Re)set the timer according to this frame's video system. */ if (system != last_system) { last_system = system; frame_timestamp = frame_timer_get(); frame_interval = (1000000000 / system->frame_rate_numer * system->frame_rate_denom); } size = read_retry(params->file, buf + DIF_SEQUENCE_SIZE, system->size - DIF_SEQUENCE_SIZE); if (size != (ssize_t)(system->size - DIF_SEQUENCE_SIZE)) { if (size < 0) perror("ERROR: read"); else fputs("ERROR: Failed to read complete frame\n", stderr); exit(1); } if (write(params->sock, buf, system->size) != (ssize_t)system->size) { perror("ERROR: write"); exit(1); } frame_timestamp += frame_interval; frame_timer_wait(frame_timestamp); } } static int is_dv_file(int fd) { uint8_t buf[DIF_SIGNATURE_SIZE]; int is_dv; off_t orig = lseek(fd, 0, SEEK_CUR); /* Can't check a non-seekable file; assume it's valid */ if (orig == -1) return 1; is_dv = (read(fd, buf, DIF_SIGNATURE_SIZE) == DIF_SIGNATURE_SIZE && !memcmp(buf, DIF_SIGNATURE, DIF_SIGNATURE_SIZE)); lseek(fd, orig, SEEK_SET); return is_dv; } int main(int argc, char ** argv) { /* Initialise settings from configuration files. */ dvswitch_read_config(handle_config); struct transfer_params params; params.opt_loop = false; /* Parse arguments. */ int opt; while ((opt = getopt_long(argc, argv, "h:p:l", options, NULL)) != -1) { switch (opt) { case 'h': free(mixer_host); mixer_host = strdup(optarg); break; case 'p': free(mixer_port); mixer_port = strdup(optarg); break; case 'l': params.opt_loop = true; break; case 'H': /* --help */ usage(argv[0]); return 0; default: usage(argv[0]); return 2; } } if (!mixer_host || !mixer_port) { fprintf(stderr, "%s: mixer hostname and port not defined\n", argv[0]); return 2; } if (optind != argc - 1) { if (optind == argc) { fprintf(stderr, "%s: missing filename\n", argv[0]); } else { fprintf(stderr, "%s: excess argument \"%s\"\n", argv[0], argv[optind + 1]); } usage(argv[0]); return 2; } const char * filename = argv[optind]; /* Prepare to read the file and connect a socket to the mixer. */ printf("INFO: Reading from %s\n", filename); params.file = open(filename, O_RDONLY, 0); if (params.file < 0) { perror("ERROR: open"); return 1; } if (!is_dv_file(params.file)) { fprintf(stderr, "ERROR: %s is not a DV file\n", filename); return 1; } printf("INFO: Connecting to %s:%s\n", mixer_host, mixer_port); params.sock = create_connected_socket(mixer_host, mixer_port); assert(params.sock >= 0); /* create_connected_socket() should handle errors */ if (write(params.sock, GREETING_SOURCE, GREETING_SIZE) != GREETING_SIZE) { perror("ERROR: write"); exit(1); } transfer_frames(¶ms); close(params.sock); close(params.file); return 0; } dvswitch-0.8.3.6/src/dvswitch.cpp000066400000000000000000000052641161012451100166460ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include "avcodec_wrap.h" #include "config.h" #include "mixer.hpp" #include "mixer_window.hpp" #include "server.hpp" namespace { struct option options[] = { {"host", 1, NULL, 'h'}, {"port", 1, NULL, 'p'}, {"help", 0, NULL, 'H'}, {NULL, 0, NULL, 0} }; std::string mixer_host; std::string mixer_port; extern "C" { void handle_config(const char * name, const char * value) { if (std::strcmp(name, "MIXER_HOST") == 0) mixer_host = value; else if (strcmp(name, "MIXER_PORT") == 0) mixer_port = value; } } void usage(const char * progname) { std::cerr << "\ Usage: " << progname << " [gtk-options] \\\n\ [{-h|--host} LISTEN-HOST] [{-p|--port} LISTEN-PORT]\n"; } } int main(int argc, char **argv) { try { dvswitch_read_config(handle_config); // Initialise Gtk Gtk::Main kit(argc, argv); // Complete option parsing with Gtk's options out of the way. int opt; while ((opt = getopt_long(argc, argv, "h:p:", options, NULL)) != -1) { switch (opt) { case 'h': mixer_host = optarg; break; case 'p': mixer_port = optarg; break; case 'H': /* --help */ usage(argv[0]); return 0; default: usage(argv[0]); return 2; } } if (mixer_host.empty() || mixer_port.empty()) { std::cerr << argv[0] << ": mixer hostname and port not defined\n"; return 2; } // The mixer must be created before the window, since we pass // a reference to the mixer into the window's constructor to // allow it to adjust the mixer's controls. // However, the mixer must also be destroyed before the // window, since as long as it exists it may call into the // window as a monitor. // This should probably be fixed by a smarter design, but for // now we arrange this by attaching the window to an auto_ptr. std::auto_ptr the_window; mixer the_mixer; server the_server(mixer_host, mixer_port, the_mixer); the_window.reset(new mixer_window(the_mixer)); the_mixer.set_monitor(the_window.get()); the_window->show(); the_window->signal_hide().connect(sigc::ptr_fun(&Gtk::Main::quit)); Gtk::Main::run(); return EXIT_SUCCESS; } catch (std::exception & e) { std::cerr << "ERROR: " << e.what() << "\n"; return EXIT_FAILURE; } catch (Glib::Exception & e) { std::cerr << "ERROR: " << e.what() << "\n"; return EXIT_FAILURE; } } dvswitch-0.8.3.6/src/format_dialog.cpp000066400000000000000000000050121161012451100176110ustar00rootroot00000000000000// Copyright 2008 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include "format_dialog.hpp" #include "gui.hpp" format_dialog::format_dialog(Gtk::Window & parent, mixer::format_settings settings) : Dialog("Format Settings", parent, /*modal=*/true), system_label_("Video system"), frame_aspect_label_("Video frame aspect ratio"), sample_rate_label_("Audio sample rate") { add_button(Gtk::StockID("gtk-apply"), 1); add_button(Gtk::StockID("gtk-cancel"), 0); Gtk::VBox * box = get_vbox(); box->set_spacing(gui_standard_spacing); system_label_.show(); box->add(system_label_); system_combo_.append_text("Automatic"); system_combo_.append_text("625 lines, 50 Hz (PAL)"); system_combo_.append_text("525 lines, 60 Hz (NTSC)"); if (settings.system == &dv_system_625_50) system_combo_.set_active(1); else if (settings.system == &dv_system_525_60) system_combo_.set_active(2); else system_combo_.set_active(0); system_combo_.show(); box->add(system_combo_); frame_aspect_label_.show(); box->add(frame_aspect_label_); frame_aspect_combo_.append_text("Automatic"); frame_aspect_combo_.append_text("Normal (4:3)"); frame_aspect_combo_.append_text("Wide (16:9)"); frame_aspect_combo_.set_active(1 + settings.frame_aspect); frame_aspect_combo_.show(); box->add(frame_aspect_combo_); sample_rate_label_.show(); box->add(sample_rate_label_); sample_rate_combo_.append_text("Automatic"); sample_rate_combo_.append_text("48 kHz"); sample_rate_combo_.append_text("44.1 kHz"); sample_rate_combo_.append_text("32 kHz"); sample_rate_combo_.set_active(1 + settings.sample_rate); sample_rate_combo_.show(); box->add(sample_rate_combo_); } mixer::format_settings format_dialog::get_settings() const { mixer::format_settings settings; int row; switch (system_combo_.get_active_row_number()) { case 0: settings.system = NULL; break; case 1: settings.system = &dv_system_625_50; break; case 2: settings.system = &dv_system_525_60; break; default: assert(!"impossible selection"); } row = frame_aspect_combo_.get_active_row_number(); assert(row >= 0 && row <= dv_frame_aspect_count); settings.frame_aspect = dv_frame_aspect(row - 1); row = sample_rate_combo_.get_active_row_number(); assert(row >= 0 && row <= dv_sample_rate_count); settings.sample_rate = dv_sample_rate(row - 1); return settings; } dvswitch-0.8.3.6/src/format_dialog.hpp000066400000000000000000000014661161012451100176270ustar00rootroot00000000000000// Copyright 2008 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_STUDIO_SETTINGS_DIALOG_HPP #define DVSWITCH_STUDIO_SETTINGS_DIALOG_HPP #include #include #include #include #include "mixer.hpp" class format_dialog : public Gtk::Dialog { public: format_dialog(Gtk::Window & parent, mixer::format_settings); mixer::format_settings get_settings() const; private: Gtk::Button apply_button_; Gtk::Button cancel_button_; Gtk::Label system_label_; Gtk::ComboBoxText system_combo_; Gtk::Label frame_aspect_label_; Gtk::ComboBoxText frame_aspect_combo_; Gtk::Label sample_rate_label_; Gtk::ComboBoxText sample_rate_combo_; }; #endif // !defined(DVSWITCH_STUDIO_SETTINGS_DIALOG_HPP) dvswitch-0.8.3.6/src/frame.c000066400000000000000000000044501161012451100155410ustar00rootroot00000000000000// Copyright 2008 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include "avcodec_wrap.h" #include "frame.h" int raw_frame_get_buffer(AVCodecContext * context, AVFrame * header) { struct raw_frame * frame = context->opaque; if (context->pix_fmt == PIX_FMT_YUV420P) { header->data[0] = frame->buffer._420.y; header->linesize[0] = FRAME_LINESIZE_4; header->data[1] = frame->buffer._420.cb; header->linesize[1] = FRAME_LINESIZE_2; header->data[2] = frame->buffer._420.cr; header->linesize[2] = FRAME_LINESIZE_2; } else if (context->pix_fmt == PIX_FMT_YUV411P) { header->data[0] = frame->buffer._411.y; header->linesize[0] = FRAME_LINESIZE_4; header->data[1] = frame->buffer._411.cb; header->linesize[1] = FRAME_LINESIZE_1; header->data[2] = frame->buffer._411.cr; header->linesize[2] = FRAME_LINESIZE_1; } else { assert(!"unexpected pixel format"); } frame->pix_fmt = context->pix_fmt; header->type = FF_BUFFER_TYPE_USER; return 0; } void raw_frame_release_buffer(AVCodecContext * context __attribute__((unused)), AVFrame * header) { for (int i = 0; i != 4; ++i) header->data[i] = 0; } int raw_frame_reget_buffer(AVCodecContext * context __attribute__((unused)), AVFrame * header __attribute__((unused))) { return 0; } void copy_raw_frame(struct raw_frame_ref dest, struct raw_frame_ref source) { assert(dest.height == source.height); assert(dest.pix_fmt == source.pix_fmt); int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(dest.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); unsigned width = FRAME_WIDTH; unsigned height = source.height; for (int plane = 0; plane != 4; ++plane) { const uint8_t * source_p = source.planes.data[plane]; if (!source_p) continue; const unsigned source_size = source.planes.linesize[plane]; uint8_t * dest_p = dest.planes.data[plane]; const unsigned dest_size = dest.planes.linesize[plane]; if (plane == 1) { width >>= chroma_shift_horiz; height >>= chroma_shift_vert; } for (unsigned y = 0; y != height; ++y) { memcpy(dest_p, source_p, width); source_p += source_size; dest_p += dest_size; } } } dvswitch-0.8.3.6/src/frame.h000066400000000000000000000052741161012451100155530ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_FRAME_H #define DVSWITCH_FRAME_H #ifndef __cplusplus #include #endif #include #include #include "avcodec_wrap.h" #include "dif.h" #ifdef __cplusplus extern "C" { #endif struct dv_frame { uint64_t timestamp; // set by mixer unsigned serial_num; // set by mixer bool do_record; // set by mixer bool cut_before; // set by mixer bool format_error; // set by mixer uint8_t buffer[DIF_MAX_FRAME_SIZE]; }; static inline const struct dv_system * dv_frame_system(const struct dv_frame * frame) { return dv_buffer_system(frame->buffer); } static inline enum dv_frame_aspect dv_frame_get_aspect(const struct dv_frame * frame) { return dv_buffer_get_aspect(frame->buffer); } static inline void dv_frame_set_aspect(struct dv_frame * frame, enum dv_frame_aspect aspect) { dv_buffer_set_aspect(frame->buffer, aspect); } static inline enum dv_sample_rate dv_frame_get_sample_rate(const struct dv_frame * frame) { return dv_buffer_get_sample_rate(frame->buffer); } #define FRAME_WIDTH 720 #define FRAME_HEIGHT_MAX 576 #define FRAME_LINESIZE_4 ((FRAME_WIDTH + 15) & ~15) #define FRAME_LINESIZE_2 ((FRAME_WIDTH / 2 + 15) & ~15) #define FRAME_LINESIZE_1 ((FRAME_WIDTH / 4 + 15) & ~15) struct raw_frame { AVFrame header; enum PixelFormat pix_fmt; enum dv_frame_aspect aspect; union { struct { uint8_t y[FRAME_LINESIZE_4 * FRAME_HEIGHT_MAX]; uint8_t cb[FRAME_LINESIZE_2 * FRAME_HEIGHT_MAX / 2]; uint8_t cr[FRAME_LINESIZE_2 * FRAME_HEIGHT_MAX / 2]; } _420; struct { uint8_t y[FRAME_LINESIZE_4 * FRAME_HEIGHT_MAX]; uint8_t cb[FRAME_LINESIZE_1 * FRAME_HEIGHT_MAX]; uint8_t cr[FRAME_LINESIZE_1 * FRAME_HEIGHT_MAX]; } _411; } buffer __attribute__((aligned(16))); }; // Buffer management functions for use with raw_frame. // These require that context->opaque is a pointer to the // struct raw_frame to be used. extern int raw_frame_get_buffer(AVCodecContext * context, AVFrame * av_frame); extern void raw_frame_release_buffer(AVCodecContext * context, AVFrame * frame); extern int raw_frame_reget_buffer(AVCodecContext * context, AVFrame * av_frame); static inline const struct dv_system * raw_frame_system(const struct raw_frame * frame) { return (const struct dv_system *)frame->header.opaque; } struct raw_frame_ref { AVPicture planes; enum PixelFormat pix_fmt; unsigned height; }; void copy_raw_frame(struct raw_frame_ref dest, struct raw_frame_ref source); #ifdef __cplusplus } #endif #endif // !defined(DVSWITCH_FRAME_H) dvswitch-0.8.3.6/src/frame_pool.cpp000066400000000000000000000021571161012451100171340ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include "avcodec_wrap.h" #include "frame.h" #include "frame_pool.hpp" namespace { boost::mutex dv_frame_pool_mutex; // controls access to the following boost::object_pool dv_frame_pool(100); void free_dv_frame(dv_frame * frame) { boost::mutex::scoped_lock lock(dv_frame_pool_mutex); if (frame) dv_frame_pool.free(frame); } boost::mutex raw_frame_pool_mutex; // controls access to the following boost::object_pool raw_frame_pool(10); void free_raw_frame(raw_frame * frame) { boost::mutex::scoped_lock lock(raw_frame_pool_mutex); if (frame) raw_frame_pool.free(frame); } } dv_frame_ptr allocate_dv_frame() { boost::mutex::scoped_lock lock(dv_frame_pool_mutex); return dv_frame_ptr(dv_frame_pool.malloc(), free_dv_frame); } raw_frame_ptr allocate_raw_frame() { boost::mutex::scoped_lock lock(raw_frame_pool_mutex); return raw_frame_ptr(raw_frame_pool.malloc(), free_raw_frame); } dvswitch-0.8.3.6/src/frame_pool.hpp000066400000000000000000000011501161012451100171310ustar00rootroot00000000000000// Copyright 2008 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_FRAME_POOL_HPP #define DVSWITCH_FRAME_POOL_HPP #include // Memory pool for frame buffers. This should make frame // (de)allocation relatively cheap. class dv_frame; class raw_frame; // Reference-counting pointers to frames typedef std::tr1::shared_ptr dv_frame_ptr; typedef std::tr1::shared_ptr raw_frame_ptr; // Allocate a DV frame buffer dv_frame_ptr allocate_dv_frame(); // Allocate a raw frame buffer raw_frame_ptr allocate_raw_frame(); #endif // !DVSWITCH_FRAME_POOL_HPP dvswitch-0.8.3.6/src/frame_timer.c000066400000000000000000000044421161012451100167420ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings. // Copyright 2008 Petter Reinholdtsen. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include "frame_timer.h" static timer_t frame_timer_id; struct timespec frame_timer_res; void frame_timer_init(void) { sigset_t sigset_alarm; sigemptyset(&sigset_alarm); sigaddset(&sigset_alarm, SIGALRM); if (pthread_sigmask(SIG_BLOCK, &sigset_alarm, NULL) != 0) { perror("FATAL: pthread_sigmask"); exit(1); } // On Linux, CLOCK_MONOTONIC matches the kernel interval timer // (resolution is controlled by HZ) and there is no // CLOCK_MONOTONIC_HR. if (clock_getres(CLOCK_MONOTONIC, &frame_timer_res) == -1) { perror("FATAL: clock_get_res"); exit(1); } // Require a 250 Hz or faster clock. The maximum clock period is // set 1% longer than this because Linux rounds to the nearest // number of whole hardware timer periods and reports that. if (frame_timer_res.tv_sec != 0 || (unsigned)frame_timer_res.tv_nsec > 1010000000 / 250) { fputs("FATAL: CLOCK_MONOTONIC resolution is too low; it must be" " at least 250 Hz\n" " (Linux: CONFIG_HZ=250)\n", stderr); exit(1); } struct sigevent event = { .sigev_notify = SIGEV_SIGNAL, .sigev_signo = SIGALRM }; if (timer_create(CLOCK_MONOTONIC, &event, &frame_timer_id) != 0) { perror("FATAL: timer_create"); exit(1); } } uint64_t frame_timer_get(void) { struct timespec result; if (clock_gettime(CLOCK_MONOTONIC, &result) != 0) { perror("FATAL: clock_gettime"); exit(1); } return (uint64_t)result.tv_sec * 1000000000 + result.tv_nsec; } void frame_timer_wait(uint64_t point) { struct itimerspec interval = { .it_value = { .tv_sec = point / 1000000000, .tv_nsec = point % 1000000000 }, .it_interval = { .tv_sec = 0, .tv_nsec = 0 } }; if (timer_settime(frame_timer_id, TIMER_ABSTIME, &interval, 0) != 0) { perror("FATAL: timer_settime"); exit(1); } sigset_t sigset_alarm; sigemptyset(&sigset_alarm); sigaddset(&sigset_alarm, SIGALRM); int dummy; sigwait(&sigset_alarm, &dummy); } dvswitch-0.8.3.6/src/frame_timer.h000066400000000000000000000013211161012451100167400ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_FRAME_TIMER_H #define DVSWITCH_FRAME_TIMER_H #include #ifdef __cplusplus extern "C" { #endif // Initialise timer in a disarmed state. Must be called in the first // thread before any more threads are created and before any of the // following functions are used. void frame_timer_init(void); // Get a timestamp. This is the time since an unspecified point in // the past, in ns. uint64_t frame_timer_get(void); // Wait until frame_timer_get() would return at least the given // timestamp. void frame_timer_wait(uint64_t timestamp); #ifdef __cplusplus } #endif #endif // !defined(DVSWITCH_FRAME_TIMER_H) dvswitch-0.8.3.6/src/geometry.h000066400000000000000000000043251161012451100163100ustar00rootroot00000000000000// Copyright 2005-2009 Ben Hutchings . // See the file "COPYING" for licence details. #ifndef DVSWITCH_GEOMETRY_H #define DVSWITCH_GEOMETRY_H #ifndef __cplusplus #include #endif struct rectangle; static inline void rectangle_extend(struct rectangle * rect, const struct rectangle * other); static inline void rectangle_clip(struct rectangle * rect, const struct rectangle * other); static inline bool rectangle_is_empty(const struct rectangle * rect); struct rectangle { int left, top; // inclusive int right, bottom; // exclusive; must be >= opposite edges #ifdef __cplusplus rectangle & operator|=(const rectangle & other) { rectangle_extend(this, &other); return *this; } rectangle & operator&=(const rectangle & other) { rectangle_clip(this, &other); return *this; } bool empty() const { return rectangle_is_empty(this); } #endif }; static inline void rectangle_extend(struct rectangle * rect, const struct rectangle * other) { if (rectangle_is_empty(other)) { // use current extents unchanged } else if (rectangle_is_empty(rect)) { // use other extents *rect = *other; } else { // find rectangle enclosing both extents if (other->left < rect->left) rect->left = other->left; if (other->right > rect->right) rect->right = other->right; if (other->top < rect->top) rect->top = other->top; if (other->bottom > rect->bottom) rect->bottom = other->bottom; } } static inline void rectangle_clip(struct rectangle * rect, const struct rectangle * other) { // find rectangle enclosed in both extents if (other->left > rect->left) rect->left = other->left; if (other->right < rect->right) rect->right = other->right; if (other->top > rect->top) rect->top = other->top; if (other->bottom < rect->bottom) rect->bottom = other->bottom; // Maintain invariant if (rect->left > rect->right) rect->left = rect->right; if (rect->top > rect->bottom) rect->top = rect->bottom; } static inline bool rectangle_is_empty(const struct rectangle * rect) { return rect->left == rect->right || rect->bottom == rect->top; } #endif // !DVSWITCH_GEOMETRY_H dvswitch-0.8.3.6/src/gui.hpp000066400000000000000000000005511161012451100155760ustar00rootroot00000000000000#ifndef DVSWITCH_GUI_HPP #define DVSWITCH_GUI_HPP #include #include #include const unsigned gui_standard_spacing = 6; inline Glib::RefPtr load_icon(const Glib::ustring & name, int size) { return Gtk::IconTheme::get_default()-> load_icon(name, size, Gtk::IconLookupFlags(0)); } #endif dvswitch-0.8.3.6/src/mixer.cpp000066400000000000000000000535571161012451100161470ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // Copyright 2008 Petter Reinholdtsen. // Copyright 2009 Wouter Verhelst. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include "auto_codec.hpp" #include "frame.h" #include "frame_timer.h" #include "mixer.hpp" #include "os_error.hpp" #include "ring_buffer.hpp" #include "video_effect.h" mixer::mixer() : clock_state_(run_state_wait), clock_thread_(boost::bind(&mixer::run_clock, this)), mixer_state_(run_state_wait), mixer_thread_(boost::bind(&mixer::run_mixer, this)), monitor_(0) { format_.system = NULL; format_.frame_aspect = dv_frame_aspect_auto; format_.sample_rate = dv_sample_rate_auto; settings_.do_record = false; settings_.cut_before = false; sources_.reserve(5); sinks_.reserve(5); } mixer::~mixer() { { boost::mutex::scoped_lock lock(source_mutex_); clock_state_ = run_state_stop; clock_state_cond_.notify_one(); // in case it's still waiting } { boost::mutex::scoped_lock lock(mixer_mutex_); mixer_state_ = run_state_stop; mixer_state_cond_.notify_one(); } clock_thread_.join(); mixer_thread_.join(); } mixer::source_id mixer::add_source(source * src) { boost::mutex::scoped_lock lock(source_mutex_); source_id id; for (id = 0; id != sources_.size(); ++id) { if (!sources_[id].src) { sources_[id].src = src; return id; } } sources_.resize(id + 1); sources_[id].src = src; return id; } void mixer::remove_source(source_id id) { boost::mutex::scoped_lock lock(source_mutex_); sources_.at(id).src = NULL; } void mixer::put_frame(source_id id, const dv_frame_ptr & frame) { bool was_full; bool should_notify_clock = false; { boost::mutex::scoped_lock lock(source_mutex_); source_data & source = sources_.at(id); was_full = source.frames.full(); if (!was_full) { frame->timestamp = frame_timer_get(); source.frames.push(frame); // Start clock ticking once one source has reached the // target queue length if (clock_state_ == run_state_wait && source.frames.size() == target_queue_len) { settings_.video_source_id = id; if (sources_[settings_.video_source_id].src) sources_[settings_.video_source_id].src-> set_active(source_active_video); settings_.audio_source_id = id; clock_state_ = run_state_run; should_notify_clock = true; // after we unlock the mutex } // Auto-select format if (clock_state_ == run_state_run) { format_settings format; format.system = dv_frame_system(frame.get()); format.frame_aspect = dv_frame_get_aspect(frame.get()); format.sample_rate = dv_frame_get_sample_rate(frame.get()); frame->format_error = false; if (format_.system == NULL) format_.system = format.system; else if (format_.system != format.system) { std::cerr << "WARN: Source " << 1 + id << " using wrong video system\n"; frame->format_error = true; } if (format_.frame_aspect == dv_frame_aspect_auto) format_.frame_aspect = format.frame_aspect; else if (format_.frame_aspect != format.frame_aspect) // Override frame aspect ratio dv_frame_set_aspect(frame.get(), format_.frame_aspect); if (format_.sample_rate == dv_sample_rate_auto) format_.sample_rate = format.sample_rate; else if (format_.sample_rate != format.sample_rate) { std::cerr << "WARN: Source " << 1 + id << "using wrong sample rate\n"; frame->format_error = true; } } } } if (should_notify_clock) clock_state_cond_.notify_one(); if (was_full) std::cerr << "WARN: Dropped frame from source " << 1 + id << " due to full queue\n"; } mixer::sink_id mixer::add_sink(sink * sink) { boost::mutex::scoped_lock lock(sink_mutex_); // XXX We may want to be able to reuse sink slots. sinks_.push_back(sink); return sinks_.size() - 1; } void mixer::remove_sink(sink_id id) { boost::mutex::scoped_lock lock(sink_mutex_); sinks_.at(id) = 0; } // Video effect settings. In future this is likely to be an abstract // base class but for now the only effect we will have is picture-in- // picture. struct mixer::video_effect_settings { source_id sec_source_id; rectangle dest_region; }; std::tr1::shared_ptr mixer::create_video_effect_pic_in_pic(source_id sec_source_id, rectangle dest_region) { // XXX Need to validate parameters before they break anything std::tr1::shared_ptr result( new video_effect_settings); result->sec_source_id = sec_source_id; result->dest_region = dest_region; return result; } mixer::format_settings mixer::get_format() const { boost::mutex::scoped_lock lock(source_mutex_); mixer::format_settings result = format_; lock.unlock(); return result; } void mixer::set_format(format_settings format) { boost::mutex::scoped_lock lock(source_mutex_); format_ = format; } void mixer::set_video_source(source_id id) { boost::mutex::scoped_lock lock(source_mutex_); if (id < sources_.size()) { if (sources_[settings_.video_source_id].src) sources_[settings_.video_source_id].src-> set_active(source_active_none); settings_.video_source_id = id; if (sources_[settings_.video_source_id].src) sources_[settings_.video_source_id].src-> set_active(source_active_video); } else throw std::range_error("video source id out of range"); } void mixer::set_video_effect( std::tr1::shared_ptr effect) { boost::mutex::scoped_lock lock(source_mutex_); if (settings_.video_effect && sources_[settings_.video_effect->sec_source_id].src) sources_[settings_.video_effect->sec_source_id].src-> set_active(source_active_none); settings_.video_effect = effect; if (settings_.video_effect && sources_[settings_.video_effect->sec_source_id].src) sources_[settings_.video_effect->sec_source_id].src-> set_active(source_active_video); } void mixer::set_audio_source(source_id id) { boost::mutex::scoped_lock lock(source_mutex_); if (id < sources_.size()) settings_.audio_source_id = id; else throw std::range_error("audio source id out of range"); } void mixer::set_monitor(monitor * monitor) { assert(monitor && !monitor_); monitor_ = monitor; } void mixer::enable_record(bool flag) { boost::mutex::scoped_lock lock(source_mutex_); settings_.do_record = flag; } void mixer::cut() { boost::mutex::scoped_lock lock(source_mutex_); settings_.cut_before = true; } namespace { // Ensure the frame timer is initialised at startup struct timer_initialiser { timer_initialiser(); } timer_dummy; timer_initialiser::timer_initialiser() { frame_timer_init(); } } void mixer::run_clock() { const struct dv_system * audio_source_system = 0; { boost::mutex::scoped_lock lock(source_mutex_); while (clock_state_ == run_state_wait) clock_state_cond_.wait(lock); } // Interval to the next frame (in ns) unsigned int frame_interval = 0; // Weighted rolling average frame interval unsigned int average_frame_interval = 0; for (uint64_t tick_timestamp = frame_timer_get(); ; tick_timestamp += frame_interval, frame_timer_wait(tick_timestamp)) { mix_data m; // Select the mixer settings and source frame(s) { boost::mutex::scoped_lock lock(source_mutex_); if (clock_state_ == run_state_stop) break; m.format = format_; m.settings = settings_; settings_.cut_before = false; m.source_frames.resize(sources_.size()); for (source_id id = 0; id != sources_.size(); ++id) { if (sources_[id].frames.empty()) { m.source_frames[id].reset(); } else { m.source_frames[id] = sources_[id].frames.front(); sources_[id].frames.pop(); } } } assert(m.settings.audio_source_id < m.source_frames.size() && m.settings.video_source_id < m.source_frames.size()); // Frame timer is based on the audio source. Synchronisation // with the audio source matters more because audio // discontinuities are even more annoying than dropped or // repeated video frames. if (dv_frame * audio_source_frame = m.source_frames[m.settings.audio_source_id].get()) { if (audio_source_system != dv_frame_system(audio_source_frame)) { audio_source_system = dv_frame_system(audio_source_frame); // Use standard frame timing initially. frame_interval = (1000000000 / audio_source_system->frame_rate_numer * audio_source_system->frame_rate_denom); average_frame_interval = frame_interval; } else { // The delay for this frame has a large effect on the // interval to the next frame because we want to // correct clock deviations quickly, but a much // smaller effect on the rolling average so that we // don't over-correct. This has experimentally been // found to work well. static const unsigned next_average_weight = 3; static const unsigned next_delay_weight = 1; static const unsigned average_rolling_weight = 15; static const unsigned average_next_weight = 1; // Try to keep target_queue_len - 0.5 frame intervals // between delivery of source frames and mixing them. // The "obvious" way to feed the delay into the // frame_time is to divide it by target_queue_len-0.5. // But this is inverse to the effect we want it to // have: if the delay is long, we need to reduce, // not increase, frame_time. So we calculate a kind // of inverse based on the amount of queue space // that should remain free. const uint64_t delay = tick_timestamp > audio_source_frame->timestamp ? tick_timestamp - audio_source_frame->timestamp : 0; const unsigned free_queue_time = full_queue_len * frame_interval > delay ? full_queue_len * frame_interval - delay : 0; frame_interval = (average_frame_interval * next_average_weight + (free_queue_time * 2 / (2 * (full_queue_len - target_queue_len) + 1) * next_delay_weight)) / (next_average_weight + next_delay_weight); average_frame_interval = (average_frame_interval * average_rolling_weight + frame_interval * average_next_weight) / (average_rolling_weight + average_next_weight); } } std::size_t free_len; { boost::mutex::scoped_lock lock(mixer_mutex_); free_len = mixer_queue_.capacity() - mixer_queue_.size(); if (free_len != 0) { mixer_queue_.push(m); // really want to move m here mixer_state_ = run_state_run; } } if (free_len != 0) { mixer_state_cond_.notify_one(); } else { std::cerr << "ERROR: Dropped source frames due to" " full mixer queue\n"; } } } namespace { raw_frame_ref make_raw_frame_ref(const raw_frame_ptr & frame) { struct raw_frame_ref result; for (int i = 0; i != 4; ++i) { result.planes.data[i] = frame->header.data[i]; result.planes.linesize[i] = frame->header.linesize[i]; } result.pix_fmt = frame->pix_fmt; result.height = raw_frame_system(frame.get())->frame_height; return result; } raw_frame_ptr decode_video_frame( const auto_codec & decoder, const dv_frame_ptr & dv_frame) { const struct dv_system * system = dv_frame_system(dv_frame.get()); raw_frame_ptr result = allocate_raw_frame(); AVPacket packet; av_init_packet(&packet); packet.data = dv_frame->buffer; packet.size = system->size; int got_frame; decoder.get()->opaque = result.get(); int used_size = avcodec_decode_video2(decoder.get(), &result->header, &got_frame, &packet); assert(got_frame && size_t(used_size) == system->size); result->header.opaque = const_cast(static_cast(system)); result->aspect = dv_frame_get_aspect(dv_frame.get()); return result; } inline unsigned bcd(unsigned v) { assert(v < 100); return ((v / 10) << 4) + v % 10; } void set_times(dv_frame & dv_frame) { // XXX We should work this out in the clock loop. time_t now; time(&now); tm now_tm; localtime_r(&now, &now_tm); // Generate nominal frame count and frame rate. unsigned frame_num = dv_frame.serial_num; unsigned frame_rate; if (dv_frame.buffer[3] & 0x80) { frame_rate = 25; } else { // Skip the first 2 frame numbers of each minute, except in // minutes divisible by 10. This results in a "drop frame // timecode" with a nominal frame rate of 30 Hz. frame_num = frame_num + 2 * frame_num / (60 * 30 - 2) - 2 * (frame_num + 2) / (10 * 60 * 30 - 18); frame_rate = 30; } // Timecode format is based on SMPTE LTC // : // 0: pack id = 0x13 // 1: LTC bits 0-3, 8-11 // bits 0-5: frame part (BCD) // bit 6: drop frame timecode flag // 2: LTC bits 16-19, 24-27 // bits 0-6: second part (BCD) // 3: LTC bits 32-35, 40-43 // bits 0-6: minute part (BCD) // 4: LTC bits 48-51, 56-59 // bits 0-5: hour part (BCD) // the remaining bits are meaningless in DV and we use zeroes uint8_t timecode[DIF_PACK_SIZE] = { 0x13, bcd(frame_num % frame_rate) | (1 << 6), bcd(frame_num / frame_rate % 60), bcd(frame_num / (60 * frame_rate) % 60), bcd(frame_num / (60 * 60 * frame_rate) % 24) }; // Record date format: // 0: pack id = 0x62 (video) or 0x52 (audio) // 1: some kind of time zone indicator or 0xff for unknown // 2: bits 6-7: unused? reserved? // bits 0-5: day part (BCD) // 3: bits 5-7: unused? reserved? day of week? // bits 0-4: month part (BCD) // 4: year part (BCD) uint8_t video_record_date[DIF_PACK_SIZE] = { 0x62, 0xff, bcd(now_tm.tm_mday), bcd(1 + now_tm.tm_mon), bcd(now_tm.tm_year % 100) }; uint8_t audio_record_date[DIF_PACK_SIZE] = { 0x52, 0xff, bcd(now_tm.tm_mday), bcd(1 + now_tm.tm_mon), bcd(now_tm.tm_year % 100) }; // Record time format (similar to timecode format): // 0: pack id = 0x63 (video) or 0x53 (audio) // 1: bits 6-7: reserved, set to 1 // bits 0-5: frame part (BCD) or 0x3f for unknown // 2: bit 7: unused? reserved? // bits 0-6: second part (BCD) // 3: bit 7: unused? reserved? // bits 0-6: minute part (BCD) // 4: bits 6-7: unused? reserved? // bits 0-5: hour part (BCD) uint8_t video_record_time[DIF_PACK_SIZE] = { 0x63, 0xff, bcd(now_tm.tm_sec), bcd(now_tm.tm_min), bcd(now_tm.tm_hour) }; uint8_t audio_record_time[DIF_PACK_SIZE] = { 0x53, 0xff, bcd(now_tm.tm_sec), bcd(now_tm.tm_min), bcd(now_tm.tm_hour) }; // In DIFs 1 and 2 (subcode) of sequence 6 onward: // - Write timecode at offset 6 and 30 // - Write video record date at offset 14 and 38 // - Write video record time at offset 22 and 46 // In DIFs 3, 4 and 5 (VAUX) of even sequences: // - Write video record date at offset 13 and 58 // - Write video record time at offset 18 and 63 // In DIF 86 of even sequences and DIF 38 of odd sequences (AAUX): // - Write audio record date at offset 3 // In DIF 102 of even sequences and DIF 54 of odd sequences (AAUX): // - Write audio record time at offset 3 for (unsigned seq_num = 0; seq_num != dv_frame_system(&dv_frame)->seq_count; ++seq_num) { if (seq_num >= 6) { for (unsigned block_num = 1; block_num <= 3; ++block_num) { for (unsigned i = 0; i <= 1; ++i) { memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + block_num * DIF_BLOCK_SIZE + i * 24 + 6, timecode, DIF_PACK_SIZE); memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + block_num * DIF_BLOCK_SIZE + i * 24 + 14, video_record_date, DIF_PACK_SIZE); memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + block_num * DIF_BLOCK_SIZE + i * 24 + 22, video_record_time, DIF_PACK_SIZE); } } } for (unsigned block_num = 3; block_num <= 5; ++block_num) { for (unsigned i = 0; i <= 1; ++i) { memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + block_num * DIF_BLOCK_SIZE + i * 45 + 13, video_record_date, DIF_PACK_SIZE); memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + block_num * DIF_BLOCK_SIZE + i * 45 + 18, video_record_time, DIF_PACK_SIZE); } } memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + ((seq_num & 1) ? 38 : 86) * DIF_BLOCK_SIZE + 3, audio_record_date, DIF_PACK_SIZE); memcpy(dv_frame.buffer + seq_num * DIF_SEQUENCE_SIZE + ((seq_num & 1) ? 54 : 102) * DIF_BLOCK_SIZE + 3, audio_record_time, DIF_PACK_SIZE); } } } void mixer::run_mixer() { dv_frame_ptr last_mixed_dv; unsigned serial_num = 0; const mix_data * m = 0; auto_codec decoder(auto_codec_open_decoder(CODEC_ID_DVVIDEO)); AVCodecContext * dec = decoder.get(); dec->get_buffer = raw_frame_get_buffer; dec->release_buffer = raw_frame_release_buffer; dec->reget_buffer = raw_frame_reget_buffer; auto_codec encoder(avcodec_alloc_context()); AVCodecContext * enc = encoder.get(); if (!enc) throw std::bad_alloc(); // Set some input parameters early to satisfy // dvvideo_init_encoder() that the input will match some DV // profile. It doesn't matter if we change these later. enc->width = 720; enc->height = 576; enc->pix_fmt = PIX_FMT_YUV420P; auto_codec_open_encoder(encoder, CODEC_ID_DVVIDEO); { // Try to use one thread per CPU, up to a limit of 8 int enc_thread_count = std::min(8, std::max(sysconf(_SC_NPROCESSORS_ONLN), 1)); if (enc_thread_count >= 2 && avcodec_thread_init(enc, enc_thread_count)) { std::cerr << "WARN: avcodec_thread_init(" << enc_thread_count << ") failed\n"; enc_thread_count = 1; } std::cout << "INFO: DV encoder threads: " << enc_thread_count << "\n"; } for (;;) { // Get the next set of source frames and mix settings (or stop // if requested) { boost::mutex::scoped_lock lock(mixer_mutex_); if (m) mixer_queue_.pop(); while (mixer_state_ != run_state_stop && mixer_queue_.empty()) mixer_state_cond_.wait(lock); if (mixer_state_ == run_state_stop) break; m = &mixer_queue_.front(); } for (unsigned id = 0; id != m->source_frames.size(); ++id) if (m->source_frames[id]) m->source_frames[id]->serial_num = serial_num; const dv_frame_ptr & audio_source_dv = m->source_frames[m->settings.audio_source_id]; const dv_frame_ptr & video_pri_source_dv = m->source_frames[m->settings.video_source_id]; dv_frame_ptr mixed_dv; raw_frame_ptr video_pri_source_raw; raw_frame_ptr video_sec_source_raw; raw_frame_ptr mixed_raw; if (!video_pri_source_dv || dv_frame_system(video_pri_source_dv.get()) != format_.system) { std::cerr << "WARN: Repeating frame due to " << (video_pri_source_dv ? "wrong video system" : "empty queue") << " for source " << 1 + m->settings.video_source_id << "\n"; // Make a copy of the last mixed frame so we can // replace the audio. (We can't modify the last frame // because sinks may still be reading from it.) mixed_dv = allocate_dv_frame(); std::memcpy(mixed_dv.get(), last_mixed_dv.get(), offsetof(dv_frame, buffer) + dv_frame_system(last_mixed_dv.get())->size); mixed_dv->serial_num = serial_num; } else if (m->settings.video_effect && m->source_frames[m->settings.video_effect->sec_source_id]) { const dv_frame_ptr video_sec_source_dv = m->source_frames[m->settings.video_effect->sec_source_id]; // Decode sources mixed_raw = decode_video_frame(decoder, video_pri_source_dv); video_sec_source_raw = decode_video_frame(decoder, video_sec_source_dv); // Mix raw video video_effect_pic_in_pic( make_raw_frame_ref(mixed_raw), m->settings.video_effect->dest_region, make_raw_frame_ref(video_sec_source_raw), raw_frame_system(video_sec_source_raw.get())->active_region); // Encode mixed video const dv_system * system = m->format.system; enc->sample_aspect_ratio.num = system->pixel_aspect[m->format.frame_aspect].width; enc->sample_aspect_ratio.den = system->pixel_aspect[m->format.frame_aspect].height; // Work around libavcodec's aspect ratio confusion (bug #790) enc->sample_aspect_ratio.num *= 40; enc->sample_aspect_ratio.den *= 41; enc->time_base.num = system->frame_rate_denom; enc->time_base.den = system->frame_rate_numer; enc->width = system->frame_width; enc->height = system->frame_height; enc->pix_fmt = mixed_raw->pix_fmt; mixed_raw->header.pts = serial_num; mixed_dv = allocate_dv_frame(); int out_size = avcodec_encode_video(enc, mixed_dv->buffer, system->size, &mixed_raw->header); assert(size_t(out_size) == system->size); mixed_dv->serial_num = serial_num; // libavcodec doesn't properly distinguish IEC and SMPTE // variants of NTSC. Fix the APTs here. if (system == &dv_system_525_60) { uint8_t * block = mixed_dv->buffer; unsigned apt = 0; for (unsigned i = 4; i != 8; ++i) block[i] = (block[i] & 0xf8) | apt; } } else { mixed_dv = video_pri_source_dv; } if (!audio_source_dv || dv_frame_get_sample_rate(audio_source_dv.get()) != m->format.sample_rate) dv_buffer_silence_audio(mixed_dv->buffer, m->format.sample_rate, serial_num); else if (mixed_dv != audio_source_dv) dv_buffer_dub_audio(mixed_dv->buffer, audio_source_dv->buffer); set_times(*mixed_dv); mixed_dv->do_record = m->settings.do_record; mixed_dv->cut_before = m->settings.cut_before; last_mixed_dv = mixed_dv; ++serial_num; // Sink the frame { boost::mutex::scoped_lock lock(sink_mutex_); for (sink_id id = 0; id != sinks_.size(); ++id) if (sinks_[id]) sinks_[id]->put_frame(mixed_dv); } if (monitor_) monitor_->put_frames(m->source_frames.size(), &m->source_frames[0], m->settings, mixed_dv, mixed_raw); } } dvswitch-0.8.3.6/src/mixer.hpp000066400000000000000000000140121161012451100161330ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_MIXER_HPP #define DVSWITCH_MIXER_HPP #include #include #include #include #include #include #include "auto_handle.hpp" #include "frame.h" #include "frame_pool.hpp" #include "geometry.h" #include "ring_buffer.hpp" namespace boost { class thread; } class mixer { public: // Identifiers to distinguish mixer's sources and sinks typedef unsigned source_id, sink_id; static const unsigned invalid_id = -1; // Settings for mixing/switching struct format_settings { const dv_system * system; dv_frame_aspect frame_aspect; dv_sample_rate sample_rate; }; struct video_effect_settings; struct mix_settings { source_id video_source_id; std::tr1::shared_ptr video_effect; source_id audio_source_id; bool do_record; bool cut_before; }; // Source activation flags enum source_activation { source_active_none = 0, // Source is used in the current video mix source_active_video = 1, }; // Interface to sinks struct sink { // Put a frame out. // The frame is shared with other sinks and must not be // modified. It should be released as soon as possible. // This will be called at the appropriate frame rate even // if there are no new frames available. The serial_num // member of the frame can be used to check whether the // frame is new. virtual void put_frame(const dv_frame_ptr &) = 0; }; // Interface to sources struct source { // Tell a source whether it is active, i.e. selected for // output. This may be used to control a tally light, for // example. virtual void set_active(source_activation) = 0; }; // Interface to monitor struct monitor { // Display or otherwise use frames. // // source_count is the number of sources assigned, though some // may no longer be registered. source_dv points to an array, // length source_count, of pointers to the frames clocked // through from these sources. Any or all of these pointers // may be null if the sources are not producing frames. // mix_settings is a copy of the settings used to select and // mix these source frames. mixed_dv is a pointer to the // mixed frame that was sent to sinks. // // mixed_raw is a pointer to the raw video for the mixed // frame, or null if the mixer did not need to decode video. // // All DV frames may be shared and must not be modified. Raw // frames may be modified by the monitor. All references and // pointers passed to the function are invalid once it // returns; it must copy shared_ptrs to ensure that frames // remain valid. // // This is called in the context of the mixer thread and should // return quickly. virtual void put_frames(unsigned source_count, const dv_frame_ptr * source_dv, mix_settings, const dv_frame_ptr & mixed_dv, const raw_frame_ptr & mixed_raw) = 0; }; mixer(); ~mixer(); // Interface for sources // Register and unregister sources source_id add_source(source *); void remove_source(source_id); // Add a new frame from the given source. This should be called at // appropriate intervals to avoid the need to drop or duplicate // frames. void put_frame(source_id, const dv_frame_ptr &); // Interface for sinks // Register and unregister sinks sink_id add_sink(sink *); void remove_sink(sink_id); // Interface for monitors void set_monitor(monitor *); static std::tr1::shared_ptr create_video_effect_pic_in_pic(source_id sec_source_id, rectangle dest_region); static std::tr1::shared_ptr null_video_effect() { return std::tr1::shared_ptr(); } // Mixer interface format_settings get_format() const; void set_format(format_settings); // Select the primary video source for output (this cancels any // video mixing effect) void set_video_source(source_id); // Set the video mixing effect (or cancel it, if the argument is // a null pointer) void set_video_effect(std::tr1::shared_ptr); // Select the audio source for output void set_audio_source(source_id); // Make a cut in the output as soon as possible, where appropriate // for the sink void cut(); // Enable/disable recording void enable_record(bool); private: // Source data. We want to allow a bit of leeway in the input // pipeline before we have to drop or repeat a frame. At the // same time we don't want to add much to latency. We try to // keep the queue half-full so there are 2 frame-times // (66-80 ms) of added latency here. static const std::size_t target_queue_len = 2; static const std::size_t full_queue_len = target_queue_len * 2; struct source_data { source_data() : src(NULL) {} ring_buffer frames; source * src; }; struct mix_data { std::vector source_frames; format_settings format; mix_settings settings; }; enum run_state { run_state_wait, run_state_run, run_state_stop }; void run_clock(); // clock thread function void run_mixer(); // mixer thread function void set_source_active(bool active); mutable boost::mutex source_mutex_; // controls access to the following format_settings format_; mix_settings settings_; std::vector sources_; run_state clock_state_; boost::condition clock_state_cond_; boost::thread clock_thread_; boost::mutex mixer_mutex_; // controls access to the following ring_buffer mixer_queue_; run_state mixer_state_; boost::condition mixer_state_cond_; boost::thread mixer_thread_; boost::mutex sink_mutex_; // controls access to the following std::vector sinks_; monitor * monitor_; }; #endif // !defined(DVSWITCH_MIXER_HPP) dvswitch-0.8.3.6/src/mixer_window.cpp000066400000000000000000000276771161012451100175420ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include #include "format_dialog.hpp" #include "frame.h" #include "gui.hpp" #include "mixer.hpp" #include "mixer_window.hpp" // Window layout: // // +-------------------------------------------------------------------+ // | â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•— | // | â•‘ menu_bar_ â•‘ | // | â• â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•£ | // | â•‘+-----â•¥-------------------------------------------------------+â•‘main_box_ // | â•‘| â•‘ |upper_box_ // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘|comm-â•‘ |â•‘ | // | â•‘|and_-â•‘ osd_/display_ |â•‘ | // | â•‘|box_ â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘| â•‘ |â•‘ | // | â•‘+-----╨-------------------------------------------------------+â•‘ | // | â• â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•£ | // | â•‘ â•‘ | // | â•‘ â•‘ | // | â•‘ selector_ â•‘ | // | â•‘ â•‘ | // | â•‘ â•‘ | // | ╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• | // +-------------------------------------------------------------------+ mixer_window::mixer_window(mixer & mixer) : mixer_(mixer), file_menu_item_("_File", true), quit_menu_item_(Gtk::StockID("gtk-quit")), settings_menu_item_("_Settings", true), format_menu_item_("_Format", true), record_button_("gtk-media-record"), cut_button_("gtk-cut"), none_button_(effect_group_, "No effect"), pip_button_(effect_group_, "_Pic-in-pic", true), apply_button_("gtk-apply"), vu_meter_(-56, 0), sec_video_source_id_(0), pip_active_(false), pip_pending_(false), wakeup_pipe_(O_NONBLOCK, O_NONBLOCK), next_source_id_(0) { record_button_.set_use_stock(); cut_button_.set_use_stock(); apply_button_.set_use_stock(); Glib::RefPtr pipe_io_source( Glib::IOSource::create(wakeup_pipe_.reader.get(), Glib::IO_IN)); pipe_io_source->set_priority(Glib::PRIORITY_DEFAULT_IDLE); pipe_io_source->connect(sigc::mem_fun(this, &mixer_window::update)); pipe_io_source->attach(); set_mnemonic_modifier(Gdk::ModifierType(0)); quit_menu_item_.signal_activate().connect(sigc::ptr_fun(&Gtk::Main::quit)); quit_menu_item_.show(); file_menu_.add(quit_menu_item_); file_menu_item_.set_submenu(file_menu_); file_menu_item_.show(); menu_bar_.add(file_menu_item_); format_menu_item_.signal_activate().connect( sigc::mem_fun(this, &mixer_window::open_format_dialog)); format_menu_item_.show(); settings_menu_.add(format_menu_item_); settings_menu_item_.set_submenu(settings_menu_); settings_menu_item_.show(); menu_bar_.add(settings_menu_item_); menu_bar_.show(); record_button_.set_mode(/*draw_indicator=*/false); record_button_.signal_toggled().connect( sigc::mem_fun(*this, &mixer_window::toggle_record)); record_button_.show(); cut_button_.set_sensitive(false); cut_button_.signal_clicked().connect(sigc::mem_fun(mixer_, &mixer::cut)); cut_button_.show(); command_sep_.show(); none_button_.set_mode(/*draw_indicator=*/false); none_button_.set_sensitive(true); none_button_.signal_clicked().connect( sigc::mem_fun(this, &mixer_window::cancel_effect)); none_button_.add_accelerator("activate", get_accel_group(), GDK_Escape, Gdk::ModifierType(0), Gtk::AccelFlags(0)); none_button_.show(); pip_button_.set_mode(/*draw_indicator=*/false); pip_button_.set_sensitive(true); pip_button_.signal_clicked().connect( sigc::mem_fun(this, &mixer_window::begin_pic_in_pic)); pip_button_.show(); apply_button_.set_sensitive(false); apply_button_.signal_clicked().connect( sigc::mem_fun(this, &mixer_window::apply_effect)); apply_button_.add_accelerator("activate", get_accel_group(), GDK_Return, Gdk::ModifierType(0), Gtk::AccelFlags(0)); apply_button_.add_accelerator("activate", get_accel_group(), GDK_KP_Enter, Gdk::ModifierType(0), Gtk::AccelFlags(0)); apply_button_.show(); meter_sep_.show(); vu_meter_.show(); display_.show(); osd_.add(display_); osd_.set_status("STOP", "gtk-media-stop"); osd_.show(); selector_.set_border_width(gui_standard_spacing); selector_.set_accel_group(get_accel_group()); selector_.signal_pri_video_selected().connect( sigc::mem_fun(*this, &mixer_window::set_pri_video_source)); selector_.signal_sec_video_selected().connect( sigc::mem_fun(*this, &mixer_window::set_sec_video_source)); selector_.signal_audio_selected().connect( sigc::mem_fun(mixer_, &mixer::set_audio_source)); selector_.show(); command_box_.set_spacing(gui_standard_spacing); command_box_.pack_start(record_button_, Gtk::PACK_SHRINK); command_box_.pack_start(cut_button_, Gtk::PACK_SHRINK); command_box_.pack_start(command_sep_, Gtk::PACK_SHRINK); command_box_.pack_start(none_button_, Gtk::PACK_SHRINK); command_box_.pack_start(pip_button_, Gtk::PACK_SHRINK); command_box_.pack_start(apply_button_, Gtk::PACK_SHRINK); command_box_.pack_start(meter_sep_, Gtk::PACK_EXPAND_PADDING); command_box_.pack_start(vu_meter_, Gtk::PACK_EXPAND_WIDGET); command_box_.show(); upper_box_.set_border_width(gui_standard_spacing); upper_box_.set_spacing(gui_standard_spacing); upper_box_.pack_start(command_box_, Gtk::PACK_SHRINK); upper_box_.pack_start(osd_, Gtk::PACK_EXPAND_PADDING); upper_box_.show(); main_box_.pack_start(menu_bar_, Gtk::PACK_SHRINK); main_box_.pack_start(upper_box_, Gtk::PACK_EXPAND_WIDGET); main_box_.pack_start(selector_, Gtk::PACK_EXPAND_PADDING); main_box_.show(); add(main_box_); } mixer_window::~mixer_window() { // display_ will be destroyed before osd_ so we must remove it first osd_.remove(display_); } void mixer_window::cancel_effect() { pip_pending_ = false; pip_active_ = false; mixer_.set_video_effect(mixer::null_video_effect()); display_.set_selection_enabled(false); apply_button_.set_sensitive(false); } void mixer_window::begin_pic_in_pic() { pip_pending_ = true; display_.set_selection_enabled(true); apply_button_.set_sensitive(true); } void mixer_window::apply_effect() { if (pip_pending_) { rectangle region = display_.get_selection(); if (region.empty()) return; pip_pending_ = false; pip_active_ = true; mixer_.set_video_effect( mixer_.create_video_effect_pic_in_pic( sec_video_source_id_, region)); display_.set_selection_enabled(false); } apply_button_.set_sensitive(false); } void mixer_window::open_format_dialog() { format_dialog dialog(*this, mixer_.get_format()); if (dialog.run()) { mixer::format_settings format = dialog.get_settings(); mixer_.set_format(format); } } void mixer_window::toggle_record() throw() { bool flag = record_button_.get_active(); mixer_.enable_record(flag); cut_button_.set_sensitive(flag); if (flag) osd_.set_status("RECORD", "gtk-media-record", 2); else osd_.set_status("STOP", "gtk-media-stop"); } void mixer_window::set_pri_video_source(mixer::source_id id) { // If the secondary source is becoming the primary source, cancel // the effect rather than mixing it with itself. if (pip_active_ && id == sec_video_source_id_) { pip_active_ = false; if (!pip_pending_) none_button_.set_active(); mixer_.set_video_effect(mixer_.null_video_effect()); } mixer_.set_video_source(id); } void mixer_window::set_sec_video_source(mixer::source_id id) { sec_video_source_id_ = id; if (pip_active_) mixer_.set_video_effect( mixer_.create_video_effect_pic_in_pic( sec_video_source_id_, display_.get_selection())); } void mixer_window::put_frames(unsigned source_count, const dv_frame_ptr * source_dv, mixer::mix_settings mix_settings, const dv_frame_ptr & mixed_dv, const raw_frame_ptr & mixed_raw) { { boost::mutex::scoped_lock lock(frame_mutex_); source_dv_.assign(source_dv, source_dv + source_count); mix_settings_ = mix_settings; mixed_dv_ = mixed_dv; mixed_raw_ = mixed_raw; } // Poke the event loop. static const char dummy[1] = {0}; write(wakeup_pipe_.writer.get(), dummy, sizeof(dummy)); } bool mixer_window::update(Glib::IOCondition) throw() { // Empty the pipe (if frames have been dropped there's nothing we // can do about that now). static char dummy[4096]; read(wakeup_pipe_.reader.get(), dummy, sizeof(dummy)); try { dv_frame_ptr mixed_dv; std::vector source_dv; raw_frame_ptr mixed_raw; { boost::mutex::scoped_lock lock(frame_mutex_); mixed_dv = mixed_dv_; mixed_dv_.reset(); source_dv = source_dv_; source_dv_.clear(); mixed_raw = mixed_raw_; mixed_raw_.reset(); } if (mixed_raw) display_.put_frame(mixed_raw); else if (mixed_dv) display_.put_frame(mixed_dv); if (mixed_dv) { int levels[2]; dv_buffer_get_audio_levels(mixed_dv->buffer, levels); vu_meter_.set_levels(levels); } selector_.set_source_count(source_dv.size()); // Update the thumbnail displays of sources. If a new mixed frame // arrives while we were doing this, return to the event loop. // (We want to handle the next mixed frame but we need to let it // handle other events as well.) Use a rota for source updates so // even if we don't have time to run them all at full frame rate // they all get updated at roughly the same rate. for (std::size_t i = 0; i != source_dv.size(); ++i) { if (next_source_id_ >= source_dv.size()) next_source_id_ = 0; mixer::source_id id = next_source_id_++; if (source_dv[id]) { selector_.put_frame(id, source_dv[id]); boost::mutex::scoped_lock lock(frame_mutex_); if (mixed_dv_) break; } } } catch (std::exception & e) { std::cerr << "ERROR: Failed to update window: " << e.what() << "\n"; } return true; // call again } dvswitch-0.8.3.6/src/mixer_window.hpp000066400000000000000000000045641161012451100175350ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_MIXER_WINDOW_HPP #define DVSWITCH_MIXER_WINDOW_HPP #include #include #include #include #include #include #include #include #include #include #include "auto_pipe.hpp" #include "dv_display_widget.hpp" #include "dv_selector_widget.hpp" #include "mixer.hpp" #include "status_overlay.hpp" #include "vu_meter.hpp" namespace Glib { class IOSource; } class mixer_window : public Gtk::Window, public mixer::monitor { public: explicit mixer_window(mixer & mixer); ~mixer_window(); private: void cancel_effect(); void begin_pic_in_pic(); void apply_effect(); void open_format_dialog(); void toggle_record() throw(); bool update(Glib::IOCondition) throw(); void set_pri_video_source(mixer::source_id); void set_sec_video_source(mixer::source_id); virtual void put_frames(unsigned source_count, const dv_frame_ptr * source_dv, mixer::mix_settings, const dv_frame_ptr & mixed_dv, const raw_frame_ptr & mixed_raw); mixer & mixer_; Gtk::VBox main_box_; Gtk::MenuBar menu_bar_; Gtk::MenuItem file_menu_item_; Gtk::Menu file_menu_; Gtk::ImageMenuItem quit_menu_item_; Gtk::MenuItem settings_menu_item_; Gtk::Menu settings_menu_; Gtk::MenuItem format_menu_item_; Gtk::HBox upper_box_; Gtk::VBox command_box_; Gtk::ToggleButton record_button_; Gtk::Button cut_button_; Gtk::HSeparator command_sep_; Gtk::RadioButtonGroup effect_group_; Gtk::RadioButton none_button_; Gtk::RadioButton pip_button_; Gtk::Button apply_button_; Gtk::HSeparator meter_sep_; vu_meter vu_meter_; status_overlay osd_; dv_full_display_widget display_; dv_selector_widget selector_; mixer::source_id sec_video_source_id_; bool pip_active_; bool pip_pending_; auto_pipe wakeup_pipe_; boost::mutex frame_mutex_; // controls access to the following std::vector source_dv_; mixer::source_id next_source_id_; mixer::mix_settings mix_settings_; dv_frame_ptr mixed_dv_; raw_frame_ptr mixed_raw_; }; #endif // !defined(DVSWITCH_MIXER_WINDOW_HPP) dvswitch-0.8.3.6/src/os_error.cpp000066400000000000000000000013071161012451100166370ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings . // See the file "COPYING" for licence details. #include "os_error.hpp" #include #include #include os_error::os_error(std::string function, int code) : std::runtime_error(function.append(": ") .append(std::strerror(code ? code : errno))), code_(code ? code : errno) {} void os_check_zero(const char * function, int result) { if (result != 0) throw os_error(function); } int os_check_nonneg(const char * function, int result) { if (result < 0) throw os_error(function); return result; } void os_check_error(const char * function, int code) { if (code != 0) throw os_error(function, code); } dvswitch-0.8.3.6/src/os_error.hpp000066400000000000000000000014201161012451100166400ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings . // See the file "COPYING" for licence details. #ifndef INC_OS_ERROR_HPP #define INC_OS_ERROR_HPP #include // Exception wrapper for error numbers used in errno and return values // of some functions. class os_error : public std::runtime_error { public: explicit os_error(std::string call, int code = 0); int get_code() { return code_; } private: int code_; }; // Throw os_error if the argument is not zero. void os_check_zero(const char *, int); // Throw os_error if the argument is negative; otherwise return the argument. int os_check_nonneg(const char *, int); // Throw os_error with the given code if it is not zero. void os_check_error(const char *, int); #endif // !defined(INC_OS_ERROR_HPP) dvswitch-0.8.3.6/src/protocol.h000066400000000000000000000013531161012451100163140ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_PROTOCOL_H #define DVSWITCH_PROTOCOL_H #define GREETING_SIZE 4 #define GREETING_SOURCE "SORC" #define GREETING_SINK "SINK" #define GREETING_RAW_SINK "RSNK" #define GREETING_REC_SINK "SNKR" #define GREETING_ACT_SOURCE "ASRC" #define SINK_FRAME_HEADER_SIZE 4 #define SINK_FRAME_CUT_FLAG_POS 0 // Length of an activation message. #define ACT_MSG_SIZE 4 // Position of the video active flag byte in the message. All non-zero // values indicate that the mixer is using video frames from the source. #define ACT_MSG_VIDEO_POS 0 // The remaining bytes of the activation message are reserved and should // be 0. #endif // !defined(DVSWITCH_PROTOCOL_H) dvswitch-0.8.3.6/src/ring_buffer.hpp000066400000000000000000000044201161012451100173010ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_RING_BUFFER_HPP #define DVSWITCH_RING_BUFFER_HPP #include #include #include template class ring_buffer { public: ring_buffer() : front_(0), back_(0) {} ring_buffer(const ring_buffer &); ~ring_buffer(); ring_buffer & operator=(const ring_buffer &); std::size_t capacity() const { return N; } std::size_t size() const { return back_ - front_; } bool empty() const { return front_ == back_; } bool full() const { return back_ - front_ == N; } // Reader functions void pop(); const T & front() const; // Writer functions void push(const T &); const T & back() const; private: std::size_t front_, back_; typename std::tr1::aligned_storage::value>::type buffer_; }; template ring_buffer::ring_buffer(const ring_buffer & other) : front_(0), back_(0) { for (std::size_t i = other.front_; i != other.back_; ++i) push(reinterpret_cast(&other.buffer_)[i % N]); } template ring_buffer::~ring_buffer() { while (!empty()) pop(); } template ring_buffer & ring_buffer::operator=(const ring_buffer & other) { while (!empty()) pop(); for (std::size_t i = other.front_; i != other.back_; ++i) push(reinterpret_cast(&other.buffer_)[i % N]); return *this; } template void ring_buffer::pop() { assert(!empty()); reinterpret_cast(&buffer_)[front_ % N].~T(); ++front_; } template const T & ring_buffer::front() const { assert(!empty()); return reinterpret_cast(&buffer_)[front_ % N]; } template void ring_buffer::push(const T & value) { assert(!full()); new (reinterpret_cast(&buffer_) + back_ % N) T(value); ++back_; } template const T & ring_buffer::back() const { assert(!empty()); return reinterpret_cast(&buffer_)[(back_ - 1) % N]; } #endif // !defined(DVSWITCH_RING_BUFFER_HPP) dvswitch-0.8.3.6/src/server.cpp000066400000000000000000000410501161012451100163120ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "frame.h" #include "mixer.hpp" #include "os_error.hpp" #include "protocol.h" #include "server.hpp" #include "socket.h" namespace { // Numbers used in the message pipe enum { message_quit = -1 }; } // connection: base class for client connections class server::connection { public: enum send_status { send_failed, sent_some, sent_all }; virtual ~connection() {} connection * do_receive(); virtual send_status do_send() { return send_failed; } protected: struct receive_buffer { receive_buffer() : pointer(0), size(0) {} receive_buffer(uint8_t * pointer, std::size_t size) : pointer(pointer), size(size) {} uint8_t * pointer; std::size_t size; }; connection(server & server, auto_fd socket); server & server_; auto_fd socket_; private: virtual receive_buffer get_receive_buffer() = 0; virtual connection * handle_complete_receive() = 0; virtual std::ostream & print_identity(std::ostream &) = 0; receive_buffer receive_buffer_; }; // unknown_connection: connection where client type is unknown as yet class server::unknown_connection : public connection { public: unknown_connection(server & server, auto_fd socket); private: virtual receive_buffer get_receive_buffer(); virtual connection * handle_complete_receive(); virtual std::ostream & print_identity(std::ostream &); uint8_t greeting_[4]; }; // source_connection: connection from source class server::source_connection : public connection, private mixer::source { public: source_connection(server & server, auto_fd socket, bool wants_act = false); virtual ~source_connection(); private: virtual send_status do_send(); virtual receive_buffer get_receive_buffer(); virtual connection * handle_complete_receive(); virtual std::ostream & print_identity(std::ostream &); virtual void set_active(mixer::source_activation); dv_frame_ptr frame_; bool first_sequence_; bool wants_act_; // client wants activation messages mixer::source_activation act_flags_; char act_message_[ACT_MSG_SIZE]; std::size_t act_message_pos_; mixer::source_id source_id_; }; // sink_connection: connection from sink class server::sink_connection : public connection, private mixer::sink { public: sink_connection(server &, auto_fd socket, bool is_raw, bool will_record); virtual ~sink_connection(); private: struct queue_elem { dv_frame_ptr frame; bool overflow_before; }; virtual send_status do_send(); virtual receive_buffer get_receive_buffer(); virtual connection * handle_complete_receive(); virtual std::ostream & print_identity(std::ostream &); virtual void put_frame(const dv_frame_ptr & frame); receive_buffer handle_unexpected_input(); bool is_raw_; bool will_record_; bool is_recording_; mixer::sink_id sink_id_; std::size_t frame_pos_; boost::mutex mutex_; // controls access to the following ring_buffer queue_; bool overflowed_; }; // server implementation server::server(const std::string & host, const std::string & port, mixer & mixer) : mixer_(mixer), listen_socket_(create_listening_socket(host.c_str(), port.c_str())), message_pipe_(O_NONBLOCK, O_NONBLOCK) { server_thread_.reset(new boost::thread(boost::bind(&server::serve, this))); } server::~server() { static const int message = message_quit; write(message_pipe_.writer.get(), &message, sizeof(int)); server_thread_->join(); } void server::serve() { enum { poll_index_message, poll_index_listen, poll_count_fixed, poll_index_clients = poll_count_fixed }; std::vector poll_fds(poll_count_fixed); std::vector > connections; poll_fds[poll_index_message].fd = message_pipe_.reader.get(); poll_fds[poll_index_message].events = POLLIN; poll_fds[poll_index_listen].fd = listen_socket_.get(); poll_fds[poll_index_listen].events = POLLIN; for (;;) { int count = poll(&poll_fds[0], poll_fds.size(), -1); if (count < 0) { int error = errno; if (error == EAGAIN || error == EINTR) continue; std::cerr << "ERROR: poll: " << std::strerror(errno) << "\n"; break; } // Check message pipe if (poll_fds[poll_index_message].revents & POLLIN) { int messages[1024]; ssize_t size = read(message_pipe_.reader.get(), messages, sizeof(messages)); if (size > 0) { assert(size % sizeof(int) == 0); for (std::size_t i = 0; i != size / sizeof(int); ++i) { if (messages[i] == message_quit) return; // otherwise message is the number of a file // descriptor we want to send on for (std::size_t j = poll_index_clients; j != poll_fds.size(); ++j) { if (poll_fds[j].fd == messages[i]) { poll_fds[j].events |= POLLOUT; break; } } } } } // Check listening socket if (poll_fds[poll_index_listen].revents & POLLIN) { auto_fd conn_socket(accept(listen_socket_.get(), 0, 0)); try { os_check_nonneg("accept", conn_socket.get()); os_check_nonneg("fcntl", fcntl(conn_socket.get(), F_SETFL, O_NONBLOCK)); pollfd new_poll_fd = { conn_socket.get(), POLLIN, 0 }; poll_fds.reserve(poll_fds.size() + 1); connections.push_back( std::tr1::shared_ptr( new unknown_connection(*this, conn_socket))); poll_fds.push_back(new_poll_fd); } catch (std::exception & e) { std::cerr << "ERROR: " << e.what() << "\n"; } } // Check client connections for (std::size_t i = 0; i != connections.size();) { short revents = poll_fds[poll_index_clients + i].revents; bool should_drop = false; try { if (revents & (POLLHUP | POLLERR)) { should_drop = true; } else if (revents & POLLIN) { connection * new_connection = connections[i]->do_receive(); if (!new_connection) should_drop = true; else if (new_connection != connections[i].get()) connections[i].reset(new_connection); } else if (revents & POLLOUT) { switch (connections[i]->do_send()) { case connection::send_failed: should_drop = true; break; case connection::sent_some: break; case connection::sent_all: poll_fds[poll_index_clients + i].events &= ~POLLOUT; break; } } } catch (std::exception & e) { std::cerr << "ERROR: " << e.what() << "\n"; should_drop = true; } if (should_drop) { connections.erase(connections.begin() + i); poll_fds.erase(poll_fds.begin() + 2 + i); } else { ++i; } } } } void server::enable_output_polling(int fd) { os_check_zero("write", write(message_pipe_.writer.get(), &fd, sizeof(int)) - sizeof(int)); } // connection server::connection::connection(server & server, auto_fd socket) : server_(server), socket_(socket) {} server::connection * server::connection::do_receive() { connection * result = 0; if (receive_buffer_.size == 0) { receive_buffer_ = get_receive_buffer(); assert(receive_buffer_.pointer && receive_buffer_.size); } ssize_t received_size = read(socket_.get(), receive_buffer_.pointer, receive_buffer_.size); if (received_size > 0) { receive_buffer_.pointer += received_size; receive_buffer_.size -= received_size; if (receive_buffer_.size == 0) result = handle_complete_receive(); else result = this; } else if (received_size == -1 && errno == EWOULDBLOCK) { // This is expected when the socket buffer is empty result = this; } if (!result) { // XXX We should distinguish several kinds of failure: network // problems, normal disconnection, protocol violation, and // resource allocation failure. std::cerr << "WARN: Dropping connection from "; print_identity(std::cerr) << "\n"; } return result; } // unknown_connection implementation server::unknown_connection::unknown_connection(server & server, auto_fd socket) : connection(server, socket) {} server::connection::receive_buffer server::unknown_connection::get_receive_buffer() { return receive_buffer(greeting_, sizeof(greeting_)); } server::connection * server::unknown_connection::handle_complete_receive() { enum { client_type_unknown, client_type_source, // source which sends greeting (>= 0.3) client_type_act_source, // source which wants activity (tally) // notifications client_type_sink, // sink which wants DIF with control headers client_type_raw_sink, // sink which wants raw DIF client_type_rec_sink, // sink which wants DIF with control headers // and is recording } client_type; if (std::memcmp(greeting_, GREETING_SOURCE, GREETING_SIZE) == 0) client_type = client_type_source; else if (std::memcmp(greeting_, GREETING_SINK, GREETING_SIZE) == 0) client_type = client_type_sink; else if (std::memcmp(greeting_, GREETING_RAW_SINK, GREETING_SIZE) == 0) client_type = client_type_raw_sink; else if (std::memcmp(greeting_, GREETING_REC_SINK, GREETING_SIZE) == 0) client_type = client_type_rec_sink; else if (std::memcmp(greeting_, GREETING_ACT_SOURCE, GREETING_SIZE) == 0) client_type = client_type_act_source; else client_type = client_type_unknown; switch (client_type) { case client_type_source: case client_type_act_source: return new source_connection(server_, socket_, client_type == client_type_act_source); case client_type_sink: case client_type_raw_sink: case client_type_rec_sink: return new sink_connection(server_, socket_, client_type == client_type_raw_sink, client_type == client_type_rec_sink); default: return 0; } } std::ostream & server::unknown_connection::print_identity(std::ostream & os) { return os << "unknown client"; } // source_connection implementation server::source_connection::source_connection(server & server, auto_fd socket, bool wants_act) : connection(server, socket), frame_(allocate_dv_frame()), first_sequence_(true), wants_act_(wants_act) { source_id_ = server_.mixer_.add_source(this); } server::source_connection::~source_connection() { server_.mixer_.remove_source(source_id_); } void server::source_connection::set_active(mixer::source_activation flags) { if (wants_act_) { act_flags_ = flags; server_.enable_output_polling(socket_.get()); } } server::connection::send_status server::source_connection::do_send() { send_status result = send_failed; if (act_message_pos_ == 0) { // Generate message memset(act_message_, 0, ACT_MSG_SIZE); act_message_[ACT_MSG_VIDEO_POS] = !!(act_flags_ & mixer::source_active_video); } ssize_t sent_size = write(socket_.get(), act_message_, ACT_MSG_SIZE); if (sent_size > 0) { act_message_pos_ += sent_size; if (act_message_pos_ == ACT_MSG_SIZE) { // We've finished sending this message, but we must check // whether the activation flags have changed. act_message_pos_ = 0; if (act_message_[ACT_MSG_VIDEO_POS] == !!(act_flags_ & mixer::source_active_video)) result = sent_all; else result = sent_some; } } else if (sent_size == -1 && errno == EWOULDBLOCK) { result = sent_some; } if (result == send_failed) { // XXX We should distinguish several kinds of failure: network // problems, normal disconnection, protocol violation, and // resource allocation failure. std::cerr << "WARN: Dropping connection from source " << 1 + source_id_ << "\n"; } return result; } server::connection::receive_buffer server::source_connection::get_receive_buffer() { if (first_sequence_) return receive_buffer(frame_->buffer, DIF_SEQUENCE_SIZE); else return receive_buffer(frame_->buffer + DIF_SEQUENCE_SIZE, dv_frame_system(frame_.get())->size - DIF_SEQUENCE_SIZE); } server::connection * server::source_connection::handle_complete_receive() { if (!first_sequence_) { server_.mixer_.put_frame(source_id_, frame_); frame_.reset(); frame_ = allocate_dv_frame(); } first_sequence_ = !first_sequence_; return this; } std::ostream & server::source_connection::print_identity(std::ostream & os) { return os << "source " << 1 + source_id_; } // sink_connection implementation server::sink_connection::sink_connection(server & server, auto_fd socket, bool is_raw, bool will_record) : connection(server, socket), is_raw_(is_raw), will_record_(will_record), is_recording_(false), frame_pos_(0), overflowed_(false) { sink_id_ = server_.mixer_.add_sink(this); } server::sink_connection::~sink_connection() { server_.mixer_.remove_sink(sink_id_); } server::connection::send_status server::sink_connection::do_send() { send_status result = send_failed; bool finished_frame = false; do { struct queue_elem elem; { boost::mutex::scoped_lock lock(mutex_); if (finished_frame) { if (will_record_) is_recording_ = queue_.front().frame->do_record; queue_.pop(); finished_frame = false; } if (queue_.empty()) { result = sent_all; break; } elem = queue_.front(); } if (will_record_ && !is_recording_ && !elem.frame->do_record) { finished_frame = true; continue; } uint8_t frame_header[SINK_FRAME_HEADER_SIZE] = {}; iovec vector[2]; int vector_size; std::size_t frame_size; if (is_raw_) { vector_size = 0; frame_size = 0; } else { uint8_t & flag = frame_header[SINK_FRAME_CUT_FLAG_POS]; if (is_recording_ && !elem.frame->do_record) flag = 'S'; // stop indicator (frame itself will not be sent) else if (elem.overflow_before) flag = 'O'; else if (elem.frame->cut_before) flag = 'C'; else flag = 0; // rest of header left as zero for expansion vector[0].iov_base = frame_header; vector[0].iov_len = SINK_FRAME_HEADER_SIZE; vector_size = 1; frame_size = SINK_FRAME_HEADER_SIZE; } if (!will_record_ || elem.frame->do_record) { vector[vector_size].iov_base = elem.frame->buffer; vector[vector_size].iov_len = dv_frame_system(elem.frame.get())->size; ++vector_size; frame_size += dv_frame_system(elem.frame.get())->size; } int vector_pos = 0; std::size_t rel_pos = frame_pos_; while (rel_pos >= vector[vector_pos].iov_len) rel_pos -= vector[vector_pos++].iov_len; vector[vector_pos].iov_base = static_cast(vector[vector_pos].iov_base) + rel_pos; vector[vector_pos].iov_len -= rel_pos; ssize_t sent_size = writev(socket_.get(), vector + vector_pos, vector_size - vector_pos); if (sent_size > 0) { frame_pos_ += sent_size; if (frame_pos_ == frame_size) { finished_frame = true; frame_pos_ = 0; } result = sent_some; } else if (sent_size == -1 && errno == EWOULDBLOCK) { result = sent_some; } } while (finished_frame); if (result == send_failed) { // XXX We should distinguish several kinds of failure: network // problems, normal disconnection, protocol violation, and // resource allocation failure. std::cerr << "WARN: Dropping connection from sink " << 1 + sink_id_ << "\n"; } return result; } server::connection::receive_buffer server::sink_connection::get_receive_buffer() { static uint8_t dummy; return receive_buffer(&dummy, sizeof(dummy)); } server::connection * server::sink_connection::handle_complete_receive() { return 0; } std::ostream & server::sink_connection::print_identity(std::ostream & os) { return os << "sink " << 1 + sink_id_; } void server::sink_connection::put_frame(const dv_frame_ptr & frame) { bool was_empty = false; { boost::mutex::scoped_lock lock(mutex_); if (queue_.full()) { if (!overflowed_) { std::cerr << "WARN: "; print_identity(std::cerr) << " overflowed\n"; overflowed_ = true; } } else { struct queue_elem elem = { frame, overflowed_ }; if (overflowed_) { std::cout << "INFO: "; print_identity(std::cout) << " recovered\n"; overflowed_ = false; } if (queue_.empty()) was_empty = true; queue_.push(elem); } } if (was_empty) server_.enable_output_polling(socket_.get()); } dvswitch-0.8.3.6/src/server.hpp000066400000000000000000000013431161012451100163200ustar00rootroot00000000000000// Copyright 2007 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_SERVER_HPP #define DVSWITCH_SERVER_HPP #include #include #include #include "auto_fd.hpp" #include "auto_pipe.hpp" #include "mixer.hpp" class server { public: server(const std::string & host, const std::string & port, mixer & mixer); ~server(); private: class connection; class unknown_connection; class source_connection; class sink_connection; void serve(); void enable_output_polling(int fd); mixer & mixer_; auto_fd listen_socket_; auto_pipe message_pipe_; std::auto_ptr server_thread_; }; #endif // !defined(DVSWITCH_SERVER_HPP) dvswitch-0.8.3.6/src/socket.c000066400000000000000000000036661161012451100157470ustar00rootroot00000000000000/* Copyright 2007-2008 Ben Hutchings. * See the file "COPYING" for licence details. */ #include #include #include #include #include #include int create_connected_socket(const char * host, const char * port) { struct addrinfo addr_hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_ADDRCONFIG }; struct addrinfo * addr; int error; if ((error = getaddrinfo(host, port, &addr_hints, &addr))) { fprintf(stderr, "ERROR: getaddrinfo: %s\n", gai_strerror(error)); exit(1); } /* XXX Should we walk the list rather than only trying the first? */ int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (sock < 0) { perror("ERROR: socket"); exit(1); } if (connect(sock, addr->ai_addr, addr->ai_addrlen) != 0) { perror("ERROR: connect"); exit(1); } freeaddrinfo(addr); return sock; } int create_listening_socket(const char * host, const char * port) { struct addrinfo addr_hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_ADDRCONFIG }; struct addrinfo * addr; int error; if ((error = getaddrinfo(host, port, &addr_hints, &addr))) { fprintf(stderr, "ERROR: getaddrinfo: %s\n", gai_strerror(error)); exit(1); } /* XXX Should we walk the list rather than only trying the first? */ int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (sock < 0) { perror("ERROR: socket"); exit(1); } static const int one = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0) { perror("ERROR: setsockopt"); exit(1); } if (bind(sock, addr->ai_addr, addr->ai_addrlen) != 0) { perror("ERROR: bind"); exit(1); } if (listen(sock, 10) != 0) { perror("ERROR: listen"); exit(1); } freeaddrinfo(addr); return sock; } dvswitch-0.8.3.6/src/socket.h000066400000000000000000000005771161012451100157520ustar00rootroot00000000000000/* Copyright 2007 Ben Hutchings. * See the file "COPYING" for licence details. */ #ifndef DVSWITCH_SOCKET_H #define DVSWITCH_SOCKET_H #ifdef __cplusplus extern "C" { #endif int create_connected_socket(const char * host, const char * port); int create_listening_socket(const char * host, const char * port); #ifdef __cplusplus } #endif #endif /* !defined(DVSWITCH_SOCKET_H) */ dvswitch-0.8.3.6/src/status_overlay.cpp000066400000000000000000000076051161012451100201000ustar00rootroot00000000000000// Copyright 2009 Ben Hutchings. // See the file "COPYING" for licence details. #include #include "gui.hpp" #include "status_overlay.hpp" const int status_scale = 64; #define STATUS_TEXT_HEIGHT "48" // must be a string :-( status_overlay::status_overlay() : main_widget_(0) { // We do not need a window and do not implement realize() set_flags(Gtk::NO_WINDOW); status_widget_.set_parent(*this); Gdk::Color colour; colour.set_grey(0); // black status_widget_.modify_bg(Gtk::STATE_NORMAL, colour); } status_overlay::~status_overlay() { status_widget_.unparent(); } void status_overlay::set_status(const Glib::ustring & text, const Glib::ustring & icon_name, unsigned timeout) { status_widget_.set_status(text, icon_name); status_widget_.show(); // Cancel any timer for the previous status if (timer_) { // Glib::Source deletes itself when destroyed, despite the // fact that it's supposed to be reference-counted. // Therefore, dispose of the RefPtr before calling destroy. Glib::TimeoutSource * timer = timer_.operator->(); timer_.reset(); timer->destroy(); } // Start new timer if necessary if (timeout) { timer_ = Glib::TimeoutSource::create(timeout * 1000); timer_->connect(sigc::mem_fun(this, &status_overlay::clear)); timer_->attach(Glib::MainContext::get_default()); } } bool status_overlay::clear() { status_widget_.hide(); timer_.reset(); return false; } GType status_overlay::child_type_vfunc() const { // If there is no main widget, any widget can be added. // If there is a min widget, no widgets can be added. return main_widget_ ? G_TYPE_NONE : GTK_TYPE_WIDGET; } void status_overlay::forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data) { if (main_widget_) callback(main_widget_->gobj(), callback_data); if (include_internals) callback(status_widget_.Widget::gobj(), callback_data); } void status_overlay::on_add(Gtk::Widget * widget) { assert(!main_widget_ && widget); main_widget_ = widget; main_widget_->set_parent(*this); } void status_overlay::on_remove(Gtk::Widget * widget) { assert(widget == main_widget_); main_widget_->unparent(); main_widget_ = 0; } void status_overlay::on_size_allocate(Gtk::Allocation & allocation) { if (main_widget_) main_widget_->size_allocate(allocation); status_widget_.size_allocate( Gtk::Allocation(allocation.get_x(), allocation.get_y() + allocation.get_height() - status_scale, allocation.get_width(), status_scale)); } void status_overlay::on_size_request(Gtk::Requisition * requisition) { if (main_widget_) { *requisition = main_widget_->size_request(); } else { requisition->height = status_scale; requisition->width = status_scale * 5; } } void status_overlay::status_widget::set_status(const Glib::ustring & text, const Glib::ustring & icon_name) { text_ = text; try { icon_ = load_icon(icon_name, status_scale); } catch (Gtk::IconThemeError &) { icon_.reset(); } queue_draw(); } bool status_overlay::status_widget::on_expose_event(GdkEventExpose *) throw() { Glib::RefPtr drawable(get_window()); if (Glib::RefPtr gc = Gdk::GC::create(drawable)) { if (icon_) drawable->draw_pixbuf(gc, icon_, 0, 0, 0, 0, -1, -1, Gdk::RGB_DITHER_NORMAL, 0, 0); if (!text_.empty()) { Glib::RefPtr pango = get_pango_context(); pango->set_font_description( Pango::FontDescription( Glib::ustring("sans " STATUS_TEXT_HEIGHT "px"))); Gdk::Color colour; colour.set_grey(65535); // white gc->set_rgb_fg_color(colour); Glib::RefPtr layout = Pango::Layout::create(pango); layout->set_text(text_); drawable->draw_layout(gc, status_scale, status_scale / 8, layout); } } return true; } dvswitch-0.8.3.6/src/status_overlay.hpp000066400000000000000000000024041161012451100200750ustar00rootroot00000000000000// Copyright 2009 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_STATUS_OVERLAY_HPP #define DVSWITCH_STATUS_OVERLAY_HPP #include #include class status_overlay : public Gtk::Container { public: status_overlay(); ~status_overlay(); void set_status(const Glib::ustring & text, const Glib::ustring & icon_name, unsigned timeout = 0); private: virtual GType child_type_vfunc() const; virtual void forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data); virtual void on_add(Gtk::Widget * widget); virtual void on_remove(Gtk::Widget * widget); virtual void on_size_allocate(Gtk::Allocation & allocation); virtual void on_size_request(Gtk::Requisition *); bool clear(); class status_widget : public Gtk::DrawingArea { public: void set_status(const Glib::ustring & text, const Glib::ustring & icon_name); private: virtual bool on_expose_event(GdkEventExpose *) throw(); Glib::ustring text_; Glib::RefPtr icon_; }; Gtk::Widget * main_widget_; status_widget status_widget_; Glib::RefPtr timer_; }; #endif // DVSWITCH_STATUS_OVERLAY_HPP dvswitch-0.8.3.6/src/video_effect.c000066400000000000000000000164001161012451100170670ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include "video_effect.h" enum { luma_bias = 16, // black level (lower values are reserved for sync) luma_max = 235, chroma_bias = 128 // neutral level (chroma components are signed) }; void video_effect_show_title_safe(struct raw_frame_ref dest) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(dest.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); unsigned width = FRAME_WIDTH; unsigned height = dest.height; unsigned border_horiz = (FRAME_WIDTH + 5) / 10; unsigned border_vert = (dest.height + 5) / 10; unsigned bias = luma_bias; // Darken the non-title-safe area for (int plane = 0; plane != 3; ++plane) { if (plane == 1) { width >>= chroma_shift_horiz; border_horiz >>= chroma_shift_horiz; height >>= chroma_shift_vert; border_vert >>= chroma_shift_vert; bias = chroma_bias; } for (unsigned y = 0; y != height; ++y) { uint8_t * p, * end; // Do left border p = dest.planes.data[plane] + dest.planes.linesize[plane] * y; end = p + border_horiz; while (p != end) *p = (*p + bias) / 2, ++p; end = p + width - border_horiz; if (y >= border_vert && y < height - border_vert) // Skip to right border p += width - 2 * border_horiz; // else continue across top border or bottom border while (p != end) *p = (*p + bias) / 2, ++p; } } } void video_effect_brighten(struct raw_frame_ref dest, struct rectangle d_rect) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(dest.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); unsigned bias = luma_max; for (int plane = 0; plane != 3; ++plane) { if (plane == 1) { d_rect.left >>= chroma_shift_horiz; d_rect.right >>= chroma_shift_horiz; d_rect.top >>= chroma_shift_vert; d_rect.bottom >>= chroma_shift_vert; bias = chroma_bias; } for (unsigned y = d_rect.top; y != (unsigned)d_rect.bottom; ++y) { uint8_t * p = (dest.planes.data[plane] + dest.planes.linesize[plane] * y + d_rect.left); uint8_t * end = p + (d_rect.right - d_rect.left); while (p != end) *p = (*p + bias) / 2, ++p; } } } void video_effect_pic_in_pic(struct raw_frame_ref dest, struct rectangle d_rect, struct raw_frame_ref source, struct rectangle s_rect) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(dest.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); // Round coordinates so they include whole numbers of chroma pixels s_rect.left &= -(1U << chroma_shift_horiz); s_rect.right &= -(1U << chroma_shift_horiz); s_rect.top &= -(1U << chroma_shift_vert); s_rect.bottom &= -(1U << chroma_shift_vert); d_rect.left &= -(1U << chroma_shift_horiz); d_rect.right &= -(1U << chroma_shift_horiz); d_rect.top &= -(1U << chroma_shift_vert); d_rect.bottom &= -(1U << chroma_shift_vert); assert(s_rect.left >= 0 && s_rect.left < s_rect.right && s_rect.right <= FRAME_WIDTH); assert(s_rect.top >= 0 && s_rect.top < s_rect.bottom && (unsigned)s_rect.bottom <= source.height); assert(d_rect.left >= 0 && d_rect.left <= d_rect.right && d_rect.right <= FRAME_WIDTH); assert(d_rect.top >= 0 && d_rect.top <= d_rect.bottom && (unsigned)d_rect.bottom <= dest.height); if (d_rect.left == d_rect.right || d_rect.top == d_rect.bottom) return; unsigned s_left = s_rect.left; unsigned s_width = s_rect.right - s_rect.left; unsigned s_top = s_rect.top; unsigned s_height = s_rect.bottom - s_rect.top; unsigned d_left = d_rect.left; unsigned d_width = d_rect.right - d_rect.left; unsigned d_top = d_rect.top; unsigned d_height = d_rect.bottom - d_rect.top; assert(d_width <= s_width && d_height <= s_height); // Scaling tables struct weights { // Weight of source column/row on current dest column/row uint16_t cur; // Weight of source column/row on next dest column/row, plus 1 // if this the last source column/row for this dest column/row. uint16_t spill; }; struct weights col_weights[FRAME_WIDTH]; struct weights row_weights[FRAME_HEIGHT_MAX]; unsigned e, x, y; uint32_t weight_scale = (((1ULL << 32) + s_width * s_height / 2) / (s_width * s_height)); e = 0; for (x = 0; x != s_width; ++x) { e += d_width; if (e >= s_width) { e -= s_width; col_weights[x].cur = d_width - e; col_weights[x].spill = 1 + e; } else { col_weights[x].cur = d_width; col_weights[x].spill = 0; } } e = 0; for (y = 0; y != s_height; ++y) { e += d_height; if (e >= s_height) { e -= s_height; row_weights[y].cur = d_height - e; row_weights[y].spill = 1 + e; } else { row_weights[y].cur = d_height; row_weights[y].spill = 0; } } assert(col_weights[s_width - 1].spill == 1); assert(col_weights[(s_width >> chroma_shift_horiz) - 1].spill == 1); assert(row_weights[s_height - 1].spill == 1); assert(row_weights[(s_height >> chroma_shift_vert) - 1].spill == 1); for (unsigned plane = 0; plane != 3; ++plane) { if (plane == 1) { d_left >>= chroma_shift_horiz; d_width >>= chroma_shift_horiz; s_left >>= chroma_shift_horiz; s_width >>= chroma_shift_horiz; d_top >>= chroma_shift_vert; d_height >>= chroma_shift_vert; s_top >>= chroma_shift_vert; s_height >>= chroma_shift_vert; } uint8_t * dest_p = (dest.planes.data[plane] + d_top * dest.planes.linesize[plane] + d_left); const unsigned dest_gap = dest.planes.linesize[plane] - d_width; uint32_t row_buffer[FRAME_WIDTH], * row_p; memset(row_buffer, 0, d_width * sizeof(uint32_t)); // Loop over source rows for (y = 0; ; ++y) { unsigned row_weight = row_weights[y].cur; unsigned row_spill = row_weights[y].spill; // Loop over source columns const uint8_t * source_p = source.planes.data[plane] + source.planes.linesize[plane] * (s_top + y) + s_left; row_p = row_buffer; uint32_t value_sum = *row_p; for (x = 0; x != s_width; ++x) { unsigned value_rw = *source_p++ * row_weight; value_sum += value_rw * col_weights[x].cur; if (col_weights[x].spill) { *row_p++ = value_sum; value_sum = *row_p + value_rw * (col_weights[x].spill - 1); } } if (!row_spill) continue; // Spit out destination row row_p = row_buffer; for (x = 0; x != d_width; ++x) *dest_p++ = (*row_p++ * (uint64_t)weight_scale + (1U << 31)) >> 32; if (y == s_height - 1) break; dest_p += dest_gap; // Scale source row to next dest row if it overlaps // otherwise just reinitialise row buffer row_weight = row_spill - 1; if (!row_weight) { memset(row_buffer, 0, d_width * sizeof(uint32_t)); } else { source_p -= s_width; row_p = row_buffer; uint32_t value_sum = 0; for (x = 0; x != s_width; ++x) { unsigned value_rw = *source_p++ * row_weight; value_sum += value_rw * col_weights[x].cur; if (col_weights[x].spill) { *row_p++ = value_sum; value_sum = value_rw * (col_weights[x].spill - 1); } } } } } } dvswitch-0.8.3.6/src/video_effect.h000066400000000000000000000012071161012451100170730ustar00rootroot00000000000000// Copyright 2007-2008 Ben Hutchings. // See the file "COPYING" for licence details. #ifndef DVSWITCH_VIDEO_EFFECT_H #define DVSWITCH_VIDEO_EFFECT_H #ifdef __cplusplus extern "C" { #endif #include "frame.h" #include "geometry.h" void video_effect_show_title_safe(struct raw_frame_ref dest); void video_effect_brighten(struct raw_frame_ref dest, struct rectangle dest_rect); void video_effect_pic_in_pic(struct raw_frame_ref dest, struct rectangle dest_rect, struct raw_frame_ref source, struct rectangle source_rect); #ifdef __cplusplus } #endif #endif // !defined(DVSWITCH_VIDEO_EFFECT_H) dvswitch-0.8.3.6/src/vu_meter.cpp000066400000000000000000000103441161012451100166340ustar00rootroot00000000000000// Copyright 2009 Ben Hutchings. // See the file "COPYING" for licence details. #include #include // for snprintf() #include "vu_meter.hpp" namespace { Glib::ustring int_to_string(int n) { char buf[sizeof(n) * 8]; // should always be large enough snprintf(buf, sizeof(buf), "%d", n); return Glib::ustring(buf); } } vu_meter::vu_meter(int minimum, int maximum) : minimum_(minimum), maximum_(maximum) { for (int channel = 0; channel != channel_count; ++channel) levels_[channel] = std::numeric_limits::min(); set_size_request(16, 32); } void vu_meter::set_levels(const int * levels) { for (int channel = 0; channel != channel_count; ++channel) levels_[channel] = levels[channel]; queue_draw(); } bool vu_meter::on_expose_event(GdkEventExpose *) throw() { int width = get_width(), height = get_height(); Glib::RefPtr drawable(get_window()); if (Glib::RefPtr gc = Gdk::GC::create(drawable)) { // Draw segments of height 4 with 2 pixel (black) dividers. // The segments fade from green (min) to red (max). // Draw ticks at the minimum, maximum and 6 dB intervals, // lablled so far as possible without overlap. static const int border_thick = 2, tick_width = 6; static const int seg_height = 4, seg_vspacing = seg_height + border_thick; static const int tick_interval = 6; Glib::RefPtr pango = get_pango_context(); Glib::RefPtr layout = Pango::Layout::create(pango); int label_width, label_height; layout->set_text(int_to_string(minimum_)); layout->get_pixel_size(label_width, label_height); layout->set_alignment(Pango::ALIGN_RIGHT); layout->set_width(label_width * Pango::SCALE); int scale_width = width - label_width - border_thick - tick_width; int scale_height = height - label_height; int seg_count = (scale_height - border_thick) / seg_vspacing; int seg_hspacing = (scale_width - border_thick) / channel_count; int seg_width = seg_hspacing - border_thick; if (seg_width <= 0 || height < label_height * 2) return true; // cannot fit the scale in int label_interval = tick_interval * std::max(1, (maximum_ - minimum_) / tick_interval / (height / label_height - 1)); drawable->draw_line(gc, label_width + border_thick, label_height / 2, label_width + border_thick + tick_width, label_height / 2); layout->set_text(int_to_string(maximum_)); drawable->draw_layout(gc, 0, 0, layout); drawable->draw_line(gc, label_width + border_thick, label_height / 2 + scale_height - 1, label_width + border_thick + tick_width, label_height / 2 + scale_height - 1); layout->set_text(int_to_string(minimum_)); drawable->draw_layout(gc, 0, height - label_height, layout); for (int value = minimum_ / tick_interval * tick_interval; value < maximum_; value += tick_interval) { int y = (scale_height - 1) * (maximum_ - value) / (maximum_ - minimum_); drawable->draw_line(gc, label_width + border_thick, y + label_height / 2, label_width + border_thick + tick_width, y + label_height / 2); if (value % label_interval == 0 && y >= label_height && y <= height - label_height * 2) { layout->set_text(int_to_string(value)); drawable->draw_layout(gc, 0, y, layout); } } Gdk::Color colour; colour.set_grey(0); gc->set_rgb_fg_color(colour); drawable->draw_rectangle(gc, true, label_width + tick_width, label_height / 2, width - label_width - tick_width, height - label_height); for (int channel = 0; channel != channel_count; ++channel) { if (levels_[channel] >= minimum_) { int seg_lit_count = 1 + (((seg_count - 1) * (levels_[channel] - minimum_) + ((maximum_ - minimum_) / 2)) / (maximum_ - minimum_)); for (int seg = 0; seg < seg_lit_count; ++seg) { colour.set_rgb(65535 * seg / seg_count, 65535 * (seg_count - seg) / seg_count, 0); gc->set_rgb_fg_color(colour); drawable->draw_rectangle(gc, true, width - scale_width + channel * seg_hspacing, label_height / 2 + (seg_count - seg) * seg_vspacing - seg_height, seg_width, seg_height); } } } } return true; } dvswitch-0.8.3.6/src/vu_meter.hpp000066400000000000000000000006351161012451100166430ustar00rootroot00000000000000// Copyright 2009 Ben Hutchings. // See the file "COPYING" for licence details. #include class vu_meter : public Gtk::DrawingArea { public: vu_meter(int minimum, int maximum); static const int channel_count = 2; void set_levels(const int * levels); private: virtual bool on_expose_event(GdkEventExpose *) throw(); int minimum_, maximum_, levels_[channel_count]; }; dvswitch-0.8.3.6/tests/000077500000000000000000000000001161012451100146535ustar00rootroot00000000000000dvswitch-0.8.3.6/tests/CMakeLists.txt000066400000000000000000000015631161012451100174200ustar00rootroot00000000000000add_definitions(${LIBAVCODEC_CFLAGS_OTHER}) include_directories(${LIBAVCODEC_INCLUDE_DIRS}) link_directories(${LIBAVCODEC_LIBRARY_DIRS}) add_executable(mixer mixer.cpp ../src/mixer.cpp ../src/frame_timer.c ../src/dif.c ../src/dif_audio.c ../src/frame_pool.cpp ../src/auto_codec.cpp ../src/frame.c ../src/os_error.cpp ../src/video_effect.c) target_link_libraries(mixer pthread rt ${BOOST_THREAD_LIBRARIES} ${LIBAVCODEC_LIBRARIES} ${LIBAVUTIL_LIBRARIES}) add_executable(ring_buffer ring_buffer.cpp) add_executable(pic_in_pic pic_in_pic.cpp ../src/video_effect.c) target_link_libraries(pic_in_pic ${LIBAVCODEC_LIBRARIES}) add_executable(pic_in_pic_speed pic_in_pic.cpp ../src/video_effect.c) set_target_properties(pic_in_pic_speed PROPERTIES COMPILE_FLAGS -DTEST_SPEED) target_link_libraries(pic_in_pic_speed ${LIBAVCODEC_LIBRARIES}) dvswitch-0.8.3.6/tests/mixer.cpp000066400000000000000000000031241161012451100165030ustar00rootroot00000000000000// Copyright 2007-2009 Ben Hutchings. // See the file "COPYING" for licence details. #include #include #include #include "frame.h" #include "frame_pool.hpp" #include "mixer.hpp" // The use of volatile in this test program is not an endorsement of its // use in production multithreaded code. It probably works here, but I // wouldn't want to depend on it. namespace { class dummy_source : public mixer::source { public: dummy_source() {} private: virtual void set_active(mixer::source_activation flags) { std::cout << "video source " << ((flags & mixer::source_active_video) ? "" : "de") << "activated\n"; } }; class dummy_sink : public mixer::sink { public: dummy_sink(volatile unsigned & sink_count) : sink_count_(sink_count) {} private: virtual void put_frame(const dv_frame_ptr &) { std::cout << "sinked frame\n"; ++sink_count_; } virtual void cut() { std::cout << "sinked cut\n"; } volatile unsigned & sink_count_; }; } int main() { volatile unsigned sink_count = 0; unsigned source_count = 0; mixer the_mixer; the_mixer.add_source(new dummy_source); the_mixer.add_sink(new dummy_sink(sink_count)); for (;;) { if (source_count - sink_count < 8) { dv_frame_ptr frame(allocate_dv_frame()); frame->buffer[3] = 0xBF; // 625/50 frame the_mixer.put_frame(0, frame); ++source_count; std::cout << "sourced frame\n"; if ((std::rand() & 0x1F) == 0) { the_mixer.cut(); std::cout << "cut\n"; } } usleep(10000); } } dvswitch-0.8.3.6/tests/pic_in_pic.cpp000066400000000000000000000200761161012451100174600ustar00rootroot00000000000000#include #include #include #include "avcodec_wrap.h" #include "video_effect.h" const uint32_t source_colour = 0xfefefe, dest_colour = 0x000000, pad_colour = 0xbadbad; #ifdef TEST_SPEED const int pad = 0; const int dims[] = { 480, 576, 702, 712, 720 }; #else const int pad = 100; const int dims[] = { 1, 2, 3, 4, 15, 16, 17, 31, 32, 33, 64, 128, 256, 480, 576, 702, 712, 719, 720 }; #endif const int n_dims = sizeof(dims) / sizeof(dims[0]); void alloc_plane(raw_frame_ref & frame, int i, int width, int height) { size_t size = (width + 2 * pad) * (height + 2 * pad); uint8_t * buf = new uint8_t[size]; frame.planes.linesize[i] = width + 2 * pad; frame.planes.data[i] = buf + frame.planes.linesize[i] * pad + pad; } raw_frame_ref alloc_frame(PixelFormat pix_fmt, int width, int height) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); raw_frame_ref frame; alloc_plane(frame, 0, width, height); alloc_plane(frame, 1, width >> chroma_shift_horiz, height >> chroma_shift_vert); alloc_plane(frame, 2, width >> chroma_shift_horiz, height >> chroma_shift_vert); frame.planes.data[3] = 0; frame.planes.linesize[3] = 0; frame.pix_fmt = pix_fmt; frame.height = height; return frame; } void free_frame(raw_frame_ref frame) { for (int i = 0; i != 3; ++i) delete[] (frame.planes.data[i] - frame.planes.linesize[i] * pad - pad); } void fill_rect_colour(raw_frame_ref frame, rectangle rect, uint32_t colour) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(frame.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); for (int i = 0; i != 3; ++i) { if (i == 1) { rect.left >>= chroma_shift_horiz; rect.right >>= chroma_shift_horiz; rect.top >>= chroma_shift_vert; rect.bottom >>= chroma_shift_vert; } const uint8_t value = colour >> (16 - 8 * i); for (int y = rect.top; y != rect.bottom; ++y) std::memset(frame.planes.data[i] + frame.planes.linesize[i] * y + rect.left, value, rect.right - rect.left); } } void assert_rect_colour(raw_frame_ref frame, rectangle rect, uint32_t colour) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(frame.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); bool matched = true; for (int i = 0; i != 3; ++i) { if (i == 1) { rect.left >>= chroma_shift_horiz; rect.right >>= chroma_shift_horiz; rect.top >>= chroma_shift_vert; rect.bottom >>= chroma_shift_vert; } const uint8_t value = colour >> (16 - 8 * i); for (int y = rect.top; y != rect.bottom; ++y) for (int x = rect.left; x != rect.right; ++x) { uint8_t found_value = *(frame.planes.data[i] + y * frame.planes.linesize[i] + x); if (found_value != value) { std::cerr << "mismatch at (" << i << "," << x << "," << y << "): expected " << unsigned(value) << " found " << unsigned(found_value) << "\n"; matched = false; } } } assert(matched); } void assert_padding_unchanged(raw_frame_ref frame, int width, int height) { rectangle top_border = { -pad, -pad, width + pad, 0 }; rectangle left_border = { -pad, 0, 0, height }; rectangle right_border = { width, 0, width + pad, height }; rectangle bottom_border = { -pad, height, width + pad, height + pad }; assert_rect_colour(frame, top_border, pad_colour); assert_rect_colour(frame, left_border, pad_colour); assert_rect_colour(frame, right_border, pad_colour); assert_rect_colour(frame, bottom_border, pad_colour); } void test_pic_in_pic(raw_frame_ref dest, int d_width, int d_height, int d_r_width, int d_r_height, raw_frame_ref source, int s_width, int s_height) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(dest.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); rectangle s_rect = { 0, 0, s_width, s_height }; for (int place_horiz = 0; place_horiz != 3; ++place_horiz) { rectangle d_rect; d_rect.left = (((d_width - d_r_width) * place_horiz / 2) & -(1U << chroma_shift_horiz)); if (place_horiz != 0 && d_rect.left == 0) continue; d_rect.right = d_rect.left + d_r_width; for (int place_vert = 0; place_vert != 3; ++place_vert) { d_rect.top = (((d_height - d_r_height) * place_vert / 2) & -(1U << chroma_shift_vert)); if (place_vert != 0 && d_rect.top == 0) continue; d_rect.bottom = d_rect.top + d_r_height; video_effect_pic_in_pic(dest, d_rect, source, s_rect); #ifndef TEST_SPEED // Check we overwrote the area we were supposed to assert_rect_colour(dest, d_rect, source_colour); // Check we missed the rest of the destination frame rectangle top_border = { 0, 0, d_width, d_rect.top }; rectangle left_border = { 0, d_rect.top , d_rect.left, d_rect.bottom }; rectangle right_border = { d_rect.right, d_rect.top, d_width, d_rect.bottom }; rectangle bottom_border = { 0, d_rect.bottom, d_width, d_height }; assert_rect_colour(dest, top_border, dest_colour); assert_rect_colour(dest, left_border, dest_colour); assert_rect_colour(dest, right_border, dest_colour); assert_rect_colour(dest, bottom_border, dest_colour); // Check we missed the padding assert_padding_unchanged(dest, d_width, d_height); // Restore destination area fill_rect_colour(dest, d_rect, dest_colour); #endif } } } void test_dest(raw_frame_ref dest, int d_width, int d_height, int s_width, int s_height) { raw_frame_ref source = alloc_frame(dest.pix_fmt, s_width, s_height); rectangle s_buf_rect = { -pad, -pad, s_width + pad, s_height + pad }; fill_rect_colour(source, s_buf_rect, pad_colour); rectangle s_frame_rect = { 0, 0, s_width, s_height }; fill_rect_colour(source, s_frame_rect, source_colour); int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(dest.pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); for (int i = 0; i != n_dims; ++i) { int d_r_width = dims[i]; if (d_r_width > d_width || d_r_width > s_width) break; if (d_r_width & ((1 << chroma_shift_horiz) - 1)) continue; for (int j = 0; j != n_dims; ++j) { int d_r_height = dims[j]; if (d_r_height > d_height || d_r_height > s_height) break; if (d_r_height & ((1 << chroma_shift_vert) - 1)) continue; test_pic_in_pic(dest, d_width, d_height, d_r_width, d_r_height, source, s_width, s_height); } } free_frame(source); } void test_format_size(PixelFormat pix_fmt, int d_width, int d_height) { raw_frame_ref dest = alloc_frame(pix_fmt, d_width, d_height); rectangle d_buf_rect = { -pad, -pad, d_width + pad, d_height + pad }; fill_rect_colour(dest, d_buf_rect, pad_colour); rectangle d_frame_rect = { 0, 0, d_width, d_height }; fill_rect_colour(dest, d_frame_rect, dest_colour); int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); for (int i = 0; i != n_dims; ++i) { int s_width = dims[i]; if ((s_width & ((1 << chroma_shift_horiz) - 1)) == 0) for (int j = 0; j != n_dims; ++j) { int s_height = dims[j]; if (s_height > FRAME_HEIGHT_MAX) break; if (s_height & ((1 << chroma_shift_vert) - 1)) continue; test_dest(dest, d_width, d_height, s_width, s_height); } } free_frame(dest); } void test_format(PixelFormat pix_fmt) { int chroma_shift_horiz, chroma_shift_vert; avcodec_get_chroma_sub_sample(pix_fmt, &chroma_shift_horiz, &chroma_shift_vert); for (int i = 0; i != n_dims; ++i) { int width = dims[i]; if ((width & ((1 << chroma_shift_horiz) - 1)) == 0) for (int j = 0; j != n_dims; ++j) { int height = dims[j]; if ((height & ((1 << chroma_shift_vert) - 1)) == 0) test_format_size(pix_fmt, width, height); } } } int main() { avcodec_init(); avcodec_register_all(); test_format(PIX_FMT_YUV420P); test_format(PIX_FMT_YUV411P); } dvswitch-0.8.3.6/tests/ring_buffer.cpp000066400000000000000000000025201161012451100176460ustar00rootroot00000000000000#ifdef NDEBUG #error "This is a test program and requires assertions to be enabled." #endif #include "ring_buffer.hpp" int main() { ring_buffer buf; assert(buf.size() == 0); assert(buf.empty()); buf.push(1); assert(buf.front() == 1); assert(buf.back() == 1); assert(buf.size() == 1); assert(!buf.empty() && !buf.full()); buf.push(2); assert(buf.front() == 1); assert(buf.back() == 2); assert(buf.size() == 2); assert(!buf.empty() && buf.full()); buf.pop(); assert(buf.front() == 2); assert(buf.back() == 2); assert(buf.size() == 1); assert(!buf.empty() && !buf.full()); ring_buffer buf2(buf); assert(buf2.front() == 2); assert(buf2.back() == 2); assert(buf2.size() == 1); assert(!buf2.empty() && !buf2.full()); buf.push(3); assert(buf.front() == 2); assert(buf.back() == 3); assert(buf.size() == 2); assert(!buf.empty() && buf.full()); assert(buf2.front() == 2); assert(buf2.back() == 2); assert(buf2.size() == 1); assert(!buf2.empty() && !buf2.full()); buf.pop(); assert(buf.size() == 1); buf.pop(); assert(buf.size() == 0); assert(buf.empty()); buf = buf2; assert(buf.front() == 2); assert(buf.back() == 2); assert(buf.size() == 1); assert(!buf.empty() && !buf.full()); }