diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a73b00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/deploy/ +/build_log.txt +/configure_log.txt +/icon_preview.png diff --git a/caiic_logo.jpg b/caiic_logo.jpg new file mode 100644 index 0000000..f8af084 Binary files /dev/null and b/caiic_logo.jpg differ diff --git a/obs-studio/deps/libdshowcapture/src/.gitattributes b/obs-studio/deps/libdshowcapture/src/.gitattributes new file mode 100644 index 0000000..4c165cd --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/.gitattributes @@ -0,0 +1,9 @@ +* text=auto + +*.sln text eol=crlf +*.vcproj text eol=crlf +*.vcxproj text eol=crlf +*.vcxproj text eol=crlf +*.vcxproj.filters text eol=crlf + +cmake/ALL_BUILD.vcxproj.user.in text eol=crlf diff --git a/obs-studio/deps/libdshowcapture/src/.gitignore b/obs-studio/deps/libdshowcapture/src/.gitignore new file mode 100644 index 0000000..be62c56 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/.gitignore @@ -0,0 +1,47 @@ +*.exe +*.dll + +/release/ +/debug/ +/builds/ +/other/ +Release_MD/ +Release/ +Debug/ +x64/ +ipch/ +GeneratedFiles/ + +.hg +tags +*.trace +*.vsp +*.psess +*.swp +*.dat +*.clbin +*.log +*.tlog +*.sdf +*.opensdf +*.xml +*.ipch +*.css +*.xslt +*.aps +*.suo +*.ncb +*.user +*.lo +*.ilk +*.la +*.o +*.obj +*.pdb +*.res +*.manifest +*.dep +*.zip +*.lnk +*.chm +*~ diff --git a/obs-studio/deps/libdshowcapture/src/.gitmodules b/obs-studio/deps/libdshowcapture/src/.gitmodules new file mode 100644 index 0000000..7ed50be --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/capture-device-support"] + path = external/capture-device-support + url = https://github.com/elgatosf/capture-device-support diff --git a/obs-studio/deps/libdshowcapture/src/CMakeLists.txt b/obs-studio/deps/libdshowcapture/src/CMakeLists.txt new file mode 100644 index 0000000..a60ab9b --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.28...3.30) + +project(libdshowcapture) + +if(NOT MSVC) + message(FATAL_ERROR "libdshowcapture only supports building with Visual Studio and MSVC.") +endif() + +option(BUILD_SHARED_LIBS "Build shared library" ON) + +# Set C and C++ language standards to C17 and C++17 +set(CMAKE_C_STANDARD 17) +set(CMAKE_C_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) + +# Disable C and C++ extensions +set(CMAKE_C_EXTENSIONS FALSE) +set(CMAKE_CXX_EXTENSIONS FALSE) + +# Set symbols to be hidden by default for C and C++ +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) + +add_library(libdshowcapture) + +target_sources( + libdshowcapture + PRIVATE + external/capture-device-support/Library/EGAVResult.cpp + external/capture-device-support/Library/ElgatoUVCDevice.cpp + external/capture-device-support/Library/win/EGAVHIDImplementation.cpp + external/capture-device-support/SampleCode/DriverInterface.cpp + source/capture-filter.cpp + source/output-filter.cpp + source/dshowcapture.cpp + source/dshowencode.cpp + source/device.cpp + source/device-vendor.cpp + source/encoder.cpp + source/dshow-base.cpp + source/dshow-demux.cpp + source/dshow-enum.cpp + source/dshow-formats.cpp + source/dshow-media-type.cpp + source/dshow-encoded-device.cpp + source/log.cpp + dshowcapture.hpp + source/external/IVideoCaptureFilter.h + source/capture-filter.hpp + source/output-filter.hpp + source/device.hpp + source/encoder.hpp + source/dshow-base.hpp + source/dshow-demux.hpp + source/dshow-device-defs.hpp + source/dshow-enum.hpp + source/dshow-formats.hpp + source/dshow-media-type.hpp + source/log.hpp +) + +# Set /W4 +# Disable pointless constant condition warnings +target_compile_options(libdshowcapture PRIVATE /W4 /wd4127 /wd4201) + +if(CMAKE_VS_PLATFORM_NAME STREQUAL Win32) + target_link_options(libdshowcapture PRIVATE /SAFESEH:NO) +endif() + +target_include_directories(libdshowcapture PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/external/capture-device-support/Library) + +target_compile_definitions( + libdshowcapture + PRIVATE + UNICODE + _UNICODE + $<$:DEBUG> + $<$:_DEBUG> + $<$:DSHOWCAPTURE_EXPORTS> + _UP_WINDOWS=1 +) + +target_link_libraries( + libdshowcapture + PRIVATE setupapi strmiids ksuser winmm wmcodecdspuuid +) diff --git a/obs-studio/deps/libdshowcapture/src/COPYING b/obs-studio/deps/libdshowcapture/src/COPYING new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; 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. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/obs-studio/deps/libdshowcapture/src/README b/obs-studio/deps/libdshowcapture/src/README new file mode 100644 index 0000000..b5d41e5 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/README @@ -0,0 +1,11 @@ + +libdshowcapture + + This library was created as a means to simplify the process of using + DirectShow to capture video and/or audio devices, such as webcams, capture + devices (internal, USB 2.0, USB 3.0), microphones, auxiliary sound inputs, + etc. + + The biggest goal of this project is to eventually support as many devices as + possible, as well as add more interesting features later on for improving + performance. diff --git a/obs-studio/deps/libdshowcapture/src/dshowcapture.hpp b/obs-studio/deps/libdshowcapture/src/dshowcapture.hpp new file mode 100644 index 0000000..89ce5fe --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/dshowcapture.hpp @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include +#include +#include + +#ifdef DSHOWCAPTURE_EXPORTS +#define DSHOWCAPTURE_EXPORT __declspec(dllexport) +#else +#define DSHOWCAPTURE_EXPORT +#endif + +#define DSHOWCAPTURE_VERSION_MAJOR 0 +#define DSHOWCAPTURE_VERSION_MINOR 10 +#define DSHOWCAPTURE_VERSION_PATCH 0 + +#define MAKE_DSHOWCAPTURE_VERSION(major, minor, patch) \ + ((major << 24) | (minor << 16) | (patch)) + +#define DSHOWCAPTURE_VERSION \ + MAKE_DSHOWCAPTURE_VERSION(DSHOWCAPTURE_VERSION_MAJOR, \ + DSHOWCAPTURE_VERSION_MINOR, \ + DSHOWCAPTURE_VERSION_PATCH) + +#define DSHOW_MAX_PLANES 8 + +namespace DShow { +/* internal forward */ +struct HDevice; +struct HVideoEncoder; +struct VideoConfig; +struct AudioConfig; + +typedef std::function + VideoProc; + +typedef std::function + AudioProc; + +typedef std::function ReactivateProc; + +enum class InitGraph { + False, + True, +}; + +/** DirectShow configuration dialog type */ +enum class DialogType { + ConfigVideo, + ConfigAudio, + ConfigCrossbar, + ConfigCrossbar2, +}; + +enum class VideoFormat { + Any, + Unknown, + + /* raw formats */ + ARGB = 100, + XRGB, + RGB24, + + /* planar YUV formats */ + I420 = 200, + NV12, + YV12, + Y800, + P010, + + /* packed YUV formats */ + YVYU = 300, + YUY2, + UYVY, + HDYC, + + /* encoded formats */ + MJPEG = 400, + H264, + HEVC, +}; + +enum class AudioFormat { + Any, + Unknown, + + /* raw formats */ + Wave16bit = 100, + WaveFloat, + + /* encoded formats */ + AAC = 200, + AC3, + MPGA, /* MPEG 1 */ +}; + +enum class AudioMode { + Capture, + DirectSound, + WaveOut, +}; + +enum class Result { + Success, + InUse, + Error, +}; + +struct VideoInfo { + int minCX, minCY; + int maxCX, maxCY; + int granularityCX, granularityCY; + long long minInterval, maxInterval; + VideoFormat format; +}; + +struct AudioInfo { + int minChannels, maxChannels; + int channelsGranularity; + int minSampleRate, maxSampleRate; + int sampleRateGranularity; + AudioFormat format; +}; + +struct DeviceId { + std::wstring name; + std::wstring path; +}; + +struct VideoDevice : DeviceId { + bool audioAttached = false; + bool separateAudioFilter = false; + std::vector caps; +}; + +struct AudioDevice : DeviceId { + std::vector caps; +}; + +struct Config : DeviceId { + /** Use the device's desired default config */ + bool useDefaultConfig = true; +}; + +struct VideoConfig : Config { + VideoProc callback; + ReactivateProc reactivateCallback; + + /** Desired width/height of video. */ + int cx = 0, cy_abs = 0; + + /** Whether or not cy was negative. */ + bool cy_flip = false; + + /** Desired frame interval (in 100-nanosecond units) */ + long long frameInterval = 0; + + /** Internal video format. */ + VideoFormat internalFormat = VideoFormat::Any; + + /** Desired video format. */ + VideoFormat format = VideoFormat::Any; +}; + +struct AudioConfig : Config { + AudioProc callback; + + /** + * Use the audio attached to the video device + * + * (name/path member variables will be ignored) + */ + bool useVideoDevice = false; + + /** Use separate filter for audio */ + bool useSeparateAudioFilter = false; + + /** Desired sample rate */ + int sampleRate = 0; + + /** Desired channels */ + int channels = 0; + + /** Desired audio format */ + AudioFormat format = AudioFormat::Any; + + /** Audio playback mode */ + AudioMode mode = AudioMode::Capture; + + /** Desired buffer */ + int buffer = 0; +}; + +class DSHOWCAPTURE_EXPORT Device { + HDevice *context; + +public: + Device(InitGraph initialize = InitGraph::False); + ~Device(); + + bool Valid() const; + + bool ResetGraph(); + void ShutdownGraph(); + + bool SetVideoConfig(VideoConfig *config); + bool SetAudioConfig(AudioConfig *config); + + /** + * Connects all the configured filters together. + * + * Call SetVideoConfig and/or SetAudioConfig before using. + */ + bool ConnectFilters(); + + Result Start(); + void Stop(); + + bool GetVideoConfig(VideoConfig &config) const; + bool GetAudioConfig(AudioConfig &config) const; + bool GetVideoDeviceId(DeviceId &id) const; + bool GetAudioDeviceId(DeviceId &id) const; + + /** + * Opens a DirectShow dialog associated with this device + * + * @param type The dialog type + */ + void OpenDialog(void *hwnd, DialogType type) const; + + static bool EnumVideoDevices(std::vector &devices); + static bool EnumAudioDevices(std::vector &devices); +}; + +struct VideoEncoderConfig : DeviceId { + int fpsNumerator; + int fpsDenominator; + int bitrate; + int keyframeInterval; + int cx; + int cy; +}; + +struct EncoderPacket { + unsigned char *data; + size_t size; + long long pts; + long long dts; +}; + +class VideoEncoder { + HVideoEncoder *context; + +public: + VideoEncoder(); + ~VideoEncoder(); + + bool Valid() const; + bool Active() const; + + bool ResetGraph(); + + bool SetConfig(VideoEncoderConfig &config); + bool GetConfig(VideoEncoderConfig &config) const; + + bool Encode(unsigned char *data[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], long long timestampStart, + long long timestampEnd, EncoderPacket &packet, + bool &new_packet); + + static bool EnumEncoders(std::vector &encoders); +}; + +enum class LogType { + Error, + Warning, + Info, + Debug, +}; + +typedef void (*LogCallback)(LogType type, const wchar_t *msg, void *param); + +DSHOWCAPTURE_EXPORT void SetLogCallback(LogCallback callback, void *param); +}; diff --git a/obs-studio/deps/libdshowcapture/src/external/.clang-format b/obs-studio/deps/libdshowcapture/src/external/.clang-format new file mode 100644 index 0000000..6420a46 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/.clang-format @@ -0,0 +1,3 @@ +Language: Cpp +SortIncludes: false +DisableFormat: true diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/.gitignore b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/.gitignore new file mode 100644 index 0000000..bd30095 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/.gitignore @@ -0,0 +1,13 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +.vs/ +out/ diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/CMakeLists.txt b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/CMakeLists.txt new file mode 100644 index 0000000..a519525 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/CMakeLists.txt @@ -0,0 +1,64 @@ +# MIT License +# +# Copyright (c) 2022 Corsair Memory, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +# CMakeList.txt : CMake project for EGAVHIDSample, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +project ("EGAVHIDSample") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(FRAMEWORK_FOLDER "Library") + +if(WIN32) + set(PLATFORM_FOLDER "win") + set(PLATFORM_SOURCES + SampleCode/DriverInterface.cpp + ) + +elseif(APPLE) + set(PLATFORM_FOLDER "mac") + set(PLATFORM_SOURCES) +endif() + +# Add source to this project's executable. +add_executable (EGAVHIDSample + ${PLATFORM_SOURCES} + "${FRAMEWORK_FOLDER}/EGAVResult.cpp" + "${FRAMEWORK_FOLDER}/ElgatoUVCDevice.cpp" + "${FRAMEWORK_FOLDER}/${PLATFORM_FOLDER}/EGAVHIDImplementation.cpp" + "SampleCode/DriverInterface.cpp" + "SampleCode/main.cpp" +) + +target_include_directories(EGAVHIDSample PRIVATE ${FRAMEWORK_FOLDER}) +target_compile_definitions(EGAVHIDSample PUBLIC EGAV_API) + +if(WIN32) + target_compile_definitions(EGAVHIDSample PUBLIC _UP_WINDOWS=1) +elseif(APPLE) + target_compile_definitions(EGAVHIDSample PUBLIC _UP_MAC=1) +endif() \ No newline at end of file diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/LICENSE b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/LICENSE new file mode 100644 index 0000000..e44c08e --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVDevice.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVDevice.h new file mode 100644 index 0000000..4b7068a --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVDevice.h @@ -0,0 +1,72 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include +#include +#include + + +//============================================================================== +// # Class EGAVDeviceID +//============================================================================== +enum class EGAVBusType { Unknown, USB, PCI }; + +struct EGAVDeviceID +{ + EGAVDeviceID() {} + + //! @brief EGAVDeviceID constructor + //! @param inBusType USB or PCIe + //! @param inVendorID USB vendor ID or PCIe subsystem vendor ID + //! @param inProductID USB product ID or PCIe subsystem device ID + //! @param inLocationID Location ID (macOS only) + EGAVDeviceID(EGAVBusType inBusType, uint16_t inVendorID, uint16_t inProductID, uint32_t inLocationID = 0) : + busType(inBusType), vendorID(inVendorID), productID(inProductID), locationID(inLocationID) + { + } + + EGAVBusType busType = EGAVBusType::Unknown; + uint16_t vendorID = 0; //!< USB vendor ID or PCI subvendor ID + uint16_t productID = 0; //!< USB product ID or PCI subdevice ID + uint32_t locationID = 0; //!< USB location ID (macOS only) + + bool Equals(const EGAVDeviceID& inDeviceID, bool inIgnoreLocation) const + { + if (this->busType != inDeviceID.busType) return false; + if (this->vendorID != inDeviceID.vendorID) return false; + if (this->productID != inDeviceID.productID) return false; + if (this->locationID != inDeviceID.locationID && !inIgnoreLocation) return false; + return true; + } + + bool operator == (const EGAVDeviceID& inDeviceID) const { return Equals(inDeviceID, false); } + bool operator != (const EGAVDeviceID& inDeviceID) const { return !Equals(inDeviceID, false); } + + std::string toString(); +}; + + + diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVHID.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVHID.h new file mode 100644 index 0000000..db07545 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVHID.h @@ -0,0 +1,69 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVHID.h + +@brief HID specific types and constants. +**/ +//============================================================================== + +#pragma once + +#include + +#include "EGAVResult.h" +#include "EGAVDevice.h" // required for EGAVDeviceID + +const int kHidDefaultReportID = 0; //! Dummy report ID + +//============================================================================== +// # Interfaces (HID) +//============================================================================== + +//! @brief HID Interface for cross-platform support. (EVH-442) +class EGAVHIDInterface +{ +public: + virtual ~EGAVHIDInterface() { } + + virtual EGAVResult InitHIDInterface(const EGAVDeviceID& inDeviceID) = 0; + virtual EGAVResult DeinitHIDInterface() = 0; + + //! @brief Reads a HID response message from the OS. + //! @param outReceivedMessage will contain the resulting message. Its length will be adjusted automatically. + //! @param report ID The reportID is always 0 (kHidDefaultReportID) for Facecam (Penna) + //! @param inReadBufferSize size of buffer passed to ID read routine. If 0 mHIDCaps->InputReportByteLength is used. + virtual EGAVResult ReadHID(std::vector& outMessage, int inReportID, int inReadBufferSize = 0) = 0; + + //! @brief Writes specified message. + //! The implementation should construct a HID report to send to the hardware + //! @param message (NOT report!) + //! @param report ID The reportID is always 0 (kHidDefaultReportID) for Facecam (Penna). The Penna-specific message tag is in the first byte of the message. + virtual EGAVResult WriteHID(const std::vector& inMessage, int inReportID) = 0; +}; + +//! @brief Platform specific factory method +std::shared_ptr CreateEGAVHIDInterface(); diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVResult.cpp b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVResult.cpp new file mode 100644 index 0000000..21835ba --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVResult.cpp @@ -0,0 +1,131 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVResult.cpp + +@brief Definition of error and success codes +**/ +//============================================================================== + +#include "EGAVResult.h" +#if _UP_WINDOWS +#include // for HRESULT +#endif + +//------------------------------------------------------------------------------ +// Constructor / Destructor +//------------------------------------------------------------------------------ + +EGAVResult::EGAVResult(EGAVResultCode inResultCode) +{ + mResultCode = inResultCode; +} + +EGAVResult::EGAVResult(EGAVResultCustomType inCustomResultType, int64_t inCustomResultCode) +{ + mResultCode = ErrCustom; + mCustomResultType = inCustomResultType; + mCustomResultCode = inCustomResultCode; +} + +#if _UP_WINDOWS +void EGAVResult::InitWithHresult(HRESULT hr) +{ + mResultCode = ErrCustom; + mCustomResultType = EGAVResultCustomType::Hresult; + mCustomResultCode = hr; +} + +void EGAVResult::InitWithWinError(LONG err) +{ + mResultCode = ErrCustom; + mCustomResultType = EGAVResultCustomType::WinError; + mCustomResultCode = err; +} +#endif + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +bool EGAVResult::Succeeded() const +{ + if (mResultCode == ErrCustom) + { + switch (mCustomResultType) + { +#if _UP_WINDOWS + case EGAVResultCustomType::Hresult: return SUCCEEDED(mCustomResultCode); + case EGAVResultCustomType::WinError: return (mCustomResultCode == ERROR_SUCCESS) ? true : false; +#elif _UP_MAC + case EGAVResultCustomType::Mac: return (mCustomResultCode == 0) ? true : false; +#endif + case EGAVResultCustomType::MainConcept: + case EGAVResultCustomType::Device: + return (mCustomResultCode == 0) ? true : false; + default: + return false; + } + } + else + { + return (int32_t)mResultCode > 0 ? true : false; + } +} + + +//------------------------------------------------------------------------------ +// Operators +//------------------------------------------------------------------------------ +void EGAVResult::operator=(const EGAVResultCode inResultCode) +{ + mCustomResultType = EGAVResultCustomType::None; + mResultCode = inResultCode; +} + +bool EGAVResult::operator==(const EGAVResult inResult) const +{ + if (mResultCode != inResult.mResultCode ) return false; + if (mCustomResultType != inResult.mCustomResultType) return false; + if (mCustomResultCode != inResult.mCustomResultCode) return false; + return true; +} + +bool EGAVResult::operator!=(const EGAVResult inResult) const +{ + return !(*this == inResult); +} + + +bool EGAVResult::operator==(const EGAVResultCode inResultCode) const +{ + return mResultCode == inResultCode; +} + +bool EGAVResult::operator!=(const EGAVResultCode inResultCode) const +{ + return mResultCode != inResultCode; +} diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVResult.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVResult.h new file mode 100644 index 0000000..5017976 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/EGAVResult.h @@ -0,0 +1,213 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVResult.h + +@brief Definition of error and success codes +**/ +//============================================================================== + +#pragma once + + +//------------------------------------------------------------------------------ +// Includes +//------------------------------------------------------------------------------ + +#include +#if _UP_WINDOWS + // FMB NOTE: For some strange reason must be before . + // This is only necessary because contents of are required in other code files. + // However, if is include somewhere it must be guaranteded that was never include before. + #define NOMINMAX + #include // for sockaddr_in + #include +#endif + +//#include "EGAVFeatures.h" // for EGAV_API + + +#define EVH_AVOID_UNNEEDED_COPY_CONSTRUCTORS (!_UP_WINDOWS) //!< 0 - explicit copy constructors: required for .NET frontends connected via SWIG e.g. 4KCU and EVH Test App + //!< 1 - to avoid "warning: definition of implicit copy constructor for different classes e.g. + //! 'EGAVAudioSampleType' is deprecated because it has a user-declared copy assignment operator [-Wdeprecated-copy]" + + + + + +//============================================================================== +// EGAVResultCustomType +//============================================================================== + +enum class EGAVResultCustomType +{ + None, + Hresult, //!< HRESULT (Windows) + WinError, //!< System error code (Windows) + MainConcept, //!< MainConcept error codes (BS_OK etc.) + Device, //!< Device Error Codes + Mac //!< Errors from macOS +}; + +enum class EGAVResultCustomTypeDevice { + None, + SpeedInsufficient, + ResultUnexpected, +}; + +//============================================================================== +// EGAVResult +//============================================================================== +typedef int EGAVResultCode; + +//! Error description +class /*EGAV_API*/ EGAVResult +{ +public: + + //------------------------------------------------------------------------------ + // Constants + //------------------------------------------------------------------------------ + + // EXTEND_EGAVResultCode: add new error codes here and in GetResultCodeString function in cpp file + static const EGAVResultCode ErrInvalidOperation = -300; //!< Execution of the operation would lead to an error/invalid state + + static const EGAVResultCode ErrUnknownUnit = -200; //!< Can't instantiate desired EGAV unit (source, sink, input, output, etc.) + + static const EGAVResultCode ErrDeviceInUse = -108; //!< Device is in use by another application (from EVHALResultCode) + static const EGAVResultCode ErrInvalidPath = -101; //!< File error: specifided path is not valid + static const EGAVResultCode ErrCouldNotOpenFile = -100; //!< File error: Could not open file + + static const EGAVResultCode ErrResultPending = -19; //!< hardware busy, try again later + static const EGAVResultCode ErrResourceNotAvail = -18; //!< Resource not available + static const EGAVResultCode ErrOutOfRange = -17; //!< Out of Range + static const EGAVResultCode ErrTimeOut = -16; //!< Operation timed out + static const EGAVResultCode ErrNotSupported = -15; //!< Operation not supported; used with firmware update + static const EGAVResultCode ErrConversionFailed = -14; //!< Conversion failed + static const EGAVResultCode ErrNotFound = -13; //!< Not found + static const EGAVResultCode ErrNoData = -12; //!< No data + static const EGAVResultCode ErrVideoScaler = -11; //!< Video scaler error + static const EGAVResultCode ErrEncoder = -10; //!< Encoder error + static const EGAVResultCode ErrInvalidFormat = -9; //!< Invalid format + static const EGAVResultCode ErrInvalidParameter = -8; //!< Invalid parameter + static const EGAVResultCode ErrInvalidState = -7; //!< Invalid state (e.g. when trying to process data while a unit is desinitialized) + static const EGAVResultCode ErrInsufficientMemory = -6; //!< Out of memory + static const EGAVResultCode ErrNotInitialized = -5; //!< Not initialized + static const EGAVResultCode ErrInvalidCast = -4; //!< Cast operation failed + static const EGAVResultCode ErrNotImplemented = -3; //!< Can't instantiate desired EGAV unit (source, sink, input, output, etc.) + static const EGAVResultCode ErrNullPointer = -2; //!< Null pointer + static const EGAVResultCode ErrUnknown = -1; //!< General failure + + static const EGAVResultCode ErrCustom = 0; //!< Custom error code: error code is in mCustomResultCode + + static const EGAVResultCode Ok = 1; //!< Success + static const EGAVResultCode OkNoDataChanged = 2; //!< Success: No data were changed (similar to HRESULT value S_FALSE ) + static const EGAVResultCode OkFileNotFound = 3; //!< Success: File was not found, but that is a valid state. + static const EGAVResultCode OkButIncomplete = 4; //!< Success: Operation didn't fail, but had some internal uncritical errors (similar to S_FALSE on Windows) + + //------------------------------------------------------------------------------ + // Construction + //------------------------------------------------------------------------------ + + //! Constructor + EGAVResult() {} + EGAVResult(EGAVResultCode inResultCode); + + EGAVResult(EGAVResultCustomType inCustomResultType, int64_t inCustomResultCode); + + //------------------------------------------------------------------------------ + // Initialization + //------------------------------------------------------------------------------ +#if _UP_WINDOWS + + //! Init with HRESULT (EGAVResultCustomType::Hresult) + void InitWithHresult(HRESULT hr); + //! Init with Windows error code (EGAVResultCustomType::WinError) + void InitWithWinError(LONG err); + +#endif + + //------------------------------------------------------------------------------ + // Helpers + //------------------------------------------------------------------------------ + + bool Succeeded() const; + bool Failed() const { return !Succeeded(); } + + EGAVResultCode GetResultCode() const { return mResultCode; } + EGAVResultCustomType GetCustomResultType() const { return mCustomResultType; } + int64_t GetCustomResultCode() const { return mCustomResultCode; } + + + void operator=(const EGAVResultCode inResultCode); + bool operator==(const EGAVResult inResult) const; + bool operator!=(const EGAVResult inResult) const; + bool operator==(const EGAVResultCode inResultCode) const; + bool operator!=(const EGAVResultCode inResultCode) const; + + //------------------------------------------------------------------------------ + // Members + //------------------------------------------------------------------------------ + + // Common + EGAVResultCode mResultCode = ErrCustom; + + // Custom error codes (platform error codes or error codes from other APIs) + EGAVResultCustomType mCustomResultType = EGAVResultCustomType::None; + int64_t mCustomResultCode = 0; + +private: + std::string mMessage; +}; + + + +//============================================================================== +// # Macros +//============================================================================== + +#define EGAVResult_CheckPointer(_p_) \ + { \ + if (!_p_) \ + return EGAVResult::ErrNullPointer; \ + } + +#define EGAVResult_CheckCondition(_cond_) \ + { \ + if (false == (_cond_)) \ + return EGAVResult::ErrUnknown; \ + } + + + +#ifndef EGAV_OVERRIDE_DEBUG_MACROS + inline void dummy() {} + + #define EPL_ASSERT_BREAK(...) dummy() + #define error_printf(...) dummy() + #define warning_printf(...) dummy() + #define info_printf(...) dummy() +#endif diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/ElgatoUVCDevice.cpp b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/ElgatoUVCDevice.cpp new file mode 100644 index 0000000..1da5ae7 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/ElgatoUVCDevice.cpp @@ -0,0 +1,265 @@ +/* +MIT License + +Copyright (c) 2022-23 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "ElgatoUVCDevice.h" + + +#define WORKAROUND_HD60_S_PLUS_PAYLOAD_SIZE 1 //!< Workaround HD60 S+ firmware issue: invalid payload length (seen with HDR and SPD info frames) + + +//============================================================================== +// # Elgato HID interface for UVC devices +//============================================================================== + + +enum class I2CAddress +{ + MCU = 0x55, +}; + +//! @brief I2C registers for MCU (I2C address 0x55). +enum class MCU_I2C_REGISTER +{ + GET_HDR_PACKET = 0x09, //!< HDR capable devices (HD60 S+, HD60 X) + XET_HDR_TONEMAPPING = 0x0A, //!< HDR capable devices (HD60 S+, HD60 X): Enable hardware tonemapping; param 0/1 (uint8_t) +}; + + + +//============================================================================== +// ## HID interface - I2C +//============================================================================== + +//! @brief HID report case for new device type. +enum class REPORT_CASE_NEW +{ + REPORT_IIC_WRITE = 6, + REPORT_IIC_READ = 7 +}; + +//! @brief HID report IDs for new device type. Can also be queried via HidP_GetValueCaps() +enum class HID_REPORT_ID_NEW +{ + I2C_READ = 5, + I2C_WRITE = 6 +}; + +//! @brief HDI report IDs for original device type. Can also be queried via HidP_GetValueCaps() +enum class HID_REPORT_ID +{ + I2C_READ_SET_ID = 9, + I2C_READ_GET_ID = 10, + I2C_WRITE_ID = 11 +}; + +const int I2C_BUFFER_HEADER_SIZE = 4; +const int MAX_COMM_READ_BUFFER_SIZE = 32; + + +//============================================================================== +// # Helpers +//============================================================================== + +std::vector GetElgatoUVCDeviceIDs() +{ + std::vector devices {deviceIDHD60SPlus, deviceIDHD60X, deviceIDHD60XRev2 }; + // EXTEND_DEVICES + return devices; +} + +//! @return true for new USB chipset +bool IsNewDeviceType(const EGAVDeviceID& inDeviceID) { return (inDeviceID != deviceIDHD60SPlus); } + + + +//============================================================================== +// # Class ElgatoUVCDevice +//============================================================================== + +ElgatoUVCDevice::ElgatoUVCDevice(std::shared_ptr hid, bool isNewDeviceType) : mHIDImpl(hid), mNewDeviceType(isNewDeviceType) {} + +EGAVResult ElgatoUVCDevice::ReadI2cData(uint8_t inI2CAddress, uint8_t inRegister, uint8_t* outData, uint8_t inLength) +{ + EGAVResult_CheckPointer(outData); + EGAVResult_CheckPointer(mHIDImpl); + + EPL_ASSERT_BREAK(inLength <= MAX_COMM_READ_BUFFER_SIZE); + + const std::lock_guard lock(mHIDMutex); + + EGAVResult res = EGAVResult::ErrUnknown; + + if (mNewDeviceType) + { + const uint8_t writeLen = 1 /* +1 for byte register address*/, readLen = inLength, reportLen = 4 + writeLen + sizeof(readLen); + std::vector outputMessage{ reportLen, (uint8_t)REPORT_CASE_NEW::REPORT_IIC_READ, inI2CAddress, writeLen, inRegister, readLen }; + EPL_ASSERT_BREAK(reportLen == outputMessage.size()); + res = mHIDImpl->WriteHID(outputMessage, (int)HID_REPORT_ID_NEW::I2C_WRITE); + if (res.Failed()) + error_printf("WriteHID() FAILED for I2C address 0x%02x, register 0x%02x", inI2CAddress, inRegister); + else + { + std::vector inputMessage; + const int inputReportLength = 0xFF | ((int)REPORT_CASE_NEW::REPORT_IIC_READ << 8); // report case is coded into report length + res = mHIDImpl->ReadHID(inputMessage, (int)HID_REPORT_ID_NEW::I2C_READ, inputReportLength); + if (res.Failed()) + error_printf("ReadHID() FAILED for I2C address 0x%02x, register 0x%02x", inI2CAddress, inRegister); + else + { + int dataLen = std::min((int)inLength, (int)(inputMessage.size() - 1)); + memcpy(outData, inputMessage.data() + 1, dataLen); + } + } + } + else + { + std::vector outputMessage{ inI2CAddress, inRegister, inLength }; + res = mHIDImpl->WriteHID(outputMessage, (int)HID_REPORT_ID::I2C_READ_SET_ID); + if (res.Failed()) + error_printf("WriteHID() FAILED for I2C address 0x%02x, register 0x%02x", inI2CAddress, inRegister); + else + { + std::vector inputMessage(I2C_BUFFER_HEADER_SIZE + MAX_COMM_READ_BUFFER_SIZE); + res = mHIDImpl->ReadHID(inputMessage, (int)HID_REPORT_ID::I2C_READ_GET_ID); + if (res.Failed()) + error_printf("ReadHID() FAILED for I2C address 0x%02x, register 0x%02x", inI2CAddress, inRegister); + else + memcpy(outData, inputMessage.data(), inLength); + } + } + + EPL_ASSERT_BREAK(res.Succeeded()); + return res; +} + + +EGAVResult ElgatoUVCDevice::WriteI2cData(uint8_t inI2CAddress, uint8_t inRegister, uint8_t* inData, uint8_t inLength) +{ + EGAVResult_CheckPointer(inData); + EGAVResult_CheckPointer(mHIDImpl); + + const std::lock_guard lock(mHIDMutex); + + EGAVResult res = EGAVResult::ErrUnknown; + + if (mNewDeviceType) + { + const uint8_t writeLen = 1 + inLength /* +1 for byte register address*/, reportLen = 4 + writeLen; + std::vector outputMessage{ reportLen, (uint8_t)REPORT_CASE_NEW::REPORT_IIC_WRITE, inI2CAddress, writeLen, inRegister }; + for (int i = 0; i < inLength; i++) + outputMessage.push_back(inData[i]); + EPL_ASSERT_BREAK(reportLen == outputMessage.size()); + + res = mHIDImpl->WriteHID(outputMessage, (int)HID_REPORT_ID_NEW::I2C_WRITE); + } + else + { + std::vector outputMessage{ inI2CAddress, inRegister, inLength }; + for (int i = 0; i < inLength; i++) + outputMessage.push_back(inData[i]); + res = mHIDImpl->WriteHID(outputMessage, (int)HID_REPORT_ID::I2C_WRITE_ID); + } + + if (res.Failed()) + error_printf("WriteHID() FAILED for I2C address 0x%02d, register 0x%02d", inI2CAddress, inRegister); + return res; +} + +void ElgatoUVCDevice::SetHDRTonemappingEnabled(bool inValue) +{ + const std::lock_guard lock(mHIDMutex); + + uint8_t buffer = inValue ? 1 : 0; + WriteI2cData((uint8_t)I2CAddress::MCU, (uint8_t)MCU_I2C_REGISTER::XET_HDR_TONEMAPPING, &buffer, sizeof(buffer)); +} + +EGAVResult ElgatoUVCDevice::GetHDMIHDRStatusPacket(HDMI_GENERIC_INFOFRAME& outFrame) +{ + const std::lock_guard lock(mHIDMutex); + + const size_t bufSize = mNewDeviceType ? 32 : 33; + uint8_t* buffer = new uint8_t[bufSize]; + EGAVResult res = ReadI2cData((uint8_t)I2CAddress::MCU, (uint8_t)MCU_I2C_REGISTER::GET_HDR_PACKET, buffer, (uint8_t)bufSize); + if (res.Succeeded()) + { + size_t size = (bufSize < sizeof(outFrame)) ? bufSize : sizeof(outFrame); + memcpy(&outFrame, mNewDeviceType ? buffer : buffer+1, size); + +#if WORKAROUND_HD60_S_PLUS_PAYLOAD_SIZE + // Workaround HD60 S+ firmware issue: invalid payload length (seen with HDR and SPD info frames) + // Also with HD60 X FW 22.03.24 (MCU: 22.03.16) + if (outFrame.header.bPayloadLength > HDMI_MAX_INFOFRAME_PAYLOAD) + { + if (HDMI_INFOFRAME_TYPE_DR == outFrame.header.bfType) + { + int diff = outFrame.header.bPayloadLength - sizeof(outFrame.plDR1); + outFrame.header.bPayloadLength = sizeof(outFrame.plDR1); + outFrame.bChecksum += diff; + } + } +#endif + } + delete [] buffer; + return res; +} + +EGAVResult ElgatoUVCDevice::IsVideoHDR(bool& outIsHDR) +{ + // Try to read HDR meta data + HDMI_GENERIC_INFOFRAME frame{}, emptyFrame{}; + memset(&emptyFrame, 0, sizeof(emptyFrame)); + EGAVResult res = GetHDMIHDRStatusPacket(frame); + if (res.Succeeded()) + { + bool isInfoFrameValid = HDMI_IsInfoFrameValid(&frame); + res = isInfoFrameValid ? EGAVResult::Ok : EGAVResult::ErrUnknown; + if (isInfoFrameValid) + { + // Check type in header and EOTF flag in payload + if (HDMI_INFOFRAME_TYPE_DR == frame.header.bfType && HDMI_DR_EOTF_SDRGAMMA != frame.plDR1.bfEOTF) + { + outIsHDR = true; + } + else if (HDMI_INFOFRAME_TYPE_DR == frame.header.bfType && HDMI_DR_EOTF_SDRGAMMA == frame.plDR1.bfEOTF) + { + outIsHDR = false; // we get here with HD60 X (22.03.24 (MCU: 22.03.16)) + } + else if (0 /*HDMI_INFOFRAME_TYPE_RESERVED*/ == frame.header.bfType && (0 == memcmp(&frame, &emptyFrame, sizeof(emptyFrame)))) + { + // all empty (seen with HD60 S+ when HDR is not active) + outIsHDR = false; + } + else if (HDMI_INFOFRAME_TYPE_DR != frame.header.bfType) + { + warning_printf("HDMI Metadata: Wrong header type: %d", frame.header.bfType); + res = EGAVResult::ErrNotFound; + } + } + else + warning_printf("HDMI Metadata: HDMI_IsInfoFrameValid() returned error (checksum)!"); + } + else + warning_printf("HDMI Metadata: GetHDMIHDRStatusPacket() failed!"); + return res; +} diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/ElgatoUVCDevice.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/ElgatoUVCDevice.h new file mode 100644 index 0000000..a91afbe --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/ElgatoUVCDevice.h @@ -0,0 +1,84 @@ +/* +MIT License + +Copyright (c) 2022-23 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include +#include + +#include "EGAVResult.h" +#include "EGAVDevice.h" +#include "HDMIInfoFramesAPI.h" + +#ifdef _MSC_VER +#include "win/EGAVHIDImplementation.h" +#else +#include "mac/EGAVHIDImplementation.h" +#endif + +// Supported devices +inline const EGAVDeviceID deviceIDHD60SPlus (EGAVBusType::USB, 0x0FD9, 0x006A); //!< HD60 S+ +inline const EGAVDeviceID deviceIDHD60X (EGAVBusType::USB, 0x0FD9, 0x0082); //!< HD60 X +inline const EGAVDeviceID deviceIDHD60XRev2 (EGAVBusType::USB, 0x0FD9, 0x008A); //!< HD60 X Rev. 2 +// EXTEND_DEVICES + + +//! @return Device IDs of supported Elgato UVC devices +std::vector GetElgatoUVCDeviceIDs(); + + +//! @return true for new devices with new USB chipset +bool IsNewDeviceType(const EGAVDeviceID& inDeviceID); + + + +//============================================================================== +// # Class ElgatoUVCDevice +//============================================================================== + +class ElgatoUVCDevice +{ + +public: + ElgatoUVCDevice(std::shared_ptr hid, bool isNewDeviceType); + + //! @brief Works with HD60 S+, HD60 X or newer + void SetHDRTonemappingEnabled(bool inEnable); + + //! @brief Works with HD60 S+, HD60 X or newer + EGAVResult GetHDMIHDRStatusPacket(HDMI_GENERIC_INFOFRAME& outFrame); + + //! @brief Works with HD60 S+, HD60 X or newer + EGAVResult IsVideoHDR(bool& outIsHDR); + + +private: + EGAVResult WriteI2cData(uint8_t inI2CAddress, uint8_t inRegister, uint8_t* inData, uint8_t inLength); + EGAVResult ReadI2cData(uint8_t inI2CAddress, uint8_t inRegister, uint8_t* outData, uint8_t inLength); + + bool mNewDeviceType = false; //!< true: HD60 X and newer devices , false: HD60 S+ + + std::shared_ptr mHIDImpl; + std::recursive_mutex mHIDMutex; +}; diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/HDMIInfoFramesAPI.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/HDMIInfoFramesAPI.h new file mode 100644 index 0000000..21c6ec7 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/HDMIInfoFramesAPI.h @@ -0,0 +1,746 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include + +#pragma pack(push, 1) + + +//==================================================================================== +// # VIDEO IDENTIFICATION CODES (VIC) +//==================================================================================== + +// Video ID Code, see CEA-861-E chapter 4.1, table 4 +// Video ID Code, see CEA-861-G chapter 4.1, table 3 + +typedef struct _HDMI_VIC_DESCRIPTOR +{ + uint8_t bID; //!< Video ID Code + int iWidth; //!< width in pixels + int iHeight; //!< height in pixels + int iFieldRate; //!< field refresh rate in Hz + bool bInterlaced; //!< interlaced + short iAspectX; //!< picture aspect ratio H + short iAspectY; //!< picture aspect ratio V +} +HDMI_VIC_DESCRIPTOR; + +// a field refresh value of 24Hz means either 24.00Hz or 23.98Hz +// a field refresh value of 30Hz means either 30.00Hz or 29.97Hz +// a field refresh value of 48Hz means either 48.00Hz or 47.95Hz +// a field refresh value of 60Hz means either 60.00Hz or 59.94Hz +// a field refresh value of 120Hz means either 120.00Hz or 119.88Hz +// a field refresh value of 240Hz means either 240.00Hz or 239.76Hz + +#define HDMI_VIC_TABLE_SIZE 220 + +extern const HDMI_VIC_DESCRIPTOR g_HDMI_VIC_TABLE[HDMI_VIC_TABLE_SIZE]; + +//==================================================================================== +// # INFO FRAME TYPES +//==================================================================================== + + +// see CEA-861-E chapter 6, table 6 +// see CEA-861-G chapter 6, table 5 + +#define HDMI_INFOFRAME_TYPE_RESERVED 0x00 // reserved + +#define HDMI_INFOFRAME_TYPE_VS 0x01 // Vendor Specific +#define HDMI_INFOFRAME_TYPE_AVI 0x02 // Auxiliary Video Information +#define HDMI_INFOFRAME_TYPE_SPD 0x03 // Source Product Description +#define HDMI_INFOFRAME_TYPE_A 0x04 // Audio +#define HDMI_INFOFRAME_TYPE_MS 0x05 // MPEG Source +#define HDMI_INFOFRAME_TYPE_VBI 0x06 // NTSC VBI +#define HDMI_INFOFRAME_TYPE_DR 0x07 // Dynamic Range and Mastering + +#define HDMI_INFOFRAME_TYPE_MIN HDMI_INFOFRAME_TYPE_VS +#define HDMI_INFOFRAME_TYPE_MAX HDMI_INFOFRAME_TYPE_DR + + +//==================================================================================== +// # INFO FRAME HEADER +//==================================================================================== + +// see CEA-861-G Annex D.1 + +#define HDMI_MAX_INFOFRAME_SIZE 31 // 3 bytes header + 1 byte checksum + 27 bytes payload +#define HDMI_MAX_INFOFRAME_PAYLOAD 27 + + +// see CEA-861-E chapter 6 + +typedef struct _HDMI_INFOFRAMEHEADER +{ + uint8_t bfType : 7; // InfoFrame Type Code (see HDMI_INFOFRAME_TYPE_*) + uint8_t bfPacketType : 1; // the HDMI Packet Type is 0x80+InfoframeType for HDMI InfoFrame Packets + uint8_t bfVersion : 7; // InfoFrame Version Number, starting with 1 + uint8_t bfChangeBit : 1; // InfoFrame Change Bit, VS Infoframe only + uint8_t bPayloadLength; // Size of InfoFrame payload, not including Type, Version, Length +} +HDMI_INFOFRAMEHEADER; + + +//==================================================================================== +// # VENDOR SPECIFIC INFO FRAME +//==================================================================================== + +// see CEA-861-E chapter 6.1, table 7 +// see CEA-861-G chapter 6.1, table 6 +// type code is 0x01, version is 0x01, size is vendor specific + +typedef struct _HDMI_VS1_PAYLOAD +{ + uint8_t IEEERegistrationID[3]; // IEEE OUI + uint8_t bVendorSpecificPayload[HDMI_MAX_INFOFRAME_SIZE - 3 - 3 - 1]; // 24 bytes +} +HDMI_VS1_PAYLOAD; + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-G chapter 6.1, table 7 +// type code is 0x01, version is 0x02, size is vendor specific +// VS infoframe version 2 uses bit 7 of the version number as the ChangeBit + +typedef struct _HDMI_VS2_PAYLOAD +{ + uint8_t IEEERegistrationID[3]; // IEEE OUI + uint8_t bVendorSpecificPayload[HDMI_MAX_INFOFRAME_SIZE - 3 - 3 - 1]; // 24 bytes +} +HDMI_VS2_PAYLOAD; + + +//==================================================================================== +// # AUXILIARY VIDEO INFORMATION INFOFRAME +//==================================================================================== + +// Scan Information, see CEA-861-E chapter 6.4, table 10 +#define HDMI_AVI_S_NODATA 0x00 // no data +#define HDMI_AVI_S_OVERSCAN 0x01 // composed for overscan +#define HDMI_AVI_S_UNDERSCAN 0x02 // composed for underscan + +// Bar Data Present, see CEA-861-E chapter 6.4, table 10 +#define HDMI_AVI_B_NODATA 0x00 // no data +#define HDMI_AVI_B_V 0x01 // vertical bar info present +#define HDMI_AVI_B_H 0x02 // horizontal bar info present +#define HDMI_AVI_B_VH 0x03 // vertical and horizontal bar info present + +// Active Format Information Present, see CEA-861-E chapter 6.4, table 10 +#define HDMI_AVI_A_NONE 0x00 // no information +#define HDMI_AVI_A_PRESENT 0x01 // information present + +// RGB or YCbCr, see CEA-861-E chapter 6.4, table 10 +// RGB or YCbCr, see CEA-861-G chapter 6.4, table 10 +#define HDMI_AVI_Y_RGB 0x00 // RGB +#define HDMI_AVI_Y_YCBCR422 0x01 // YCbCr 4:2:2 +#define HDMI_AVI_Y_YCBCR444 0x02 // YCbCr 4:4:4 +#define HDMI_AVI_Y_YCBCR420 0x03 // YCbCr 4:2:0 +#define HDMI_AVI_Y_IDO 0x07 // IDO-Defined + +// Active Portion Aspect Ration, see CEA-861-E chapter 6.4, table 11 +#define HDMI_AVI_R_SAME 0x08 // same as coded frame aspect ration +#define HDMI_AVI_R_4TO3 0x09 // 4:3 (center) +#define HDMI_AVI_R_16TO9 0x0A // 16:9 (center) +#define HDMI_AVI_R_14TO9 0x0B // 14:9 (center) + +// Coded Frame Aspect Ration, see CEA-861-E chapter 6.4, table 11 +#define HDMI_AVI_M_NODATA 0x00 // no data +#define HDMI_AVI_M_4TO3 0x01 // 4:3 +#define HDMI_AVI_M_16TO9 0x02 // 16:9 + +// Colorimetry, see CEA-861-E chapter 6.4, table 11 +#define HDMI_AVI_C_NODATA 0x00 // no data +#define HDMI_AVI_C_SMTPE170M 0x01 // SMTPE 170M +#define HDMI_AVI_C_ITUR709 0x02 // ITU-R 709 +#define HDMI_AVI_C_EXTENDED 0x03 // extended colorimetry information valid + +// Non-Uniform Picture Scaling, see CEA-861-E chapter 6.4, table 13 +#define HDMI_AVI_SC_NO 0x00 // no known scaling +#define HDMI_AVI_SC_H 0x01 // picture has been scaled horizontally +#define HDMI_AVI_SC_V 0x02 // picture has been scaled vertically +#define HDMI_AVI_SC_HV 0x03 // picture has been scaled horizontally and vertically + +// RGB Quantization Range, see CEA-861-E chapter 6.4, table 13 +#define HDMI_AVI_Q_DEFAULT 0x00 // default, depends on video format +#define HDMI_AVI_Q_LIMITED 0x01 // limited range +#define HDMI_AVI_Q_FULL 0x02 // full range + +// Extended Colorimetry, see CEA-861-E chapter 6.4, table 13 +// Extended Colorimetry, see CEA-861-G chapter 6.4, table 13 +#define HDMI_AVI_EC_XVYCC601 0x00 // xvYCC 601 +#define HDMI_AVI_EC_XVYCC709 0x01 // xvYCC 709 +#define HDMI_AVI_EC_SYCC601 0x02 // sYCC 601 +#define HDMI_AVI_EC_ADOBEYCC601 0x03 // Adobe YCC 601 +#define HDMI_AVI_EC_ADOBERGB 0x04 // Adobe RGB +#define HDMI_AVI_EC_BT2020C 0x05 // ITU BT2020 YcCbcCrc +#define HDMI_AVI_EC_BT2020 0x06 // ITU BT2020 RGB or YCbCr +#define HDMI_AVI_EC_EXTENDED 0x07 // extended information, see HDMI_AVI_ACE_* + +// IT Content, see CEA-861-E chapter 6.4, table 13 +#define HDMI_AVI_ITC_NODATA 0x00 // no data +#define HDMI_AVI_ITC_VALID 0x01 // IT Content, CN is valid + +// Pixel Repetition Factor, see CEA-861-E chapter 6.4, table 15 +#define HDMI_AVI_PR_NONE 0x00 // pixels are not repeated, i.e. only sent once in total +#define HDMI_AVI_PR_1 0x01 // pixels are repeated once, i.e. sent twice in total +#define HDMI_AVI_PR_2 0x02 // pixels are repeated twice, i.e. sent three times in total +#define HDMI_AVI_PR_3 0x03 // pixels are repeated three times, i.e. sent 4 times in total +#define HDMI_AVI_PR_4 0x04 // pixels are repeated 4 times, i.e. sent 5 times in total +#define HDMI_AVI_PR_5 0x05 // pixels are repeated 5 times, i.e. sent 6 times in total +#define HDMI_AVI_PR_6 0x06 // pixels are repeated 6 times, i.e. sent 7 times in total +#define HDMI_AVI_PR_7 0x07 // pixels are repeated 7 times, i.e. sent 8 times in total +#define HDMI_AVI_PR_8 0x08 // pixels are repeated 8 times, i.e. sent 9 times in total +#define HDMI_AVI_PR_9 0x09 // pixels are repeated 9 times, i.e. sent 10 times in total + +// IT Content Type, see CEA-861-E chapter 6.4, table 16 +#define HDMI_AVI_CN_GRAPHICS 0x00 // graphics +#define HDMI_AVI_CN_PHOTO 0x01 // photo +#define HDMI_AVI_CN_CINEMA 0x02 // cinema +#define HDMI_AVI_CN_GAME 0x03 // game + +// YCC Quantization Range, see CEA-861-E chapter 6.4, table 17 +#define HDMI_AVI_YQ_LIMITED 0x00 // limited range +#define HDMI_AVI_YQ_FULL 0x01 // full range + +// Additional Colorimetry Extension, see CEA-861-G chapter 6.4, table 25 +#define HDMI_AVI_ACE_DCIP3D65 0x00 // DCI-P3 R'G'B' (D65) +#define HDMI_AVI_ACE_DCIP3TH 0x01 // DCI-P3 R'G'B' (Theater) + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.3, table 8 +// type code is 0x02, version is 0x01, size is 13 +// ATTENTION: this AVI version is obsolete and should not be used! + +typedef struct _HDMI_AVI1_PAYLOAD +{ + // data byte 1, see CEA-861-E chapter 6.4, table 10 + uint8_t bfScanInformation : 2; // S0, S1 + uint8_t bfBarDataPresent : 2; // B0, B1 + uint8_t bfActiveFormatInformationPresent : 1; // A0 + uint8_t bfRGBorYCbCr : 2; // Y0, Y1 + uint8_t bfFutureUse1 : 1; // reserved, zero + + // data byte 2, see CEA-861-E chapter 6.4, table 11 + uint8_t bfActivePortionAspectRatio : 4; // R0, R1, R2, R3 + uint8_t bfCodedFrameAspectRatio : 2; // M0, M1 + uint8_t bfColorimetry : 2; // C0, C1 + + // data byte 3, see CEA-861-E chapter 6.4, table 13 + uint8_t bfNonUniformPictureScaling : 2; // SC0, SC1 + uint8_t bfFutureUse3 : 6; // reserved, zero + + uint8_t bfFutureUse4 = 0; // reserved, zero + uint8_t bfFutureUse5 = 0; // reserved, zero + + uint16_t wLineNumberOfEndOfTopBar; // ETB + uint16_t wLineNumberOfStartOfBottomBar; // SBB + uint16_t wPixelNumberOfEndOfLeftBar; // ELB + uint16_t wPixelNumberOfStartOfRightBar; // SRB +} +HDMI_AVI1_PAYLOAD; + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.4, table 9 +// see CEA-861-E chapter 6.4, table 8 +// type code is 0x02, version is 0x02, size is 13 + +typedef struct _HDMI_AVI2_PAYLOAD +{ + // data byte 1, see CEA-861-E chapter 6.4, table 10 + uint8_t bfScanInformation : 2; // S0, S1 + uint8_t bfBarDataPresent : 2; // B0, B1 + uint8_t bfActiveFormatInformationPresent : 1; // A0 + uint8_t bfRGBorYCbCr : 2; // Y0, Y1 + uint8_t bfFutureUse1 : 1; // reserved, zero + + // data byte 2, see CEA-861-E chapter 6.4, table 11 + uint8_t bfActivePortionAspectRatio : 4; // R0, R1, R2, R3 + uint8_t bfCodedFrameAspectRatio : 2; // M0, M1 + uint8_t bfColorimetry : 2; // C0, C1 + + // data byte 3, see CEA-861-E chapter 6.4, table 13 + uint8_t bfNonUniformPictureScaling : 2; // SC0, SC1 + uint8_t bfRGBQuantizationRange : 2; // Q0, Q1 + uint8_t bfExtendedColorimetry : 3; // EC0, EC1, EC2 + uint8_t bfITContent : 1; // ITC + + // data byte 4, see CEA-861-E chapter 4.1, table 4 + uint8_t bfVIC : 7; // VIC + uint8_t bfFutureUse4 : 1; // reserved, zero + + // data byte 5, see CEA-861-E chapter 6.4, tables 15, 16, 17 + uint8_t bfPixelRepetitionFactor : 4; // PR0, PR1, PR2, PR3 + uint8_t bfITContentType : 2; // CN0, CN1 + uint8_t bfYCCQuantizationRange : 2; // YQ0, YQ1 + + uint16_t wLineNumberOfEndOfTopBar; // ETB + uint16_t wLineNumberOfStartOfBottomBar; // SBB + uint16_t wPixelNumberOfEndOfLeftBar; // ELB + uint16_t wPixelNumberOfStartOfRightBar; // SRB +} +HDMI_AVI2_PAYLOAD; + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.4, table 8 +// type code is 0x02, version is 0x03, size is 13 + +typedef struct _HDMI_AVI3_PAYLOAD +{ + // data byte 1, see CEA-861-G chapter 6.4, table 10 + uint8_t bfScanInformation : 2; // S0, S1 + uint8_t bfBarDataPresent : 2; // B0, B1 + uint8_t bfActiveFormatInformationPresent : 1; // A0 + uint8_t bfRGBorYCbCr : 3; // Y0, Y1, Y2 + + // data byte 2, see CEA-861-E chapter 6.4, table 11 + uint8_t bfActivePortionAspectRatio : 4; // R0, R1, R2, R3 + uint8_t bfCodedFrameAspectRatio : 2; // M0, M1 + uint8_t bfColorimetry : 2; // C0, C1 + + // data byte 3, see CEA-861-E chapter 6.4, table 13 + uint8_t bfNonUniformPictureScaling : 2; // SC0, SC1 + uint8_t bfRGBQuantizationRange : 2; // Q0, Q1 + uint8_t bfExtendedColorimetry : 3; // EC0, EC1, EC2 + uint8_t bfITContent : 1; // ITC + + // data byte 4, see CEA-861-E chapter 4.1, table 4 + uint8_t bVIC; // VIC + + // data byte 5, see CEA-861-E chapter 6.4, tables 15, 16, 17 + uint8_t bfPixelRepetitionFactor : 4; // PR0, PR1, PR2, PR3 + uint8_t bfITContentType : 2; // CN0, CN1 + uint8_t bfYCCQuantizationRange : 2; // YQ0, YQ1 + + uint16_t wLineNumberOfEndOfTopBar; // ETB + uint16_t wLineNumberOfStartOfBottomBar; // SBB + uint16_t wPixelNumberOfEndOfLeftBar; // ELB + uint16_t wPixelNumberOfStartOfRightBar; // SRB +} +HDMI_AVI3_PAYLOAD; + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.4, table 9 +// type code is 0x02, version is 0x04, size is 14 + +typedef struct _HDMI_AVI4_PAYLOAD +{ + // data byte 1, see CEA-861-G chapter 6.4, table 10 + uint8_t bfScanInformation : 2; // S0, S1 + uint8_t bfBarDataPresent : 2; // B0, B1 + uint8_t bfActiveFormatInformationPresent : 1; // A0 + uint8_t bfRGBorYCbCr : 3; // Y0, Y1, Y2 + + // data byte 2, see CEA-861-E chapter 6.4, table 11 + uint8_t bfActivePortionAspectRatio : 4; // R0, R1, R2, R3 + uint8_t bfCodedFrameAspectRatio : 2; // M0, M1 + uint8_t bfColorimetry : 2; // C0, C1 + + // data byte 3, see CEA-861-E chapter 6.4, table 13 + uint8_t bfNonUniformPictureScaling : 2; // SC0, SC1 + uint8_t bfRGBQuantizationRange : 2; // Q0, Q1 + uint8_t bfExtendedColorimetry : 3; // EC0, EC1, EC2 + uint8_t bfITContent : 1; // ITC + + // data byte 4, see CEA-861-E chapter 4.1, table 4 + uint8_t bVIC; // VIC + + // data byte 5, see CEA-861-E chapter 6.4, tables 15, 16, 17 + uint8_t bfPixelRepetitionFactor : 4; // PR0, PR1, PR2, PR3 + uint8_t bfITContentType : 2; // CN0, CN1 + uint8_t bfYCCQuantizationRange : 2; // YQ0, YQ1 + + uint16_t wLineNumberOfEndOfTopBar; // ETB + uint16_t wLineNumberOfStartOfBottomBar; // SBB + uint16_t wPixelNumberOfEndOfLeftBar; // ELB + uint16_t wPixelNumberOfStartOfRightBar; // SRB + + // data byte 14, see CEA-861-G chapter 6.4, tables 15, 16, 17 + uint8_t bfReserved14 : 4; // reserved, zero + uint8_t bfAdditionalColorimetry : 4; // ACE +} +HDMI_AVI4_PAYLOAD; + + +//------------------------------------------------------------------------------------ + + +#define HDMI_ERROR -1 + +#define HDMI_UNKNOWN 0 + +#define HDMI_FORMAT_RGB 1 // RGB +#define HDMI_FORMAT_YCBCR420 2 // YCbCr 4:2:0 +#define HDMI_FORMAT_YCBCR422 3 // YCbCr 4:2:2 +#define HDMI_FORMAT_YCBCR444 4 // YCbCr 4:4:4 + +#define HDMI_COLOR_ADOBERGB 1 +#define HDMI_COLOR_BT2020 2 +#define HDMI_COLOR_DCIP3D65 3 +#define HDMI_COLOR_DCIP3TH 4 +#define HDMI_COLOR_SMPTE170M 5 +#define HDMI_COLOR_BT709 6 +#define HDMI_COLOR_XVYCC601 7 +#define HDMI_COLOR_XVYCC709 8 +#define HDMI_COLOR_SYCC601 9 +#define HDMI_COLOR_ADOBEYCC601 10 +#define HDMI_COLOR_BT2020C 11 + + +//==================================================================================== +// # SOURCE PRODUCT DESCRIPTION INFOFRAME (SPD) +//==================================================================================== + + +// Source Information, see CEA-861-E chapter 6.5, table 22 +#define HDMI_SPD_SI_UNKNOWN 0x00 +#define HDMI_SPD_SI_STB 0x01 +#define HDMI_SPD_SI_DVD 0x02 +#define HDMI_SPD_SI_DVHS 0x03 +#define HDMI_SPD_SI_DVR 0x04 +#define HDMI_SPD_SI_DVC 0x05 +#define HDMI_SPD_SI_DSC 0x06 +#define HDMI_SPD_SI_VCD 0x07 +#define HDMI_SPD_SI_GAME 0x08 +#define HDMI_SPD_SI_PC 0x09 +#define HDMI_SPD_SI_BD 0x0A +#define HDMI_SPD_SI_SACD 0x0B +#define HDMI_SPD_SI_HDDVD 0x0C +#define HDMI_SPD_SI_PMP 0x0D + +const char* HDMI_SPD_ToString(uint8_t inByte); + +//! @brief Maps abbreviated names to user-friendly names (e.g. MSFT --> Microsoft) +//! @param inManufacturer manufacturer string as contained in HDMI info packet +std::string HDMI_SPD_MapManufacturerString(const std::string& inManufacturer); + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.5, table 21 +// type code is 0x03, version is 1, size is 25 + +typedef struct _HDMI_SPD1_PAYLOAD +{ + uint8_t bVendorName[8]; + uint8_t bProductDescription[16]; + uint8_t bSourceInformation; +} +HDMI_SPD1_PAYLOAD; + + +//==================================================================================== +// AUDIO INFOFRAME +//==================================================================================== + + +// Audio Channel Count, see CEA-861-E chapter 6.6.1, table 24 +#define HDMI_A_CC_STREAM 0x00 // see stream header + +// Audio Coding Type, see CEA-861-E chapter 6.6.1, table 24 +#define HDMI_A_CT_STREAM 0x00 // see stream header +#define HDMI_A_CT_PCM 0x01 // PCM +#define HDMI_A_CT_AC3 0x02 // AC-3 +#define HDMI_A_CT_MPEG1 0x03 // MPEG-1 +#define HDMI_A_CT_MP3 0x04 // MP3 +#define HDMI_A_CT_MPEG2 0x05 // MPEG-2 +#define HDMI_A_CT_AACLC 0x06 // AAC-LC +#define HDMI_A_CT_DTS 0x07 // DTS +#define HDMI_A_CT_ATRAC 0x08 // ATRAC +#define HDMI_A_CT_DSD 0x09 // DSD +#define HDMI_A_CT_EAC3 0x0A // E-AC-3 +#define HDMI_A_CT_DTSHD 0x0B // DTS-HD +#define HDMI_A_CT_MLP 0x0C // MLP +#define HDMI_A_CT_DST 0x0D // DST +#define HDMI_A_CT_WMAPRO 0x0E // WMA Pro +#define HDMI_A_CT_CXT 0x0F // see Audio Coding Extension Type (CXT) + +// Sample Size, see CEA-861-E chapter 6.6.1, table 25 +#define HDMI_A_SS_STREAM 0x00 // see stream header +#define HDMI_A_SS_16BIT 0x01 // 16bit +#define HDMI_A_SS_20BIT 0x02 // 20bit +#define HDMI_A_SS_24BIT 0x03 // 24bit + +// Sample Frequency, see CEA-861-E chapter 6.6.1, table 25 +#define HDMI_A_SF_STREAM 0x00 // see stream header +#define HDMI_A_SF_32000 0x01 // 32kHz +#define HDMI_A_SF_44100 0x02 // 44.1kHz +#define HDMI_A_SF_48000 0x03 // 48kHz +#define HDMI_A_SF_88200 0x04 // 88.2kHz +#define HDMI_A_SF_96000 0x05 // 96kHz +#define HDMI_A_SF_176400 0x06 // 176.4kHz +#define HDMI_A_SF_192000 0x07 // 192kHz + +// Audio Coding Extension Type, see CEA-861-E chapter 6.6.1, table 26 +#define HDMI_A_CXT_CT 0x00 // see Audio Coding Type (CT) +#define HDMI_A_CXT_HEAAC 0x01 // HE-AAC +#define HDMI_A_CXT_HEAAC2 0x02 // HE-AAC v2 +#define HDMI_A_CXT_MPEGSURROUND 0x03 // MPEG Surround + +// Down-mix Inhibit, see CEA-861-E chapter 6.6.2, table 30 +#define HDMI_A_DM_PERMITTED 0x00 // down mix permitted +#define HDMI_A_DM_PROHIBITED 0x01 // down mix prohibited + +// LFE Playback Level, see CEA-861-E chapter 6.6.2, table 31 +#define HDMI_A_LFEPBL_UNKNOWN 0x00 // unknown +#define HDMI_A_LFEPBL_0DB 0x01 // 0 dB +#define HDMI_A_LFEPBL_10DB 0x02 // +10 dB + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.6, table 23 +// type code is 0x04, version is 1, size is 10 + +typedef struct _HDMI_A1_PAYLOAD +{ + // data byte 1, see CEA-861-E chapter 6.6.1, table 24 + uint8_t bfChannelCount : 3; // CC0, CC1, CC2 (channel count - 1) + uint8_t bfReserved1 : 1; // reserved, zero + uint8_t bfAudioCodingType : 4; // CT0, CT1, CT2, CT3 + + // data byte 2, see CEA-861-E chapter 6.6.1, table 25 + uint8_t bfSampleSize : 2; // SS0, SS1 + uint8_t bfSampleFrequency : 3; // SF0, SF1, SF2 + uint8_t bfReserved2 : 3; // reserved, zero + + // data byte 3, see CEA-861-E chapter 6.6.1, table 26 + uint8_t bfAudioCodingExtensionType : 5; // CXT0, CXT1, CXT2, CXT3, CXT4 + uint8_t bfReserved3 : 3; // reserved, zero + + // data byte 4, see CEA-861-E chapter 6.6.2, table 28 + uint8_t bChannelAllocation; // CA (channel to speaker) + + // data byte 5, see CEA-861-E chapter 6.6.2, tables 29, 30, 31 + uint8_t bfLFEPlaybackLevel : 2; // LFEPBL0, LFEPBL1 + uint8_t bfReserved5 : 1; // reserved, zero + uint8_t bfLevelShiftValue : 4; // LSV0, LSV1 (dB) + uint8_t bfDownMixInhibitFlag : 1; // DM_INH + + uint8_t bReserved6 = 0; // reserved, or Speaker Mask, or Channel Index + uint8_t bReserved7 = 0; // reserved, or Speaker Mask, or Channel Index + uint8_t bReserved8 = 0; // reserved, or Speaker Mask, or Channel Index + uint8_t bReserved9 = 0; // reserved, or Speaker Mask, or Channel Index + uint8_t bReserved10 = 0; // reserved, zero +} +HDMI_A1_PAYLOAD; + + +//==================================================================================== +// MPEG SOURCE INFOFRAME +//==================================================================================== + + +// MPEG Frame, see CEA-861-E chapter 6.7, table 33 +#define HDMI_MS_MF_UNKNOWN 0x00 // unknown +#define HDMI_MS_MF_I 0x01 // I-Frame +#define HDMI_MS_MF_P 0x02 // P-Frame +#define HDMI_MS_MF_B 0x03 // B-Frame + +// Field Repeat, see CEA-861-E chapter 6.7, table 33 +#define HDMI_MS_FR_NEW 0x00 // new field +#define HDMI_MS_FR_REPEATED 0x01 // repeated field + + +//------------------------------------------------------------------------------------ + + +// see CEA-861-E chapter 6.7, table 32 +// type code is 0x05, version is 1, size is 10 +// ATTENTION: it is recommended not to use this info frame + +typedef struct _HDMI_MS1_PAYLOAD +{ + uint32_t dwMPEGBitRate; // MPEG bit rate in Hz + + // data byte 5, see CEA-861-E chapter 6.7, table 33 + uint8_t bfMPEGFrame : 2; // MF0, MF1 + uint8_t bfReserved5a : 2; // reserved, zero + uint8_t bfFieldRepeat : 1; // FR0 + uint8_t bfReserved5b : 3; // reserved, zero + + uint8_t bReserved6 = 0; // reserved + uint8_t bReserved7 = 0; // reserved + uint8_t bReserved8 = 0; // reserved + uint8_t bReserved9 = 0; // reserved + uint8_t bReserved10 = 0; // reserved +} +HDMI_MS1_PAYLOAD; + + +//==================================================================================== +// NTSC VBI INFOFRAME +//==================================================================================== + + +// see CEA-861-E chapter 6.8, table 34 +// type code is 0x06, version is 1, size depends + +typedef struct _HDMI_VBI1_PAYLOAD +{ + uint8_t bPESDataField[HDMI_MAX_INFOFRAME_SIZE - 3 - 1]; // PES data field, limited to max 27 bytes +} +HDMI_VBI1_PAYLOAD; + + +//==================================================================================== +// # DYNAMIC RANGE AND MASTERING INFOFRAME +//==================================================================================== + +/* +Example for valid DR info frame: + +87 (type) | Header +01 (version) | +1A (length) | +8D (checksum) +02 00 FA 00 AE 02 85 00 | Payload +29 00 A3 02 5C 01 40 01 | +51 01 DB 05 00 00 DB 05 | +1F 03 | +*/ + +//------------------------------------------------------------------------------------ + + +// EOTF, see CEA-861.3-A chapter 3.2, table 3 +#define HDMI_DR_EOTF_SDRGAMMA 0x00 // traditional gamma, SDR +#define HDMI_DR_EOTF_HDRGAMMA 0x01 // traditional gamma, HDR +#define HDMI_DR_EOTF_ST2084 0x02 // ST2084 PQ +#define HDMI_DR_EOTF_HLG 0x03 // BT2100 HLG + +// Metadata, see CEA-861.3-A chapter 3.2, table 4 +#define HDMI_DR_MD_STATIC 0x00 // static metadata type 1 + + +//------------------------------------------------------------------------------------ + + +// used for static metadata, see CEA-861.3-A chapter 3.2.1 +typedef struct _HDMI_XY +{ + uint16_t X; // encoded in units of 0.00002 + uint16_t Y; // encoded in units of 0.00002 +} +HDMI_XY; + + +//------------------------------------------------------------------------------------ + +//! HDR Meta Data +//! type code is 0x07, version is 1, size depends (30 for static metadata type 1) +//! @sa CEA-861.3-A chapter 3.2, table 2 + +typedef struct _HDMI_DR1_PAYLOAD +{ + // data byte 1, see CEA-861.3-A chapter 3.2, table 3 + uint8_t bfEOTF : 3; // EOTF + uint8_t bfReserved1 : 5; // reserved + + // data byte 2, see CEA-861.3-A chapter 3.2, table 4 + uint8_t bfMetadataID : 3; // static metadata descriptor ID + uint8_t bfReserved2 : 5; // reserved + + // data bytes 3-22, for static metadata type 1, see CEA-861.3-A chapter 3.2.1, table 5 + HDMI_XY xyDisplayPrimaries[3]; // chromaticity of red or green or blue (ST2086) + HDMI_XY xyWhitePoint; // white point (ST2086) + uint16_t wMaxDisplayLuminance; // maximum display mastering luminance (ST2086), nit + uint16_t wMinDisplayLuminance; // minimum display mastering luminance (ST2086), 0.0001 nit + uint16_t wMaxCLL; // maximum content light level, nit + uint16_t wMaxFALL; // maximum frame-average light level, nit +} +HDMI_DR1_PAYLOAD; + + +//==================================================================================== +// GENERIC INFOFRAME TYPE +//==================================================================================== + + +typedef struct _HDMI_GENERIC_INFOFRAME +{ + HDMI_INFOFRAMEHEADER header; // type, version, length + uint8_t bChecksum; // the sum of all bytes in the info frame must be zero + + union + { + // generic byte array, to address the InfoFrame by index + uint8_t bPayload[HDMI_MAX_INFOFRAME_SIZE - 3 - 1] = { 0 }; + + // specific infoframes + HDMI_VS1_PAYLOAD plVS1; + HDMI_VS2_PAYLOAD plVS2; + HDMI_AVI1_PAYLOAD plAVI1; + HDMI_AVI2_PAYLOAD plAVI2; + HDMI_AVI3_PAYLOAD plAVI3; + HDMI_AVI4_PAYLOAD plAVI4; + HDMI_SPD1_PAYLOAD plSPD1; + HDMI_A1_PAYLOAD plA1; + HDMI_MS1_PAYLOAD plMS1; + HDMI_VBI1_PAYLOAD plVBI1; + HDMI_DR1_PAYLOAD plDR1; + }; +} +HDMI_GENERIC_INFOFRAME; + + +#pragma pack(pop) + + +//==================================================================================== +// # FUNCTIONS +//==================================================================================== + +// Verifies the checksum of the HDMI Info Frame +inline bool HDMI_IsInfoFrameValid(const _HDMI_GENERIC_INFOFRAME* pInfoFrame) +{ + if (pInfoFrame == NULL) return false; + + unsigned char* data = (unsigned char*)pInfoFrame; + int size = sizeof(HDMI_INFOFRAMEHEADER) + 1 + pInfoFrame->header.bPayloadLength; + + unsigned char checksum = 0; + while (size-- != 0) checksum += *data++; + + return (checksum == 0); +} + + + diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/mac/EGAVHIDImplementation.cpp b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/mac/EGAVHIDImplementation.cpp new file mode 100644 index 0000000..c34a91c --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/mac/EGAVHIDImplementation.cpp @@ -0,0 +1,284 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVHIDImplementation.cpp + +@brief macOS implementation of EGAVHIDInterface +**/ +//============================================================================== + +#include "EGAVHIDImplementation.h" +#include +#include +#include + + +//------------------------------------------------------------------------------ +// PREPROCESSOR SWITCHES +//------------------------------------------------------------------------------ + +std::shared_ptr CreateEGAVHIDInterface() +{ + return std::make_shared(); +} + +//============================================================================== +// # HID Device Enumeration +//============================================================================== + +static int LocationIDOfHIDDevice(IOHIDDeviceRef hidRef) +{ + int locationID = 0; + + // this block is just here to see if we can find the corresponding device + + CFTypeRef uniqueID = IOHIDDeviceGetProperty(hidRef, CFSTR(kIOHIDUniqueIDKey)); + if (CFGetTypeID(uniqueID) == CFNumberGetTypeID()) + { + uint64_t uniqueID64 = 0; + CFNumberGetValue((CFNumberRef)uniqueID, kCFNumberLongLongType, &uniqueID64); + //NSLog(@"uniqueID %lld or 0x%16llx", uniqueID64, uniqueID64); + + CFMutableDictionaryRef matchingDict = IORegistryEntryIDMatching(uniqueID64); + + // next call consumes a reference to matchingDict + io_service_t matchedService = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict); + if (matchedService != 0) + { + CFNumberRef loc = (CFNumberRef)IORegistryEntrySearchCFProperty(matchedService, kIOServicePlane, CFSTR("locationID"), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + if (loc != NULL) + { + CFNumberGetValue(loc, kCFNumberIntType, &locationID); + CFRelease(loc); + } + } + } + + return locationID; +} + +static void HIDDeviceMatchingCallback(void* inContext, + IOReturn /*inResult*/, + void* /*inSender*/, + IOHIDDeviceRef inIOHIDDeviceRef) +{ + reinterpret_cast(inContext)->DeviceAdded(inIOHIDDeviceRef); +} + +static void HIDDeviceRemovalCallback(void* inContext, + IOReturn /*inResult*/, + void* /*inSender*/, + IOHIDDeviceRef inIOHIDDeviceRef) +{ + reinterpret_cast(inContext)->DeviceRemoved(inIOHIDDeviceRef); +} + + + + +//============================================================================== +// # Class EGAVHID +//============================================================================== + +EGAVHID::EGAVHID() +{ +} + +void EGAVHID::DeviceAdded(IOHIDDeviceRef deviceRef) +{ + if (mLocationID == 0) // we don't care about a specific locationID + { + mHIDDevice = deviceRef; + info_printf("## DeviceAdded()"); + } + else if (LocationIDOfHIDDevice(deviceRef) == mLocationID) + { + mHIDDevice = deviceRef; + info_printf("## DeviceAdded(): Location ID %d", mLocationID); + } +} + +void EGAVHID::DeviceRemoved(IOHIDDeviceRef deviceRef) +{ + if (mHIDDevice == deviceRef) + { + info_printf("## DeviceRemoved()"); + mHIDDevice = 0; + } +} + + +//============================================================================== +// ## HID interface +//============================================================================== + +EGAVResult EGAVHID::InitHIDInterface(const EGAVDeviceID& inDeviceID, EGAVUnitPtr /*inOwner*/, bool /* inIgnoreDevicePathCheck = false */) +{ + dbgFunctionI(); + EGAVResult res = EGAVResult::Ok; + + mLocationID = inDeviceID.locationID; + + if (!mWorkerCreated) // program dies if thread is assigned when it isn't already null + { + mWorkerCreated = true; + mWorker = std::thread([this, inDeviceID] + { + mRunLoop = CFRunLoopGetCurrent(); + + IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone); + + IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceMatchingCallback, this); + IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemovalCallback, this); + + CFMutableDictionaryRef matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFNumberRef vendor = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &inDeviceID.vendorID); + CFNumberRef product = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &inDeviceID.productID); + + CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDVendorIDKey), vendor); + CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDProductIDKey), product); + + IOHIDManagerSetDeviceMatching(manager, matchingDict); + + IOHIDManagerScheduleWithRunLoop(manager, mRunLoop, kCFRunLoopDefaultMode); + + IOReturn ret = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone); + EPL_ASSERT_BREAK(ret == kIOReturnSuccess); + + CFRunLoopRun(); + + IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); + + CFRelease(manager); + }); + } + + // Wait for device found + int64_t startTimeMsec = EplTime_GetMonotonicMilliseconds(); + const int64_t kHidDiscoveryTimeoutMsec = 1500; + while (!mHIDDevice && EplTime_GetMonotonicMilliseconds() - startTimeMsec < kHidDiscoveryTimeoutMsec) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + if (mHIDDevice) + { + // Query input report size //! @todo do this only once + CFIndex reportSize = 0; + CFTypeRef number = IOHIDDeviceGetProperty(mHIDDevice, CFSTR(kIOHIDMaxInputReportSizeKey)); + CFNumberGetValue((CFNumberRef)number, kCFNumberCFIndexType, &reportSize); + mInputReportSize = (int)reportSize; + + number = IOHIDDeviceGetProperty(mHIDDevice, CFSTR(kIOHIDMaxOutputReportSizeKey)); + CFNumberGetValue((CFNumberRef)number, kCFNumberCFIndexType, &reportSize); + mOutputReportSize = (int)reportSize; + } + + return mHIDDevice ? EGAVResult::Ok : EGAVResult::ErrNotFound; +} + +EGAVResult EGAVHID::DeinitHIDInterface() +{ + dbgFunctionI(); + mHIDDevice = nullptr; + if (mRunLoop != nullptr) + { + CFRunLoopStop(mRunLoop); + mWorker.join(); + mRunLoop = nullptr; + } + mWorkerCreated = false; + return EGAVResult::Ok; +} + +EGAVResult EGAVHID::ReadHID(std::vector& outMessage, int inReportId, int inReadBufferSize /*= 0*/) +{ + EGAVResult_CheckPointer(mHIDDevice); + + EGAVResult res = EGAVResult::Ok; + + std::vector report(mInputReportSize); // from the hardware, no zero prepended + report[0] = inReportId; + + int usedSize = 0; + + { + CFIndex bufferSize = inReadBufferSize > 0 ? inReadBufferSize : report.size(); + IOReturn err = IOHIDDeviceGetReport(mHIDDevice, kIOHIDReportTypeInput, inReportId, + &report[0], &bufferSize); + if (err == noErr) + usedSize = (int)bufferSize; + else error_printf("IOHIDDeviceGetReport() failed with IOReturn %d (0x%08X)", err, err); + res = (err == noErr) ? EGAVResult::Ok : EGAVResult::ErrUnknown; + } + + if (res.Succeeded()) + { + outMessage.clear(); + + // Facecam: calling code expects the report ID (0) in front of the report (Facecam) + //! @todo check if this behavior is really necessary. Check on Windows also. + if (inReportId == kHidDefaultReportID) + outMessage.push_back(inReportId); + + for (int i = 0; i< usedSize; i++) + outMessage.push_back(report[i]); + } + return res; +} + +//! If device only has one report ID, it is zero (kHidDefaultReportID) +EGAVResult EGAVHID::WriteHID(const std::vector& inMessage, int inReportID) +{ + // dbgFunctionI(); + + EGAVResult_CheckPointer(mHIDDevice); + + std::vector report; + + // From Device Class Definition for Human Interface Devices (HID) Version 1.11 + // If a device has multiple report structures, all data transfers start with a 1-byte identifier prefix that indicates which report structure + // applies to the transfer. This allows the class driver to distinguish incoming pointer data from keyboard data by examining the transfer prefix. + if (inReportID != kHidDefaultReportID) + report.push_back(inReportID); + + for (auto m : inMessage) + report.push_back(m); + + report.resize(mInputReportSize, 0); // pad report with zeros, ensure it is always the right length + IOReturn err = IOHIDDeviceSetReport(mHIDDevice, kIOHIDReportTypeOutput, inReportID, + &report[0], report.size()); + + // 0xE00002D6 - kIOReturnTimeout + // 0xE00002EB - kIOReturnAborted + if (err != noErr) + error_printf("IOHIDDeviceSetReport() failed with IOReturn %d (0x%08X)", err, err); + + EGAVResult res = (err == noErr) ? EGAVResult::Ok : EGAVResult::ErrUnknown; + + return res; +} diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/mac/EGAVHIDImplementation.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/mac/EGAVHIDImplementation.h new file mode 100644 index 0000000..649859f --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/mac/EGAVHIDImplementation.h @@ -0,0 +1,76 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVHIDImplementation.h + +@brief macOS implementation of EGAVHIDInterface +**/ +//============================================================================== + +#pragma once + +#include +#include + +// macOS +#include + +#include "EGAVEngine/EGAVHID.h" + +class HIDTransport; + +class EGAVHID : public EGAVHIDInterface +{ +public: + EGAVHID(); + + void DeviceAdded(IOHIDDeviceRef deviceRef); + void DeviceRemoved(IOHIDDeviceRef deviceRef); + + //----------------------------------------------------------------------------- + // ## EGAVHIDInterface implementation + //----------------------------------------------------------------------------- + virtual EGAVResult InitHIDInterface(const EGAVDeviceID& inDeviceID, EGAVUnitPtr inOwner, bool inIgnoreDevicePathCheck = false) override; + virtual EGAVResult DeinitHIDInterface() override; + + //! @brief Reads a HID response message from the OS. + //! @param outMessage will contain the resulting message. Its length will be adjusted automatically. + virtual EGAVResult ReadHID(std::vector& outMessage, int inReportID, int inReadBufferSize = 0) override; + + //! @brief Writes a HID report (with ID 0) containing inMessage to the OS. + //! @param inMessage the report contents, not including the report ID. + virtual EGAVResult WriteHID(const std::vector& inMessage, int inReportID) override; + +private: + int mLocationID = 0; + int mInputReportSize = 0, mOutputReportSize = 0; + + std::atomic mHIDDevice = nullptr; + + std::atomic mRunLoop = nullptr; + std::thread mWorker; //!< background worker for HID device discovery + bool mWorkerCreated = false; +}; diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/win/EGAVHIDImplementation.cpp b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/win/EGAVHIDImplementation.cpp new file mode 100644 index 0000000..cdc5d17 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/win/EGAVHIDImplementation.cpp @@ -0,0 +1,216 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVHIDImplementation.cpp + +@brief Windows implementation of EGAVHIDInterface +**/ +//============================================================================== + +// https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/introduction-to-hid-concepts + +#include "EGAVHIDImplementation.h" + +#include // for ostringstream + +// Windows headers; for HID interface +#include +#pragma comment(lib,"hid.lib") +#include +#include + +#pragma comment(lib, "setupapi.lib") + + +#ifndef SAFE_CLOSE_HANDLE + #define SAFE_CLOSE_HANDLE(_handle_) \ + { \ + if ((_handle_ != 0) && (_handle_ != INVALID_HANDLE_VALUE))\ + { \ + CloseHandle(_handle_); \ + _handle_= INVALID_HANDLE_VALUE; \ + } \ + } +#endif + +std::string GetHIDDevicePath(int inIndex) +{ + std::string devicePath; + + GUID guid; + HidD_GetHidGuid(&guid); + + HDEVINFO DeviceInfo = SetupDiGetClassDevs(&guid, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); + + SP_DEVICE_INTERFACE_DATA DeviceInterface; + DeviceInterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + if (!SetupDiEnumDeviceInterfaces(DeviceInfo, NULL, &guid, inIndex, &DeviceInterface)) + { + SetupDiDestroyDeviceInfoList(DeviceInfo); + return ""; + } + + unsigned long size = 0; + SetupDiGetDeviceInterfaceDetail(DeviceInfo, &DeviceInterface, NULL, 0, &size, 0); + + PSP_INTERFACE_DEVICE_DETAIL_DATA pDeviceDetail = (PSP_INTERFACE_DEVICE_DETAIL_DATA)malloc(size); + if (pDeviceDetail) + { + pDeviceDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail(DeviceInfo, &DeviceInterface, pDeviceDetail, size, &size, NULL)) + devicePath = CT2A(pDeviceDetail->DevicePath); + free(pDeviceDetail); + } + + SetupDiDestroyDeviceInfoList(DeviceInfo); + return devicePath; +} + + + +std::shared_ptr CreateEGAVHIDInterface() +{ + return std::make_shared(); +} + + +//============================================================================== +// ## Class EGAVHID +//============================================================================== + +EGAVHID::EGAVHID() + : mHIDCaps(std::make_unique<_HIDP_CAPS>()) +{ +} + +EGAVResult EGAVHID::InitHIDInterface(const EGAVDeviceID& inDeviceID) +{ + EGAVResult res = EGAVResult::ErrNotFound; + + + DWORD index = 0; + std::string path; + while ((path = GetHIDDevicePath(index++)) != "") + { + HANDLE hHidDevice = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hHidDevice == INVALID_HANDLE_VALUE) + continue; + + bool isCorrectHidDevice = false; + HIDD_ATTRIBUTES attr; + if (HidD_GetAttributes(hHidDevice, &attr)) + isCorrectHidDevice = (attr.VendorID == inDeviceID.vendorID && attr.ProductID == inDeviceID.productID); + + if (isCorrectHidDevice) + { + mHIDHandle = hHidDevice; + + { + PHIDP_PREPARSED_DATA p; + HidD_GetPreparsedData(mHIDHandle, &p); + HIDP_CAPS c; + HidP_GetCaps(p, &c); + + *mHIDCaps = c; + + HidD_FreePreparsedData(p); + } + + res = EGAVResult::Ok; + break; + } + CloseHandle(hHidDevice); + } + + return res; +} + +EGAVResult EGAVHID::DeinitHIDInterface() +{ + SAFE_CLOSE_HANDLE(mHIDHandle); + return EGAVResult::Ok; +} + +//! This reads the report from the hardware +//! See Facemcam (Penna) sample code in https://elgato.atlassian.net/browse/EVH-493 +EGAVResult EGAVHID::ReadHID(std::vector& outMessage, int inReportID, int inReadBufferSize/* = 0*/) +{ + if (outMessage.size() >= mHIDCaps->InputReportByteLength) + return EGAVResult::ErrInvalidParameter; + + std::vector inputReport(mHIDCaps->InputReportByteLength); + + inputReport[0] = (uint8_t)inReportID; + + if (inReadBufferSize > 0) + inputReport.resize(inReadBufferSize); // Required for Cam Link PD575 (EVH-1418) + + BOOL success = HidD_GetInputReport(mHIDHandle, &inputReport[0], (ULONG)inputReport.size()); + EGAVResult res = success ? EGAVResult::Ok : EGAVResult::ErrInvalidOperation; + + // 121- ERROR_SEM_TIMEOUT + // 31 - ERROR_GEN_FAILURE - for invalid report ID + if (FALSE == success) // 87 - ERROR_INVALID_PARAMETER - if (buffer size != caps.InputReportByteLength) + { + // error_printf("HidD_GetInputReport() for report ID %d FAILED with %d", inReportID, GetLastError()); + } + else + { + outMessage.assign(inputReport.begin(), inputReport.end()); + } + + return res; +} + +// this only prepends a zero byte (the report ID) to the message, pads it out to the size of an output +// report and sends it to the hardware + +EGAVResult EGAVHID::WriteHID(const std::vector& inMessage, int inReportID) +{ + if (!mHIDCaps) + return EGAVResult::ErrInvalidState; + + if (inMessage.size() > mHIDCaps->OutputReportByteLength-1) + return EGAVResult::ErrInvalidParameter; + + std::vector outputReport(mHIDCaps->OutputReportByteLength, 0); + + outputReport[0] = (uint8_t)inReportID; + memcpy(&outputReport[1], &inMessage[0], inMessage.size()); + + // If the top-level collection includes report IDs, the caller must set the first byte of the ReportBuffer parameter to a non-zero report ID. + BOOL success = HidD_SetOutputReport(mHIDHandle, &outputReport[0], (ULONG)outputReport.size()); + EGAVResult res = success ? EGAVResult::Ok : EGAVResult::ErrInvalidOperation; + // 1167 - ERROR_DEVICE_NOT_CONNECTED + if (FALSE == success) // 87 - ERROR_INVALID_PARAMETER - if (buffer size != c.OutputReportByteLength) + { + // error_printf("#### HID: HidD_SetOutputReport() FAILED with %d", GetLastError()); + } + + return res; +} \ No newline at end of file diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/win/EGAVHIDImplementation.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/win/EGAVHIDImplementation.h new file mode 100644 index 0000000..8eac577 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/Library/win/EGAVHIDImplementation.h @@ -0,0 +1,65 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file EGAVHIDImplementation.h + +@brief Windows implementation of EGAVHIDInterface +**/ +//============================================================================== + +#pragma once + +#include + +#include "EGAVHID.h" + +struct _HIDP_CAPS; + +class EGAVHID : public EGAVHIDInterface +{ +public: + EGAVHID(); + + //----------------------------------------------------------------------------- + // ## EGAVHIDInterface implementation + //----------------------------------------------------------------------------- + //! @param inIgnoreDevicePathCheck true for Cyclops because HID has a different DeviceID + virtual EGAVResult InitHIDInterface(const EGAVDeviceID& inDeviceID) override; + virtual EGAVResult DeinitHIDInterface() override; + + //! @brief Reads a HID response message from the OS. + //! @param outMessage will contain the resulting message. Its length will be adjusted automatically. + virtual EGAVResult ReadHID(std::vector& outMessage, int inReportID, int inReadBufferSize = 0) override; + + //! @brief Writes a HID report (with ID 0) containing inMessage to the OS. + //! @param inMessage the report contents, not including the report ID. + virtual EGAVResult WriteHID(const std::vector& inMessage, int inReportID) override; + + HANDLE GetHIDHandle() { return mHIDHandle; } +private: + HANDLE mHIDHandle = nullptr; + std::unique_ptr<_HIDP_CAPS> mHIDCaps; +}; \ No newline at end of file diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/DriverInterface.cpp b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/DriverInterface.cpp new file mode 100644 index 0000000..91b0b2c --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/DriverInterface.cpp @@ -0,0 +1,156 @@ +/* +MIT License + +Copyright (c) 2022 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file DriverInterface.cpp + +@brief EGAVDeviceProperties class implementation + Support for 4K60 Pro MK.2 and 4K60 S+ +**/ +//============================================================================== + +#include "HDMIInfoFramesAPI.h" +#include "DriverInterface.h" + +#ifndef EGAV_OVERRIDE_DEBUG_MACROS + inline void dummy() {} + + #define warning_printf(...) dummy() + + #ifndef HR_CHKRET_POINTER + #define HR_CHKRET_POINTER(_ptr_) if (!(_ptr_)) return E_POINTER; + #endif +#endif + + +//! Property IDs for IKsPropertySet +enum class DriverProperty +{ + XET_ENCODER_VIDEO_FORMAT = 400, //!< 4K60 S+ encoder format. uint32_t parameter: 0 - H.264 and 1 - HEVC + GET_HDMI_HDR_PACKET_00_15 = 720, //!< HDMI HDR status packet - part 1 + GET_HDMI_HDR_PACKET_16_31 = 721, //!< HDMI HDR status packet - part 2 + XET_HDMI_HDR_TO_SDR = 722 //!< 4K60 Pro MK.2 Set HDR tonemapping. uint32_t parameter: 1 - on / 0 - off +}; + +static const int HDMI_PACKET_SIZE = 32; + + +EGAVDeviceProperties::EGAVDeviceProperties(IKsPropertySet* inKsPropertySet, DeviceType inDeviceType) + : mICustomPropertySet(inKsPropertySet), mDeviceType(inDeviceType) +{ + switch (inDeviceType) + { + case EGAVDeviceProperties::DeviceType::GC4K60ProMK2: + mCustomPropertySetGUID = { 0xD1E5209F, 0x68FD, 0x4529, 0xBE, 0xE0, 0x5E, 0x7A, 0x1F, 0x47, 0x92, 0x26 }; + break; + case EGAVDeviceProperties::DeviceType::GC4K60SPlus: + mCustomPropertySetGUID = { 0xD1E5209F, 0x68FD, 0x4529, 0xBE, 0xE0, 0x5E, 0x7A, 0x1F, 0x47, 0x92, 0x24 }; + break; + default: + break; + } +} + +HRESULT EGAVDeviceProperties::SetEncoderType(bool inHEVC) +{ + if (mDeviceType != DeviceType::GC4K60SPlus) + return E_FAIL; + + HR_CHKRET_POINTER(mICustomPropertySet); + + uint32_t param = inHEVC ? 1 : 0; + HRESULT hr = mICustomPropertySet->Set(mCustomPropertySetGUID, (DWORD)DriverProperty::XET_ENCODER_VIDEO_FORMAT, nullptr, 0, ¶m, sizeof(param)); + return hr; +} + +HRESULT EGAVDeviceProperties::SetHDRTonemapping(bool inEnable) +{ + if (mDeviceType != DeviceType::GC4K60ProMK2) + return E_FAIL; + + HR_CHKRET_POINTER(mICustomPropertySet); + + uint32_t param = inEnable ? 1 : 0; + HRESULT hr = mICustomPropertySet->Set(mCustomPropertySetGUID, (DWORD)DriverProperty::XET_HDMI_HDR_TO_SDR, nullptr, 0, ¶m, sizeof(param)); + return hr; +} + +HRESULT EGAVDeviceProperties::GetHDMIHDRStatusPacket(uint8_t *outBuffer, int inBufferSize) +{ + HR_CHKRET_POINTER(outBuffer); + + if (inBufferSize < HDMI_PACKET_SIZE) + return E_INVALIDARG; + + HR_CHKRET_POINTER(mICustomPropertySet); + + DWORD dwRet = 0; + HRESULT hr = mICustomPropertySet->Get(mCustomPropertySetGUID, (DWORD)DriverProperty::GET_HDMI_HDR_PACKET_00_15, nullptr, 0, &outBuffer[0], 16, &dwRet); + if (SUCCEEDED(hr)) + hr = mICustomPropertySet->Get(mCustomPropertySetGUID, (DWORD)DriverProperty::GET_HDMI_HDR_PACKET_16_31, nullptr, 0, &outBuffer[16], 16, &dwRet); + return hr ; +} + +HRESULT EGAVDeviceProperties::IsVideoHDR(bool& outIsHDR) +{ + outIsHDR = false; + + // Try to read HDR meta data + static const uint8_t emptyBuffer[HDMI_PACKET_SIZE] = { 0 }; + uint8_t buffer[HDMI_PACKET_SIZE] = { 0 }; + HRESULT hr = GetHDMIHDRStatusPacket(buffer, sizeof(buffer)); + if (SUCCEEDED(hr)) + { + HDMI_GENERIC_INFOFRAME* frame = (HDMI_GENERIC_INFOFRAME*)(&buffer[0]); + hr = (true == HDMI_IsInfoFrameValid(frame)) ? S_OK : E_FAIL; + if (SUCCEEDED(hr)) + { + // Check type in header and EOTF flag in payload + if (HDMI_INFOFRAME_TYPE_DR == frame->header.bfType && HDMI_DR_EOTF_SDRGAMMA != frame->plDR1.bfEOTF) + { + outIsHDR = true; + } + else if (HDMI_INFOFRAME_TYPE_DR == frame->header.bfType && HDMI_DR_EOTF_SDRGAMMA == frame->plDR1.bfEOTF) + { + outIsHDR = false; + } + else if (0 /*HDMI_INFOFRAME_TYPE_RESERVED */ == frame->header.bfType && (0 == memcmp(buffer, emptyBuffer, sizeof(buffer)))) + { + outIsHDR = false; + } + else if (HDMI_INFOFRAME_TYPE_DR != frame->header.bfType) + { + warning_printf("HDMI Metadata: Wrong header type: %d", frame->header.bfType); + hr = E_FAIL; + } + } + else + warning_printf("HDMI Metadata: HDMI_IsInfoFrameValid() returned error (checksum)!"); + } + else + warning_printf("HDMI Metadata: GetHDMIHDRStatusPacket() failed!"); + + return hr; +} diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/DriverInterface.h b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/DriverInterface.h new file mode 100644 index 0000000..4a24a97 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/DriverInterface.h @@ -0,0 +1,72 @@ +/* +MIT License + +Copyright (c) 2022-23 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +//============================================================================== +/** +@file DriverInterface.h + +@brief EGAVDeviceProperties class implementation. + Support for 4K60 Pro MK.2 and 4K60 S+ +**/ +//============================================================================== + + +#include +#include + +#include "HDMIInfoFramesAPI.h" + +//! @brief Device properties for Elgato's non-UVC devices +class EGAVDeviceProperties +{ +public: + enum class DeviceType + { + None = 0, + GC4K60ProMK2, //!< 4K60 Pro MK.2: PCI\VEN_12AB& DEV_0710& SUBSYS_000E1CFA + GC4K60SPlus //!< 4K60 S+: USB\VID_0FD9&PID_0068 or USB\VID_0FD9&PID_0075 + }; + + //! @brief + //! @param inKsPropertySet Interface for driver property set. + //! Can be queried from the DirectShow filter via IBaseFilter::QueryInterface() + //! @param inDeviceType + EGAVDeviceProperties(IKsPropertySet* inKsPropertySet, DeviceType inDeviceType); + + //! @brief 4K60 S+ only + //! @param inHEVC 1 - HEVC, 0 - H.264 + HRESULT SetEncoderType(bool inHEVC); + + //! @brief 4K60 Pro MK.2 only + //! @param inEnable 1 - enable tone mapping, 0 - disable HDR tonemapping + HRESULT SetHDRTonemapping(bool inEnable); + + HRESULT IsVideoHDR(bool& outIsHDR); + HRESULT GetHDMIHDRStatusPacket(uint8_t* outBuffer, int inBufferSize); + +private: + DeviceType mDeviceType = DeviceType::None; + GUID mCustomPropertySetGUID = GUID_NULL; + CComPtr mICustomPropertySet; +}; \ No newline at end of file diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/main.cpp b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/main.cpp new file mode 100644 index 0000000..0dbe61d --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/SampleCode/main.cpp @@ -0,0 +1,91 @@ +/* +MIT License + +Copyright (c) 2022-23 Corsair Memory, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include + +#include "ElgatoUVCDevice.h" + + +//============================================================================== +// # Constants +//============================================================================== + +const EGAVDeviceID& selectedDeviceID = deviceIDHD60SPlus; // Select your device here + + +//============================================================================== +// # main() +//============================================================================== +int main() +{ + std::cout << "========================================" << std::endl; + std::cout << " Sample: HDR Tonemapping" << std::endl; + std::cout << "========================================" << std::endl; + std::cout << std::endl; + + std::shared_ptr hid = std::make_shared(); + EGAVResult res = hid->InitHIDInterface(selectedDeviceID); + if (res.Failed()) + { + std::cout << "InitHIDInterface() failed. Do you have the correct device connected?" << std::endl << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + } + else + { + ElgatoUVCDevice device(hid, IsNewDeviceType(selectedDeviceID)); + HDMI_GENERIC_INFOFRAME frame{}; + memset(&frame, 0, sizeof(frame)); + res = device.GetHDMIHDRStatusPacket(frame); + if (res.Succeeded()) + { + bool isHDR = false; + res = device.IsVideoHDR(isHDR); + std::cout << "Video is " << (isHDR ? "HDR" : "SDR") << std::endl; + if (res.Succeeded() && isHDR) + { + std::cout << "Disable HDR tonemapping" << std::endl; + device.SetHDRTonemappingEnabled(false); + +#if 1 // TEST: TOGGLE TONEMAPPINING + for (int i = 0; i < 2; i++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + std::cout << "Enable HDR tonemapping" << std::endl; + device.SetHDRTonemappingEnabled(true); + + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + std::cout << "Disable HDR tonemapping" << std::endl; + device.SetHDRTonemappingEnabled(false); + } +#endif + } + } + hid->DeinitHIDInterface(); + } + + return 0; +} diff --git a/obs-studio/deps/libdshowcapture/src/external/capture-device-support/readme.md b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/readme.md new file mode 100644 index 0000000..acd2cdc --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/external/capture-device-support/readme.md @@ -0,0 +1,63 @@ +HID API for Elgato UVC devices +============================== + +The folder `Library` contains cross-platform code to access device-specific features of selected Elgato +video products. + +The CMake project in this folder builds a small console app for testing. +Modify `selectedDeviceID` in `SampleCode/main.cpp` to select the correct device type. + +Supported platforms +------------------- +* Windows (10 or higher) +* macOS + +Supported devices +----------------- +* HD60 S+ +* HD60 X + +Supported features +----------------- +* Switch on-device HDR tonemapping on/off +* Read HDMI HDR status packet (for HDR detection) + +Limitations +----------- +The library was written for macOS and Windows. +However, the sample project was only built with Visual Studio 2019 and tested on Windows so far. + +-------------------------------------------------------------------------------- + +Driver API for Elgato devices +============================= + +For non-UVC devices device properties can be accessed via a custom driver property set (`IKsPropertySet`). + +Sample code is provided, see `SampleCode/DriverInterface.h/.cpp` + +Supported platforms +------------------- +* Windows (10 or higher) + +Supported devices +----------------- +* 4K60 Pro MK.2 +* 4K60 S+ + +Supported features +------------------ +* Switch on-device HDR tonemapping on/off (4K60 Pro MK.2 only) +* Set video compression (4K60 S+ only) +* Read HDMI HDR status packet (for HDR detection) + +#### 4K60 Pro MK.2 + +To receive HDR the format on DirectShow filter pin must be set to P010. + +#### 4K60 S+ + +The 4K60 S+ always produces compressed video output. +For HDR the encoder format must be set to HEVC via the driver interface. + + diff --git a/obs-studio/deps/libdshowcapture/src/source/CoTaskMemPtr.hpp b/obs-studio/deps/libdshowcapture/src/source/CoTaskMemPtr.hpp new file mode 100644 index 0000000..db0c148 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/CoTaskMemPtr.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 Lain Bailey + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +template class CoTaskMemPtr { + T *ptr; + + inline void Clear() + { + if (ptr) + CoTaskMemFree(ptr); + } + +public: + inline CoTaskMemPtr() : ptr(NULL) {} + inline CoTaskMemPtr(T *ptr_) : ptr(ptr_) {} + inline ~CoTaskMemPtr() { Clear(); } + + inline operator T *() const { return ptr; } + inline T *operator->() const { return ptr; } + + inline CoTaskMemPtr &operator=(T *val) + { + Clear(); + ptr = val; + return *this; + } + + inline T **operator&() + { + Clear(); + ptr = NULL; + return &ptr; + } +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/ComPtr.hpp b/obs-studio/deps/libdshowcapture/src/source/ComPtr.hpp new file mode 100644 index 0000000..9bddc20 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/ComPtr.hpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2023 Lain Bailey + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +/* Oh no I have my own com pointer class, the world is ending, how dare you + * write your own! */ + +template class ComPtr { + +protected: + T *ptr; + + inline void Kill() + { + if (ptr) + ptr->Release(); + } + + inline void Replace(T *p) + { + if (ptr != p) { + if (p) + p->AddRef(); + if (ptr) + ptr->Release(); + ptr = p; + } + } + +public: + inline ComPtr() : ptr(nullptr) {} + inline ComPtr(T *p) : ptr(p) + { + if (ptr) + ptr->AddRef(); + } + inline ComPtr(const ComPtr &c) : ptr(c.ptr) + { + if (ptr) + ptr->AddRef(); + } + inline ComPtr(ComPtr &&c) noexcept : ptr(c.ptr) { c.ptr = nullptr; } + inline ~ComPtr() { Kill(); } + + inline void Clear() + { + if (ptr) { + ptr->Release(); + ptr = nullptr; + } + } + + inline ComPtr &operator=(T *p) + { + Replace(p); + return *this; + } + + inline ComPtr &operator=(const ComPtr &c) + { + Replace(c.ptr); + return *this; + } + + inline ComPtr &operator=(ComPtr &&c) noexcept + { + if (&ptr != &c.ptr) { + Kill(); + ptr = c.ptr; + c.ptr = nullptr; + } + + return *this; + } + + inline T *Detach() + { + T *out = ptr; + ptr = nullptr; + return out; + } + + inline void CopyTo(T **out) + { + if (out) { + if (ptr) + ptr->AddRef(); + *out = ptr; + } + } + + inline ULONG Release() + { + ULONG ref; + + if (!ptr) + return 0; + ref = ptr->Release(); + ptr = nullptr; + return ref; + } + + inline T **Assign() + { + Clear(); + return &ptr; + } + inline void Set(T *p) + { + Kill(); + ptr = p; + } + + inline T *Get() const { return ptr; } + + inline T **operator&() { return Assign(); } + + inline operator T *() const { return ptr; } + inline T *operator->() const { return ptr; } + + inline bool operator==(T *p) const { return ptr == p; } + inline bool operator!=(T *p) const { return ptr != p; } + + inline bool operator!() const { return !ptr; } +}; + +template class ComQIPtr : public ComPtr { + +public: + inline ComQIPtr(IUnknown *unk) + { + this->ptr = nullptr; + unk->QueryInterface(__uuidof(T), (void **)&this->ptr); + } + + inline ComPtr &operator=(IUnknown *unk) + { + ComPtr::Clear(); + unk->QueryInterface(__uuidof(T), (void **)&this->ptr); + return *this; + } +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/avermedia-encode.h b/obs-studio/deps/libdshowcapture/src/source/avermedia-encode.h new file mode 100644 index 0000000..6ef436f --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/avermedia-encode.h @@ -0,0 +1,66 @@ +/* + * aver_prophwencode.h -- This header filer is provide to 3rd party AP + * to use the HW encode function of AVerMedia. + * + * Copyright (C) 2014 AVerMedia TECHNOLOGIES, Inc. + * + * Authors: Morris Pan, AVerMedia TECHNOLOGIES, Inc. + * + * This content is released under the MIT License + * (http://opensource.org/licenses/MIT). + * + */ +#pragma once + +static const GUID AVER_HW_ENCODE_PROPERTY = {0x1bd55918, + 0xbaf5, + 0x4781, + {0x8d, 0x76, 0xe0, 0xa0, 0xa5, + 0xe1, 0xd2, 0xb8}}; + +enum { + // @brief PropertySet Enumeration + // param AVER_PARAMETERS + PROPERTY_HW_ENCODE_PARAMETER = 0 +}; + +enum { + // property to set/get the encode frame rate + // ulParam1 = Frames per second + AVER_PARAMETER_ENCODE_FRAME_RATE = 0, + + // property to set/get the encode bit rate + // ulParam1 = Bitrate (kb/s) + AVER_PARAMETER_ENCODE_BIT_RATE = 1, + + // property to get the output resolution + // ulParam1 = Resolution width + // ulParam2 = Resolution height + AVER_PARAMETER_CURRENT_RESOLUTION = 2, + + // property to set the output resolution + // ulParam1 = Resolution width + // ulParam2 = Resolution height + AVER_PARAMETER_ENCODE_RESOLUTION = 3, + + // property to set/get the encode GOP + // ulParam1 = GOP length + AVER_PARAMETER_ENCODE_GOP = 4, + + // property to insert an I frame to the encoded stream + AVER_PARAMETER_INSERT_I_FRAME = 6 +}; + +struct AVER_PARAMETERS { + // @brief Use the PROPERTY_PARAMETER Property to Get or Set the + // Device Parameter. + // + // param ulIndex Parameter Index (AVER_PARAMETER_*) + // param ulParam1 Parameter 1 (if any) + // param ulParam2 Parameter 2 (if any) + // param ulParam3 Parameter 3 (if any) + ULONG ulIndex; + ULONG ulParam1; + ULONG ulParam2; + ULONG ulParam3; +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/capture-filter.cpp b/obs-studio/deps/libdshowcapture/src/source/capture-filter.cpp new file mode 100644 index 0000000..7d8884f --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/capture-filter.cpp @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "capture-filter.hpp" +#include "log.hpp" + +namespace DShow { + +#if 0 +#define PrintFunc(x) Debug(x) +#else +#define PrintFunc(x) +#endif + +#define FILTER_NAME L"Capture Filter" +#define VIDEO_PIN_NAME L"Video Capture" +#define AUDIO_PIN_NAME L"Audio Capture" + +CapturePin::CapturePin(CaptureFilter *filter_, const PinCaptureInfo &info) + : refCount(0), captureInfo(info), filter(filter_) +{ + connectedMediaType->majortype = info.expectedMajorType; +} + +CapturePin::~CapturePin() {} + +STDMETHODIMP CapturePin::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown) { + AddRef(); + *ppv = this; + } else if (riid == IID_IPin) { + AddRef(); + *ppv = (IPin *)this; + } else if (riid == IID_IMemInputPin) { + AddRef(); + *ppv = (IMemInputPin *)this; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } + + return NOERROR; +} + +STDMETHODIMP_(ULONG) CapturePin::AddRef() +{ + return (ULONG)InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) CapturePin::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return (ULONG)refCount; +} + +// IPin methods +STDMETHODIMP CapturePin::Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"CapturePin::Connect"); + + if (filter->state == State_Running) + return VFW_E_NOT_STOPPED; + + if (connectedPin) + return VFW_E_ALREADY_CONNECTED; + + if (!pmt) + return S_OK; + + if (pmt->majortype != GUID_NULL && + pmt->majortype != captureInfo.expectedMajorType) + return S_FALSE; + + if (pmt->majortype == captureInfo.expectedMajorType && + !IsValidMediaType(pmt)) + return S_FALSE; + + DSHOW_UNUSED(pReceivePin); + return S_OK; +} + +STDMETHODIMP CapturePin::ReceiveConnection(IPin *pConnector, + const AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"CapturePin::ReceiveConnection"); + + if (filter->state != State_Stopped) + return VFW_E_NOT_STOPPED; + if (!pConnector || !pmt) + return E_POINTER; + if (connectedPin) + return VFW_E_ALREADY_CONNECTED; + + if (QueryAccept(pmt) != S_OK) + return VFW_E_TYPE_NOT_ACCEPTED; + + connectedPin = pConnector; + connectedMediaType = pmt; + + return S_OK; +} + +STDMETHODIMP CapturePin::Disconnect() +{ + PrintFunc(L"CapturePin::Disconnect"); + + if (!connectedPin) + return S_FALSE; + + connectedPin = nullptr; + return S_OK; +} + +STDMETHODIMP CapturePin::ConnectedTo(IPin **pPin) +{ + PrintFunc(L"CapturePin::ConnectedTo"); + + if (!connectedPin) + return VFW_E_NOT_CONNECTED; + + IPin *pin = connectedPin; + pin->AddRef(); + *pPin = pin; + return S_OK; +} + +STDMETHODIMP CapturePin::ConnectionMediaType(AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"CapturePin::ConnectionMediaType"); + + if (!connectedPin) + return VFW_E_NOT_CONNECTED; + + return CopyMediaType(pmt, connectedMediaType); +} + +STDMETHODIMP CapturePin::QueryPinInfo(PIN_INFO *pInfo) +{ + PrintFunc(L"CapturePin::QueryPinInfo"); + + pInfo->pFilter = filter; + if (filter) { + IBaseFilter *ptr = filter; + ptr->AddRef(); + } + + if (captureInfo.expectedMajorType == MEDIATYPE_Video) + memcpy(pInfo->achName, VIDEO_PIN_NAME, sizeof(VIDEO_PIN_NAME)); + else + memcpy(pInfo->achName, AUDIO_PIN_NAME, sizeof(AUDIO_PIN_NAME)); + + pInfo->dir = PINDIR_INPUT; + + return NOERROR; +} + +STDMETHODIMP CapturePin::QueryDirection(PIN_DIRECTION *pPinDir) +{ + *pPinDir = PINDIR_INPUT; + return NOERROR; +} + +#define CAPTURE_PIN_NAME L"Capture Pin" + +STDMETHODIMP CapturePin::QueryId(LPWSTR *lpId) +{ + wchar_t *str = (wchar_t *)CoTaskMemAlloc(sizeof(CAPTURE_PIN_NAME)); + memcpy(str, CAPTURE_PIN_NAME, sizeof(CAPTURE_PIN_NAME)); + *lpId = str; + return S_OK; +} + +STDMETHODIMP CapturePin::QueryAccept(const AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"CapturePin::QueryAccept"); + + if (pmt->majortype != captureInfo.expectedMajorType) + return S_FALSE; + if (!IsValidMediaType(pmt)) + return S_FALSE; + + if (connectedPin) + connectedMediaType = pmt; + + return S_OK; +} + +STDMETHODIMP CapturePin::EnumMediaTypes(IEnumMediaTypes **ppEnum) +{ + PrintFunc(L"CapturePin::EnumMediaTypes"); + + *ppEnum = new CaptureEnumMediaTypes(this); + if (!*ppEnum) + return E_OUTOFMEMORY; + + return NOERROR; +} + +STDMETHODIMP CapturePin::QueryInternalConnections(IPin **apPin, ULONG *nPin) +{ + PrintFunc(L"CapturePin::QueryInternalConnections"); + + DSHOW_UNUSED(apPin); + DSHOW_UNUSED(nPin); + return E_NOTIMPL; +} + +STDMETHODIMP CapturePin::EndOfStream() +{ + PrintFunc(L"CapturePin::EndOfStream"); + + return S_OK; +} + +STDMETHODIMP CapturePin::BeginFlush() +{ + PrintFunc(L"CapturePin::BeginFlush"); + + flushing = true; + return S_OK; +} + +STDMETHODIMP CapturePin::EndFlush() +{ + PrintFunc(L"CapturePin::EndFlush"); + + flushing = false; + return S_OK; +} + +STDMETHODIMP CapturePin::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, + double dRate) +{ + PrintFunc(L"CapturePin::NewSegment"); + + DSHOW_UNUSED(tStart); + DSHOW_UNUSED(tStop); + DSHOW_UNUSED(dRate); + return S_OK; +} + +// IMemInputPin methods +STDMETHODIMP CapturePin::GetAllocator(IMemAllocator **ppAllocator) +{ + PrintFunc(L"CapturePin::GetAllocator"); + + DSHOW_UNUSED(ppAllocator); + return VFW_E_NO_ALLOCATOR; +} + +STDMETHODIMP CapturePin::NotifyAllocator(IMemAllocator *pAllocator, + BOOL bReadOnly) +{ + PrintFunc(L"CapturePin::NotifyAllocator"); + + DSHOW_UNUSED(pAllocator); + DSHOW_UNUSED(bReadOnly); + return S_OK; +} + +STDMETHODIMP CapturePin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps) +{ + PrintFunc(L"CapturePin::GetAllocatorRequirements"); + + DSHOW_UNUSED(pProps); + return E_NOTIMPL; +} + +STDMETHODIMP CapturePin::Receive(IMediaSample *pSample) +{ + PrintFunc(L"CapturePin::Receive"); + + if (flushing) + return S_FALSE; + + if (pSample) + captureInfo.callback(pSample); + + return S_OK; +} + +STDMETHODIMP CapturePin::ReceiveMultiple(IMediaSample **pSamples, long nSamples, + long *nSamplesProcessed) +{ + PrintFunc(L"CapturePin::ReceiveMultiple"); + + if (flushing) + return S_FALSE; + + for (long i = 0; i < nSamples; i++) + if (pSamples[i]) + captureInfo.callback(pSamples[i]); + + *nSamplesProcessed = nSamples; + + return S_OK; +} + +STDMETHODIMP CapturePin::ReceiveCanBlock() +{ + return S_FALSE; +} + +bool CapturePin::IsValidMediaType(const AM_MEDIA_TYPE *pmt) const +{ + if (pmt->pbFormat) { + if (pmt->subtype != captureInfo.expectedSubType || + pmt->majortype != captureInfo.expectedMajorType) + return false; + + if (captureInfo.expectedMajorType == MEDIATYPE_Video) { + const BITMAPINFOHEADER *bih = GetBitmapInfoHeader(*pmt); + if (!bih || bih->biHeight == 0 || bih->biWidth == 0) + return false; + } + } + + return true; +} + +// ============================================================================ + +class MiscFlagsHandler : public IAMFilterMiscFlags { + volatile long refCount = 0; + +public: + inline MiscFlagsHandler() {} + virtual ~MiscFlagsHandler() {} + + STDMETHODIMP QueryInterface(REFIID riid, void **ppv) + { + if (riid == IID_IUnknown) { + AddRef(); + *ppv = this; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } + + return NOERROR; + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&refCount); + } + + STDMETHODIMP_(ULONG) Release() + { + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return refCount; + } + + STDMETHODIMP_(ULONG) GetMiscFlags() + { + return AM_FILTER_MISC_FLAGS_IS_RENDERER; + } +}; + +CaptureFilter::CaptureFilter(const PinCaptureInfo &info) + : refCount(0), + state(State_Stopped), + pin(new CapturePin(this, info)), + misc(new MiscFlagsHandler) +{ +} + +CaptureFilter::~CaptureFilter() {} + +// IUnknown methods +STDMETHODIMP CaptureFilter::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown) { + AddRef(); + *ppv = this; + } else if (riid == IID_IPersist) { + AddRef(); + *ppv = (IPersist *)this; + } else if (riid == IID_IMediaFilter) { + AddRef(); + *ppv = (IMediaFilter *)this; + } else if (riid == IID_IBaseFilter) { + AddRef(); + *ppv = (IBaseFilter *)this; + } else if (riid == IID_IAMFilterMiscFlags) { + misc.CopyTo((IAMFilterMiscFlags **)ppv); + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } + + return NOERROR; +} + +STDMETHODIMP_(ULONG) CaptureFilter::AddRef() +{ + return InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) CaptureFilter::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return refCount; +} + +// IPersist method +STDMETHODIMP CaptureFilter::GetClassID(CLSID *pClsID) +{ + DSHOW_UNUSED(pClsID); + return E_NOTIMPL; +} + +// IMediaFilter methods +STDMETHODIMP CaptureFilter::GetState(DWORD dwMSecs, FILTER_STATE *State) +{ + PrintFunc(L"CaptureFilter::GetState"); + + *State = state; + + DSHOW_UNUSED(dwMSecs); + return S_OK; +} + +STDMETHODIMP CaptureFilter::SetSyncSource(IReferenceClock *pClock) +{ + DSHOW_UNUSED(pClock); + return S_OK; +} + +STDMETHODIMP CaptureFilter::GetSyncSource(IReferenceClock **pClock) +{ + *pClock = nullptr; + return NOERROR; +} + +STDMETHODIMP CaptureFilter::Stop() +{ + PrintFunc(L"CaptureFilter::Stop"); + + state = State_Stopped; + return S_OK; +} + +STDMETHODIMP CaptureFilter::Pause() +{ + PrintFunc(L"CaptureFilter::Pause"); + + state = State_Paused; + return S_OK; +} + +STDMETHODIMP CaptureFilter::Run(REFERENCE_TIME tStart) +{ + PrintFunc(L"CaptureFilter::Run"); + + state = State_Running; + + DSHOW_UNUSED(tStart); + return S_OK; +} + +// IBaseFilter methods +STDMETHODIMP CaptureFilter::EnumPins(IEnumPins **ppEnum) +{ + PrintFunc(L"CaptureFilter::EnumPins"); + + *ppEnum = new CaptureEnumPins(this, nullptr); + return (*ppEnum == nullptr) ? E_OUTOFMEMORY : NOERROR; +} + +STDMETHODIMP CaptureFilter::FindPin(LPCWSTR Id, IPin **ppPin) +{ + PrintFunc(L"CaptureFilter::FindPin"); + + if (Id == nullptr || ppPin == nullptr) + return E_POINTER; + + if (lstrcmpW(Id, CAPTURE_PIN_NAME) == 0) { + *ppPin = pin; + pin->AddRef(); + return S_OK; + } else { + *ppPin = nullptr; + return VFW_E_NOT_FOUND; + } +} + +STDMETHODIMP CaptureFilter::QueryFilterInfo(FILTER_INFO *pInfo) +{ + PrintFunc(L"CaptureFilter::QueryFilterInfo"); + + memcpy(pInfo->achName, FILTER_NAME, sizeof(FILTER_NAME)); + + pInfo->pGraph = graph; + if (graph) { + IFilterGraph *graph_ptr = graph; + graph_ptr->AddRef(); + } + return NOERROR; +} + +STDMETHODIMP CaptureFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName) +{ + DSHOW_UNUSED(pName); + + graph = pGraph; + return NOERROR; +} + +STDMETHODIMP CaptureFilter::QueryVendorInfo(LPWSTR *pVendorInfo) +{ + DSHOW_UNUSED(pVendorInfo); + return E_NOTIMPL; +} + +// ============================================================================ + +CaptureEnumPins::CaptureEnumPins(CaptureFilter *filter_, CaptureEnumPins *pEnum) + : filter(filter_) +{ + curPin = (pEnum != nullptr) ? pEnum->curPin : 0; +} + +CaptureEnumPins::~CaptureEnumPins() {} + +// IUnknown +STDMETHODIMP CaptureEnumPins::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown || riid == IID_IEnumPins) { + AddRef(); + *ppv = (IEnumPins *)this; + return NOERROR; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) CaptureEnumPins::AddRef() +{ + return (ULONG)InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) CaptureEnumPins::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return (ULONG)refCount; +} + +// IEnumPins +STDMETHODIMP CaptureEnumPins::Next(ULONG cPins, IPin **ppPins, ULONG *pcFetched) +{ + UINT nFetched = 0; + + if (curPin == 0 && cPins > 0) { + IPin *pPin = filter->GetPin(); + + *ppPins = pPin; + pPin->AddRef(); + + nFetched = 1; + curPin++; + } + + if (pcFetched) + *pcFetched = nFetched; + + return (nFetched == cPins) ? S_OK : S_FALSE; +} + +STDMETHODIMP CaptureEnumPins::Skip(ULONG cPins) +{ + return ((curPin += cPins) > 1) ? S_FALSE : S_OK; +} + +STDMETHODIMP CaptureEnumPins::Reset() +{ + curPin = 0; + return S_OK; +} + +STDMETHODIMP CaptureEnumPins::Clone(IEnumPins **ppEnum) +{ + *ppEnum = new CaptureEnumPins(filter, this); + return (*ppEnum == nullptr) ? E_OUTOFMEMORY : NOERROR; +} + +// ============================================================================ + +CaptureEnumMediaTypes::CaptureEnumMediaTypes(CapturePin *pin_) : pin(pin_) {} + +CaptureEnumMediaTypes::~CaptureEnumMediaTypes() {} + +STDMETHODIMP CaptureEnumMediaTypes::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown || riid == IID_IEnumMediaTypes) { + AddRef(); + *ppv = this; + return NOERROR; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) CaptureEnumMediaTypes::AddRef() +{ + return (ULONG)InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) CaptureEnumMediaTypes::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return (ULONG)refCount; +} + +// IEnumMediaTypes +STDMETHODIMP CaptureEnumMediaTypes::Next(ULONG cMediaTypes, + AM_MEDIA_TYPE **ppMediaTypes, + ULONG *pcFetched) +{ + PrintFunc(L"CaptureEnumMediaTypes::Next"); + + UINT nFetched = 0; + + if (curMT == 0 && cMediaTypes > 0) { + *ppMediaTypes = pin->connectedMediaType.Duplicate(); + nFetched = 1; + curMT++; + } + + if (pcFetched) + *pcFetched = nFetched; + + return (nFetched == cMediaTypes) ? S_OK : S_FALSE; +} + +STDMETHODIMP CaptureEnumMediaTypes::Skip(ULONG cMediaTypes) +{ + PrintFunc(L"CaptureEnumMediaTypes::Skip"); + + return ((curMT += cMediaTypes) > 1) ? S_FALSE : S_OK; +} + +STDMETHODIMP CaptureEnumMediaTypes::Reset() +{ + PrintFunc(L"CaptureEnumMediaTypes::Reset"); + + curMT = 0; + return S_OK; +} + +STDMETHODIMP CaptureEnumMediaTypes::Clone(IEnumMediaTypes **ppEnum) +{ + *ppEnum = new CaptureEnumMediaTypes(pin); + return (*ppEnum == nullptr) ? E_OUTOFMEMORY : NOERROR; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/capture-filter.hpp b/obs-studio/deps/libdshowcapture/src/source/capture-filter.hpp new file mode 100644 index 0000000..858a147 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/capture-filter.hpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "dshow-base.hpp" +#include "dshow-media-type.hpp" +#include "../dshowcapture.hpp" + +namespace DShow { + +class CaptureFilter; +class CaptureSource; + +typedef void (*CaptureCallback)(void *param, IMediaSample *sample); + +struct PinCaptureInfo { + std::function callback; + GUID expectedMajorType{}; + GUID expectedSubType{}; +}; + +class CapturePin : public IPin, public IMemInputPin { + friend class CaptureEnumMediaTypes; + + volatile long refCount; + + PinCaptureInfo captureInfo; + ComPtr connectedPin; + CaptureFilter *filter; + MediaType connectedMediaType; + volatile bool flushing = false; + + bool IsValidMediaType(const AM_MEDIA_TYPE *pmt) const; + +public: + CapturePin(CaptureFilter *filter, const PinCaptureInfo &info); + virtual ~CapturePin(); + + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IPin methods + STDMETHODIMP Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt); + STDMETHODIMP ReceiveConnection(IPin *connector, + const AM_MEDIA_TYPE *pmt); + STDMETHODIMP Disconnect(); + STDMETHODIMP ConnectedTo(IPin **pPin); + STDMETHODIMP ConnectionMediaType(AM_MEDIA_TYPE *pmt); + STDMETHODIMP QueryPinInfo(PIN_INFO *pInfo); + STDMETHODIMP QueryDirection(PIN_DIRECTION *pPinDir); + STDMETHODIMP QueryId(LPWSTR *lpId); + STDMETHODIMP QueryAccept(const AM_MEDIA_TYPE *pmt); + STDMETHODIMP EnumMediaTypes(IEnumMediaTypes **ppEnum); + STDMETHODIMP QueryInternalConnections(IPin **apPin, ULONG *nPin); + STDMETHODIMP EndOfStream(); + + STDMETHODIMP BeginFlush(); + STDMETHODIMP EndFlush(); + STDMETHODIMP NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, + double dRate); + + // IMemInputPin methods + STDMETHODIMP GetAllocator(IMemAllocator **ppAllocator); + STDMETHODIMP NotifyAllocator(IMemAllocator *pAllocator, BOOL bReadOnly); + STDMETHODIMP GetAllocatorRequirements(ALLOCATOR_PROPERTIES *pProps); + STDMETHODIMP Receive(IMediaSample *pSample); + STDMETHODIMP ReceiveMultiple(IMediaSample **pSamples, long nSamples, + long *nSamplesProcessed); + STDMETHODIMP ReceiveCanBlock(); +}; + +class CaptureFilter : public IBaseFilter { + friend class CapturePin; + + volatile long refCount; + FILTER_STATE state; + ComPtr graph; + ComPtr pin; + + ComPtr misc; + +public: + CaptureFilter(const PinCaptureInfo &info); + virtual ~CaptureFilter(); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IPersist method + STDMETHODIMP GetClassID(CLSID *pClsID); + + // IMediaFilter methods + STDMETHODIMP GetState(DWORD dwMSecs, FILTER_STATE *State); + STDMETHODIMP SetSyncSource(IReferenceClock *pClock); + STDMETHODIMP GetSyncSource(IReferenceClock **pClock); + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + STDMETHODIMP Run(REFERENCE_TIME tStart); + + // IBaseFilter methods + STDMETHODIMP EnumPins(IEnumPins **ppEnum); + STDMETHODIMP FindPin(LPCWSTR Id, IPin **ppPin); + STDMETHODIMP QueryFilterInfo(FILTER_INFO *pInfo); + STDMETHODIMP JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName); + STDMETHODIMP QueryVendorInfo(LPWSTR *pVendorInfo); + + inline CapturePin *GetPin() const { return (CapturePin *)pin; } +}; + +class CaptureEnumPins : public IEnumPins { + volatile long refCount = 1; + ComPtr filter; + UINT curPin; + +public: + CaptureEnumPins(CaptureFilter *filter, CaptureEnumPins *pEnum); + virtual ~CaptureEnumPins(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumPins + STDMETHODIMP Next(ULONG cPins, IPin **ppPins, ULONG *pcFetched); + STDMETHODIMP Skip(ULONG cPins); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(IEnumPins **ppEnum); +}; + +class CaptureEnumMediaTypes : public IEnumMediaTypes { + volatile long refCount = 1; + ComPtr pin; + UINT curMT = 0; + +public: + CaptureEnumMediaTypes(CapturePin *pin); + virtual ~CaptureEnumMediaTypes(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumMediaTypes + STDMETHODIMP Next(ULONG cMediaTypes, AM_MEDIA_TYPE **ppMediaTypes, + ULONG *pcFetched); + STDMETHODIMP Skip(ULONG cMediaTypes); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(IEnumMediaTypes **ppEnum); +}; + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/device-vendor.cpp b/obs-studio/deps/libdshowcapture/src/source/device-vendor.cpp new file mode 100644 index 0000000..a842c4f --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/device-vendor.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include +#include "../external/capture-device-support/SampleCode/DriverInterface.h" +#include "device.hpp" +#include "log.hpp" + +#include + +#include + +namespace DShow { + +bool IsVendorVideoHDR(IKsPropertySet *propertySet) +{ + EGAVDeviceProperties properties( + propertySet, EGAVDeviceProperties::DeviceType::GC4K60SPlus); + bool isHDR = false; + return SUCCEEDED(properties.IsVideoHDR(isHDR)) ? isHDR : false; +} + +void SetVendorVideoFormat(IKsPropertySet *propertySet, bool hevcTrueAvcFalse) +{ + EGAVDeviceProperties properties( + propertySet, EGAVDeviceProperties::DeviceType::GC4K60SPlus); + const HRESULT hr = properties.SetEncoderType(hevcTrueAvcFalse); + if (SUCCEEDED(hr)) { + Info(L"Elgato GC4K60SPlus encoder type=%ls", + hevcTrueAvcFalse ? L"HEVC" : L"AVC"); + } +} + +static void SetTonemapperAvermedia(IKsPropertySet *propertySet, bool enable) +{ + typedef struct _KSPROPERTY_AVER_HW_HDR2SDR { + KSPROPERTY Property; + DWORD Enable; + } KSPROPERTY_AVER_HW_HDR2SDR, *PKSPROPERTY_AVER_HW_HDR2SDR; + + static constexpr GUID KSPROPSETID_AVER_HDR_PROPERTY = { + 0x8A80D56F, + 0xFAC5, + 0x4692, + { + 0xA4, + 0x16, + 0xCF, + 0x20, + 0xD4, + 0xA1, + 0x8F, + 0x47, + }, + }; + KSPROPERTY_AVER_HW_HDR2SDR data{}; + data.Enable = enable; + const HRESULT hr = propertySet->Set( + KSPROPSETID_AVER_HDR_PROPERTY, 2, &data.Enable, + sizeof(data) - sizeof(data.Property), &data, sizeof(data)); + if (SUCCEEDED(hr)) + Info(L"AVerMedia tonemapper enable=%lu", data.Enable); +} + +/* Special thanks to AVerMedia development team */ +static bool FindExtensionUnitNodeID(DWORD *pnNode, IBaseFilter *spCapture) +{ + bool bFindNode = false; + if (!spCapture) + return bFindNode; + + ComPtr spKsTopologyInfo = NULL; + HRESULT hr = spCapture->QueryInterface(IID_PPV_ARGS(&spKsTopologyInfo)); + if (spKsTopologyInfo == NULL || FAILED(hr)) + return bFindNode; + + DWORD nNodeNum = 0; + hr = spKsTopologyInfo->get_NumNodes(&nNodeNum); + if (FAILED(hr) || nNodeNum <= 0) + return bFindNode; + + GUID guidNodeType; + for (int i = 0; i < nNodeNum; i++) { + spKsTopologyInfo->get_NodeType(i, &guidNodeType); + if (IsEqualGUID(guidNodeType, KSNODETYPE_DEV_SPECIFIC)) { + *pnNode = i; + bFindNode = true; + } + } + + return bFindNode; +} + +static bool SetTonemapperAvermedia2(IBaseFilter *filter, bool enable) +{ + static constexpr GUID GUID_GC553 = { + 0xC835261B, + 0xFF1C, + 0x4C9A, + { + 0xB2, + 0xF7, + 0x93, + 0xC9, + 0x1F, + 0xCF, + 0xBE, + 0x77, + }, + }; + + static constexpr int nId = 11; + + ComPtr spKsControl; + HRESULT hr = filter->QueryInterface(IID_PPV_ARGS(&spKsControl)); + if (spKsControl == NULL || FAILED(hr)) + return false; + + KSP_NODE ExtensionProp{}; + if (!FindExtensionUnitNodeID(&ExtensionProp.NodeId, filter)) + return false; + + ExtensionProp.Property.Set = GUID_GC553; + ExtensionProp.Property.Id = nId; + ExtensionProp.Property.Flags = KSPROPERTY_TYPE_GET | + KSPROPERTY_TYPE_TOPOLOGY; + + char pData[20]; + + ULONG ulBytesReturned; + hr = spKsControl->KsProperty(&ExtensionProp.Property, + sizeof(ExtensionProp), pData, + sizeof(pData), &ulBytesReturned); + if (FAILED(hr) || (ulBytesReturned < 18)) + return false; + + pData[15] = 0x02; + pData[17] = enable; + + ExtensionProp.Property.Flags = KSPROPERTY_TYPE_SET | + KSPROPERTY_TYPE_TOPOLOGY; + hr = spKsControl->KsProperty(&ExtensionProp.Property, + sizeof(ExtensionProp), pData, + sizeof(pData), &ulBytesReturned); + const bool succeeded = SUCCEEDED(hr); + if (succeeded) + Info(L"AVerMedia GC553 tonemapper enable=%d", (int)enable); + + return succeeded; +} + +static void SetTonemapperElgato(IKsPropertySet *propertySet, bool enable) +{ + EGAVDeviceProperties properties( + propertySet, EGAVDeviceProperties::DeviceType::GC4K60ProMK2); + const HRESULT hr = properties.SetHDRTonemapping(enable); + if (SUCCEEDED(hr)) { + Info(L"Elgato GC4K60ProMK2 tonemapper enable=%d", (int)enable); + } else { + for (const EGAVDeviceID &deviceID : GetElgatoUVCDeviceIDs()) { + std::shared_ptr hid = + CreateEGAVHIDInterface(); + if (hid->InitHIDInterface(deviceID).Succeeded()) { + ElgatoUVCDevice device( + hid, IsNewDeviceType(deviceID)); + device.SetHDRTonemappingEnabled(enable); + Info(L"Elgato UVC device (PID = 0x%04X) tonemapper enable=%d", + deviceID.productID, (int)enable); + hid->DeinitHIDInterface(); + } + } + } +} + +void SetVendorTonemapperUsage(IBaseFilter *filter, bool enable) +{ + if (filter) { + ComPtr propertySet = + ComQIPtr(filter); + if (propertySet) { + SetTonemapperAvermedia(propertySet, enable); + SetTonemapperAvermedia2(filter, enable); + SetTonemapperElgato(propertySet, enable); + } + } +} + +} /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/device.cpp b/obs-studio/deps/libdshowcapture/src/source/device.cpp new file mode 100644 index 0000000..644548e --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/device.cpp @@ -0,0 +1,875 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "device.hpp" +#include "dshow-device-defs.hpp" +#include "dshow-media-type.hpp" +#include "dshow-formats.hpp" +#include "dshow-enum.hpp" +#include "log.hpp" + +#define ROCKET_WAIT_TIME_MS 5000 + +namespace DShow { + +/* device-vendor.cpp API */ +extern bool IsVendorVideoHDR(IKsPropertySet *propertySet); +extern void SetVendorVideoFormat(IKsPropertySet *propertySet, + bool hevcTrueAvcFalse); +extern void SetVendorTonemapperUsage(IBaseFilter *filter, bool enable); + +bool SetRocketEnabled(IBaseFilter *encoder, bool enable); + +HDevice::HDevice() : initialized(false), active(false) {} + +HDevice::~HDevice() +{ + if (active) + Stop(); + + DisconnectFilters(); + + /* + * the sleeps for the rocket are required. It seems that you cannot + * simply start/stop the stream right away after/before you enable or + * disable the rocket. If you start it too fast after enabling, it + * won't return any data. If you try to turn off the rocket too + * quickly after stopping, then it'll be perpetually stuck on, and then + * you'll have to unplug/replug the device to get it working again. + */ + if (!!rocketEncoder) { + Sleep(ROCKET_WAIT_TIME_MS); + SetRocketEnabled(rocketEncoder, false); + } +} + +bool HDevice::EnsureInitialized(const wchar_t *func) +{ + if (!initialized) { + Error(L"%s: context not initialized", func); + return false; + } + + return true; +} + +bool HDevice::EnsureActive(const wchar_t *func) +{ + if (!active) { + Error(L"%s: cannot be used while inactive", func); + return false; + } + + return true; +} + +bool HDevice::EnsureInactive(const wchar_t *func) +{ + if (active) { + Error(L"%s: cannot be used while active", func); + return false; + } + + return true; +} + +inline void HDevice::SendToCallback(bool video, unsigned char *data, + size_t size, long long startTime, + long long stopTime, long rotation) +{ + if (!size) + return; + + if (video) + videoConfig.callback(videoConfig, data, size, startTime, + stopTime, rotation); + else + audioConfig.callback(audioConfig, data, size, startTime, + stopTime); +} + +void HDevice::Receive(bool isVideo, IMediaSample *sample) +{ + BYTE *ptr; + MediaTypePtr mt; + long roll = 0; + bool encoded = isVideo ? ((int)videoConfig.format >= 400) + : ((int)audioConfig.format >= 200); + + if (!sample) + return; + + if (isVideo ? !videoConfig.callback : !audioConfig.callback) + return; + + if (reactivatePending) + return; + + /* auto-rotation for devices such as streamcam */ + if (isVideo && rotatableDevice) { + ComQIPtr cc(videoFilter); + if (cc) { + long ccf = 0; + cc->Get(CameraControl_Roll, &roll, &ccf); + } + } + + if (isVideo && videoConfig.reactivateCallback) { + ComQIPtr propertySet(videoFilter); + if (propertySet) { + const bool hdr = IsVendorVideoHDR(propertySet); + if (deviceHdrSignal != hdr) { + deviceHdrSignal = hdr; +#ifdef ENABLE_HEVC + SetVendorVideoFormat(propertySet, hdr); +#endif + videoConfig.reactivateCallback(); + reactivatePending = true; + return; + } + } + } + + if (sample->GetMediaType(&mt) == S_OK) { + if (isVideo) { + videoMediaType = mt; + ConvertVideoSettings(); + } else { + audioMediaType = mt; + ConvertAudioSettings(); + } + } + + int size = sample->GetActualDataLength(); + if (!size) + return; + + if (FAILED(sample->GetPointer(&ptr))) + return; + + long long startTime, stopTime; + bool hasTime = SUCCEEDED(sample->GetTime(&startTime, &stopTime)); + + if (encoded) { + EncodedData &data = isVideo ? encodedVideo : encodedAudio; + + /* packets that have time are the first packet in a group of + * segments */ + if (hasTime) { + SendToCallback(isVideo, data.bytes.data(), + data.bytes.size(), data.lastStartTime, + data.lastStopTime, roll); + + data.bytes.resize(0); + data.lastStartTime = startTime; + data.lastStopTime = stopTime; + } + + data.bytes.insert(data.bytes.end(), (unsigned char *)ptr, + (unsigned char *)ptr + size); + + } else if (hasTime) { + SendToCallback(isVideo, ptr, size, startTime, stopTime, roll); + } +} + +void HDevice::ConvertVideoSettings() +{ + VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)videoMediaType->pbFormat; + BITMAPINFOHEADER *bmih = GetBitmapInfoHeader(videoMediaType); + + if (bmih) { + Debug(L"Video media type changed"); + + videoConfig.cx = bmih->biWidth; + videoConfig.cy_abs = labs(bmih->biHeight); + videoConfig.cy_flip = bmih->biHeight < 0; + videoConfig.frameInterval = vih->AvgTimePerFrame; + + bool same = videoConfig.internalFormat == videoConfig.format; + GetMediaTypeVFormat(videoMediaType, videoConfig.internalFormat); + + if (same) + videoConfig.format = videoConfig.internalFormat; + } +} + +void HDevice::ConvertAudioSettings() +{ + WAVEFORMATEX *wfex = + reinterpret_cast(audioMediaType->pbFormat); + + Debug(L"Audio media type changed"); + + audioConfig.sampleRate = wfex->nSamplesPerSec; + audioConfig.channels = wfex->nChannels; + + if (wfex->wFormatTag == WAVE_FORMAT_RAW_AAC1) + audioConfig.format = AudioFormat::AAC; + else if (wfex->wFormatTag == WAVE_FORMAT_DVM) + audioConfig.format = AudioFormat::AC3; + else if (wfex->wFormatTag == WAVE_FORMAT_MPEG) + audioConfig.format = AudioFormat::MPGA; + else if (wfex->wBitsPerSample == 16) + audioConfig.format = AudioFormat::Wave16bit; + else if (wfex->wBitsPerSample == 32) + audioConfig.format = AudioFormat::WaveFloat; + else + audioConfig.format = AudioFormat::Unknown; +} + +#define HD_PVR1_NAME L"Hauppauge HD PVR Capture" + +bool HDevice::SetupExceptionVideoCapture(IBaseFilter *filter, + VideoConfig &config) +{ + ComPtr pin; + + if (GetPinByName(filter, PINDIR_OUTPUT, L"656", &pin)) + return SetupEncodedVideoCapture(filter, config, HD_PVR2); + + else if (GetPinByName(filter, PINDIR_OUTPUT, L"TS Out", &pin)) + return SetupEncodedVideoCapture(filter, config, Roxio); + + return false; +} + +static bool GetPinMediaType(IPin *pin, MediaType &mt) +{ + ComPtr mediaTypes; + + if (SUCCEEDED(pin->EnumMediaTypes(&mediaTypes))) { + MediaTypePtr curMT; + ULONG count = 0; + + while (mediaTypes->Next(1, &curMT, &count) == S_OK) { + if (curMT->formattype == FORMAT_VideoInfo) { + mt = curMT; + return true; + } + } + } + + return false; +} + +bool HDevice::SetupVideoCapture(IBaseFilter *filter, VideoConfig &config) +{ + ComPtr pin; + HRESULT hr; + bool success; + + if (config.name.find(L"C875") != std::string::npos || + config.name.find(L"Prif Streambox") != std::string::npos || + config.name.find(L"C835") != std::string::npos) + return SetupEncodedVideoCapture(filter, config, AV_LGP); + + else if (config.name.find(L"IT9910") != std::string::npos) + return SetupEncodedVideoCapture(filter, config, HD_PVR_Rocket); + + else if (config.name.find(HD_PVR1_NAME) != std::string::npos) + return SetupEncodedVideoCapture(filter, config, HD_PVR1); + + rotatableDevice = videoConfig.name.find(L"StreamCam") != + std::string::npos; + + success = GetFilterPin(filter, MEDIATYPE_Video, PIN_CATEGORY_CAPTURE, + PINDIR_OUTPUT, &pin); + if (!success) { + if (SetupExceptionVideoCapture(filter, config)) { + return true; + } else { + Error(L"Could not get video pin"); + return false; + } + } + + ComQIPtr pinConfig(pin); + if (pinConfig == NULL) { + Error(L"Could not get IAMStreamConfig for device"); + return false; + } + + if (config.useDefaultConfig) { + MediaTypePtr defaultMT; + + hr = pinConfig->GetFormat(&defaultMT); + if (hr == E_NOTIMPL) { + if (!GetPinMediaType(pin, videoMediaType)) { + Error(L"Couldn't get pin media type"); + return false; + } + + } else if (FAILED(hr)) { + ErrorHR(L"Could not get default format for video", hr); + return false; + + } else { + videoMediaType = defaultMT; + } + + ConvertVideoSettings(); + + config.format = config.internalFormat = VideoFormat::Any; + } + + if (!GetClosestVideoMediaType(filter, config, videoMediaType)) { + Error(L"Could not get closest video media type"); + return false; + } + + hr = pinConfig->SetFormat(videoMediaType); + if (FAILED(hr) && hr != E_NOTIMPL) { + ErrorHR(L"Could not set video format", hr); + return false; + } + + ConvertVideoSettings(); + + PinCaptureInfo info; + info.callback = [this](IMediaSample *s) { Receive(true, s); }; + info.expectedMajorType = videoMediaType->majortype; + + /* attempt to force intermediary filters for these types */ + if (videoConfig.format == VideoFormat::XRGB) + info.expectedSubType = MEDIASUBTYPE_RGB32; + else if (videoConfig.format == VideoFormat::ARGB) + info.expectedSubType = MEDIASUBTYPE_ARGB32; + else if (videoConfig.format == VideoFormat::RGB24) + info.expectedSubType = MEDIASUBTYPE_RGB24; + else if (videoConfig.format == VideoFormat::YVYU) + info.expectedSubType = MEDIASUBTYPE_YVYU; + else if (videoConfig.format == VideoFormat::YUY2) + info.expectedSubType = MEDIASUBTYPE_YUY2; + else if (videoConfig.format == VideoFormat::UYVY) + info.expectedSubType = MEDIASUBTYPE_UYVY; + else + info.expectedSubType = videoMediaType->subtype; + + videoCapture = new CaptureFilter(info); + videoFilter = filter; + + graph->AddFilter(videoCapture, L"Video Capture Filter"); + graph->AddFilter(videoFilter, L"Video Filter"); + return true; +} + +bool HDevice::SetVideoConfig(VideoConfig *config) +{ + ComPtr filter; + + if (!EnsureInitialized(L"SetVideoConfig") || + !EnsureInactive(L"SetVideoConfig")) + return false; + + videoMediaType = NULL; + graph->RemoveFilter(videoFilter); + graph->RemoveFilter(videoCapture); + videoFilter.Release(); + videoCapture.Release(); + + if (!config) + return true; + + if (config->name.empty() && config->path.empty()) { + Error(L"No video device name or path specified"); + return false; + } + + bool success = GetDeviceFilter(CLSID_VideoInputDeviceCategory, + config->name.c_str(), + config->path.c_str(), &filter); + if (!success) { + Error(L"Video device '%s': %s not found", config->name.c_str(), + config->path.c_str()); + return false; + } + + if (filter == NULL) { + Error(L"Could not get video filter"); + return false; + } + + deviceHdrSignal = false; + reactivatePending = false; + + ComPtr propertySet = ComQIPtr(filter); + if (propertySet) { + const bool hdr = IsVendorVideoHDR(propertySet); +#ifdef ENABLE_HEVC + SetVendorVideoFormat(propertySet, hdr); +#endif + deviceHdrSignal = hdr; + } + + videoConfig = *config; + + if (!SetupVideoCapture(filter, videoConfig)) + return false; + + *config = videoConfig; + return true; +} + +bool HDevice::SetupExceptionAudioCapture(IPin *pin) +{ + ComPtr enumMediaTypes; + ULONG count = 0; + HRESULT hr; + MediaTypePtr mt; + + hr = pin->EnumMediaTypes(&enumMediaTypes); + if (FAILED(hr)) { + WarningHR(L"SetupExceptionAudioCapture: pin->EnumMediaTypes " + L"failed", + hr); + return false; + } + + enumMediaTypes->Reset(); + + if (enumMediaTypes->Next(1, &mt, &count) == S_OK && + mt->formattype == FORMAT_WaveFormatEx) { + audioMediaType = mt; + return true; + } + + return false; +} + +static bool is24BitAudio(AM_MEDIA_TYPE *mt) +{ + if (mt->formattype == FORMAT_WaveFormatEx) { + WAVEFORMATEX *wfex = (WAVEFORMATEX *)mt->pbFormat; + return wfex->wBitsPerSample == 24; + } + + return false; +} + +bool HDevice::SetupAudioCapture(IBaseFilter *filter, AudioConfig &config) +{ + ComPtr pin; + MediaTypePtr defaultMT; + bool success; + HRESULT hr; + + success = GetFilterPin(filter, MEDIATYPE_Audio, PIN_CATEGORY_CAPTURE, + PINDIR_OUTPUT, &pin); + if (!success) { + Error(L"Could not get audio pin"); + return false; + } + + ComQIPtr pinConfig(pin); + + if (config.useDefaultConfig) { + MediaTypePtr defaultMT; + + if (pinConfig && SUCCEEDED(pinConfig->GetFormat(&defaultMT))) { + if (is24BitAudio(defaultMT)) { + WAVEFORMATEX *wfex = + (WAVEFORMATEX *)defaultMT->pbFormat; + config.sampleRate = wfex->nSamplesPerSec; + config.channels = wfex->nChannels; + config.format = AudioFormat::Wave16bit; + config.useDefaultConfig = false; + } else { + audioMediaType = defaultMT; + } + } else { + if (!SetupExceptionAudioCapture(pin)) { + Error(L"Could not get default format for " + L"audio pin"); + return false; + } + } + } + + if (!config.useDefaultConfig) { + if (!GetClosestAudioMediaType(filter, config, audioMediaType)) { + Error(L"Could not get closest audio media type"); + return false; + } + } + + if (!!pinConfig) { + hr = pinConfig->SetFormat(audioMediaType); + + if (FAILED(hr) && hr != E_NOTIMPL) { + Error(L"Could not set audio format"); + return false; + } + } + + ConvertAudioSettings(); + + PinCaptureInfo info; + info.callback = [this](IMediaSample *s) { Receive(false, s); }; + info.expectedMajorType = audioMediaType->majortype; + info.expectedSubType = audioMediaType->subtype; + + audioCapture = new CaptureFilter(info); + audioFilter = filter; + audioConfig = config; + + graph->AddFilter(audioCapture, L"Audio Capture Filter"); + if (!config.useVideoDevice) + graph->AddFilter(audioFilter, L"Audio Filter"); + return true; +} + +bool HDevice::SetupAudioOutput(IBaseFilter *filter, AudioConfig &config) +{ + ComPtr outputFilter; + const CLSID *clsID; + HRESULT hr; + + if (config.mode == AudioMode::WaveOut) { + clsID = &CLSID_AudioRender; + } else { + clsID = &CLSID_DSoundRender; + } + + hr = CoCreateInstance(*clsID, nullptr, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, (void **)&outputFilter); + if (FAILED(hr)) { + ErrorHR(L"Failed to create audio sound output filter", hr); + return false; + } + + audioFilter = filter; + audioOutput = std::move(outputFilter); + + graph->AddFilter(audioOutput, L"Audio Output Filter"); + if (!config.useVideoDevice) + graph->AddFilter(audioFilter, L"Audio Filter"); + return true; +} + +bool HDevice::SetAudioConfig(AudioConfig *config) +{ + ComPtr filter; + + if (!EnsureInitialized(L"SetAudioConfig") || + !EnsureInactive(L"SetAudioConfig")) + return false; + + if (!audioConfig.useVideoDevice) + graph->RemoveFilter(audioFilter); + graph->RemoveFilter(audioCapture); + graph->RemoveFilter(audioOutput); + audioFilter.Release(); + audioCapture.Release(); + audioOutput.Release(); + audioMediaType = NULL; + + if (!config) + return true; + + if (!config->useVideoDevice && !config->useSeparateAudioFilter && + config->name.empty() && config->path.empty()) { + Error(L"No audio device name or path specified"); + return false; + } + + if (config->useVideoDevice) { + if (videoFilter == NULL) { + Error(L"Tried to use video device's built-in audio, " + L"but no video device is present"); + return false; + } + + filter = videoFilter; + } else if (config->useSeparateAudioFilter) { + bool success = + GetDeviceAudioFilter(videoConfig.path.c_str(), &filter); + if (!success) { + Error(L"Corresponding audio device for '%s' not found", + videoConfig.path.c_str()); + return false; + } + + } else { + bool success = GetDeviceFilter(CLSID_AudioInputDeviceCategory, + config->name.c_str(), + config->path.c_str(), &filter); + if (!success) { + Error(L"Audio device '%s': %s not found", + config->name.c_str(), config->path.c_str()); + return false; + } + } + + if (filter == NULL) + return false; + + audioConfig = *config; + + if (config->mode == AudioMode::Capture) { + if (!SetupAudioCapture(filter, audioConfig)) + return false; + + *config = audioConfig; + return true; + } + + return SetupAudioOutput(filter, audioConfig); +} + +bool HDevice::CreateGraph() +{ + if (initialized) { + Warning(L"Graph already created"); + return false; + } + + if (!CreateFilterGraph(&graph, &builder, &control)) + return false; + + initialized = true; + return true; +} + +bool HDevice::FindCrossbar(IBaseFilter *filter, IBaseFilter **crossbar) +{ + ComPtr pin; + REGPINMEDIUM medium; + HRESULT hr; + + hr = builder->FindInterface(NULL, NULL, filter, IID_IAMCrossbar, + (void **)crossbar); + if (SUCCEEDED(hr)) + return true; + + if (!GetPinByName(filter, PINDIR_INPUT, nullptr, &pin)) + return false; + if (!GetPinMedium(pin, medium)) + return false; + if (!GetFilterByMedium(AM_KSCATEGORY_CROSSBAR, medium, crossbar)) + return false; + + graph->AddFilter(*crossbar, L"Crossbar Filter"); + return true; +} + +bool HDevice::ConnectPins(const GUID &category, const GUID &type, + IBaseFilter *filter, IBaseFilter *capture) +{ + HRESULT hr; + ComPtr crossbar; + ComPtr filterPin; + ComPtr capturePin; + bool connectCrossbar = !encodedDevice && type == MEDIATYPE_Video; + + if (!EnsureInitialized(L"HDevice::ConnectPins") || + !EnsureInactive(L"HDevice::ConnectPins")) + return false; + + if (connectCrossbar && FindCrossbar(filter, &crossbar)) { + if (!DirectConnectFilters(graph, crossbar, filter)) { + Warning(L"HDevice::ConnectPins: Failed to connect " + L"crossbar"); + return false; + } + } + + if (!GetFilterPin(filter, type, category, PINDIR_OUTPUT, &filterPin)) { + Error(L"HDevice::ConnectPins: Failed to find pin"); + return false; + } + + if (!GetPinByName(capture, PINDIR_INPUT, nullptr, &capturePin)) { + Error(L"HDevice::ConnectPins: Failed to find capture pin"); + return false; + } + + hr = graph->ConnectDirect(filterPin, capturePin, nullptr); + if (FAILED(hr)) { + WarningHR(L"HDevice::ConnectPins: failed to connect pins", hr); + return false; + } + + return true; +} + +bool HDevice::RenderFilters(const GUID &category, const GUID &type, + IBaseFilter *filter, IBaseFilter *capture) +{ + HRESULT hr; + + if (!EnsureInitialized(L"HDevice::RenderFilters") || + !EnsureInactive(L"HDevice::RenderFilters")) + return false; + + hr = builder->RenderStream(&category, &type, filter, NULL, capture); + if (FAILED(hr)) { + WarningHR(L"HDevice::ConnectFilters: RenderStream failed", hr); + return false; + } + + return true; +} + +void HDevice::SetAudioBuffering(int bufferingMs) +{ + ComPtr pin; + bool success = GetFilterPin(audioFilter, MEDIATYPE_Audio, + PIN_CATEGORY_CAPTURE, PINDIR_OUTPUT, &pin); + if (!success) + return; + + ComQIPtr config(pin); + if (!config) + return; + + ComQIPtr neg(pin); + if (!neg) + return; + + MediaTypePtr mt; + if (FAILED(config->GetFormat(&mt))) + return; + + if (mt->formattype != FORMAT_WaveFormatEx) + return; + if (mt->cbFormat != sizeof(WAVEFORMATEX)) + return; + + WAVEFORMATEX *wfex = (WAVEFORMATEX *)mt->pbFormat; + + ALLOCATOR_PROPERTIES props; + props.cBuffers = -1; + props.cbBuffer = wfex->nAvgBytesPerSec * bufferingMs / 1000; + props.cbAlign = -1; + props.cbPrefix = -1; + HRESULT hr = neg->SuggestAllocatorProperties(&props); + if (FAILED(hr)) + WarningHR(L"Could not set allocator properties on audio " + L"capture pin", + hr); +} + +bool HDevice::ConnectFilters() +{ + bool success = true; + + if (!EnsureInitialized(L"ConnectFilters") || + !EnsureInactive(L"ConnectFilters")) + return false; + + if (videoCapture != NULL) { + /* use hardware tonemapper for narrow format (SDR), not wide (HDR) */ + const bool enable_tonemapper = videoConfig.format != + VideoFormat::P010; + SetVendorTonemapperUsage(videoFilter, enable_tonemapper); + + success = ConnectPins(PIN_CATEGORY_CAPTURE, MEDIATYPE_Video, + videoFilter, videoCapture); + if (!success) { + success = RenderFilters(PIN_CATEGORY_CAPTURE, + MEDIATYPE_Video, videoFilter, + videoCapture); + } + } + + if ((audioCapture || audioOutput) && success) { + IBaseFilter *filter = (audioCapture != nullptr) + ? audioCapture.Get() + : audioOutput.Get(); + + /* Stream engine has a bug where it will break if you try to + * set different audio buffering, so don't use audio buffering + * if using the stream engine's audio */ + bool streamEngine = audioConfig.useVideoDevice && + (videoConfig.name.find(L"Stream Engine") != + std::string::npos); + + if (!streamEngine && audioCapture != nullptr) + SetAudioBuffering(audioConfig.buffer ? audioConfig.buffer : 10); + + success = ConnectPins(PIN_CATEGORY_CAPTURE, MEDIATYPE_Audio, + audioFilter, filter); + if (!success) { + success = RenderFilters(PIN_CATEGORY_CAPTURE, + MEDIATYPE_Audio, audioFilter, + filter); + } + } + + if (success) + LogFilters(graph); + + return success; +} + +void HDevice::DisconnectFilters() +{ + ComPtr filterEnum; + HRESULT hr; + + if (!graph) + return; + + hr = graph->EnumFilters(&filterEnum); + if (FAILED(hr)) + return; + + ComPtr filter; + while (filterEnum->Next(1, &filter, nullptr) == S_OK) { + graph->RemoveFilter(filter); + filterEnum->Reset(); + } +} + +Result HDevice::Start() +{ + HRESULT hr; + + if (!EnsureInitialized(L"Start") || !EnsureInactive(L"Start")) + return Result::Error; + + if (!!rocketEncoder) + Sleep(ROCKET_WAIT_TIME_MS); + + hr = control->Run(); + + if (FAILED(hr)) { + if (hr == (HRESULT)0x8007001F) { + WarningHR(L"Run failed, device already in use", hr); + return Result::InUse; + } else { + WarningHR(L"Run failed", hr); + return Result::Error; + } + } + + active = true; + return Result::Success; +} + +void HDevice::Stop() +{ + if (active) { + control->Stop(); + active = false; + } +} + +} /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/device.hpp b/obs-studio/deps/libdshowcapture/src/source/device.hpp new file mode 100644 index 0000000..216b98a --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/device.hpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "../dshowcapture.hpp" +#include "capture-filter.hpp" + +#include +#include +using namespace std; + +namespace DShow { + +struct EncodedData { + long long lastStartTime = 0; + long long lastStopTime = 0; + vector bytes; +}; + +struct EncodedDevice { + VideoFormat videoFormat; + ULONG videoPacketID; + long width; + long height; + long long frameInterval; + + AudioFormat audioFormat; + ULONG audioPacketID; + DWORD samplesPerSec; +}; + +struct HDevice { + ComPtr graph; + ComPtr builder; + ComPtr control; + + ComPtr videoFilter; + ComPtr audioFilter; + ComPtr videoCapture; + ComPtr audioCapture; + ComPtr audioOutput; + ComPtr rocketEncoder; + MediaType videoMediaType; + MediaType audioMediaType; + VideoConfig videoConfig; + AudioConfig audioConfig; + + bool encodedDevice = false; + bool rotatableDevice = false; + bool deviceHdrSignal = false; + bool reactivatePending = false; + bool initialized; + bool active; + + EncodedData encodedVideo; + EncodedData encodedAudio; + + HDevice(); + ~HDevice(); + + void ConvertVideoSettings(); + void ConvertAudioSettings(); + + bool EnsureInitialized(const wchar_t *func); + bool EnsureActive(const wchar_t *func); + bool EnsureInactive(const wchar_t *func); + + inline void SendToCallback(bool video, unsigned char *data, size_t size, + long long startTime, long long stopTime, + long rotation); + + void Receive(bool video, IMediaSample *sample); + + bool SetupEncodedVideoCapture(IBaseFilter *filter, VideoConfig &config, + const EncodedDevice &info); + + bool SetupExceptionVideoCapture(IBaseFilter *filter, + VideoConfig &config); + + bool SetupExceptionAudioCapture(IPin *pin); + + bool SetupVideoCapture(IBaseFilter *filter, VideoConfig &config); + bool SetupAudioCapture(IBaseFilter *filter, AudioConfig &config); + + bool SetupAudioOutput(IBaseFilter *filter, AudioConfig &config); + + bool SetVideoConfig(VideoConfig *config); + bool SetAudioConfig(AudioConfig *config); + + bool CreateGraph(); + bool FindCrossbar(IBaseFilter *filter, IBaseFilter **crossbar); + bool ConnectPins(const GUID &category, const GUID &type, + IBaseFilter *filter, IBaseFilter *capture); + bool RenderFilters(const GUID &category, const GUID &type, + IBaseFilter *filter, IBaseFilter *capture); + void SetAudioBuffering(int bufferingMs); + bool ConnectFilters(); + void DisconnectFilters(); + Result Start(); + void Stop(); +}; + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-base.cpp b/obs-studio/deps/libdshowcapture/src/source/dshow-base.cpp new file mode 100644 index 0000000..0a223b7 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-base.cpp @@ -0,0 +1,1031 @@ +/* + * Copyright (C) 2014 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "dshow-base.hpp" +#include "dshow-enum.hpp" +#include "log.hpp" + +#include + +#include +#include + +#include // for DRV_QUERYDEVICEINTERFACE +#include // for SetupDixxx +#include // for CM_xxx +#include // for std::transform + +using namespace std; + +namespace DShow { + +bool CreateFilterGraph(IGraphBuilder **pgraph, ICaptureGraphBuilder2 **pbuilder, + IMediaControl **pcontrol) +{ + ComPtr graph; + ComPtr builder; + ComPtr control; + HRESULT hr; + + hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + IID_IFilterGraph, (void **)&graph); + if (FAILED(hr)) { + ErrorHR(L"Failed to create IGraphBuilder", hr); + return false; + } + + hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, + CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, + (void **)&builder); + if (FAILED(hr)) { + ErrorHR(L"Failed to create ICaptureGraphBuilder2", hr); + return false; + } + + hr = builder->SetFiltergraph(graph); + if (FAILED(hr)) { + ErrorHR(L"Failed to set filter graph", hr); + return false; + } + + hr = graph->QueryInterface(IID_IMediaControl, (void **)&control); + if (FAILED(hr)) { + ErrorHR(L"Failed to create IMediaControl", hr); + return false; + } + + *pgraph = graph.Detach(); + *pbuilder = builder.Detach(); + *pcontrol = control.Detach(); + return true; +} + +void LogFilters(IGraphBuilder *graph) +{ + ComPtr filterEnum; + ComPtr filter; + HRESULT hr; + + hr = graph->EnumFilters(&filterEnum); + if (FAILED(hr)) + return; + + Debug(L"Loaded filters:"); + + while (filterEnum->Next(1, &filter, NULL) == S_OK) { + FILTER_INFO filterInfo; + + hr = filter->QueryFilterInfo(&filterInfo); + if (SUCCEEDED(hr)) { + if (filterInfo.pGraph) + filterInfo.pGraph->Release(); + + Debug(L"\t%s", filterInfo.achName); + } + } +} + +struct DeviceFilterCallbackInfo { + ComPtr filter; + const wchar_t *name; + const wchar_t *path; +}; + +static bool GetDeviceCallback(DeviceFilterCallbackInfo &info, + IBaseFilter *filter, const wchar_t *name, + const wchar_t *path) +{ + if (info.name && *info.name && wcscmp(name, info.name) != 0) + return true; + + info.filter = filter; + + /* continue if path does not match */ + if (!path || !info.path || wcscmp(path, info.path) != 0) + return true; + + return false; +} + +bool GetDeviceFilter(const IID &type, const wchar_t *name, const wchar_t *path, + IBaseFilter **out) +{ + DeviceFilterCallbackInfo info; + info.name = name; + info.path = path; + + if (!EnumDevices(type, EnumDeviceCallback(GetDeviceCallback), &info)) + return false; + + if (info.filter != NULL) { + *out = info.filter.Detach(); + return true; + } + + return false; +} + +/* checks to see if a pin's config caps have a specific media type */ +static bool PinConfigHasMajorType(IPin *pin, const GUID &type) +{ + HRESULT hr; + ComPtr config; + int count, size; + + hr = pin->QueryInterface(IID_IAMStreamConfig, (void **)&config); + if (FAILED(hr)) + return false; + + hr = config->GetNumberOfCapabilities(&count, &size); + if (FAILED(hr)) + return false; + + vector caps; + caps.resize(size); + + for (int i = 0; i < count; i++) { + MediaTypePtr mt; + if (SUCCEEDED(config->GetStreamCaps(i, &mt, caps.data()))) + if (mt->majortype == type) + return true; + } + + return false; +} + +/* checks to see if a pin has a certain major media type */ +static bool PinHasMajorType(IPin *pin, const GUID &type) +{ + HRESULT hr; + MediaTypePtr mt; + ComPtr mediaEnum; + + /* first, check the config caps. */ + if (PinConfigHasMajorType(pin, type)) + return true; + + /* then let's check the media type for the pin */ + if (FAILED(pin->EnumMediaTypes(&mediaEnum))) + return false; + + ULONG curVal; + hr = mediaEnum->Next(1, &mt, &curVal); + if (hr != S_OK) + return false; + + return mt->majortype == type; +} + +static inline bool PinIsDirection(IPin *pin, PIN_DIRECTION dir) +{ + if (!pin) + return false; + + PIN_DIRECTION pinDir; + return SUCCEEDED(pin->QueryDirection(&pinDir)) && pinDir == dir; +} + +static HRESULT GetPinCategory(IPin *pin, GUID &category) +{ + if (!pin) + return E_POINTER; + + ComQIPtr propertySet(pin); + DWORD size; + + if (propertySet == NULL) + return E_NOINTERFACE; + + return propertySet->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, + 0, &category, sizeof(GUID), &size); +} + +static inline bool PinIsCategory(IPin *pin, const GUID &category) +{ + if (!pin) + return false; + + GUID pinCategory; + HRESULT hr = GetPinCategory(pin, pinCategory); + + /* if the pin has no category interface, chances are we created it */ + if (FAILED(hr)) + return (hr == E_NOINTERFACE); + + return category == pinCategory; +} + +static inline bool PinNameIs(IPin *pin, const wchar_t *name) +{ + if (!pin) + return false; + if (!name) + return true; + + PIN_INFO pinInfo; + + if (FAILED(pin->QueryPinInfo(&pinInfo))) + return false; + + if (pinInfo.pFilter) + pinInfo.pFilter->Release(); + + return wcscmp(name, pinInfo.achName) == 0; +} + +static inline bool PinMatches(IPin *pin, const GUID &type, const GUID &category, + PIN_DIRECTION &dir) +{ + if (!PinHasMajorType(pin, type)) + return false; + if (!PinIsDirection(pin, dir)) + return false; + if (!PinIsCategory(pin, category)) + return false; + + return true; +} + +bool GetFilterPin(IBaseFilter *filter, const GUID &type, const GUID &category, + PIN_DIRECTION dir, IPin **pin) +{ + ComPtr curPin; + ComPtr pinsEnum; + ULONG num; + + if (!filter) + return false; + if (FAILED(filter->EnumPins(&pinsEnum))) + return false; + + while (pinsEnum->Next(1, &curPin, &num) == S_OK) { + + if (PinMatches(curPin, type, category, dir)) { + *pin = curPin; + (*pin)->AddRef(); + return true; + } + } + + return false; +} + +bool GetPinByName(IBaseFilter *filter, PIN_DIRECTION dir, const wchar_t *name, + IPin **pin) +{ + ComPtr curPin; + ComPtr pinsEnum; + ULONG num; + + if (!filter) + return false; + if (FAILED(filter->EnumPins(&pinsEnum))) + return false; + + while (pinsEnum->Next(1, &curPin, &num) == S_OK) { + if (PinIsDirection(curPin, dir) && PinNameIs(curPin, name)) { + *pin = curPin.Detach(); + return true; + } + } + + return false; +} + +bool GetPinByMedium(IBaseFilter *filter, REGPINMEDIUM &medium, IPin **pin) +{ + ComPtr curPin; + ComPtr pinsEnum; + ULONG num; + + if (!filter) + return false; + if (FAILED(filter->EnumPins(&pinsEnum))) + return false; + + while (pinsEnum->Next(1, &curPin, &num) == S_OK) { + REGPINMEDIUM curMedium; + + if (GetPinMedium(curPin, curMedium) && + memcmp(&medium, &curMedium, sizeof(medium)) == 0) { + *pin = curPin.Detach(); + return true; + } + } + + return false; +} + +static bool GetFilterByMediumFromMoniker(IMoniker *moniker, + REGPINMEDIUM &medium, + IBaseFilter **filter) +{ + ComPtr curFilter; + HRESULT hr; + + hr = moniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, + (void **)&curFilter); + if (SUCCEEDED(hr)) { + ComPtr pin; + if (GetPinByMedium(curFilter, medium, &pin)) { + *filter = curFilter.Detach(); + return true; + } + } else { + WarningHR(L"GetFilterByMediumFromMoniker: BindToObject failed", + hr); + } + + return false; +} + +bool GetFilterByMedium(const CLSID &id, REGPINMEDIUM &medium, + IBaseFilter **filter) +{ + ComPtr deviceEnum; + ComPtr enumMoniker; + ComPtr moniker; + DWORD count = 0; + HRESULT hr; + + hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, + CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, + (void **)&deviceEnum); + if (FAILED(hr)) { + WarningHR(L"GetFilterByMedium: Failed to create device enum", + hr); + return false; + } + + hr = deviceEnum->CreateClassEnumerator(id, &enumMoniker, 0); + if (hr != S_OK) { + WarningHR(L"GetFilterByMedium: Failed to create enum moniker", + hr); + return false; + } + + enumMoniker->Reset(); + + while (enumMoniker->Next(1, &moniker, &count) == S_OK) { + if (GetFilterByMediumFromMoniker(moniker, medium, filter)) + return true; + } + + return false; +} + +bool GetPinMedium(IPin *pin, REGPINMEDIUM &medium) +{ + ComQIPtr ksPin(pin); + CoTaskMemPtr items; + + if (!ksPin) + return false; + + if (FAILED(ksPin->KsQueryMediums(&items))) + return false; + + REGPINMEDIUM *curMed = reinterpret_cast(items + 1); + for (ULONG i = 0; i < items->Count; i++, curMed++) { + if (!IsEqualGUID(curMed->clsMedium, GUID_NULL) && + !IsEqualGUID(curMed->clsMedium, KSMEDIUMSETID_Standard)) { + medium = *curMed; + return true; + } + } + + memset(&medium, 0, sizeof(medium)); + return false; +} + +static inline bool PinIsConnected(IPin *pin) +{ + ComPtr connectedPin; + return SUCCEEDED(pin->ConnectedTo(&connectedPin)); +} + +static bool DirectConnectOutputPin(IFilterGraph *graph, IPin *pin, + IBaseFilter *filterIn) +{ + ComPtr curPin; + ComPtr pinsEnum; + ULONG num; + + if (!graph || !filterIn || !pin) + return false; + if (FAILED(filterIn->EnumPins(&pinsEnum))) + return false; + + while (pinsEnum->Next(1, &curPin, &num) == S_OK) { + + if (PinIsDirection(curPin, PINDIR_INPUT) && + !PinIsConnected(curPin)) { + if (graph->ConnectDirect(pin, curPin, nullptr) == S_OK) + return true; + } + } + + return false; +} + +bool DirectConnectFilters(IFilterGraph *graph, IBaseFilter *filterOut, + IBaseFilter *filterIn) +{ + ComPtr curPin; + ComPtr pinsEnum; + ULONG num; + bool connected = false; + + if (!graph || !filterOut || !filterIn) + return false; + if (FAILED(filterOut->EnumPins(&pinsEnum))) + return false; + + while (pinsEnum->Next(1, &curPin, &num) == S_OK) { + + if (PinIsDirection(curPin, PINDIR_OUTPUT) && + !PinIsConnected(curPin)) { + if (DirectConnectOutputPin(graph, curPin, filterIn)) + connected = true; + } + } + + return connected; +} + +HRESULT MapPinToPacketID(IPin *pin, ULONG packetID) +{ + ComQIPtr pidMap(pin); + if (!pidMap) + return E_NOINTERFACE; + + return pidMap->MapPID(1, &packetID, MEDIA_ELEMENTARY_STREAM); +} + +wstring ConvertHRToEnglish(HRESULT hr) +{ + LPWSTR buffer = NULL; + wstring str; + + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, hr, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (LPTSTR)&buffer, 0, NULL); + + if (buffer) { + str = buffer; + LocalFree(buffer); + } + + return str; +} + +static HRESULT DevicePathToDeviceInstancePath(const wchar_t *devicePath, + wchar_t *devInstPath, int size) +{ + /* Sanity checks */ + if (!devicePath) + return E_POINTER; + if (!devInstPath) + return E_POINTER; + + /* Convert to uppercase STL string */ + wstring parseDevicePath = devicePath; + for (wchar_t &c : parseDevicePath) + c = (wchar_t)toupper(c); + + /* Find start position ('\\?\' or '\?\') */ + wstring startToken = L"\\\\?\\"; + size_t start = parseDevicePath.find(startToken, 0); + if (start == string::npos) { + startToken = L"\\??\\"; + start = parseDevicePath.find(startToken, 0); + if (start == string::npos) + return E_FAIL; + } + parseDevicePath = parseDevicePath.substr( + startToken.size(), parseDevicePath.size() - startToken.size()); + + /* Find end position (last occurrence of '#') */ + wchar_t endToken = '#'; + size_t end = + parseDevicePath.find_last_of(endToken, parseDevicePath.size()); + if (end == string::npos) + return E_FAIL; + parseDevicePath = parseDevicePath.substr(0, end); + + /* Replace '#' by '\' */ + std::replace(parseDevicePath.begin(), parseDevicePath.end(), L'#', + L'\\'); + + /* Set output parameter */ + StringCchCopyW(devInstPath, size, parseDevicePath.c_str()); + + return S_OK; +} + +static HRESULT GetParentDeviceInstancePath(const wchar_t *devInstPath, + wchar_t *parentDevInstPath, int size) +{ + /* Init return value */ + HRESULT hr = E_FAIL; + + /* Get device info */ + HDEVINFO hDevInfo = SetupDiCreateDeviceInfoList(nullptr, NULL); + if (NULL != hDevInfo) { + SP_DEVINFO_DATA did; + did.cbSize = sizeof(SP_DEVINFO_DATA); + BOOL success = SetupDiOpenDeviceInfo(hDevInfo, devInstPath, + NULL, 0, &did); + if (success) { + + /* Get parent device */ + DEVINST devParent; + CONFIGRET ret = + CM_Get_Parent(&devParent, did.DevInst, 0); + if (CR_SUCCESS == ret) { + + /* Get parent device instance path */ + ret = CM_Get_Device_ID( + devParent, parentDevInstPath, size, 0); + if (CR_SUCCESS == ret) + hr = S_OK; + } + + /* Cleanup */ + SetupDiDeleteDeviceInfo(hDevInfo, &did); + } + + /* Cleanup */ + SetupDiDestroyDeviceInfoList(hDevInfo); + } + + return hr; +} + +static bool IsSameInstPath(const wchar_t *audDevPath, + const wchar_t *vidDevInstPath) +{ + /* Get audio device instance path */ + wchar_t audDevInstPath[512]; + HRESULT hr = DevicePathToDeviceInstancePath(audDevPath, audDevInstPath, + _ARRAYSIZE(audDevInstPath)); + + /* Compare audio and video device instance path */ + if (FAILED(hr)) + return false; + + return wcscmp(audDevInstPath, vidDevInstPath) == 0; +} + +static HRESULT +GetAudioCaptureParentDeviceInstancePath(IMoniker *audioCapture, + wchar_t *parentDevInstPath, int size) +{ + /* Sanity checks */ + if (!audioCapture) + return E_POINTER; + + /* Bind to property bag */ + ComPtr propertyBag; + HRESULT hr = audioCapture->BindToStorage(0, 0, IID_IPropertyBag, + (void **)&propertyBag); + if (SUCCEEDED(hr)) { + + /* Init variant */ + VARIANT var; + VariantInit(&var); + + /* Get "WaveInId" */ + hr = propertyBag->Read(L"WaveInId", &var, nullptr); + if (SUCCEEDED(hr) && var.vt == VT_I4) { + + /* Get device path */ + wchar_t devicePath[512]; + MMRESULT res = waveInMessage((HWAVEIN)var.iVal, + DRV_QUERYDEVICEINTERFACE, + (DWORD_PTR)devicePath, + sizeof(devicePath)); + if (res == MMSYSERR_NOERROR) { + /* Get device instance path */ + wchar_t devInstPath[512]; + hr = DevicePathToDeviceInstancePath( + devicePath, devInstPath, + _ARRAYSIZE(devInstPath)); + + /* Get parent */ + if (SUCCEEDED(hr)) + hr = GetParentDeviceInstancePath( + devInstPath, parentDevInstPath, + size); + } + } + + /* Cleanup */ + VariantClear(&var); + } + + return hr; +} + +static bool IsMonikerSameParentInstPath(IMoniker *moniker, + const wchar_t *vidDevInstPath) +{ + /* Get video parent device instance path */ + wchar_t vidParentDevInstPath[512]; + HRESULT hr = GetParentDeviceInstancePath( + vidDevInstPath, vidParentDevInstPath, + _ARRAYSIZE(vidParentDevInstPath)); + + if (FAILED(hr)) + return false; + + /* Get audio parent device instance path */ + wchar_t audParentDevInstPath[512]; + hr = GetAudioCaptureParentDeviceInstancePath( + moniker, audParentDevInstPath, + _ARRAYSIZE(audParentDevInstPath)); + + if (FAILED(hr)) + return false; + + /* Compare audio and video parent device instance path */ + return wcscmp(audParentDevInstPath, vidParentDevInstPath) == 0; +} + +#define VEN_ID_SIZE 4 + +static inline bool MatchingStartToken(const wstring &path, + const wstring &start_token) +{ + return path.find(start_token) == 0 && + path.size() >= start_token.size() + VEN_ID_SIZE; +} + +static bool IsUncoupledDevice(const wchar_t *vidDevInstPath) +{ + /* Sanity checks */ + if (!vidDevInstPath) + return false; + + const wstring path = vidDevInstPath; + + /* USB */ + const wstring usbToken = L"USB\\VID_"; + const wstring usbVidIdWhitelist[] = { + L"0FD9", /* elgato */ + L"3842", /* evga */ + L"0B05", /* asus */ + L"07CA", /* avermedia */ + L"048D", /* digitnow/pengo */ + L"04B4", /* mokose */ + L"0557", /* aten */ + L"1164", /* startek/kapchr */ + L"1532", /* razer */ + L"1BCF", /* mypin/treaslin/mirabox */ + L"1E4E", /* pengo/cloneralliance */ + L"1E71", /* nzxt */ + L"2040", /* hauppauge */ + L"2935", /* magewell */ + L"298F", /* genki */ + L"2B77", /* epiphan */ + L"32ED", /* ezcap */ + L"534D", /* brand-less/pacoxi/ucec */ + L"EBA4", /* zasluke */ + }; + + if (MatchingStartToken(path, usbToken)) { + /* Get USB vendor ID */ + const wstring vid = path.substr(usbToken.size(), VEN_ID_SIZE); + for (const wstring &whitelistId : usbVidIdWhitelist) { + if (vid == whitelistId) { + return true; + } + } + } + + /* PCI */ + const wstring pciVenToken = L"PCI\\VEN_"; + const wstring pciSubsysToken = L"SUBSYS_"; + const wstring pciVenIdWhitelist[] = { + L"1CD7", /* magewell */ + L"8888", /* acasis */ + L"1461", /* avermedia */ + }; + const wstring pciSubsysIdWhitelist[] = { + L"1CFA", /* elgato */ + }; + + if (MatchingStartToken(path, pciVenToken)) { + const wstring vid = path.substr(usbToken.size(), VEN_ID_SIZE); + for (const wstring &whitelistId : pciVenIdWhitelist) { + if (vid == whitelistId) { + return true; + } + } + + size_t subsysPos = path.find(pciSubsysToken); + size_t subsysIdPos = + subsysPos + pciSubsysToken.size() + VEN_ID_SIZE; + size_t expectedSize = subsysIdPos + VEN_ID_SIZE; + + if (subsysPos != string::npos && path.size() >= expectedSize) { + /* Get PCI subsystem vendor ID */ + const wstring ssid = + path.substr(subsysIdPos, VEN_ID_SIZE); + for (const wstring &whitelistId : + pciSubsysIdWhitelist) { + if (ssid == whitelistId) { + return true; + } + } + } + } + + return false; +} + +static HRESULT ReadProperty(IMoniker *moniker, const wchar_t *property, + wchar_t *value, int size) +{ + /* Sanity checks */ + if (!moniker) + return E_POINTER; + if (!property) + return E_POINTER; + if (!value) + return E_POINTER; + + /* Increment reference count */ + moniker->AddRef(); + + /* Bind to property bag */ + ComPtr propertyBag; + HRESULT hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, + (void **)&propertyBag); + if (SUCCEEDED(hr)) { + /* Initialize variant */ + VARIANT var; + VariantInit(&var); + + /* Read property */ + hr = propertyBag->Read(property, &var, nullptr); + if (SUCCEEDED(hr)) + StringCchCopyW(value, size, var.bstrVal); + + /* Cleanup */ + VariantClear(&var); + } + + /* Decrement reference count */ + moniker->Release(); + + return hr; +} + +static HRESULT GetFriendlyName(REFCLSID deviceClass, const wchar_t *devPath, + wchar_t *name, int nameSize) +{ + /* Sanity checks */ + if (!devPath) + return E_POINTER; + if (!name) + return E_POINTER; + + /* Create device enumerator */ + ComPtr createDevEnum; + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, + (void **)&createDevEnum); + + /* Enumerate filters */ + ComPtr enumMoniker; + if (SUCCEEDED(hr)) { + /* returns S_FALSE if no devices are installed */ + hr = createDevEnum->CreateClassEnumerator(deviceClass, + &enumMoniker, 0); + if (!enumMoniker) + hr = E_FAIL; + } + + /* Cycle through the enumeration */ + if (SUCCEEDED(hr)) { + ULONG fetched = 0; + ComPtr moniker; + + enumMoniker->Reset(); + + while (enumMoniker->Next(1, &moniker, &fetched) == S_OK) { + + /* Get device path from moniker */ + wchar_t monikerDevPath[512]; + hr = ReadProperty(moniker, L"DevicePath", + monikerDevPath, + _ARRAYSIZE(monikerDevPath)); + + /* Find desired filter */ + if (wcscmp(devPath, monikerDevPath) == 0) { + + /* Get friendly name */ + hr = ReadProperty(moniker, L"FriendlyName", + name, nameSize); + return hr; + } + } + } + + return E_FAIL; +} + +static bool MatchFriendlyNames(const wchar_t *vidName, const wchar_t *audName) +{ + /* Sanity checks */ + if (!vidName) + return false; + if (!audName) + return false; + + /* Convert strings to lower case */ + wstring strVidName = vidName; + for (wchar_t &c : strVidName) + c = (wchar_t)tolower(c); + wstring strAudName = audName; + for (wchar_t &c : strAudName) + c = (wchar_t)tolower(c); + + /* Remove 'video' from friendly name */ + size_t posVid; + const wstring searchVid[] = {L"(video) ", L"(video)", L"video ", + L"video", L"hdmi", L" / multiview"}; + for (int i = 0; i < _ARRAYSIZE(searchVid); i++) { + const wstring &search = searchVid[i]; + while ((posVid = strVidName.find(search)) != + std::string::npos) { + strVidName.replace(posVid, search.length(), L""); + } + } + + /* Remove 'audio' from friendly name */ + size_t posAud; + const wstring searchAud[] = {L"(audio) ", L"(audio)", L"audio ", + L"audio"}; + for (int i = 0; i < _ARRAYSIZE(searchAud); i++) { + const wstring &search = searchAud[i]; + while ((posAud = strAudName.find(search)) != + std::string::npos) { + strAudName.replace(posAud, search.length(), L""); + } + } + + return strVidName == strAudName; +} + +static bool GetDeviceAudioFilterInternal(REFCLSID deviceClass, + const wchar_t *vidDevPath, + IBaseFilter **audioCaptureFilter, + bool matchFilterName = false) +{ + /* Get video device instance path */ + wchar_t vidDevInstPath[512]; + HRESULT hr = DevicePathToDeviceInstancePath(vidDevPath, vidDevInstPath, + _ARRAYSIZE(vidDevInstPath)); + if (FAILED(hr)) + return false; + +#if 1 + /* Only enabled for certain whitelisted devices for now */ + if (!IsUncoupledDevice(vidDevInstPath)) + return false; +#endif + + /* Get friendly name */ + wchar_t vidName[512]; + if (matchFilterName) { + hr = GetFriendlyName(CLSID_VideoInputDeviceCategory, vidDevPath, + vidName, _ARRAYSIZE(vidName)); + if (FAILED(hr)) + return false; + } + + /* Create device enumerator */ + ComPtr createDevEnum; + if (SUCCEEDED(hr)) + hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, + (void **)&createDevEnum); + + /* Enumerate filters */ + ComPtr enumMoniker; + if (SUCCEEDED(hr)) { + /* returns S_FALSE if no devices are installed */ + hr = createDevEnum->CreateClassEnumerator(deviceClass, + &enumMoniker, 0); + if (!enumMoniker) + hr = E_FAIL; + } + + /* Cycle through the enumeration */ + if (SUCCEEDED(hr)) { + ULONG fetched = 0; + ComPtr moniker; + + enumMoniker->Reset(); + + while (enumMoniker->Next(1, &moniker, &fetched) == S_OK) { + bool samePath = false; + + /* Get device path */ + wchar_t audDevPath[512]; + hr = ReadProperty(moniker, L"DevicePath", audDevPath, + _ARRAYSIZE(audDevPath)); + if (SUCCEEDED(hr)) { + /* Skip if it is the video device */ + if (wcscmp(audDevPath, vidDevPath) == 0) + continue; + + samePath = IsSameInstPath(audDevPath, + vidDevInstPath); + } else { + samePath = IsMonikerSameParentInstPath( + moniker, vidDevInstPath); + } + + /* Get audio capture filter */ + if (samePath) { + /* Match video and audio filter names */ + bool isSameFilterName = false; + if (matchFilterName) { + wchar_t audName[512]; + hr = ReadProperty(moniker, + L"FriendlyName", + audName, + _ARRAYSIZE(audName)); + if (SUCCEEDED(hr)) { + isSameFilterName = + MatchFriendlyNames( + vidName, + audName); + } + } + + if (!matchFilterName || isSameFilterName) { + hr = moniker->BindToObject( + 0, 0, IID_IBaseFilter, + (void **)audioCaptureFilter); + if (SUCCEEDED(hr)) + return true; + } + } + } + } + + return false; +} + +bool GetDeviceAudioFilter(const wchar_t *vidDevPath, + IBaseFilter **audioCaptureFilter) +{ + /* Search in "Audio capture sources" and match filter name */ + bool success = GetDeviceAudioFilterInternal( + CLSID_AudioInputDeviceCategory, vidDevPath, audioCaptureFilter, + true); + + /* Search in "WDM Streaming Capture Devices" and match filter name */ + if (!success) + success = GetDeviceAudioFilterInternal(KSCATEGORY_CAPTURE, + vidDevPath, + audioCaptureFilter, + true); + + /* Search in "Audio capture sources" */ + if (!success) + success = GetDeviceAudioFilterInternal( + CLSID_AudioInputDeviceCategory, vidDevPath, + audioCaptureFilter); + + /* Search in "WDM Streaming Capture Devices" */ + if (!success) + success = GetDeviceAudioFilterInternal( + KSCATEGORY_CAPTURE, vidDevPath, audioCaptureFilter); + + return success; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-base.hpp b/obs-studio/deps/libdshowcapture/src/source/dshow-base.hpp new file mode 100644 index 0000000..696922b --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-base.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#define WIN32_MEAN_AND_LEAN +#define __STREAMS__ +#include +#include +#include +#include +#include +#include +#include + +#include "ComPtr.hpp" +#include "CoTaskMemPtr.hpp" + +#include +using namespace std; + +#define DSHOW_UNUSED(param) (void)param; + +namespace DShow { + +bool CreateFilterGraph(IGraphBuilder **graph, ICaptureGraphBuilder2 **builder, + IMediaControl **control); + +void LogFilters(IGraphBuilder *graph); + +bool GetDeviceFilter(const IID &type, const wchar_t *name, const wchar_t *path, + IBaseFilter **filter); + +bool GetFilterPin(IBaseFilter *filter, const GUID &type, const GUID &category, + PIN_DIRECTION dir, IPin **pin); + +bool GetPinByName(IBaseFilter *filter, PIN_DIRECTION dir, const wchar_t *name, + IPin **pin); + +bool GetPinByMedium(IBaseFilter *filter, REGPINMEDIUM &medium, IPin **pin); +bool GetFilterByMedium(const CLSID &id, REGPINMEDIUM &medium, + IBaseFilter **filter); + +bool GetPinMedium(IPin *pin, REGPINMEDIUM &medium); + +bool DirectConnectFilters(IFilterGraph *graph, IBaseFilter *filterOut, + IBaseFilter *filterIn); + +/** + * This maps a created demuxer pin to a packet ID for the mux stream. Note + * that this needs to be called after the device filters are connected to the + * demux filter. + */ +HRESULT MapPinToPacketID(IPin *pin, ULONG packetID); + +wstring ConvertHRToEnglish(HRESULT hr); + +/** + * Get audio filter for the same device as the given video device path + */ +bool GetDeviceAudioFilter(const wchar_t *videoDevicePath, + IBaseFilter **audioCaptureFilter); + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-demux.cpp b/obs-studio/deps/libdshowcapture/src/source/dshow-demux.cpp new file mode 100644 index 0000000..5c19eb6 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-demux.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "dshow-demux.hpp" +#include "dshow-formats.hpp" +#include "log.hpp" + +namespace DShow { + +static inline DWORD VideoFormatToFourCC(VideoFormat format) +{ + if (format == VideoFormat::H264) + return MAKEFOURCC('H', '2', '6', '4'); + + return 0; +} + +static inline const GUID &VideoFormatToSubType(VideoFormat format) +{ + if (format == VideoFormat::H264) + return MEDIASUBTYPE_H264; + + return GUID_NULL; +} + +bool CreateDemuxVideoPin(IBaseFilter *demuxFilter, MediaType &mt, long width, + long height, long long frameTime, VideoFormat format) +{ + ComQIPtr demuxer(demuxFilter); + if (!demuxer) { + Warning(L"CreateDemuxVideoPin: Failed to get " + L"IMpeg2Demultiplexer from filter"); + return false; + } + + ComPtr pin; + HRESULT hr; + + VIDEOINFOHEADER *vih = mt.AllocFormat(); + vih->bmiHeader.biSize = sizeof(vih->bmiHeader); + vih->bmiHeader.biWidth = width; + vih->bmiHeader.biHeight = height; + vih->bmiHeader.biCompression = VideoFormatToFourCC(format); + vih->rcSource.right = width; + vih->rcSource.bottom = height; + vih->AvgTimePerFrame = frameTime; + + if (!vih->bmiHeader.biCompression) { + Warning(L"CreateDemuxVideoPin: Invalid video format"); + return false; + } + + mt->majortype = MEDIATYPE_Video; + mt->subtype = VideoFormatToSubType(format); + mt->formattype = FORMAT_VideoInfo; + mt->bTemporalCompression = true; + + wchar_t *name = (wchar_t *)CoTaskMemAlloc(sizeof(DEMUX_VIDEO_PIN)); + memcpy(name, DEMUX_VIDEO_PIN, sizeof(DEMUX_VIDEO_PIN)); + + hr = demuxer->CreateOutputPin(mt, name, &pin); + if (FAILED(hr)) { + WarningHR(L"CreateDemuxVideoPin: Failed to create video pin " + L"on demuxer", + hr); + return false; + } + + return true; +} + +static inline WORD AudioFormatToFormatTag(AudioFormat format) +{ + if (format == AudioFormat::AAC) + return WAVE_FORMAT_RAW_AAC1; + else if (format == AudioFormat::AC3) + return WAVE_FORMAT_DVM; + else if (format == AudioFormat::MPGA) + return WAVE_FORMAT_MPEG; + + return 0; +} + +static inline const GUID &AudioFormatToSubType(AudioFormat format) +{ + if (format == AudioFormat::AAC) + return MEDIASUBTYPE_RAW_AAC1; + else if (format == AudioFormat::AC3) + return MEDIASUBTYPE_DVM; + else if (format == AudioFormat::MPGA) + return MEDIASUBTYPE_MPEG1AudioPayload; + + return GUID_NULL; +} + +bool CreateDemuxAudioPin(IBaseFilter *demuxFilter, MediaType &mt, + DWORD samplesPerSec, WORD bitsPerSample, WORD channels, + AudioFormat format) +{ + ComQIPtr demuxer(demuxFilter); + if (!demuxer) { + Warning(L"CreateDemuxAudioPin: Failed to get " + L"IMpeg2Demultiplexer from filter"); + return false; + } + + ComPtr pin; + HRESULT hr; + + WAVEFORMATEX *wfex = mt.AllocFormat(); + wfex->wFormatTag = AudioFormatToFormatTag(format); + wfex->nChannels = channels; + wfex->nSamplesPerSec = samplesPerSec; + wfex->wBitsPerSample = bitsPerSample; + + if (!wfex->wFormatTag) { + Warning(L"CreateDemuxAudioPin: Invalid audio format"); + return false; + } + + mt->majortype = MEDIATYPE_Audio; + mt->subtype = AudioFormatToSubType(format); + mt->formattype = FORMAT_WaveFormatEx; + mt->bTemporalCompression = true; + + wchar_t *name = (wchar_t *)CoTaskMemAlloc(sizeof(DEMUX_AUDIO_PIN)); + memcpy(name, DEMUX_AUDIO_PIN, sizeof(DEMUX_AUDIO_PIN)); + + hr = demuxer->CreateOutputPin(mt, name, &pin); + if (FAILED(hr)) { + WarningHR(L"CreateDemuxAudioPin: Failed to create audio pin " + L"on demuxer", + hr); + return false; + } + + return true; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-demux.hpp b/obs-studio/deps/libdshowcapture/src/source/dshow-demux.hpp new file mode 100644 index 0000000..741bdec --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-demux.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "../dshowcapture.hpp" +#include "dshow-base.hpp" +#include "dshow-media-type.hpp" + +namespace DShow { + +#define DEMUX_VIDEO_PIN L"Demuxer Video Pin" +#define DEMUX_AUDIO_PIN L"Demuxer Audio Pin" + +bool CreateDemuxVideoPin(IBaseFilter *demuxFilter, MediaType &mt, long width, + long height, long long frameTime, VideoFormat format); + +bool CreateDemuxAudioPin(IBaseFilter *demuxFilter, MediaType &mt, + DWORD samplesPerSec, WORD bitsPerSample, WORD channels, + AudioFormat format); + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-device-defs.hpp b/obs-studio/deps/libdshowcapture/src/source/dshow-device-defs.hpp new file mode 100644 index 0000000..04ac483 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-device-defs.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +namespace DShow { + +#define COMMON_ENCODED_CX 720 +#define COMMON_ENCODED_CY 480 +#define COMMON_ENCODED_INTERVAL (10010000000LL / 60000LL) +#define COMMON_ENCODED_VFORMAT VideoFormat::H264 +#define COMMON_ENCODED_SAMPLERATE 48000 + +static const EncodedDevice HD_PVR1 = {COMMON_ENCODED_VFORMAT, + 0x1011UL, + COMMON_ENCODED_CX, + COMMON_ENCODED_CY, + COMMON_ENCODED_INTERVAL, + AudioFormat::AC3, + 0x1100UL, + COMMON_ENCODED_SAMPLERATE}; + +static const EncodedDevice HD_PVR2 = {COMMON_ENCODED_VFORMAT, + 0x1011UL, + COMMON_ENCODED_CX, + COMMON_ENCODED_CY, + COMMON_ENCODED_INTERVAL, + AudioFormat::AAC, + 0x1100UL, + COMMON_ENCODED_SAMPLERATE}; + +static const EncodedDevice Roxio = { + COMMON_ENCODED_VFORMAT, + 0x1011UL, + COMMON_ENCODED_CX, + COMMON_ENCODED_CY, + COMMON_ENCODED_INTERVAL, + AudioFormat::AAC, + 0x010FUL, + COMMON_ENCODED_SAMPLERATE, +}; + +static const EncodedDevice HD_PVR_Rocket = {COMMON_ENCODED_VFORMAT, + 0x07D1UL, + COMMON_ENCODED_CX, + COMMON_ENCODED_CY, + COMMON_ENCODED_INTERVAL, + AudioFormat::AAC, + 0x07D2UL, + COMMON_ENCODED_SAMPLERATE}; + +static const EncodedDevice AV_LGP = {COMMON_ENCODED_VFORMAT, + 68, + COMMON_ENCODED_CX, + COMMON_ENCODED_CY, + COMMON_ENCODED_INTERVAL, + AudioFormat::AAC, + 69, + COMMON_ENCODED_SAMPLERATE}; + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-encoded-device.cpp b/obs-studio/deps/libdshowcapture/src/source/dshow-encoded-device.cpp new file mode 100644 index 0000000..0d4d352 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-encoded-device.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "dshow-base.hpp" +#include "dshow-media-type.hpp" +#include "dshow-formats.hpp" +#include "dshow-demux.hpp" +#include "capture-filter.hpp" +#include "device.hpp" +#include "log.hpp" + +namespace DShow { + +static inline bool CreateFilters(IBaseFilter *filter, IBaseFilter **crossbar, + IBaseFilter **encoder, IBaseFilter **demuxer) +{ + ComPtr inputPin; + ComPtr outputPin; + REGPINMEDIUM inMedium; + REGPINMEDIUM outMedium; + bool hasOutMedium; + HRESULT hr; + + if (!GetPinByName(filter, PINDIR_INPUT, nullptr, &inputPin)) { + Warning(L"Encoded Device: Failed to get input pin"); + return false; + } + + if (!GetPinByName(filter, PINDIR_OUTPUT, nullptr, &outputPin)) { + Warning(L"Encoded Device: Failed to get output pin"); + return false; + } + + if (!GetPinMedium(inputPin, inMedium)) { + Warning(L"Encoded Device: Failed to get input pin medium"); + return false; + } + + hasOutMedium = GetPinMedium(outputPin, outMedium); + + if (!GetFilterByMedium(AM_KSCATEGORY_CROSSBAR, inMedium, crossbar)) { + Warning(L"Encoded Device: Failed to get crossbar filter"); + return false; + } + + /* perfectly okay if there's no encoder filter, some don't have them */ + if (hasOutMedium) + GetFilterByMedium(KSCATEGORY_ENCODER, outMedium, encoder); + + hr = CoCreateInstance(CLSID_MPEG2Demultiplexer, nullptr, + CLSCTX_INPROC_SERVER, IID_IBaseFilter, + (void **)demuxer); + if (FAILED(hr)) { + WarningHR(L"Encoded Device: Failed to create demuxer", hr); + return false; + } + + return true; +} + +static inline bool ConnectEncodedFilters(IGraphBuilder *graph, + IBaseFilter *filter, + IBaseFilter *crossbar, + IBaseFilter *encoder, + IBaseFilter *demuxer) +{ + if (!DirectConnectFilters(graph, crossbar, filter)) { + Warning(L"Encoded Device: Failed to connect crossbar to " + L"device"); + return false; + } + + if (!!encoder) { + if (!DirectConnectFilters(graph, filter, encoder)) { + Warning(L"Encoded Device: Failed to connect device to " + L"encoder"); + return false; + } + + if (!DirectConnectFilters(graph, encoder, demuxer)) { + Warning(L"Encoded Device: Failed to connect encoder to " + L"demuxer"); + return false; + } + } else { + if (!DirectConnectFilters(graph, filter, demuxer)) { + Warning(L"Encoded Device: Failed to connect device to " + L"demuxer"); + return false; + } + } + + return true; +} + +static inline bool MapPacketIDs(IBaseFilter *demuxer, ULONG video, ULONG audio) +{ + ComPtr videoPin, audioPin; + HRESULT hr; + + if (!GetPinByName(demuxer, PINDIR_OUTPUT, DEMUX_VIDEO_PIN, &videoPin)) { + Warning(L"Encoded Device: Could not get video pin from " + L"demuxer"); + return false; + } + + if (!GetPinByName(demuxer, PINDIR_OUTPUT, DEMUX_AUDIO_PIN, &audioPin)) { + Warning(L"Encoded Device: Could not get audio pin from " + L"demuxer"); + return false; + } + + hr = MapPinToPacketID(videoPin, video); + if (FAILED(hr)) { + WarningHR(L"Encoded Device: Failed to map demuxer video pin " + L"packet ID", + hr); + return false; + } + + hr = MapPinToPacketID(audioPin, audio); + if (FAILED(hr)) { + WarningHR(L"Encoded Device: Failed to map demuxer audio pin " + L"packet ID", + hr); + return false; + } + + return true; +} + +/* + * rocket-specific workaround code. I have no idea what any of these numbers + * are except the GUID which was obvious. All I know is calling + * IKsPropertySet::Set turns on/off some sort of 'mode' on the device. + * I discovered this merely by chance while monitoring API usage in other + * programs because I could not figure out how the hell to get this thing + * to turn on. + */ +static const GUID RocketEncoderGUID = {0x99100000, + 0xa330, + 0x11e1, + {0xa3, 0x80, 0x99, 0x10, 0x68, 0x64, + 0x00, 0x00}}; + +struct RocketPropStruct { + DWORD dwSize; + DWORD unknown1; + DWORD unknown2; + DWORD unknown3; + DWORD code; + DWORD unknown4; + BOOL enabled; +}; + +struct RocketInstStruct { + DWORD code; + DWORD unknown1; +}; + +bool SetRocketEnabled(IBaseFilter *encoder, bool enable) +{ + static const ULONG rocketEnableId = 0x9910E001; + static const DWORD rocketEnableCode = 0x38384001; + RocketInstStruct rocketInstance = {}; + RocketPropStruct rocketProperty = {}; + + ComQIPtr propertySet(encoder); + if (!propertySet) + return false; + + rocketProperty.dwSize = sizeof(rocketProperty); + rocketProperty.code = rocketEnableCode; + rocketProperty.enabled = enable; + rocketInstance.code = rocketEnableCode; + + HRESULT hr = propertySet->Set(RocketEncoderGUID, rocketEnableId, + &rocketInstance, sizeof(rocketInstance), + &rocketProperty, sizeof(rocketProperty)); + + return SUCCEEDED(hr); +} + +bool HDevice::SetupEncodedVideoCapture(IBaseFilter *filter, VideoConfig &config, + const EncodedDevice &info) +{ + ComPtr crossbar; + ComPtr encoder; + ComPtr demuxer; + MediaType mtVideo; + MediaType mtAudio; + + if (!CreateFilters(filter, &crossbar, &encoder, &demuxer)) + return false; + + if (!CreateDemuxVideoPin(demuxer, mtVideo, info.width, info.height, + info.frameInterval, info.videoFormat)) + return false; + + if (!CreateDemuxAudioPin(demuxer, mtAudio, info.samplesPerSec, 16, 2, + info.audioFormat)) + return false; + + config.cx = info.width; + config.cy_abs = labs(info.height); + config.cy_flip = info.height < 0; + config.frameInterval = info.frameInterval; + config.format = info.videoFormat; + config.internalFormat = info.videoFormat; + + PinCaptureInfo pci; + pci.callback = [this](IMediaSample *s) { Receive(true, s); }; + pci.expectedMajorType = mtVideo->majortype; + pci.expectedSubType = mtVideo->subtype; + + videoCapture = new CaptureFilter(pci); + videoFilter = demuxer; + + if (!!encoder && config.name.find(L"IT9910") != std::string::npos) { + rocketEncoder = encoder; + + if (!SetRocketEnabled(rocketEncoder, true)) + return false; + } + + graph->AddFilter(crossbar, L"Crossbar"); + graph->AddFilter(filter, L"Device"); + graph->AddFilter(demuxer, L"Demuxer"); + graph->AddFilter(videoCapture, L"Capture Filter"); + + if (!!encoder) + graph->AddFilter(encoder, L"Encoder"); + + bool success = ConnectEncodedFilters(graph, filter, crossbar, encoder, + demuxer); + if (success) + success = MapPacketIDs(demuxer, info.videoPacketID, + info.audioPacketID); + + encodedDevice = success; + return success; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-enum.cpp b/obs-studio/deps/libdshowcapture/src/source/dshow-enum.cpp new file mode 100644 index 0000000..6a7548b --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-enum.cpp @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include +#include +#include "dshow-enum.hpp" +#include "dshow-formats.hpp" +#include "log.hpp" + +#undef DEFINE_GUID +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID DECLSPEC_SELECTANY name = { \ + l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}} + +#include "external/IVideoCaptureFilter.h" + +namespace DShow { +using namespace std; + +typedef bool (*EnumCapsCallback)(void *param, const AM_MEDIA_TYPE &mt, + const BYTE *data); + +static void EnumElgatoCaps(IPin *pin, EnumCapsCallback callback, void *param) +{ + ComPtr mediaTypes; + + if (SUCCEEDED(pin->EnumMediaTypes(&mediaTypes))) { + MediaTypePtr mt; + ULONG count = 0; + + while (mediaTypes->Next(1, &mt, &count) == S_OK) { + if (!callback(param, *mt, nullptr)) + break; + } + } +} + +static bool EnumPinCaps(IPin *pin, EnumCapsCallback callback, void *param) +{ + HRESULT hr; + ComQIPtr config(pin); + int count, size; + + if (config == NULL) + return false; + + hr = config->GetNumberOfCapabilities(&count, &size); + if (SUCCEEDED(hr)) { + vector caps; + caps.resize(size); + + for (int i = 0; i < count; i++) { + MediaTypePtr mt; + hr = config->GetStreamCaps(i, &mt, caps.data()); + if (SUCCEEDED(hr)) + if (!callback(param, *mt, caps.data())) + break; + } + } else if (hr == E_NOTIMPL) { + EnumElgatoCaps(pin, callback, param); + } else { + return false; + } + + return true; +} + +/* Note: DEVICE_VideoInfo is not to be confused with Device::VideoInfo */ +static bool Get_FORMAT_VideoInfo_Data(VideoInfo &info, const AM_MEDIA_TYPE &mt, + const BYTE *data) +{ + const VIDEO_STREAM_CONFIG_CAPS *vscc; + const VIDEOINFOHEADER *viHeader; + const BITMAPINFOHEADER *bmiHeader; + VideoFormat format; + + vscc = reinterpret_cast(data); + viHeader = reinterpret_cast(mt.pbFormat); + bmiHeader = &viHeader->bmiHeader; + + if (!GetMediaTypeVFormat(mt, format)) + return false; + + info.format = format; + + if (vscc) { + info.minInterval = vscc->MinFrameInterval; + info.maxInterval = vscc->MaxFrameInterval; + info.minCX = vscc->MinOutputSize.cx; + info.minCY = vscc->MinOutputSize.cy; + info.maxCX = vscc->MaxOutputSize.cx; + info.maxCY = vscc->MaxOutputSize.cy; + + if (!info.minCX || !info.minCY || !info.maxCX || !info.maxCY) { + info.minCX = info.maxCX = bmiHeader->biWidth; + info.minCY = info.maxCY = bmiHeader->biHeight; + } + + info.granularityCX = max(vscc->OutputGranularityX, 1); + info.granularityCY = max(vscc->OutputGranularityY, 1); + } else { + info.minInterval = info.maxInterval = 10010000000LL / 60000LL; + info.minCX = info.maxCX = bmiHeader->biWidth; + info.minCY = info.maxCY = bmiHeader->biHeight; + info.granularityCX = 1; + info.granularityCY = 1; + } + + return true; +} + +static bool Get_FORMAT_WaveFormatEx_Data(AudioInfo &info, + const AM_MEDIA_TYPE &mt, + const BYTE *data) +{ + const AUDIO_STREAM_CONFIG_CAPS *ascc; + const WAVEFORMATEX *wfex; + + ascc = reinterpret_cast(data); + wfex = reinterpret_cast(mt.pbFormat); + if (!wfex || !ascc) { + return false; + } + + switch (wfex->wBitsPerSample) { + case 16: + info.format = AudioFormat::Wave16bit; + break; + case 32: + info.format = AudioFormat::WaveFloat; + break; + } + + info.minChannels = ascc->MinimumChannels; + info.maxChannels = ascc->MaximumChannels; + info.channelsGranularity = ascc->ChannelsGranularity; + info.minSampleRate = ascc->MinimumSampleFrequency; + info.maxSampleRate = ascc->MaximumSampleFrequency; + info.sampleRateGranularity = ascc->SampleFrequencyGranularity; + return true; +} + +struct ClosestVideoData { + VideoConfig &config; + MediaType &mt; + long long bestVal; + bool found; + + ClosestVideoData &operator=(ClosestVideoData const &) = delete; + ClosestVideoData &operator=(ClosestVideoData &&) = delete; + + inline ClosestVideoData(VideoConfig &config, MediaType &mt) + : config(config), mt(mt), bestVal(0), found(false) + { + } +}; + +static inline void ClampToGranularity(LONG &val, int minVal, int granularity) +{ + val -= ((val - minVal) % granularity); +} + +static inline int GetFormatRating(VideoFormat format) +{ + if (format >= VideoFormat::I420 && format < VideoFormat::YVYU) + return 0; + else if (format >= VideoFormat::YVYU && format < VideoFormat::MJPEG) + return 5; + else if (format == VideoFormat::MJPEG) + return 10; + + return 15; +} + +static bool ClosestVideoMTCallback(ClosestVideoData &data, + const AM_MEDIA_TYPE &mt, const BYTE *capData) +{ + VideoInfo info; + + if (mt.formattype == FORMAT_VideoInfo) { + if (!Get_FORMAT_VideoInfo_Data(info, mt, capData)) + return true; + } else { + return true; + } + + MediaType copiedMT = mt; + VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)copiedMT->pbFormat; + BITMAPINFOHEADER *bmih = GetBitmapInfoHeader(copiedMT); + + if (data.config.internalFormat != VideoFormat::Any && + data.config.internalFormat != info.format) + return true; + + int xVal = 0; + int yVal = 0; + int formatVal = 0; + long long frameVal = 0; + + if (data.config.cx < info.minCX) + xVal = info.minCX - data.config.cx; + else if (data.config.cx > info.maxCX) + xVal = data.config.cx - info.maxCX; + + const int absMinCY = abs(info.minCY); + const int absMaxCY = abs(info.maxCY); + if (data.config.cy_abs < absMinCY) + yVal = absMinCY - data.config.cy_abs; + else if (data.config.cy_abs > absMaxCY) + yVal = data.config.cy_abs - absMaxCY; + + const long long frameInterval = data.config.frameInterval; + if (frameInterval < info.minInterval) + frameVal = info.minInterval - frameInterval; + else if (frameInterval > info.maxInterval) + frameVal = frameInterval - info.maxInterval; + + formatVal = GetFormatRating(info.format); + + long long totalVal = frameVal + yVal + xVal + formatVal; + + if (!data.found || data.bestVal > totalVal) { + if (xVal == 0) { + bmih->biWidth = data.config.cx; + ClampToGranularity(bmih->biWidth, info.minCX, + info.granularityCX); + } + + if (yVal == 0) { + LONG cy_abs_clamp = data.config.cy_abs; + ClampToGranularity(cy_abs_clamp, info.minCY, + info.granularityCY); + bmih->biHeight = data.config.cy_flip ? -cy_abs_clamp + : cy_abs_clamp; + } + + if (frameVal == 0) { + // Close enough. Fixes GV-USB2 29.97 FPS setting. + if (abs(vih->AvgTimePerFrame - frameInterval) > 1) + vih->AvgTimePerFrame = frameInterval; + } + + data.found = true; + data.bestVal = totalVal; + data.mt = copiedMT; + + if (totalVal == 0) + return false; + } + + return true; +} + +bool GetClosestVideoMediaType(IBaseFilter *filter, VideoConfig &config, + MediaType &mt) +{ + ComPtr pin; + ClosestVideoData data(config, mt); + bool success; + + success = GetFilterPin(filter, MEDIATYPE_Video, PIN_CATEGORY_CAPTURE, + PINDIR_OUTPUT, &pin); + if (!success || pin == NULL) { + Error(L"GetClosestVideoMediaType: Could not get pin"); + return false; + } + + success = EnumPinCaps(pin, EnumCapsCallback(ClosestVideoMTCallback), + &data); + if (!success) { + Error(L"GetClosestVideoMediaType: Could not enumerate caps"); + return false; + } + + return data.found; +} + +struct ClosestAudioData { + AudioConfig &config; + MediaType &mt; + int bestVal; + bool found; + + ClosestAudioData &operator=(ClosestAudioData const &) = delete; + ClosestAudioData &operator=(ClosestAudioData &&) = delete; + + inline ClosestAudioData(AudioConfig &config, MediaType &mt) + : config(config), mt(mt), bestVal(0), found(false) + { + } +}; + +static bool ClosestAudioMTCallback(ClosestAudioData &data, + const AM_MEDIA_TYPE &mt, const BYTE *capData) +{ + AudioInfo info = {}; + + if (mt.formattype == FORMAT_WaveFormatEx) { + if (!Get_FORMAT_WaveFormatEx_Data(info, mt, capData)) + return false; + } else { + return true; + } + + MediaType copiedMT = mt; + WAVEFORMATEX *wfex = (WAVEFORMATEX *)copiedMT->pbFormat; + + if (data.config.format != AudioFormat::Any && + data.config.format != info.format) + return true; + + int sampleRateVal = 0; + int channelsVal = 0; + + if (data.config.sampleRate < info.minSampleRate) + sampleRateVal = info.minSampleRate - data.config.sampleRate; + else if (data.config.sampleRate > info.maxSampleRate) + sampleRateVal = data.config.sampleRate - info.maxSampleRate; + else if (data.config.sampleRate == info.minSampleRate) + sampleRateVal = data.config.sampleRate; + else if (data.config.sampleRate == info.maxSampleRate) + sampleRateVal = data.config.sampleRate; + + if (data.config.channels < info.minChannels) + channelsVal = info.minChannels - data.config.channels; + else if (data.config.channels == info.minChannels) + channelsVal = info.minChannels; + else if (info.maxChannels > data.config.channels) + channelsVal = data.config.channels - info.maxChannels; + else if (data.config.channels == info.maxChannels) + channelsVal = data.config.channels; + + int totalVal = sampleRateVal + channelsVal; + + if (!data.found || data.bestVal > totalVal) { + if (channelsVal == 0) { + LONG channels = data.config.channels; + ClampToGranularity(channels, info.minChannels, + info.channelsGranularity); + wfex->nChannels = (WORD)channels; + + wfex->nBlockAlign = + wfex->wBitsPerSample * wfex->nChannels / 8; + } + + if (sampleRateVal == 0) { + wfex->nSamplesPerSec = data.config.sampleRate; + ClampToGranularity((LONG &)wfex->nSamplesPerSec, + info.minSampleRate, + info.sampleRateGranularity); + } + + wfex->nAvgBytesPerSec = + wfex->nSamplesPerSec * wfex->nBlockAlign; + + data.mt = copiedMT; + data.found = true; + data.bestVal = totalVal; + + if (totalVal == 0) + return false; + } + + return true; +} + +bool GetClosestAudioMediaType(IBaseFilter *filter, AudioConfig &config, + MediaType &mt) +{ + ComPtr pin; + ClosestAudioData data(config, mt); + bool success; + + success = GetFilterPin(filter, MEDIATYPE_Audio, PIN_CATEGORY_CAPTURE, + PINDIR_OUTPUT, &pin); + if (!success || pin == NULL) { + Error(L"GetClosestAudioMediaType: Could not get pin"); + return false; + } + + success = EnumPinCaps(pin, EnumCapsCallback(ClosestAudioMTCallback), + &data); + if (!success) { + Error(L"GetClosestAudioMediaType: Could not enumerate caps"); + return false; + } + + return data.found; +} + +static bool EnumVideoCap(vector &caps, const AM_MEDIA_TYPE &mt, + const BYTE *data) +{ + VideoInfo info; + + if (mt.formattype == FORMAT_VideoInfo) + if (Get_FORMAT_VideoInfo_Data(info, mt, data)) + caps.push_back(info); + + return true; +} + +bool EnumVideoCaps(IPin *pin, vector &caps) +{ + return EnumPinCaps(pin, EnumCapsCallback(EnumVideoCap), &caps); +} + +static bool EnumAudioCap(vector &caps, const AM_MEDIA_TYPE &mt, + const BYTE *data) +{ + AudioInfo info; + + if (mt.formattype == FORMAT_WaveFormatEx) { + if (Get_FORMAT_WaveFormatEx_Data(info, mt, data)) + caps.push_back(info); + } + + return true; +} + +bool EnumAudioCaps(IPin *pin, vector &caps) +{ + return EnumPinCaps(pin, EnumCapsCallback(EnumAudioCap), &caps); +} + +static bool decklinkVideoPresent = false; + +static bool EnumDevice(const GUID &type, IMoniker *deviceInfo, + EnumDeviceCallback callback, void *param) +{ + ComPtr propertyData; + ComPtr filter; + HRESULT hr; + + hr = deviceInfo->BindToStorage(0, 0, IID_IPropertyBag, + (void **)&propertyData); + if (FAILED(hr)) + return true; + + VARIANT deviceName, devicePath; + deviceName.vt = VT_BSTR; + devicePath.vt = VT_BSTR; + devicePath.bstrVal = NULL; + + hr = propertyData->Read(L"FriendlyName", &deviceName, NULL); + if (FAILED(hr)) + return true; + + /* workaround to a crash in decklink drivers; if no decklink device + * is plugged in to the system, it will still try to enumerate the + * decklink audio device, but will crash when trying to bind it to + * a filter due to a bug in the drivers */ + if (deviceName.bstrVal && type == CLSID_AudioInputDeviceCategory && + wcsstr(deviceName.bstrVal, L"Decklink") != nullptr && + !decklinkVideoPresent) { + return true; + } + + propertyData->Read(L"DevicePath", &devicePath, NULL); + + hr = deviceInfo->BindToObject(NULL, 0, IID_IBaseFilter, + (void **)&filter); + if (SUCCEEDED(hr)) { + if (!callback(param, filter, deviceName.bstrVal, + devicePath.bstrVal)) + return false; + } + + return true; +} + +static bool EnumExceptionVideoDevices(EnumDeviceCallback callback, void *param) +{ + ComPtr filter; + HRESULT hr; + + hr = CoCreateInstance(CLSID_ElgatoVideoCaptureFilter, nullptr, + CLSCTX_INPROC_SERVER, IID_IBaseFilter, + (void **)&filter); + if (SUCCEEDED(hr)) { + if (!callback(param, filter, L"Elgato Game Capture HD", + L"__elgato")) + return false; + } + + return true; +} + +static recursive_mutex enumMutex; + +static bool CheckForDLCallback(void *unused, IBaseFilter *filter, + const wchar_t *deviceName, + const wchar_t *devicePath) +{ + if (wcsstr(deviceName, L"Decklink") != nullptr) { + decklinkVideoPresent = true; + return false; + } + + DSHOW_UNUSED(unused); + DSHOW_UNUSED(filter); + DSHOW_UNUSED(devicePath); + return true; +} + +static void CheckForDecklinkVideo() +{ + decklinkVideoPresent = false; + EnumDevices(CLSID_VideoInputDeviceCategory, CheckForDLCallback, + nullptr); +} + +bool EnumDevices(const GUID &type, EnumDeviceCallback callback, void *param) +{ + lock_guard lock(enumMutex); + ComPtr deviceEnum; + ComPtr enumMoniker; + ComPtr deviceInfo; + HRESULT hr; + DWORD count = 0; + + if (type == CLSID_AudioInputDeviceCategory) { + CheckForDecklinkVideo(); + } + + hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, + (void **)&deviceEnum); + if (FAILED(hr)) { + WarningHR(L"EnumAudioDevices: Could not create " + L"ICreateDeviceEnum", + hr); + return false; + } + + hr = deviceEnum->CreateClassEnumerator(type, &enumMoniker, 0); + if (FAILED(hr)) { + WarningHR(L"EnumAudioDevices: CreateClassEnumerator failed", + hr); + return false; + } + + if (hr == S_OK) { + while (enumMoniker->Next(1, &deviceInfo, &count) == S_OK) { + if (!EnumDevice(type, deviceInfo, callback, param)) + return true; + } + } + + if (type == CLSID_VideoInputDeviceCategory) + if (!EnumExceptionVideoDevices(callback, param)) + return true; + + return true; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-enum.hpp b/obs-studio/deps/libdshowcapture/src/source/dshow-enum.hpp new file mode 100644 index 0000000..e8b02b4 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-enum.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "../dshowcapture.hpp" +#include "dshow-base.hpp" +#include "dshow-media-type.hpp" + +#include + +using namespace std; + +namespace DShow { + +bool GetClosestVideoMediaType(IBaseFilter *filter, VideoConfig &config, + MediaType &mt); +bool GetClosestAudioMediaType(IBaseFilter *filter, AudioConfig &config, + MediaType &mt); + +bool EnumVideoCaps(IPin *pin, vector &caps); +bool EnumAudioCaps(IPin *pin, vector &caps); + +typedef bool (*EnumDeviceCallback)(void *param, IBaseFilter *filter, + const wchar_t *deviceName, + const wchar_t *devicePath); + +bool EnumDevices(const GUID &type, EnumDeviceCallback callback, void *param); + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-formats.cpp b/obs-studio/deps/libdshowcapture/src/source/dshow-formats.cpp new file mode 100644 index 0000000..4baf381 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-formats.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "dshow-formats.hpp" +#include "dshow-media-type.hpp" + +#ifndef __MINGW32__ + +const GUID MEDIASUBTYPE_RAW_AAC1 = {0x000000FF, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, + 0x71}}; + +const GUID MEDIASUBTYPE_I420 = {0x30323449, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, + 0x71}}; + +const GUID MEDIASUBTYPE_DVM = {0x00002000, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, + 0x71}}; + +#endif + +const GUID MEDIASUBTYPE_Y800 = {0x30303859, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, + 0x71}}; + +#ifdef ENABLE_HEVC +const GUID MEDIASUBTYPE_HEVC = {0x43564548, + 0x0000, + 0x0010, + {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, + 0x71}}; +#endif + +namespace DShow { + +DWORD VFormatToFourCC(VideoFormat format) +{ + switch (format) { + /* raw formats */ + case VideoFormat::ARGB: + return MAKEFOURCC('A', 'R', 'G', 'B'); + case VideoFormat::XRGB: + return MAKEFOURCC('R', 'G', 'B', '4'); + + /* planar YUV formats */ + case VideoFormat::I420: + return MAKEFOURCC('I', '4', '2', '0'); + case VideoFormat::NV12: + return MAKEFOURCC('N', 'V', '1', '2'); + case VideoFormat::YV12: + return MAKEFOURCC('Y', 'V', '1', '2'); + case VideoFormat::Y800: + return MAKEFOURCC('Y', '8', '0', '0'); + case VideoFormat::P010: + return MAKEFOURCC('P', '0', '1', '0'); + + /* packed YUV formats */ + case VideoFormat::YVYU: + return MAKEFOURCC('Y', 'V', 'Y', 'U'); + case VideoFormat::YUY2: + return MAKEFOURCC('Y', 'U', 'Y', '2'); + case VideoFormat::UYVY: + return MAKEFOURCC('U', 'Y', 'V', 'Y'); + case VideoFormat::HDYC: + return MAKEFOURCC('H', 'D', 'Y', 'C'); + + /* encoded formats */ + case VideoFormat::MJPEG: + return MAKEFOURCC('M', 'J', 'P', 'G'); + case VideoFormat::H264: + return MAKEFOURCC('H', '2', '6', '4'); +#ifdef ENABLE_HEVC + case VideoFormat::HEVC: + return MAKEFOURCC('H', 'E', 'V', 'C'); +#endif + + default: + return 0; + } +} + +GUID VFormatToSubType(VideoFormat format) +{ + switch (format) { + /* raw formats */ + case VideoFormat::ARGB: + return MEDIASUBTYPE_ARGB32; + case VideoFormat::XRGB: + return MEDIASUBTYPE_RGB32; + + /* planar YUV formats */ + case VideoFormat::I420: + return MEDIASUBTYPE_I420; + case VideoFormat::NV12: + return MEDIASUBTYPE_NV12; + case VideoFormat::YV12: + return MEDIASUBTYPE_YV12; + case VideoFormat::Y800: + return MEDIASUBTYPE_Y800; + case VideoFormat::P010: + return MEDIASUBTYPE_P010; + + /* packed YUV formats */ + case VideoFormat::YVYU: + return MEDIASUBTYPE_YVYU; + case VideoFormat::YUY2: + return MEDIASUBTYPE_YUY2; + case VideoFormat::UYVY: + return MEDIASUBTYPE_UYVY; + + /* encoded formats */ + case VideoFormat::MJPEG: + return MEDIASUBTYPE_MJPG; + case VideoFormat::H264: + return MEDIASUBTYPE_H264; +#ifdef ENABLE_HEVC + case VideoFormat::HEVC: + return MEDIASUBTYPE_HEVC; +#endif + + default: + return GUID(); + } +} + +WORD VFormatBits(VideoFormat format) +{ + switch (format) { + /* raw formats */ + case VideoFormat::ARGB: + case VideoFormat::XRGB: + return 32; + + /* planar YUV formats */ + case VideoFormat::I420: + case VideoFormat::NV12: + case VideoFormat::YV12: + return 12; + case VideoFormat::Y800: + return 8; + + /* packed YUV formats */ + case VideoFormat::YVYU: + case VideoFormat::YUY2: + case VideoFormat::UYVY: + return 16; + + default: + return 0; + } +} + +WORD VFormatPlanes(VideoFormat format) +{ + switch (format) { + /* raw formats */ + case VideoFormat::ARGB: + case VideoFormat::XRGB: + return 1; + + /* planar YUV formats */ + case VideoFormat::I420: + return 3; + case VideoFormat::NV12: + case VideoFormat::YV12: + return 2; + case VideoFormat::Y800: + return 1; + + /* packed YUV formats */ + case VideoFormat::YVYU: + case VideoFormat::YUY2: + case VideoFormat::UYVY: + return 1; + + default: + return 0; + } +} + +static bool GetFourCCVFormat(DWORD fourCC, VideoFormat &format) +{ + switch (fourCC) { + /* raw formats */ + case MAKEFOURCC('R', 'G', 'B', '2'): + format = VideoFormat::XRGB; + break; + case MAKEFOURCC('R', 'G', 'B', '4'): + format = VideoFormat::XRGB; + break; + case MAKEFOURCC('A', 'R', 'G', 'B'): + format = VideoFormat::ARGB; + break; + + /* planar YUV formats */ + case MAKEFOURCC('I', '4', '2', '0'): + case MAKEFOURCC('I', 'Y', 'U', 'V'): + format = VideoFormat::I420; + break; + case MAKEFOURCC('Y', 'V', '1', '2'): + format = VideoFormat::YV12; + break; + case MAKEFOURCC('N', 'V', '1', '2'): + format = VideoFormat::NV12; + break; + case MAKEFOURCC('Y', '8', '0', '0'): + format = VideoFormat::Y800; + break; + case MAKEFOURCC('P', '0', '1', '0'): + format = VideoFormat::P010; + break; + + /* packed YUV formats */ + case MAKEFOURCC('Y', 'V', 'Y', 'U'): + format = VideoFormat::YVYU; + break; + case MAKEFOURCC('Y', 'U', 'Y', '2'): + format = VideoFormat::YUY2; + break; + case MAKEFOURCC('U', 'Y', 'V', 'Y'): + format = VideoFormat::UYVY; + break; + case MAKEFOURCC('H', 'D', 'Y', 'C'): + format = VideoFormat::HDYC; + break; + + /* compressed formats */ + case MAKEFOURCC('H', '2', '6', '4'): + format = VideoFormat::H264; + break; +#ifdef ENABLE_HEVC + case MAKEFOURCC('H', 'E', 'V', 'C'): + format = VideoFormat::HEVC; + break; +#endif + + /* compressed formats that can automatically create intermediary + * filters for decompression */ + case MAKEFOURCC('M', 'J', 'P', 'G'): + format = VideoFormat::MJPEG; + break; + + default: + return false; + } + + return true; +} + +bool GetMediaTypeVFormat(const AM_MEDIA_TYPE &mt, VideoFormat &format) +{ + if (mt.majortype != MEDIATYPE_Video) + return false; + + const BITMAPINFOHEADER *bmih = GetBitmapInfoHeader(mt); + + format = VideoFormat::Unknown; + + /* raw formats */ + if (mt.subtype == MEDIASUBTYPE_RGB24) + format = VideoFormat::XRGB; + else if (mt.subtype == MEDIASUBTYPE_RGB32) + format = VideoFormat::XRGB; + else if (mt.subtype == MEDIASUBTYPE_ARGB32) + format = VideoFormat::ARGB; + + /* planar YUV formats */ + else if (mt.subtype == MEDIASUBTYPE_I420) + format = VideoFormat::I420; + else if (mt.subtype == MEDIASUBTYPE_IYUV) + format = VideoFormat::I420; + else if (mt.subtype == MEDIASUBTYPE_YV12) + format = VideoFormat::YV12; + else if (mt.subtype == MEDIASUBTYPE_NV12) + format = VideoFormat::NV12; + else if (mt.subtype == MEDIASUBTYPE_Y800) + format = VideoFormat::Y800; + else if (mt.subtype == MEDIASUBTYPE_P010) + format = VideoFormat::P010; + + /* packed YUV formats */ + else if (mt.subtype == MEDIASUBTYPE_YVYU) + format = VideoFormat::YVYU; + else if (mt.subtype == MEDIASUBTYPE_YUY2) + format = VideoFormat::YUY2; + else if (mt.subtype == MEDIASUBTYPE_UYVY) + format = VideoFormat::UYVY; + + /* compressed formats */ + else if (mt.subtype == MEDIASUBTYPE_H264) + format = VideoFormat::H264; +#ifdef ENABLE_HEVC + else if (mt.subtype == MEDIASUBTYPE_HEVC) + format = VideoFormat::HEVC; +#endif + + /* compressed formats that can automatically create intermediary + * filters for decompression */ + else if (mt.subtype == MEDIASUBTYPE_MJPG) + format = VideoFormat::MJPEG; + + /* no valid types, check fourcc value instead */ + else + return bmih ? GetFourCCVFormat(bmih->biCompression, format) + : false; + + return true; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-formats.hpp b/obs-studio/deps/libdshowcapture/src/source/dshow-formats.hpp new file mode 100644 index 0000000..9de11af --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-formats.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "../dshowcapture.hpp" +#include "dshow-base.hpp" + +#include +#include + +namespace DShow { + +DWORD VFormatToFourCC(VideoFormat format); +WORD VFormatBits(VideoFormat format); +WORD VFormatPlanes(VideoFormat format); +GUID VFormatToSubType(VideoFormat format); + +bool GetMediaTypeVFormat(const AM_MEDIA_TYPE &mt, VideoFormat &format); + +}; /*namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-media-type.cpp b/obs-studio/deps/libdshowcapture/src/source/dshow-media-type.cpp new file mode 100644 index 0000000..ce32163 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-media-type.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "dshow-media-type.hpp" + +namespace DShow { + +HRESULT CopyMediaType(AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource) +{ + if (!pmtSource || !pmtTarget) + return S_FALSE; + + *pmtTarget = *pmtSource; + + if (pmtSource->cbFormat && pmtSource->pbFormat) { + pmtTarget->pbFormat = + (PBYTE)CoTaskMemAlloc(pmtSource->cbFormat); + + if (pmtTarget->pbFormat == nullptr) { + pmtTarget->cbFormat = 0; + return E_OUTOFMEMORY; + } else { + memcpy(pmtTarget->pbFormat, pmtSource->pbFormat, + pmtTarget->cbFormat); + } + } + + if (pmtTarget->pUnk != nullptr) + pmtTarget->pUnk->AddRef(); + + return S_OK; +} + +void FreeMediaType(AM_MEDIA_TYPE &mt) +{ + if (mt.cbFormat != 0) { + CoTaskMemFree((LPVOID)mt.pbFormat); + mt.cbFormat = 0; + mt.pbFormat = nullptr; + } + + if (mt.pUnk) { + mt.pUnk->Release(); + mt.pUnk = nullptr; + } +} + +BITMAPINFOHEADER *GetBitmapInfoHeader(AM_MEDIA_TYPE &mt) +{ + if (mt.formattype == FORMAT_VideoInfo) { + VIDEOINFOHEADER *vih; + vih = reinterpret_cast(mt.pbFormat); + return &vih->bmiHeader; + + } else if (mt.formattype == FORMAT_VideoInfo2) { + VIDEOINFOHEADER2 *vih; + vih = reinterpret_cast(mt.pbFormat); + return &vih->bmiHeader; + } + + return NULL; +} + +const BITMAPINFOHEADER *GetBitmapInfoHeader(const AM_MEDIA_TYPE &mt) +{ + if (mt.formattype == FORMAT_VideoInfo) { + const VIDEOINFOHEADER *vih; + vih = reinterpret_cast(mt.pbFormat); + return &vih->bmiHeader; + + } else if (mt.formattype == FORMAT_VideoInfo2) { + const VIDEOINFOHEADER2 *vih; + vih = reinterpret_cast(mt.pbFormat); + return &vih->bmiHeader; + } + + return NULL; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshow-media-type.hpp b/obs-studio/deps/libdshowcapture/src/source/dshow-media-type.hpp new file mode 100644 index 0000000..fc2a81b --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshow-media-type.hpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "dshow-base.hpp" + +namespace DShow { + +HRESULT CopyMediaType(AM_MEDIA_TYPE *pmtTarget, const AM_MEDIA_TYPE *pmtSource); +void FreeMediaType(AM_MEDIA_TYPE &mt); + +BITMAPINFOHEADER *GetBitmapInfoHeader(AM_MEDIA_TYPE &mt); +const BITMAPINFOHEADER *GetBitmapInfoHeader(const AM_MEDIA_TYPE &mt); + +class MediaTypePtr; + +class MediaType { + friend class MediaTypePtr; + + AM_MEDIA_TYPE type; + +public: + inline MediaType() { memset(&type, 0, sizeof(type)); } + + inline MediaType(const MediaType &mt) + { + CopyMediaType(&type, &mt.type); + } + + inline MediaType(const AM_MEDIA_TYPE &type_) + { + CopyMediaType(&type, &type_); + } + + inline ~MediaType() { FreeMediaType(type); } + + inline operator AM_MEDIA_TYPE *() { return &type; } + inline operator AM_MEDIA_TYPE &() { return type; } + inline operator const AM_MEDIA_TYPE *() const { return &type; } + inline operator const AM_MEDIA_TYPE &() const { return type; } + inline AM_MEDIA_TYPE *Ptr() { return &type; } + + inline AM_MEDIA_TYPE *operator->() { return &type; } + inline const AM_MEDIA_TYPE *operator->() const { return &type; } + + inline AM_MEDIA_TYPE *Duplicate() const + { + AM_MEDIA_TYPE *ptr = + (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(*ptr)); + if (ptr) { + memset(ptr, 0, sizeof(*ptr)); + CopyMediaType(ptr, &type); + } + return ptr; + } + + inline bool operator==(const AM_MEDIA_TYPE *pMT) const + { + return pMT == &type; + } + + inline void operator=(const MediaType &mt) + { + FreeMediaType(type); + CopyMediaType(&type, &mt.type); + } + + inline void operator=(const AM_MEDIA_TYPE *pMT) + { + FreeMediaType(type); + CopyMediaType(&type, pMT); + } + + inline void operator=(const AM_MEDIA_TYPE &type_) + { + FreeMediaType(type); + CopyMediaType(&type, &type_); + } + + template inline T *AllocFormat() + { + if (type.pbFormat) { + CoTaskMemFree(type.pbFormat); + type.pbFormat = nullptr; + type.cbFormat = 0; + } + + type.pbFormat = (PBYTE)CoTaskMemAlloc(sizeof(T)); + type.cbFormat = sizeof(T); + memset(type.pbFormat, 0, sizeof(T)); + + return (T *)type.pbFormat; + } +}; + +class MediaTypePtr { + friend class MediaType; + + AM_MEDIA_TYPE *ptr; + +public: + inline void Clear() + { + if (ptr) { + FreeMediaType(*ptr); + CoTaskMemFree(ptr); + ptr = nullptr; + } + } + + inline MediaTypePtr() : ptr(nullptr) {} + inline MediaTypePtr(AM_MEDIA_TYPE *ptr_) : ptr(ptr_) {} + inline ~MediaTypePtr() { Clear(); } + + inline AM_MEDIA_TYPE **operator&() + { + Clear(); + return &ptr; + } + + inline AM_MEDIA_TYPE *operator->() const { return ptr; } + + inline operator AM_MEDIA_TYPE *() const { return ptr; } + + inline void operator=(AM_MEDIA_TYPE *ptr_) + { + Clear(); + ptr = ptr_; + } + + inline bool operator==(const AM_MEDIA_TYPE *ptr_) const + { + return ptr == ptr_; + } +}; + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshowcapture.cpp b/obs-studio/deps/libdshowcapture/src/source/dshowcapture.cpp new file mode 100644 index 0000000..03711fa --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshowcapture.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "../dshowcapture.hpp" +#include "dshow-base.hpp" +#include "dshow-enum.hpp" +#include "device.hpp" +#include "dshow-device-defs.hpp" +#include "log.hpp" + +#include + +namespace DShow { + +Device::Device(InitGraph initialize) : context(new HDevice) +{ + if (initialize == InitGraph::True) + context->CreateGraph(); +} + +Device::~Device() +{ + delete context; +} + +bool Device::Valid() const +{ + return context->initialized; +} + +bool Device::ResetGraph() +{ + /* cheap and easy way to clear all the filters */ + delete context; + context = new HDevice; + + return context->CreateGraph(); +} + +void Device::ShutdownGraph() +{ + delete context; + context = new HDevice; +} + +bool Device::SetVideoConfig(VideoConfig *config) +{ + return context->SetVideoConfig(config); +} + +bool Device::SetAudioConfig(AudioConfig *config) +{ + return context->SetAudioConfig(config); +} + +bool Device::ConnectFilters() +{ + return context->ConnectFilters(); +} + +Result Device::Start() +{ + return context->Start(); +} + +void Device::Stop() +{ + context->Stop(); +} + +bool Device::GetVideoConfig(VideoConfig &config) const +{ + if (context->videoCapture == NULL) + return false; + + config = context->videoConfig; + return true; +} + +bool Device::GetAudioConfig(AudioConfig &config) const +{ + if (context->audioCapture == NULL) + return false; + + config = context->audioConfig; + return true; +} + +bool Device::GetVideoDeviceId(DeviceId &id) const +{ + if (context->videoCapture == NULL) + return false; + + id = context->videoConfig; + return true; +} + +bool Device::GetAudioDeviceId(DeviceId &id) const +{ + if (context->audioCapture == NULL) + return false; + + id = context->audioConfig; + return true; +} + +static void OpenPropertyPages(HWND hwnd, IUnknown *propertyObject) +{ + if (!propertyObject) + return; + + ComQIPtr pages(propertyObject); + CAUUID cauuid; + + if (pages != NULL) { + if (SUCCEEDED(pages->GetPages(&cauuid)) && cauuid.cElems) { + OleCreatePropertyFrame(hwnd, 0, 0, NULL, 1, + (LPUNKNOWN *)&propertyObject, + cauuid.cElems, cauuid.pElems, 0, + 0, NULL); + CoTaskMemFree(cauuid.pElems); + } + } +} + +void Device::OpenDialog(void *hwnd, DialogType type) const +{ + ComPtr ptr; + HRESULT hr; + + if (type == DialogType::ConfigVideo) { + ptr = context->videoFilter; + } else if (type == DialogType::ConfigCrossbar || + type == DialogType::ConfigCrossbar2) { + hr = context->builder->FindInterface(NULL, NULL, + context->videoFilter, + IID_IAMCrossbar, + (void **)&ptr); + if (FAILED(hr)) { + WarningHR(L"Failed to find crossbar", hr); + return; + } + + if (ptr != NULL && type == DialogType::ConfigCrossbar2) { + ComQIPtr xbar(ptr); + ComQIPtr filter(xbar); + + hr = context->builder->FindInterface( + &LOOK_UPSTREAM_ONLY, NULL, filter, + IID_IAMCrossbar, (void **)&ptr); + if (FAILED(hr)) { + WarningHR(L"Failed to find crossbar2", hr); + return; + } + } + } else if (type == DialogType::ConfigAudio) { + ptr = context->audioFilter; + } + + if (!ptr) { + Warning(L"Could not find filter to open dialog type: %d with", + (int)type); + return; + } + + OpenPropertyPages((HWND)hwnd, ptr); +} + +static void EnumEncodedVideo(std::vector &devices, + const wchar_t *deviceName, + const wchar_t *devicePath, + const EncodedDevice &info) +{ + VideoDevice device; + VideoInfo caps; + + device.name = deviceName; + device.path = devicePath; + device.audioAttached = true; + device.separateAudioFilter = false; + + caps.minCX = caps.maxCX = info.width; + caps.minCY = caps.maxCY = info.height; + caps.granularityCX = caps.granularityCY = 1; + caps.minInterval = caps.maxInterval = info.frameInterval; + caps.format = info.videoFormat; + + device.caps.push_back(caps); + devices.push_back(device); +} + +static void EnumExceptionVideoDevice(std::vector &devices, + IBaseFilter *filter, + const wchar_t *deviceName, + const wchar_t *devicePath) +{ + ComPtr pin; + + if (GetPinByName(filter, PINDIR_OUTPUT, L"656", &pin)) + EnumEncodedVideo(devices, deviceName, devicePath, HD_PVR2); + + else if (GetPinByName(filter, PINDIR_OUTPUT, L"TS Out", &pin)) + EnumEncodedVideo(devices, deviceName, devicePath, Roxio); +} + +static bool EnumVideoDevice(std::vector &devices, + IBaseFilter *filter, const wchar_t *deviceName, + const wchar_t *devicePath) +{ + ComPtr pin; + ComPtr audioPin; + ComPtr audioFilter; + VideoDevice info; + + if (wcsstr(deviceName, L"C875") != nullptr || + wcsstr(deviceName, L"Prif Streambox") != nullptr || + wcsstr(deviceName, L"C835") != nullptr) { + EnumEncodedVideo(devices, deviceName, devicePath, AV_LGP); + return true; + + } else if (wcsstr(deviceName, L"Hauppauge HD PVR Capture") != nullptr) { + EnumEncodedVideo(devices, deviceName, devicePath, HD_PVR1); + return true; + } + + bool success = GetFilterPin(filter, MEDIATYPE_Video, + PIN_CATEGORY_CAPTURE, PINDIR_OUTPUT, &pin); + + /* if this device has no standard capture pin, see if it's an + * encoded device, and get its information if so (all encoded devices + * are exception devices pretty much) */ + if (!success) { + EnumExceptionVideoDevice(devices, filter, deviceName, + devicePath); + return true; + } + + if (!EnumVideoCaps(pin, info.caps)) + return true; + + info.audioAttached = GetFilterPin(filter, MEDIATYPE_Audio, + PIN_CATEGORY_CAPTURE, PINDIR_OUTPUT, + &audioPin); + + // Fallback: Find a corresponding audio filter for the same device + if (!info.audioAttached) { + info.separateAudioFilter = + GetDeviceAudioFilter(devicePath, &audioFilter); + info.audioAttached = info.separateAudioFilter; + } + + info.name = deviceName; + if (devicePath) + info.path = devicePath; + + devices.push_back(info); + return true; +} + +bool Device::EnumVideoDevices(std::vector &devices) +{ + devices.clear(); + return EnumDevices(CLSID_VideoInputDeviceCategory, + EnumDeviceCallback(EnumVideoDevice), &devices); +} + +static bool EnumAudioDevice(vector &devices, IBaseFilter *filter, + const wchar_t *deviceName, + const wchar_t *devicePath) +{ + ComPtr pin; + AudioDevice info; + + bool success = GetFilterPin(filter, MEDIATYPE_Audio, + PIN_CATEGORY_CAPTURE, PINDIR_OUTPUT, &pin); + if (!success) + return true; + + if (!EnumAudioCaps(pin, info.caps)) + return true; + + info.name = deviceName; + if (devicePath) + info.path = devicePath; + + devices.push_back(info); + return true; +} + +bool Device::EnumAudioDevices(vector &devices) +{ + devices.clear(); + return EnumDevices(CLSID_AudioInputDeviceCategory, + EnumDeviceCallback(EnumAudioDevice), &devices); +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/dshowencode.cpp b/obs-studio/deps/libdshowcapture/src/source/dshowencode.cpp new file mode 100644 index 0000000..0e1667d --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/dshowencode.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "../dshowcapture.hpp" +#include "dshow-base.hpp" +#include "dshow-enum.hpp" +#include "encoder.hpp" +#include "log.hpp" + +namespace DShow { + +VideoEncoder::VideoEncoder() : context(new HVideoEncoder) {} + +VideoEncoder::~VideoEncoder() +{ + delete context; +} + +bool VideoEncoder::Valid() const +{ + return context->initialized; +} + +bool VideoEncoder::Active() const +{ + return context->active; +} + +bool VideoEncoder::ResetGraph() +{ + delete context; + context = new HVideoEncoder; + + return context->initialized; +} + +bool VideoEncoder::SetConfig(VideoEncoderConfig &config) +{ + if (context->active) { + delete context; + context = new HVideoEncoder; + } + + return context->SetConfig(config); +} + +bool VideoEncoder::GetConfig(VideoEncoderConfig &config) const +{ + if (context->encoder == nullptr) + return false; + + config = context->config; + return true; +} + +bool VideoEncoder::Encode(unsigned char *data[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], + long long timestampStart, long long timestampEnd, + EncoderPacket &packet, bool &new_packet) +{ + if (context->encoder == nullptr) + return false; + + return context->Encode(data, linesize, timestampStart, timestampEnd, + packet, new_packet); +} + +static bool EnumVideoEncoder(vector &encoders, IBaseFilter *encoder, + const wchar_t *deviceName, + const wchar_t *devicePath) +{ + DeviceId id; + bool validDevice = wcsstr(deviceName, L"C985") || + wcsstr(deviceName, L"C353"); + + if (!validDevice) + return true; + + id.name = deviceName; + id.path = devicePath; + encoders.push_back(id); + + (void)encoder; + return true; +} + +bool VideoEncoder::EnumEncoders(vector &encoders) +{ + encoders.clear(); + return EnumDevices(KSCATEGORY_ENCODER, + EnumDeviceCallback(EnumVideoEncoder), &encoders); +} + +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/encoder.cpp b/obs-studio/deps/libdshowcapture/src/source/encoder.cpp new file mode 100644 index 0000000..8bf57f0 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/encoder.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "encoder.hpp" +#include "log.hpp" +#include "avermedia-encode.h" + +namespace DShow { + +HVideoEncoder::HVideoEncoder() +{ + initialized = CreateFilterGraph(&graph, &builder, &control); +} + +HVideoEncoder::~HVideoEncoder() +{ + ComPtr filterEnum; + IBaseFilter *filter; + HRESULT hr; + + if (!initialized) + return; + + if (active) + control->Stop(); + + /* seems like you have to manually release the entire graph otherwise + * the encoder device might not end up releasing properly */ + hr = graph->EnumFilters(&filterEnum); + if (hr == S_OK) { + while (filterEnum->Next(1, &filter, nullptr) == S_OK) { + graph->RemoveFilter(filter); + filterEnum->Reset(); + filter->Release(); + } + } +} + +bool HVideoEncoder::ConnectFilters() +{ + ComPtr deviceIn; + ComPtr deviceOut; + ComPtr encoderIn; + ComPtr encoderOut; + bool success; + HRESULT hr; + + success = GetPinByName(device, PINDIR_INPUT, L"YUV In", &deviceIn); + if (!success) { + Warning(L"Failed to get YUV In pin"); + return false; + } + + success = GetPinByName(device, PINDIR_OUTPUT, L"Virtual Video Out", + &deviceOut); + if (!success) { + Warning(L"Failed to get Virtual Video Out pin"); + return false; + } + + success = GetPinByName(encoder, PINDIR_INPUT, L"Virtual Video In", + &encoderIn); + if (!success) { + Warning(L"Failed to get encoder input pin"); + return false; + } + + success = GetPinByName(encoder, PINDIR_OUTPUT, nullptr, &encoderOut); + if (!success) { + Warning(L"Failed to get encoder output pin"); + return false; + } + + hr = graph->ConnectDirect(output->GetPin(), deviceIn, nullptr); + if (FAILED(hr)) { + WarningHR(L"Failed to connect output to device", hr); + return false; + } + + hr = graph->ConnectDirect(deviceOut, encoderIn, nullptr); + if (FAILED(hr)) { + WarningHR(L"Failed to connect device to encoder", hr); + return false; + } + + hr = graph->ConnectDirect(encoderOut, capture->GetPin(), nullptr); + if (FAILED(hr)) { + WarningHR(L"Failed to connect encoder to capture", hr); + return false; + } + + return true; +} + +static bool GetPinFirstMediaType(IPin *pin, AM_MEDIA_TYPE **mt) +{ + ComPtr mediaEnum; + HRESULT hr; + ULONG fetched; + + hr = pin->EnumMediaTypes(&mediaEnum); + if (FAILED(hr)) { + Warning(L"Failed to get pin media type enum"); + return false; + } + + if (mediaEnum->Next(1, mt, &fetched) != S_OK) { + Warning(L"Failed to get pin media type"); + return false; + } + + return true; +} + +bool HVideoEncoder::SetupCrossbar() +{ + ComPtr crossbar; + ComPtr pin; + REGPINMEDIUM medium; + + /* C353 has no crossbar */ + if (config.name.find(L"C353") != std::string::npos) + return true; + + if (!GetPinByName(device, PINDIR_INPUT, L"Analog Video In", &pin)) { + Warning(L"Failed to get Analog Video In pin"); + return false; + } + if (!GetPinMedium(pin, medium)) { + Warning(L"Failed to get Analog Video In pin medium"); + return false; + } + if (!GetFilterByMedium(AM_KSCATEGORY_CROSSBAR, medium, &crossbar)) { + Warning(L"Failed to get crossbar filter"); + return false; + } + + graph->AddFilter(crossbar, L"Crossbar Filter"); + + if (!DirectConnectFilters(graph, crossbar, device)) { + Warning(L"Failed to connect crossbar to device"); + return false; + } + + return true; +} + +bool HVideoEncoder::SetupEncoder(IBaseFilter *filter) +{ + ComPtr deviceFilter; + ComPtr inputPin; + ComPtr outputPin; + REGPINMEDIUM medium; + MediaTypePtr mtRaw; + MediaTypePtr mtEncoded; + + if (!GetPinByName(filter, PINDIR_INPUT, nullptr, &inputPin)) { + Warning(L"Could not get encoder input pin"); + return false; + } + if (!GetPinByName(filter, PINDIR_OUTPUT, nullptr, &outputPin)) { + Warning(L"Could not get encoder output pin"); + return false; + } + if (!GetPinMedium(inputPin, medium)) { + Warning(L"Could not get input pin medium"); + return false; + } + + inputPin.Release(); + + if (!GetFilterByMedium(CLSID_VideoInputDeviceCategory, medium, + &deviceFilter)) { + Warning(L"Could not get device filter from medium"); + return false; + } + if (!GetPinByName(deviceFilter, PINDIR_INPUT, L"YUV In", &inputPin)) { + Warning(L"Could not device YUV pin"); + return false; + } + if (!GetPinFirstMediaType(inputPin, &mtRaw)) { + Warning(L"Could not get YUV pin media type"); + return false; + } + if (!GetPinFirstMediaType(outputPin, &mtEncoded)) { + Warning(L"Could not get encoder output pin media type"); + return false; + } + + PinCaptureInfo captureInfo; + captureInfo.callback = [this](IMediaSample *s) { Receive(s); }; + captureInfo.expectedMajorType = mtEncoded->majortype; + captureInfo.expectedSubType = mtEncoded->subtype; + + long long frameTime; + frameTime = config.fpsDenominator; + frameTime *= 10000000; + frameTime /= config.fpsNumerator; + + encoder = filter; + device = std::move(deviceFilter); + capture = new CaptureFilter(captureInfo); + output = new OutputFilter(VideoFormat::YV12, config.cx, config.cy, + frameTime); + + graph->AddFilter(output, nullptr); + graph->AddFilter(device, L"Device Filter"); + graph->AddFilter(encoder, L"Encoder Filter"); + graph->AddFilter(capture, nullptr); + return true; +} + +static inline void Clamp(ULONG &val, ULONG minVal, ULONG maxVal) +{ + if (val < minVal) + val = minVal; + else if (val > maxVal) + val = maxVal; +} + +HRESULT SetAVMEncoderSetting(IKsPropertySet *propertySet, ULONG setting, + ULONG param1, ULONG param2) +{ + AVER_PARAMETERS params = {}; + params.ulIndex = setting; + params.ulParam1 = param1; + params.ulParam2 = param2; + + if (setting == AVER_PARAMETER_ENCODE_FRAME_RATE) { + Clamp(param1, 15, 60); + } else if (setting == AVER_PARAMETER_ENCODE_BIT_RATE) { + Clamp(param1, 1000, 60000); + } else if (setting == AVER_PARAMETER_CURRENT_RESOLUTION) { + Clamp(param1, 1, 30); + } + + return propertySet->Set(AVER_HW_ENCODE_PROPERTY, + PROPERTY_HW_ENCODE_PARAMETER, ¶ms, + sizeof(params), ¶ms, sizeof(params)); +} + +bool SetAvermediaEncoderConfig(IBaseFilter *encoder, VideoEncoderConfig &config) +{ + HRESULT hr; + + ComQIPtr propertySet(encoder); + if (!propertySet) { + Warning(L"Could not get IKsPropertySet for encoder"); + return false; + } + + double fps = + double(config.fpsNumerator) / double(config.fpsDenominator); + + hr = SetAVMEncoderSetting(propertySet, AVER_PARAMETER_ENCODE_FRAME_RATE, + ULONG(fps), 0); + if (FAILED(hr)) { + WarningHR(L"Failed to set Avermedia encoder FPS", hr); + return false; + } + + hr = SetAVMEncoderSetting(propertySet, AVER_PARAMETER_ENCODE_BIT_RATE, + ULONG(config.bitrate), 0); + if (FAILED(hr)) { + WarningHR(L"Failed to set Avermedia encoder bitrate", hr); + return false; + } + + hr = SetAVMEncoderSetting(propertySet, + AVER_PARAMETER_CURRENT_RESOLUTION, + ULONG(config.cx), ULONG(config.cy)); + if (FAILED(hr)) { + WarningHR(L"Failed to set Avermedia encoder current res", hr); + return false; + } + + hr = SetAVMEncoderSetting(propertySet, AVER_PARAMETER_ENCODE_RESOLUTION, + ULONG(config.cx), ULONG(config.cy)); + if (FAILED(hr)) { + WarningHR(L"Failed to set Avermedia encoder res", hr); + return false; + } + + hr = SetAVMEncoderSetting(propertySet, AVER_PARAMETER_ENCODE_GOP, + ULONG(config.keyframeInterval), 0); + if (FAILED(hr)) { + WarningHR(L"Failed to set Avermedia encoder GOP", hr); + return false; + } + + return true; +} + +bool HVideoEncoder::SetConfig(VideoEncoderConfig &config) +{ + ComPtr filter; + ComPtr crossbar; + + if (config.name.empty() && config.path.empty()) { + Warning(L"No video encoder name or path specified"); + return false; + } + + bool success = GetDeviceFilter(KSCATEGORY_ENCODER, config.name.c_str(), + config.path.c_str(), &filter); + if (!success) { + Warning(L"Video encoder '%s': %s not found", + config.name.c_str(), config.path.c_str()); + return false; + } + + if (!filter) { + Warning(L"Could not get encoder filter"); + return false; + } + + this->config = config; + + if (!SetupEncoder(filter)) { + Warning(L"Failed to set up encoder"); + return false; + } + if (!SetupCrossbar()) { + Warning(L"Failed to set up crossbar"); + return false; + } + if (!SetAvermediaEncoderConfig(device, config)) { + Warning(L"Failed to set Avermedia encoder settings"); + return false; + } + if (!ConnectFilters()) { + Warning(L"Failed to connect encoder filters"); + return false; + } + + LogFilters(graph); + + HRESULT hr = control->Run(); + if (FAILED(hr)) { + WarningHR(L"Run failed", hr); + return false; + } + + active = true; + return true; +} + +void HVideoEncoder::Receive(IMediaSample *s) +{ + BYTE *data; + size_t size; + + if (FAILED(s->GetPointer(&data))) + return; + + size = (size_t)s->GetActualDataLength(); + if (!size) + return; + + packetMutex.lock(); + packets.emplace_back(data, size); + packetMutex.unlock(); +} + +bool HVideoEncoder::Encode(unsigned char *data[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], + long long timestampStart, long long timestampEnd, + EncoderPacket &packet, bool &new_packet) +{ + new_packet = false; + + if (!active) + return false; + + output->Send(data, linesize, timestampStart, timestampEnd); + ptsVals.push_back(timestampStart); + + packetMutex.lock(); + if (packets.size() > 0) { + curPacket = move(packets.front()); + long long ptsOut = ptsVals[0]; + packets.pop_front(); + ptsVals.pop_front(); + + packet.data = curPacket.data.data(); + packet.size = curPacket.data.size(); + packet.pts = ptsOut; + packet.dts = ptsOut; + new_packet = true; + } + packetMutex.unlock(); + return true; +} + +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/encoder.hpp b/obs-studio/deps/libdshowcapture/src/source/encoder.hpp new file mode 100644 index 0000000..b866382 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/encoder.hpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "../dshowcapture.hpp" +#include "output-filter.hpp" +#include "capture-filter.hpp" + +#include +#include +#include +#include +using namespace std; + +struct EncodedData { + vector data; + + inline EncodedData() = default; + + inline EncodedData(unsigned char *data_, size_t size) + { + data.resize(size); + memcpy(data.data(), data_, size); + } +}; + +namespace DShow { + +struct HVideoEncoder { + ComPtr graph; + ComPtr builder; + ComPtr control; + + ComPtr encoder; + ComPtr device; + ComPtr output; + ComPtr capture; + + VideoEncoderConfig config; + + mutex packetMutex; + deque packets; + EncodedData curPacket; + + deque ptsVals; + + bool initialized = false; + bool active = false; + + HVideoEncoder(); + ~HVideoEncoder(); + + bool SetupCrossbar(); + + void Receive(IMediaSample *s); + + bool ConnectFilters(); + + bool SetupEncoder(IBaseFilter *filter); + + bool SetConfig(VideoEncoderConfig &config); + + bool Encode(unsigned char *frame[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], long long timestampStart, + long long timestampEnd, EncoderPacket &packet, + bool &new_packet); +}; + +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/external/.clang-format b/obs-studio/deps/libdshowcapture/src/source/external/.clang-format new file mode 100644 index 0000000..6420a46 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/external/.clang-format @@ -0,0 +1,3 @@ +Language: Cpp +SortIncludes: false +DisableFormat: true diff --git a/obs-studio/deps/libdshowcapture/src/source/external/IVideoCaptureFilter.h b/obs-studio/deps/libdshowcapture/src/source/external/IVideoCaptureFilter.h new file mode 100644 index 0000000..26d68eb --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/external/IVideoCaptureFilter.h @@ -0,0 +1,238 @@ +//============================================================================= +//! @file IVideoCaptureFilter.h +//! @bc ----------------------------------------------------------------------- +//! @ec @brief Interface declaration for Elgato Video Capture Filter +//! @author F.M.Birth, T.Schnitzler +//! @date 01-Oct-12 FMB - Creation +//! @date 08-Apr-13 TS - Added IVideoCaptureFilter2 +//! @date 14-Nov-13 TS - Added IVideoCaptureFilter3 +//! @date 21-Jul-14 TS - Added IVideoCaptureFilter4 +//! @date 12-Aug-14 TS - Added IVideoCaptureFilter5 +//! @date 28-Aug-14 FDj - MIT license added +//! @note Supports Elgato Game Capture HD +//! @bc ----------------------------------------------------------------------- +//! @ec @par Copyright +//! @n (c) 2012-14, Elgato Systems. All Rights Reserved. +//! @n +//! @n The MIT License (MIT) +//! @n +//! @n Permission is hereby granted, free of charge, to any person obtaining a copy +//! @n of this software and associated documentation files (the "Software"), to deal +//! @n in the Software without restriction, including without limitation the rights +//! @n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! @n copies of the Software, and to permit persons to whom the Software is +//! @n furnished to do so, subject to the following conditions: +//! @n +//! @n The above copyright notice and this permission notice shall be included in all +//! @n copies or substantial portions of the Software. +//! @n +//! @n THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! @n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! @n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! @n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! @n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! @n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//! @n SOFTWARE. +//! @n +//============================================================================= + +#pragma once + +/*============================================================================= +// FILTER INTERFACE +=============================================================================*/ +#define VIDEO_CAPTURE_FILTER_NAME "Elgato Game Capture HD" +#define VIDEO_CAPTURE_FILTER_NAME_L L"Elgato Game Capture HD" + +// {39F50F4C-99E1-464a-B6F9-D605B4FB5918} +DEFINE_GUID(CLSID_ElgatoVideoCaptureFilter, +0x39f50f4c, 0x99e1, 0x464a, 0xb6, 0xf9, 0xd6, 0x5, 0xb4, 0xfb, 0x59, 0x18); + +// {39F50F4C-99E1-464a-B6F9-D605B4FB5919} +DEFINE_GUID(CLSID_ElgatoVideoCaptureFilterProperties, +0x39f50f4c, 0x99e1, 0x464a, 0xb6, 0xf9, 0xd6, 0x5, 0xb4, 0xfb, 0x59, 0x19); + +// {39F50F4C-99E1-464a-B6F9-D605B4FB5920} +DEFINE_GUID(IID_IElgatoVideoCaptureFilter, +0x39f50f4c, 0x99e1, 0x464a, 0xb6, 0xf9, 0xd6, 0x5, 0xb4, 0xfb, 0x59, 0x20); + +// {585B2914-252E-49bd-B730-7B4C40F4D4E5} +DEFINE_GUID(IID_IElgatoVideoCaptureFilter2, +0x585b2914, 0x252e, 0x49bd, 0xb7, 0x30, 0x7b, 0x4c, 0x40, 0xf4, 0xd4, 0xe5); + +// {CC415EB7-B1C7-428c-9E5E-D9747DB4BE76} +DEFINE_GUID(IID_IElgatoVideoCaptureFilter3, +0xcc415eb7, 0xb1c7, 0x428c, 0x9e, 0x5e, 0xd9, 0x74, 0x7d, 0xb4, 0xbe, 0x76); + +// {197992FF-ED65-47CB-8032-D287AB40B33F} +DEFINE_GUID(IID_IElgatoVideoCaptureFilter4, +0x197992ff, 0xed65, 0x47cb, 0x80, 0x32, 0xd2, 0x87, 0xab, 0x40, 0xb3, 0x3f); + +// {7E6E9E9E-4062-4364-99B1-15C2F662B502} +DEFINE_GUID(IID_IElgatoVideoCaptureFilter5, +0x7e6e9e9e, 0x4062, 0x4364, 0x99, 0xb1, 0x15, 0xc2, 0xf6, 0x62, 0xb5, 0x2); + +/*============================================================================= +// IElgatoVideoCaptureFilter +=============================================================================*/ + +//! Interface +DECLARE_INTERFACE_(IElgatoVideoCaptureFilter, IUnknown) +{ +}; + +/*============================================================================= +// IElgatoVideoCaptureFilter2 +=============================================================================*/ + +//! Video Capture device type +enum VIDEO_CAPTURE_FILTER_DEVICE_TYPE +{ + VIDEO_CAPTURE_FILTER_DEVICE_TYPE_INVALID = 0, //!< Invalid + VIDEO_CAPTURE_FILTER_DEVICE_TYPE_GAME_CAPTURE_HD = 2, //!< Game Capture HD (VID: 0x0fd9 PID: 0x0044, 0x004e, 0x0051) + VIDEO_CAPTURE_FILTER_DEVICE_TYPE_GAME_CAPTURE_HD60 = 8, //!< Game Capture HD60 (VID: 0x0fd9 PID: 0x005c) + NUM_VIDEO_CAPTURE_FILTER_DEVICE_TYPE +}; + +//! Input device +enum VIDEO_CAPTURE_FILTER_INPUT_DEVICE +{ + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_INVALID = 0, //!< Invalid + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_XBOX360 = 1, //!< Microsoft Xbox 360 + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_PLAYSTATION3 = 2, //!< Sony PlayStation 3 + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_IPAD = 3, //!< Apple iPad + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_IPOD_IPHONE = 4, //!< Apple iPod or iPhone + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_WII = 5, //!< Nintendo Wii + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_OTHER = 6, //!< Other + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_WII_U = 7, //!< Nintendo Wii U + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_XBOX_ONE = 8, //!< Microsoft Xbox One + VIDEO_CAPTURE_FILTER_INPUT_DEVICE_PLAYSTATION4 = 9, //!< Sony PlayStation 4 +}; + +//! Video inputs +enum VIDEO_CAPTURE_FILTER_VIDEO_INPUT +{ + VIDEO_CAPTURE_FILTER_VIDEO_INPUT_INVALID = 0, //!< Invalid + VIDEO_CAPTURE_FILTER_VIDEO_INPUT_COMPOSITE = 1, //!< Composite + VIDEO_CAPTURE_FILTER_VIDEO_INPUT_SVIDEO = 2, //!< S-Video + VIDEO_CAPTURE_FILTER_VIDEO_INPUT_COMPONENT = 3, //!< Component + VIDEO_CAPTURE_FILTER_VIDEO_INPUT_HDMI = 4, //!< HDMI +}; + +//! Video encoder profile +enum VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE +{ + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_INVALID = 0x00000000, //!< Invalid + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_240 = 0x00000001, //!< 320x240 + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_360 = 0x00000002, //!< 480x360 + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_480 = 0x00000004, //!< 640x480 + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_720 = 0x00000008, //!< 1280x720 + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_1080 = 0x00000010, //!< 1920x1080 +}; + +//! Color range +enum VIDEO_CAPTURE_FILTER_COLOR_RANGE +{ + VIDEO_CAPTURE_FILTER_COLOR_RANGE_INVALID = 0, //!< Invalid + VIDEO_CAPTURE_FILTER_COLOR_RANGE_FULL = 1, //!< 0-255 + VIDEO_CAPTURE_FILTER_COLOR_RANGE_LIMITED = 2, //!< 16-235 + VIDEO_CAPTURE_FILTER_COLOR_RANGE_SHOOT = 3, //!< +}; + +//! Settings +struct VIDEO_CAPTURE_FILTER_SETTINGS +{ + TCHAR deviceName[256]; //!< Device name (get only) + VIDEO_CAPTURE_FILTER_INPUT_DEVICE inputDevice; //!< Input device (e.g. Xbox360) + VIDEO_CAPTURE_FILTER_VIDEO_INPUT videoInput; //!< Video input (e.g. HDMI) + VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE profile; //!< Video encoder profile (maximum resolution) + BOOL useAnalogAudioInput; //!< for HDMI with analog audio input + VIDEO_CAPTURE_FILTER_COLOR_RANGE hdmiColorRange; //!< HDMI color range + int brightness; //!< Brightness (0-10000) + int contrast; //!< Contrast (0-10000) + int saturation; //!< Saturation (0-10000) + int hue; //!< Hue (0-10000) + int analogAudioGain; //!< Analog audio gain (-60 - 12 dB) + int digitalAudioGain; //!< Digital audio gain (-60 - 12 dB) + BOOL preserveInputFormat; //!< Input Format will be preserved (e.g. do not convert interlaced to progressive) + BOOL stretchStandardDefinitionInput; //!< Stretch SD input to 16:9 +}; + +//! Interface +DECLARE_INTERFACE_(IElgatoVideoCaptureFilter2, IElgatoVideoCaptureFilter) +{ + // Get current settings + STDMETHOD(GetSettings)(THIS_ VIDEO_CAPTURE_FILTER_SETTINGS *pSettings) PURE; + + // Set settings + STDMETHOD(SetSettings)(THIS_ const VIDEO_CAPTURE_FILTER_SETTINGS *pcSettings) PURE; +}; + +/*============================================================================= +// IElgatoVideoCaptureFilter3 +=============================================================================*/ + +//! Interface +DECLARE_INTERFACE_(IElgatoVideoCaptureFilter3, IElgatoVideoCaptureFilter2) +{ + //! Get A/V delay in milli-seconds (approximate delay between input signal and DirectShow + //! filter output) + STDMETHOD(GetDelayMs)(THIS_ int* pnDelayMs) PURE; +}; + +/*============================================================================= +// IElgatoVideoCaptureFilter4 +=============================================================================*/ + +//! Messages +enum VIDEO_CAPTURE_FILTER_NOTIFICATION +{ + //! Description: Delay of the device has changed. Call GetDelayMs() to get the new delay. + VIDEO_CAPTURE_FILTER_NOTIFICATION_DEVICE_DELAY_CHANGED = 110, //!< Data: none + + //! Description: Output format has changed. Update your signal path accordingly. + VIDEO_CAPTURE_FILTER_NOTIFICATION_CAPTURE_OUTPUT_FORMAT_CHANGED = 305, //!< Data: none +}; + +//! Custom event that can be received by IMediaEvent::GetEvent. If SetNotificationCallback() was not set this method is used to send notifications. +//! lEventCode = VIDEO_CAPTURE_FILTER_EVENT +//! lParam1 = VIDEO_CAPTURE_FILTER_NOTIFICATION +//! lParam2 = reserved for future use (e.g. notifications with additional data) +#define VIDEO_CAPTURE_FILTER_EVENT EC_USER + 0x0FD9 + +//! Message callback +typedef void (CALLBACK* PFN_VIDEO_CAPTURE_FILTER_NOTIFICATION_CALLBACK)(VIDEO_CAPTURE_FILTER_NOTIFICATION nMessage, void* pData, int nSize, void* pContext); + +//! Interface +DECLARE_INTERFACE_(IElgatoVideoCaptureFilter4, IElgatoVideoCaptureFilter3) +{ + //! Check device is present + STDMETHOD(GetDevicePresent)(THIS_ BOOL* pfDevicePresent) PURE; + + //! Get current device type + STDMETHOD(GetDeviceType)(THIS_ VIDEO_CAPTURE_FILTER_DEVICE_TYPE* pnDeviceType) PURE; + + //! Set callback to receive notifications + STDMETHOD(SetNotificationCallback)(THIS_ PFN_VIDEO_CAPTURE_FILTER_NOTIFICATION_CALLBACK pCallback, void* pContext) PURE; +}; + +/*============================================================================= +// IElgatoVideoCaptureFilter5 +=============================================================================*/ + +//! Extended Settings +struct VIDEO_CAPTURE_FILTER_SETTINGS_EX +{ + VIDEO_CAPTURE_FILTER_SETTINGS Settings; + BOOL enableFullFrameRate; //!< Enable full frame rate (50/60 fps) + BYTE reserved[20 * 1024]; +}; + +DECLARE_INTERFACE_(IElgatoVideoCaptureFilter5, IElgatoVideoCaptureFilter4) +{ + //! Get current settings + STDMETHOD(GetSettingsEx)(THIS_ VIDEO_CAPTURE_FILTER_SETTINGS_EX *pSettings) PURE; + + //! Set settings + STDMETHOD(SetSettingsEx)(THIS_ const VIDEO_CAPTURE_FILTER_SETTINGS_EX *pcSettings) PURE; +}; diff --git a/obs-studio/deps/libdshowcapture/src/source/log.cpp b/obs-studio/deps/libdshowcapture/src/source/log.cpp new file mode 100644 index 0000000..1589a5a --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/log.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "dshow-base.hpp" +#include "log.hpp" +#include "../dshowcapture.hpp" + +namespace DShow { + +void *logParam = NULL; +static LogCallback logCallback = NULL; + +void SetLogCallback(LogCallback callback, void *param) +{ + logCallback = callback; + logParam = param; +} + +static void Log(LogType type, const wchar_t *format, va_list args) +{ + wchar_t str[4096]; + vswprintf_s(str, 4096, format, args); + + if (logCallback) + logCallback(type, str, logParam); +} + +void Error(const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + Log(LogType::Error, format, args); + va_end(args); +} + +void Warning(const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + Log(LogType::Warning, format, args); + va_end(args); +} + +void Info(const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + Log(LogType::Info, format, args); + va_end(args); +} + +void Debug(const wchar_t *format, ...) +{ + va_list args; + va_start(args, format); + Log(LogType::Debug, format, args); + va_end(args); +} + +void ErrorHR(const wchar_t *str, HRESULT hr) +{ + Error(L"%s (0x%08lX): %s", str, hr, ConvertHRToEnglish(hr).c_str()); +} + +void WarningHR(const wchar_t *str, HRESULT hr) +{ + Warning(L"%s (0x%08lX): %s", str, hr, ConvertHRToEnglish(hr).c_str()); +} + +void InfoHR(const wchar_t *str, HRESULT hr) +{ + Info(L"%s (0x%08lX): %s", str, hr, ConvertHRToEnglish(hr).c_str()); +} + +void DebugHR(const wchar_t *str, HRESULT hr) +{ + Debug(L"%s (0x%08lX): %s", str, hr, ConvertHRToEnglish(hr).c_str()); +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/log.hpp b/obs-studio/deps/libdshowcapture/src/source/log.hpp new file mode 100644 index 0000000..7240c09 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/log.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +namespace DShow { + +void Error(const wchar_t *format, ...); +void Warning(const wchar_t *format, ...); +void Info(const wchar_t *format, ...); +void Debug(const wchar_t *format, ...); + +void ErrorHR(const wchar_t *str, HRESULT hr); +void WarningHR(const wchar_t *str, HRESULT hr); +void InfoHR(const wchar_t *str, HRESULT hr); +void DebugHR(const wchar_t *str, HRESULT hr); + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/output-filter.cpp b/obs-studio/deps/libdshowcapture/src/source/output-filter.cpp new file mode 100644 index 0000000..fe91463 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/output-filter.cpp @@ -0,0 +1,992 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include "output-filter.hpp" +#include "dshow-formats.hpp" +#include "log.hpp" + +#include + +namespace DShow { + +#if 0 +#define PrintFunc(x) Debug(x) +#else +#define PrintFunc(x) +#endif + +#define FILTER_NAME L"Output Filter" +#define VIDEO_PIN_NAME L"Video Output" +#define AUDIO_PIN_NAME L"Audio Output" + +OutputPin::OutputPin(OutputFilter *filter_) : refCount(0), filter(filter_) {} + +OutputPin::OutputPin(OutputFilter *filter_, VideoFormat format, int cx, int cy, + long long interval) + : OutputPin(filter_) +{ + curCX = cx; + curCY = cy; + curInterval = interval; + + AddVideoFormat(format, cx, cy, interval); + SetVideoFormat(format, cx, cy, interval); +} + +OutputPin::~OutputPin() {} + +STDMETHODIMP OutputPin::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown) { + AddRef(); + *ppv = this; + } else if (riid == IID_IPin) { + AddRef(); + *ppv = (IPin *)this; + } else if (riid == IID_IMemInputPin) { + AddRef(); + *ppv = (IMemInputPin *)this; + } else if (riid == IID_IAMStreamConfig) { + AddRef(); + *ppv = (IAMStreamConfig *)this; + return S_OK; + } else if (riid == IID_IKsPropertySet) { + AddRef(); + *ppv = (IKsPropertySet *)this; + return S_OK; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } + + return NOERROR; +} + +STDMETHODIMP_(ULONG) OutputPin::AddRef() +{ + return (ULONG)InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) OutputPin::Release() +{ + long newRefs = InterlockedDecrement(&refCount); + if (!newRefs) { + delete this; + return 0; + } + + return (ULONG)newRefs; +} + +// IPin methods +STDMETHODIMP OutputPin::Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) +{ + HRESULT hr; + + PrintFunc(L"OutputPin::Connect"); + + if (filter->state == State_Running) + return VFW_E_NOT_STOPPED; + + if (connectedPin) + return VFW_E_ALREADY_CONNECTED; + + hr = pReceivePin->ReceiveConnection(this, mt); + if (FAILED(hr)) { +#if 0 /* debug code to test caps on fail */ + ComPtr enumMT; + pReceivePin->EnumMediaTypes(&enumMT); + + if (enumMT) { + MediaTypePtr mt; + ULONG count = 0; + + while (enumMT->Next(1, &mt, &count) == S_OK) { + int test = 0; + test = 1; + } + } +#endif + return E_FAIL; + } + + if (!AllocateBuffers(pReceivePin, true)) { + return E_FAIL; + } + + connectedPin = pReceivePin; + DSHOW_UNUSED(pmt); + return S_OK; +} + +STDMETHODIMP OutputPin::ReceiveConnection(IPin *pConnector, + const AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"OutputPin::ReceiveConnection"); + + DSHOW_UNUSED(pConnector); + DSHOW_UNUSED(pmt); + return S_OK; +} + +STDMETHODIMP OutputPin::Disconnect() +{ + PrintFunc(L"OutputPin::Disconnect"); + + if (!connectedPin) + return S_FALSE; + + if (!!allocator) { + allocator->Decommit(); + allocator.Clear(); + } + + connectedPin = nullptr; + return S_OK; +} + +STDMETHODIMP OutputPin::ConnectedTo(IPin **pPin) +{ + PrintFunc(L"OutputPin::ConnectedTo"); + + if (!connectedPin) { + *pPin = nullptr; + return VFW_E_NOT_CONNECTED; + } + + IPin *pin = connectedPin; + pin->AddRef(); + *pPin = pin; + return S_OK; +} + +STDMETHODIMP OutputPin::ConnectionMediaType(AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"OutputPin::ConnectionMediaType"); + + if (!connectedPin) + return VFW_E_NOT_CONNECTED; + + return CopyMediaType(pmt, mt); +} + +STDMETHODIMP OutputPin::QueryPinInfo(PIN_INFO *pInfo) +{ + PrintFunc(L"OutputPin::QueryPinInfo"); + + pInfo->pFilter = filter; + if (filter) { + IBaseFilter *ptr = filter; + ptr->AddRef(); + } + + if (mt->majortype == MEDIATYPE_Video) + memcpy(pInfo->achName, VIDEO_PIN_NAME, sizeof(VIDEO_PIN_NAME)); + else + memcpy(pInfo->achName, AUDIO_PIN_NAME, sizeof(AUDIO_PIN_NAME)); + + pInfo->dir = PINDIR_OUTPUT; + + return NOERROR; +} + +STDMETHODIMP OutputPin::QueryDirection(PIN_DIRECTION *pPinDir) +{ + *pPinDir = PINDIR_OUTPUT; + return NOERROR; +} + +#define OUTPUT_PIN_NAME L"Output Pin" + +STDMETHODIMP OutputPin::QueryId(LPWSTR *lpId) +{ + wchar_t *str = (wchar_t *)CoTaskMemAlloc(sizeof(OUTPUT_PIN_NAME)); + memcpy(str, OUTPUT_PIN_NAME, sizeof(OUTPUT_PIN_NAME)); + *lpId = str; + return S_OK; +} + +STDMETHODIMP OutputPin::QueryAccept(const AM_MEDIA_TYPE *) +{ + PrintFunc(L"OutputPin::QueryAccept"); + + return S_OK; +} + +STDMETHODIMP OutputPin::EnumMediaTypes(IEnumMediaTypes **ppEnum) +{ + PrintFunc(L"OutputPin::EnumMediaTypes"); + + *ppEnum = new OutputEnumMediaTypes(this); + if (!*ppEnum) + return E_OUTOFMEMORY; + + return NOERROR; +} + +STDMETHODIMP OutputPin::QueryInternalConnections(IPin **apPin, ULONG *nPin) +{ + PrintFunc(L"OutputPin::QueryInternalConnections"); + + DSHOW_UNUSED(apPin); + DSHOW_UNUSED(nPin); + return E_NOTIMPL; +} + +STDMETHODIMP OutputPin::EndOfStream() +{ + PrintFunc(L"OutputPin::EndOfStream"); + + return S_OK; +} + +STDMETHODIMP OutputPin::BeginFlush() +{ + PrintFunc(L"OutputPin::BeginFlush"); + + flushing = true; + return S_OK; +} + +STDMETHODIMP OutputPin::EndFlush() +{ + PrintFunc(L"OutputPin::EndFlush"); + + flushing = false; + return S_OK; +} + +STDMETHODIMP OutputPin::NewSegment(REFERENCE_TIME, REFERENCE_TIME, double) +{ + PrintFunc(L"OutputPin::NewSegment"); + + return S_OK; +} + +STDMETHODIMP OutputPin::GetFormat(AM_MEDIA_TYPE **ppmt) +{ + PrintFunc(L"OutputPin::GetFormat"); + + if (!ppmt) { + return E_POINTER; + } + + *ppmt = mt.Duplicate(); + return S_OK; +} + +STDMETHODIMP OutputPin::GetNumberOfCapabilities(int *piCount, int *piSize) +{ + PrintFunc(L"OutputPin::GetNumberOfCapabilities"); + + if (!piCount || !piSize) { + return E_POINTER; + } + + *piCount = (int)mtList.size(); + *piSize = sizeof(VIDEO_STREAM_CONFIG_CAPS); + return S_OK; +} + +STDMETHODIMP OutputPin::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **ppmt, + BYTE *pSCC) +{ + PrintFunc(L"OutputPin::GetStreamCaps"); + + int count = (int)mtList.size(); + + if (!ppmt || !pSCC) { + return E_POINTER; + } + if (iIndex > (count - 1)) { + return S_FALSE; + } + if (iIndex < 0) { + return E_INVALIDARG; + } + + AM_MEDIA_TYPE *pmt = mtList[iIndex].Duplicate(); + VIDEOINFOHEADER *vih = reinterpret_cast(pmt->pbFormat); + + VIDEO_STREAM_CONFIG_CAPS caps = {}; + caps.guid = FORMAT_VideoInfo; + caps.MinFrameInterval = vih->AvgTimePerFrame; + caps.MaxFrameInterval = vih->AvgTimePerFrame; + caps.MinOutputSize.cx = vih->bmiHeader.biWidth; + caps.MinOutputSize.cy = vih->bmiHeader.biHeight; + caps.MaxOutputSize = caps.MinOutputSize; + caps.InputSize = caps.MinOutputSize; + caps.MinCroppingSize = caps.MinOutputSize; + caps.MaxCroppingSize = caps.MinOutputSize; + caps.CropGranularityX = vih->bmiHeader.biWidth; + caps.CropGranularityY = vih->bmiHeader.biHeight; + caps.MinBitsPerSecond = vih->dwBitRate; + caps.MaxBitsPerSecond = caps.MinBitsPerSecond; + + *ppmt = pmt; + + memcpy(pSCC, &caps, sizeof(caps)); + return S_OK; +} + +STDMETHODIMP OutputPin::SetFormat(AM_MEDIA_TYPE *pmt) +{ + PrintFunc(L"OutputPin::SetFormat"); + + if (pmt == nullptr) + return VFW_E_INVALIDMEDIATYPE; + + mt = pmt; + + GetMediaTypeVFormat(mt, curVFormat); + + VIDEOINFOHEADER *vih = reinterpret_cast(mt->pbFormat); + curCX = vih->bmiHeader.biWidth; + curCY = vih->bmiHeader.biHeight; + curInterval = vih->AvgTimePerFrame; + + return S_OK; +} + +STDMETHODIMP OutputPin::Set(REFGUID, DWORD, void *, DWORD, void *, DWORD) +{ + PrintFunc(L"OutputPin::Set"); + return E_NOTIMPL; +} + +STDMETHODIMP OutputPin::Get(REFGUID guidPropSet, DWORD dwPropID, void *, DWORD, + void *pPropData, DWORD cbPropData, + DWORD *pcbReturned) +{ + PrintFunc(L"OutputPin::Get"); + + if (guidPropSet != AMPROPSETID_Pin) + return E_PROP_SET_UNSUPPORTED; + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + return E_PROP_ID_UNSUPPORTED; + if (pPropData == NULL && pcbReturned == NULL) + return E_POINTER; + + if (pcbReturned) + *pcbReturned = sizeof(GUID); + if (pPropData == NULL) + return S_OK; + if (cbPropData < sizeof(GUID)) + return E_UNEXPECTED; + + *(GUID *)pPropData = PIN_CATEGORY_CAPTURE; + return S_OK; +} + +STDMETHODIMP OutputPin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, + DWORD *pTypeSupport) +{ + PrintFunc(L"OutputPin::QuerySupported"); + + if (guidPropSet != AMPROPSETID_Pin) + return E_PROP_SET_UNSUPPORTED; + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + return E_PROP_ID_UNSUPPORTED; + if (pTypeSupport) + *pTypeSupport = KSPROPERTY_SUPPORT_GET; + return S_OK; +} + +bool OutputPin::AllocateBuffers(IPin *target, bool connecting) +{ + HRESULT hr; + + ComQIPtr memInput(target); + if (!memInput) + return false; + + if (!!allocator) { + allocator->Decommit(); + } + + hr = memInput->GetAllocator(&allocator); + if (hr == VFW_E_NO_ALLOCATOR) + hr = CoCreateInstance(CLSID_MemoryAllocator, NULL, + CLSCTX_INPROC_SERVER, + __uuidof(IMemAllocator), + (void **)&allocator); + + if (FAILED(hr)) + return false; + + VIDEOINFOHEADER *vih = reinterpret_cast(mt->pbFormat); + + int cx = vih->bmiHeader.biWidth; + int cy = vih->bmiHeader.biHeight; + + WORD bits = VFormatBits(curVFormat); + bufSize = cx * cy * bits / 8; + + ALLOCATOR_PROPERTIES props; + + hr = memInput->GetAllocatorRequirements(&props); + if (hr == E_NOTIMPL) { + props.cBuffers = 4; + props.cbAlign = 32; + props.cbPrefix = 0; + + } else if (FAILED(hr)) { + return false; + } + + props.cbBuffer = (long)bufSize; + + ALLOCATOR_PROPERTIES actual; + hr = allocator->SetProperties(&props, &actual); + if (FAILED(hr)) + return false; + + if (!connecting && FAILED(allocator->Commit())) { + return false; + } + + memInput->NotifyAllocator(allocator, false); + return true; +} + +static MediaType CreateMediaType(VideoFormat format, int cx, int cy, + long long interval) +{ + MediaType mt; + + WORD bits = VFormatBits(format); + DWORD size = cx * cy * bits / 8; + uint64_t rate = + (uint64_t)size * 10000000ULL / (uint64_t)interval * 8ULL; + + VIDEOINFOHEADER *vih = mt.AllocFormat(); + vih->bmiHeader.biSize = sizeof(vih->bmiHeader); + vih->bmiHeader.biWidth = cx; + vih->bmiHeader.biHeight = cy; + vih->bmiHeader.biPlanes = VFormatPlanes(format); + vih->bmiHeader.biBitCount = bits; + vih->bmiHeader.biSizeImage = size; + vih->bmiHeader.biCompression = VFormatToFourCC(format); + vih->rcSource.right = cx; + vih->rcSource.bottom = cy; + vih->rcTarget = vih->rcSource; + vih->dwBitRate = (DWORD)rate; + vih->AvgTimePerFrame = interval; + + mt->majortype = MEDIATYPE_Video; + mt->subtype = VFormatToSubType(format); + mt->formattype = FORMAT_VideoInfo; + mt->bFixedSizeSamples = true; + mt->lSampleSize = size; + + return mt; +} + +void OutputPin::AddVideoFormat(VideoFormat format, int cx, int cy, + long long interval) +{ + MediaType newMT = CreateMediaType(format, cx, cy, interval); + mtList.push_back(newMT); +} + +bool OutputPin::SetVideoFormat(VideoFormat format, int cx, int cy, + long long interval) +{ + mt = CreateMediaType(format, cx, cy, interval); + + if (curCX != cx || curCY != cy || curInterval != interval || + curVFormat != format) { + curVFormat = format; + curCX = cx; + curCY = cy; + curInterval = interval; + + if (!!connectedPin) { + setSampleMediaType = true; + return ReallocateBuffers(); + } + } + + return true; +} + +bool OutputPin::LockSampleData(unsigned char **ptr) +{ + if (!connectedPin) + return false; + + ComQIPtr memInput(connectedPin); + HRESULT hr; + + if (!memInput || !allocator) + return false; + + hr = allocator->GetBuffer(&sample, nullptr, nullptr, 0); + if (FAILED(hr)) + return false; + + if (FAILED(sample->SetActualDataLength((long)bufSize))) + return false; + if (FAILED(sample->SetDiscontinuity(false))) + return false; + if (FAILED(sample->SetPreroll(false))) + return false; + if (FAILED(sample->SetSyncPoint(true))) + return false; + if (FAILED(sample->GetPointer(ptr))) + return false; + + if (setSampleMediaType) { + sample->SetMediaType(mt); + setSampleMediaType = false; + } + + return true; +} + +void OutputPin::Send(unsigned char *data[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], + long long timestampStart, long long timestampEnd) +{ + BYTE *ptr; + if (!LockSampleData(&ptr)) + return; + + size_t total = 0; + for (size_t i = 0; i < DSHOW_MAX_PLANES; i++) { + if (!linesize[i]) + break; + + memcpy(ptr + total, data[i], linesize[i]); + total += linesize[i]; + } + + UnlockSampleData(timestampStart, timestampEnd); +} + +void OutputPin::UnlockSampleData(long long timestampStart, + long long timestampEnd) +{ + if (!connectedPin) + return; + + ComQIPtr memInput(connectedPin); + REFERENCE_TIME startTime = timestampStart; + REFERENCE_TIME endTime = timestampEnd; + + sample->SetMediaTime(&startTime, &endTime); + sample->SetTime(&startTime, &endTime); + + memInput->Receive(sample); + + sample.Clear(); +} + +void OutputPin::Stop() +{ + if (!!connectedPin) { + connectedPin->BeginFlush(); + connectedPin->EndFlush(); + } +} + +// ============================================================================ + +class SourceMiscFlags : public IAMFilterMiscFlags { + volatile long refCount = 0; + +public: + inline SourceMiscFlags() {} + virtual ~SourceMiscFlags() {} + + STDMETHODIMP QueryInterface(REFIID riid, void **ppv) + { + if (riid == IID_IUnknown) { + AddRef(); + *ppv = this; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } + + return NOERROR; + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&refCount); + } + + STDMETHODIMP_(ULONG) Release() + { + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return refCount; + } + + STDMETHODIMP_(ULONG) GetMiscFlags() + { + return AM_FILTER_MISC_FLAGS_IS_SOURCE; + } +}; + +OutputFilter::OutputFilter() + : refCount(0), + state(State_Stopped), + graph(nullptr), + pin(new OutputPin(this)), + misc(new SourceMiscFlags) +{ +} + +OutputFilter::OutputFilter(VideoFormat format, int cx, int cy, + long long interval) + : refCount(0), + state(State_Stopped), + graph(nullptr), + pin(new OutputPin(this, format, cx, cy, interval)), + misc(new SourceMiscFlags) +{ +} + +OutputFilter::~OutputFilter() {} + +// IUnknown methods +STDMETHODIMP OutputFilter::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown) { + AddRef(); + *ppv = this; + } else if (riid == IID_IPersist) { + AddRef(); + *ppv = (IPersist *)this; + } else if (riid == IID_IMediaFilter) { + AddRef(); + *ppv = (IMediaFilter *)this; + } else if (riid == IID_IBaseFilter) { + AddRef(); + *ppv = (IBaseFilter *)this; + } else if (riid == IID_IAMFilterMiscFlags) { + misc.CopyTo((IAMFilterMiscFlags **)ppv); + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } + + return NOERROR; +} + +STDMETHODIMP_(ULONG) OutputFilter::AddRef() +{ + return InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) OutputFilter::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return refCount; +} + +// IPersist method +STDMETHODIMP OutputFilter::GetClassID(CLSID *pClsID) +{ + DSHOW_UNUSED(pClsID); + return E_NOTIMPL; +} + +// IMediaFilter methods +STDMETHODIMP OutputFilter::GetState(DWORD dwMSecs, FILTER_STATE *State) +{ + PrintFunc(L"OutputFilter::GetState"); + + *State = state; + + DSHOW_UNUSED(dwMSecs); + return S_OK; +} + +STDMETHODIMP OutputFilter::SetSyncSource(IReferenceClock *pClock) +{ + clock = pClock; + return S_OK; +} + +STDMETHODIMP OutputFilter::GetSyncSource(IReferenceClock **pClock) +{ + *pClock = clock.Get(); + if (*pClock) { + (*pClock)->AddRef(); + } + return NOERROR; +} + +STDMETHODIMP OutputFilter::Stop() +{ + PrintFunc(L"OutputFilter::Stop"); + + if (state != State_Stopped) { + pin->Stop(); + } + + state = State_Stopped; + return S_OK; +} + +STDMETHODIMP OutputFilter::Pause() +{ + PrintFunc(L"OutputFilter::Pause"); + + OutputPin *pin = GetPin(); + if (!!pin->allocator && state == State_Stopped) { + pin->allocator->Commit(); + } + + state = State_Paused; + return S_OK; +} + +STDMETHODIMP OutputFilter::Run(REFERENCE_TIME tStart) +{ + PrintFunc(L"OutputFilter::Run"); + + state = State_Running; + + DSHOW_UNUSED(tStart); + return S_OK; +} + +// IBaseFilter methods +STDMETHODIMP OutputFilter::EnumPins(IEnumPins **ppEnum) +{ + PrintFunc(L"OutputFilter::EnumPins"); + + *ppEnum = new OutputEnumPins(this, nullptr); + return (*ppEnum == nullptr) ? E_OUTOFMEMORY : NOERROR; +} + +STDMETHODIMP OutputFilter::FindPin(LPCWSTR Id, IPin **ppPin) +{ + PrintFunc(L"OutputFilter::FindPin"); + + if (Id == nullptr || ppPin == nullptr) + return E_POINTER; + + if (lstrcmpW(Id, OUTPUT_PIN_NAME) == 0) { + *ppPin = pin; + pin->AddRef(); + return S_OK; + } else { + *ppPin = nullptr; + return VFW_E_NOT_FOUND; + } +} + +STDMETHODIMP OutputFilter::QueryFilterInfo(FILTER_INFO *pInfo) +{ + PrintFunc(L"OutputFilter::QueryFilterInfo"); + + StringCbCopyW(pInfo->achName, sizeof(pInfo->achName), FilterName()); + + pInfo->pGraph = graph; + if (graph) { + IFilterGraph *graph_ptr = graph; + graph_ptr->AddRef(); + } + return NOERROR; +} + +STDMETHODIMP OutputFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName) +{ + DSHOW_UNUSED(pName); + + graph = pGraph; + return NOERROR; +} + +STDMETHODIMP OutputFilter::QueryVendorInfo(LPWSTR *pVendorInfo) +{ + DSHOW_UNUSED(pVendorInfo); + return E_NOTIMPL; +} + +const wchar_t *OutputFilter::FilterName() const +{ + return FILTER_NAME; +} + +// ============================================================================ + +OutputEnumPins::OutputEnumPins(OutputFilter *filter_, OutputEnumPins *pEnum) + : filter(filter_) +{ + curPin = (pEnum != nullptr) ? pEnum->curPin : 0; +} + +OutputEnumPins::~OutputEnumPins() {} + +// IUnknown +STDMETHODIMP OutputEnumPins::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown || riid == IID_IEnumPins) { + AddRef(); + *ppv = (IEnumPins *)this; + return NOERROR; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) OutputEnumPins::AddRef() +{ + return (ULONG)InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) OutputEnumPins::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return (ULONG)refCount; +} + +// IEnumPins +STDMETHODIMP OutputEnumPins::Next(ULONG cPins, IPin **ppPins, ULONG *pcFetched) +{ + UINT nFetched = 0; + + if (curPin == 0 && cPins > 0) { + IPin *pPin = filter->GetPin(); + + *ppPins = pPin; + pPin->AddRef(); + + nFetched = 1; + curPin++; + } + + if (pcFetched) + *pcFetched = nFetched; + + return (nFetched == cPins) ? S_OK : S_FALSE; +} + +STDMETHODIMP OutputEnumPins::Skip(ULONG cPins) +{ + return ((curPin += cPins) > 1) ? S_FALSE : S_OK; +} + +STDMETHODIMP OutputEnumPins::Reset() +{ + curPin = 0; + return S_OK; +} + +STDMETHODIMP OutputEnumPins::Clone(IEnumPins **ppEnum) +{ + *ppEnum = new OutputEnumPins(filter, this); + return (*ppEnum == nullptr) ? E_OUTOFMEMORY : NOERROR; +} + +// ============================================================================ + +OutputEnumMediaTypes::OutputEnumMediaTypes(OutputPin *pin_) : pin(pin_) {} + +OutputEnumMediaTypes::~OutputEnumMediaTypes() {} + +STDMETHODIMP OutputEnumMediaTypes::QueryInterface(REFIID riid, void **ppv) +{ + if (riid == IID_IUnknown || riid == IID_IEnumMediaTypes) { + AddRef(); + *ppv = this; + return NOERROR; + } else { + *ppv = nullptr; + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(ULONG) OutputEnumMediaTypes::AddRef() +{ + return (ULONG)InterlockedIncrement(&refCount); +} + +STDMETHODIMP_(ULONG) OutputEnumMediaTypes::Release() +{ + if (!InterlockedDecrement(&refCount)) { + delete this; + return 0; + } + + return (ULONG)refCount; +} + +// IEnumMediaTypes +STDMETHODIMP OutputEnumMediaTypes::Next(ULONG cMediaTypes, + AM_MEDIA_TYPE **ppMediaTypes, + ULONG *pcFetched) +{ + PrintFunc(L"OutputEnumMediaTypes::Next"); + + UINT total = (UINT)pin->mtList.size(); + UINT nFetched = 0; + + for (ULONG i = 0; i < cMediaTypes && curMT < total; i++) { + *(ppMediaTypes++) = pin->mtList[curMT++].Duplicate(); + nFetched++; + } + + if (pcFetched) + *pcFetched = nFetched; + + return (nFetched == cMediaTypes) ? S_OK : S_FALSE; +} + +STDMETHODIMP OutputEnumMediaTypes::Skip(ULONG cMediaTypes) +{ + PrintFunc(L"OutputEnumMediaTypes::Skip"); + + UINT total = (UINT)pin->mtList.size(); + return ((curMT += cMediaTypes) > total) ? S_FALSE : S_OK; +} + +STDMETHODIMP OutputEnumMediaTypes::Reset() +{ + PrintFunc(L"OutputEnumMediaTypes::Reset"); + + curMT = 0; + return S_OK; +} + +STDMETHODIMP OutputEnumMediaTypes::Clone(IEnumMediaTypes **ppEnum) +{ + *ppEnum = new OutputEnumMediaTypes(pin); + return (*ppEnum == nullptr) ? E_OUTOFMEMORY : NOERROR; +} + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/source/output-filter.hpp b/obs-studio/deps/libdshowcapture/src/source/output-filter.hpp new file mode 100644 index 0000000..d6c6226 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/source/output-filter.hpp @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2023 Lain Bailey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#pragma once + +#include "dshow-base.hpp" +#include "dshow-media-type.hpp" +#include "../dshowcapture.hpp" + +namespace DShow { + +class OutputFilter; + +class OutputPin : public IPin, public IAMStreamConfig, public IKsPropertySet { + friend class OutputEnumMediaTypes; + friend class OutputFilter; + + volatile long refCount; + + std::vector mtList; + + MediaType mt; + VideoFormat curVFormat; + long long curInterval = 0; + int curCX = 0; + int curCY = 0; + bool setSampleMediaType = false; + + ComPtr connectedPin; + OutputFilter *filter; + volatile bool flushing = false; + ComPtr allocator; + ComPtr sample; + size_t bufSize; + + bool IsValidMediaType(const AM_MEDIA_TYPE *pmt) const; + + bool AllocateBuffers(IPin *target, bool connecting = false); + +public: + OutputPin(OutputFilter *filter); + OutputPin(OutputFilter *filter, VideoFormat format, int cx, int cy, + long long interval); + virtual ~OutputPin(); + + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IPin methods + STDMETHODIMP Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt); + STDMETHODIMP ReceiveConnection(IPin *connector, + const AM_MEDIA_TYPE *pmt); + STDMETHODIMP Disconnect(); + STDMETHODIMP ConnectedTo(IPin **pPin); + STDMETHODIMP ConnectionMediaType(AM_MEDIA_TYPE *pmt); + STDMETHODIMP QueryPinInfo(PIN_INFO *pInfo); + STDMETHODIMP QueryDirection(PIN_DIRECTION *pPinDir); + STDMETHODIMP QueryId(LPWSTR *lpId); + STDMETHODIMP QueryAccept(const AM_MEDIA_TYPE *pmt); + STDMETHODIMP EnumMediaTypes(IEnumMediaTypes **ppEnum); + STDMETHODIMP QueryInternalConnections(IPin **apPin, ULONG *nPin); + STDMETHODIMP EndOfStream(); + + STDMETHODIMP BeginFlush(); + STDMETHODIMP EndFlush(); + STDMETHODIMP NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, + double dRate); + + // IAMStreamConfig methods + STDMETHODIMP GetFormat(AM_MEDIA_TYPE **ppmt) override; + STDMETHODIMP GetNumberOfCapabilities(int *piCount, + int *piSize) override; + STDMETHODIMP GetStreamCaps(int iIndex, AM_MEDIA_TYPE **ppmt, + BYTE *pSCC) override; + STDMETHODIMP SetFormat(AM_MEDIA_TYPE *pmt) override; + + // IKsPropertySet methods + STDMETHODIMP Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData, + DWORD cbInstanceData, void *pPropData, + DWORD cbPropData) override; + + STDMETHODIMP Get(REFGUID guidPropSet, DWORD dwPropID, + void *pInstanceData, DWORD cbInstanceData, + void *pPropData, DWORD cbPropData, + DWORD *pcbReturned) override; + + STDMETHODIMP QuerySupported(REFGUID guidPropSet, DWORD dwPropID, + DWORD *pTypeSupport) override; + + // Other methods + inline bool ReallocateBuffers() + { + return !!connectedPin ? AllocateBuffers(connectedPin) : false; + } + + inline VideoFormat GetVideoFormat() const { return curVFormat; } + inline int GetCX() const { return curCX; } + inline int GetCY() const { return curCY; } + inline long long GetInterval() const { return curInterval; } + + void AddVideoFormat(VideoFormat format, int cx, int cy, + long long interval); + bool SetVideoFormat(VideoFormat format, int cx, int cy, + long long interval); + + void Send(unsigned char *data[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], long long timestampStart, + long long timestampEnd); + + bool LockSampleData(unsigned char **ptr); + void UnlockSampleData(long long timestampStart, long long timestampEnd); + + void Stop(); +}; + +class OutputFilter : public IBaseFilter { + friend class OutputPin; + + volatile long refCount; + FILTER_STATE state; + IFilterGraph *graph; + ComPtr pin; + + ComPtr misc; + +protected: + ComPtr clock; + +public: + OutputFilter(); + OutputFilter(VideoFormat format, int cx, int cy, long long interval); + virtual ~OutputFilter(); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IPersist method + STDMETHODIMP GetClassID(CLSID *pClsID); + + // IMediaFilter methods + STDMETHODIMP GetState(DWORD dwMSecs, FILTER_STATE *State); + STDMETHODIMP SetSyncSource(IReferenceClock *pClock); + STDMETHODIMP GetSyncSource(IReferenceClock **pClock); + STDMETHODIMP Stop(); + STDMETHODIMP Pause(); + STDMETHODIMP Run(REFERENCE_TIME tStart); + + // IBaseFilter methods + STDMETHODIMP EnumPins(IEnumPins **ppEnum); + STDMETHODIMP FindPin(LPCWSTR Id, IPin **ppPin); + STDMETHODIMP QueryFilterInfo(FILTER_INFO *pInfo); + STDMETHODIMP JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName); + STDMETHODIMP QueryVendorInfo(LPWSTR *pVendorInfo); + + virtual const wchar_t *FilterName() const; + + inline OutputPin *GetPin() const { return (OutputPin *)pin; } + + inline bool ReallocateBuffers() { return pin->ReallocateBuffers(); } + + inline VideoFormat GetVideoFormat() const + { + return pin->GetVideoFormat(); + } + + inline int GetCX() const { return pin->GetCX(); } + inline int GetCY() const { return pin->GetCY(); } + inline long long GetInterval() const { return pin->GetInterval(); } + + inline void AddVideoFormat(VideoFormat format, int cx, int cy, + long long interval) + { + pin->AddVideoFormat(format, cx, cy, interval); + } + inline bool SetVideoFormat(VideoFormat format, int cx, int cy, + long long interval) + { + return pin->SetVideoFormat(format, cx, cy, interval); + } + + inline void Send(unsigned char *data[DSHOW_MAX_PLANES], + size_t linesize[DSHOW_MAX_PLANES], + long long timestampStart, long long timestampEnd) + { + pin->Send(data, linesize, timestampStart, timestampEnd); + } + + inline bool LockSampleData(unsigned char **ptr) + { + return pin->LockSampleData(ptr); + } + + inline void UnlockSampleData(long long timestampStart, + long long timestampEnd) + { + pin->UnlockSampleData(timestampStart, timestampEnd); + } +}; + +class OutputEnumPins : public IEnumPins { + volatile long refCount = 1; + ComPtr filter; + UINT curPin; + +public: + OutputEnumPins(OutputFilter *filter, OutputEnumPins *pEnum); + virtual ~OutputEnumPins(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumPins + STDMETHODIMP Next(ULONG cPins, IPin **ppPins, ULONG *pcFetched); + STDMETHODIMP Skip(ULONG cPins); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(IEnumPins **ppEnum); +}; + +class OutputEnumMediaTypes : public IEnumMediaTypes { + volatile long refCount = 1; + ComPtr pin; + UINT curMT = 0; + +public: + OutputEnumMediaTypes(OutputPin *pin); + virtual ~OutputEnumMediaTypes(); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void **ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IEnumMediaTypes + STDMETHODIMP Next(ULONG cMediaTypes, AM_MEDIA_TYPE **ppMediaTypes, + ULONG *pcFetched); + STDMETHODIMP Skip(ULONG cMediaTypes); + STDMETHODIMP Reset(); + STDMETHODIMP Clone(IEnumMediaTypes **ppEnum); +}; + +}; /* namespace DShow */ diff --git a/obs-studio/deps/libdshowcapture/src/tests/.gitignore b/obs-studio/deps/libdshowcapture/src/tests/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture.sln b/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture.sln new file mode 100644 index 0000000..b127de3 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dshowcapture", "dshowcapture\dshowcapture.vcxproj", "{FFF52519-38BB-4155-851D-4209EC67ACA7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Debug|Win32.ActiveCfg = Debug|Win32 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Debug|Win32.Build.0 = Debug|Win32 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Debug|x64.ActiveCfg = Debug|x64 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Debug|x64.Build.0 = Debug|x64 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Release|Win32.ActiveCfg = Release|Win32 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Release|Win32.Build.0 = Release|Win32 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Release|x64.ActiveCfg = Release|x64 + {FFF52519-38BB-4155-851D-4209EC67ACA7}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture/dshowcapture.vcxproj b/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture/dshowcapture.vcxproj new file mode 100644 index 0000000..7917122 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture/dshowcapture.vcxproj @@ -0,0 +1,179 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {FFF52519-38BB-4155-851D-4209EC67ACA7} + Win32Proj + dshowcapture + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + strmiids.lib;%(AdditionalDependencies) + + + + + + + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + strmiids.lib;%(AdditionalDependencies) + + + + + Level4 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + strmiids.lib;%(AdditionalDependencies) + + + + + Level4 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + strmiids.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture/dshowcapture.vcxproj.filters b/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture/dshowcapture.vcxproj.filters new file mode 100644 index 0000000..cc3c525 --- /dev/null +++ b/obs-studio/deps/libdshowcapture/src/vs/2013/dshowcapture/dshowcapture.vcxproj.filters @@ -0,0 +1,102 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/obs-studio/frontend/OBSApp.cpp b/obs-studio/frontend/OBSApp.cpp index 2b95502..43f5c5e 100644 --- a/obs-studio/frontend/OBSApp.cpp +++ b/obs-studio/frontend/OBSApp.cpp @@ -748,34 +748,7 @@ bool OBSApp::InitLocale() } if (!userLocale && defaultLang) { - for (auto &locale_ : GetPreferredLocales()) { - if (locale_ == lang) { - return true; - } - - stringstream file; - file << "locale/" << locale_ << ".ini"; - - string path; - if (!GetDataFilePath(file.str().c_str(), path)) { - continue; - } - - if (!text_lookup_add(textLookup, path.c_str())) { - continue; - } - - blog(LOG_INFO, "Using preferred locale '%s'", locale_.c_str()); - locale = locale_; - - // set application default locale to the new chosen one - if (!locale.empty()) { - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - } - - return true; - } - + // Default to English UI; ignore system preferred locales. return true; } diff --git a/obs-studio/frontend/cmake/windows/obs-studio.ico b/obs-studio/frontend/cmake/windows/obs-studio.ico index 7127372..bbb75dc 100644 Binary files a/obs-studio/frontend/cmake/windows/obs-studio.ico and b/obs-studio/frontend/cmake/windows/obs-studio.ico differ diff --git a/obs-studio/frontend/dialogs/OBSAbout.cpp b/obs-studio/frontend/dialogs/OBSAbout.cpp index 9660008..61799ba 100644 --- a/obs-studio/frontend/dialogs/OBSAbout.cpp +++ b/obs-studio/frontend/dialogs/OBSAbout.cpp @@ -1,18 +1,9 @@ #include "OBSAbout.hpp" -#include -#include - #include -#include - #include "moc_OBSAbout.cpp" -using namespace json11; - -extern bool steam; - OBSAbout::OBSAbout(QWidget *parent) : QDialog(parent), ui(new Ui::OBSAbout) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -31,142 +22,6 @@ OBSAbout::OBSAbout(QWidget *parent) : QDialog(parent), ui(new Ui::OBSAbout) ui->version->setText(ver + bitness); - ui->contribute->setText(QTStr("About.Contribute")); - - if (steam) { - delete ui->donate; - } else { - ui->donate->setText("  " + QTStr("About.Donate") + - ""); - ui->donate->setTextInteractionFlags(Qt::TextBrowserInteraction); - ui->donate->setOpenExternalLinks(true); - } - - ui->getInvolved->setText("  " + - QTStr("About.GetInvolved") + ""); - ui->getInvolved->setTextInteractionFlags(Qt::TextBrowserInteraction); - ui->getInvolved->setOpenExternalLinks(true); - - ui->about->setText("" + QTStr("About") + ""); - ui->authors->setText("" + QTStr("About.Authors") + ""); - ui->license->setText("" + QTStr("About.License") + ""); - ui->name->setProperty("class", "text-heading"); ui->version->setProperty("class", "text-large"); - ui->about->setProperty("class", "bg-base"); - ui->authors->setProperty("class", "bg-base"); - ui->license->setProperty("class", "bg-base"); - ui->info->setProperty("class", ""); - - connect(ui->about, &ClickableLabel::clicked, this, &OBSAbout::ShowAbout); - connect(ui->authors, &ClickableLabel::clicked, this, &OBSAbout::ShowAuthors); - connect(ui->license, &ClickableLabel::clicked, this, &OBSAbout::ShowLicense); - - QPointer about(this); - - OBSBasic *main = OBSBasic::Get(); - if (main->patronJson.empty() && !main->patronJsonThread) { - RemoteTextThread *thread = - new RemoteTextThread("https://obsproject.com/patreon/about-box.json", "application/json"); - QObject::connect(thread, &RemoteTextThread::Result, main, &OBSBasic::UpdatePatronJson); - QObject::connect(thread, &RemoteTextThread::Result, this, &OBSAbout::ShowAbout); - main->patronJsonThread.reset(thread); - thread->start(); - } else { - ShowAbout(); - } -} - -void OBSAbout::ShowAbout() -{ - OBSBasic *main = OBSBasic::Get(); - - if (main->patronJson.empty()) { - return; - } - - std::string error; - Json json = Json::parse(main->patronJson, error); - const Json::array &patrons = json.array_items(); - QString text; - - text += "

Top Patreon contributors:

"; - text += "

"; - bool first = true; - bool top = true; - - for (const Json &patron : patrons) { - std::string name = patron["name"].string_value(); - std::string link = patron["link"].string_value(); - int amount = patron["amount"].int_value(); - - if (top && amount < 5000) { - text += "

"; - top = false; - } else if (!first) { - text += "
"; - } - - if (!link.empty()) { - text += ""; - } - text += QT_UTF8(name.c_str()).toHtmlEscaped(); - if (!link.empty()) { - text += ""; - } - - if (first) { - first = false; - } - } - - ui->textBrowser->setHtml(text); -} - -void OBSAbout::ShowAuthors() -{ - std::string path; - QString error = QTStr("About.Error").arg("https://github.com/obsproject/obs-studio/blob/master/AUTHORS"); - -#ifdef __APPLE__ - if (!GetDataFilePath("AUTHORS", path)) { -#else - if (!GetDataFilePath("authors/AUTHORS", path)) { -#endif - ui->textBrowser->setPlainText(error); - return; - } - - ui->textBrowser->setPlainText(QString::fromStdString(path)); - - BPtr text = os_quick_read_utf8_file(path.c_str()); - - if (!text || !*text) { - ui->textBrowser->setPlainText(error); - return; - } - - ui->textBrowser->setPlainText(QT_UTF8(text)); -} - -void OBSAbout::ShowLicense() -{ - std::string path; - QString error = QTStr("About.Error").arg("https://github.com/obsproject/obs-studio/blob/master/COPYING"); - - if (!GetDataFilePath("license/gplv2.txt", path)) { - ui->textBrowser->setPlainText(error); - return; - } - - BPtr text = os_quick_read_utf8_file(path.c_str()); - - if (!text || !*text) { - ui->textBrowser->setPlainText(error); - return; - } - - ui->textBrowser->setPlainText(QT_UTF8(text)); } diff --git a/obs-studio/frontend/dialogs/OBSAbout.hpp b/obs-studio/frontend/dialogs/OBSAbout.hpp index e3ec87c..ce22ee5 100644 --- a/obs-studio/frontend/dialogs/OBSAbout.hpp +++ b/obs-studio/frontend/dialogs/OBSAbout.hpp @@ -11,9 +11,4 @@ public: explicit OBSAbout(QWidget *parent = 0); std::unique_ptr ui; - -private slots: - void ShowAbout(); - void ShowAuthors(); - void ShowLicense(); }; diff --git a/obs-studio/frontend/forms/OBSAbout.ui b/obs-studio/frontend/forms/OBSAbout.ui index ee271ba..9cbb4c0 100644 --- a/obs-studio/frontend/forms/OBSAbout.ui +++ b/obs-studio/frontend/forms/OBSAbout.ui @@ -6,246 +6,90 @@ 0 0 - 840 - 519 + 520 + 300 About - + + + 30 + - 0 + 30 - 0 + 30 - 0 + 30 - 0 + 30 - + + + + 200 + 200 + + + + + 200 + 200 + + + + + + + :res/images/obs.png + + + true + + + + + - 30 - - - 30 - - - 10 - - - 6 - - - 6 + 0 - - - - 256 - 256 - - - - - 256 - 256 - - + - - - - :res/images/obs.png - - - true + OBS Studio - - - 0 + + + Version - - - - OBS Studio - - - - - - - Version - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::Fixed - - - - 20 - 20 - - - - - - - - About.Info - - - true - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::Fixed - - - - 20 - 20 - - - - - - - - Contribute - - - true - - - - - - - Donate - - - - - - - Get Involved - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::Fixed - - - - 20 - 20 - - - - - - - - true - - - - + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + - - - - - 0 - 60 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - About - - - Qt::AlignmentFlag::AlignCenter - - - - - - - Authors - - - Qt::AlignmentFlag::AlignCenter - - - - - - - License - - - Qt::AlignmentFlag::AlignCenter - - - - - - - - - ClickableLabel - QLabel -
components/ClickableLabel.hpp
-
-
diff --git a/obs-studio/frontend/forms/images/obs.png b/obs-studio/frontend/forms/images/obs.png index dbae094..def164b 100644 Binary files a/obs-studio/frontend/forms/images/obs.png and b/obs-studio/frontend/forms/images/obs.png differ diff --git a/obs-studio/frontend/settings/OBSBasicSettings.cpp b/obs-studio/frontend/settings/OBSBasicSettings.cpp index 43a82d4..babcfcd 100644 --- a/obs-studio/frontend/settings/OBSBasicSettings.cpp +++ b/obs-studio/frontend/settings/OBSBasicSettings.cpp @@ -1654,6 +1654,8 @@ void OBSBasicSettings::LoadResolutionLists() addRes(as_width, as_height); } + addRes(3840, 2160); + addRes(2560, 1440); addRes(1920, 1080); addRes(1280, 720); diff --git a/obs-studio/frontend/widgets/OBSBasic.cpp b/obs-studio/frontend/widgets/OBSBasic.cpp index 97e89f4..107a5b6 100644 --- a/obs-studio/frontend/widgets/OBSBasic.cpp +++ b/obs-studio/frontend/widgets/OBSBasic.cpp @@ -622,16 +622,6 @@ bool OBSBasic::InitBasicConfigDefaults() cx *= devicePixelRatioF(); cy *= devicePixelRatioF(); - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - bool changed = false; /* ----------------------------------------------------- */ diff --git a/obs-studio/frontend/wizards/AutoConfigTestPage.cpp b/obs-studio/frontend/wizards/AutoConfigTestPage.cpp index ef4affc..1a1044e 100644 --- a/obs-studio/frontend/wizards/AutoConfigTestPage.cpp +++ b/obs-studio/frontend/wizards/AutoConfigTestPage.cpp @@ -467,14 +467,14 @@ struct Result { static void CalcBaseRes(int &baseCX, int &baseCY) { - const int maxBaseArea = 1920 * 1200; - const int clipResArea = 1920 * 1080; + const int maxBaseArea = 3840 * 2160; + const int clipResArea = 3840 * 2160; /* if base resolution unusually high, recalculate to a more reasonable - * value to start the downscaling at, based upon 1920x1080's area. + * value to start the downscaling at, based upon 3840x2160's area. * * for 16:9 resolutions this will always change the starting value to - * 1920x1080 */ + * 3840x2160 */ if ((baseCX * baseCY) > maxBaseArea) { long double xyAspect = (long double)baseCX / (long double)baseCY; baseCY = (int)sqrt((long double)clipResArea / xyAspect); @@ -559,15 +559,15 @@ bool AutoConfigTestPage::TestSoftwareEncoding() int maxDataRate; if (lcores > 8 || pcores > 4) { /* superb */ - maxDataRate = 1920 * 1200 * 60 + 1000; + maxDataRate = 3840 * 2160 * 60 + 1000; } else if (lcores > 4 && pcores == 4) { /* great */ - maxDataRate = 1920 * 1080 * 60 + 1000; + maxDataRate = 2560 * 1440 * 60 + 1000; } else if (pcores == 4) { /* okay */ - maxDataRate = 1920 * 1080 * 30 + 1000; + maxDataRate = 1920 * 1080 * 60 + 1000; } else { /* toaster */ @@ -779,7 +779,7 @@ void AutoConfigTestPage::FindIdealHardwareResolution() int pcores = os_get_physical_cores(); int maxDataRate; if (pcores >= 4) { - maxDataRate = 1920 * 1200 * 60 + 1000; + maxDataRate = 3840 * 2160 * 60 + 1000; } else { maxDataRate = 1280 * 720 * 30 + 1000; } diff --git a/obs-studio/frontend/wizards/AutoConfigVideoPage.cpp b/obs-studio/frontend/wizards/AutoConfigVideoPage.cpp index 549238f..886eea2 100644 --- a/obs-studio/frontend/wizards/AutoConfigVideoPage.cpp +++ b/obs-studio/frontend/wizards/AutoConfigVideoPage.cpp @@ -76,6 +76,8 @@ AutoConfigVideoPage::AutoConfigVideoPage(QWidget *parent) : QWizardPage(parent), ui->canvasRes->addItem(str, encRes); }; + addRes(3840, 2160); + addRes(2560, 1440); addRes(1920, 1080); addRes(1280, 720); diff --git a/obs-studio/plugins/CMakeLists.txt b/obs-studio/plugins/CMakeLists.txt index d9717dd..764f683 100644 --- a/obs-studio/plugins/CMakeLists.txt +++ b/obs-studio/plugins/CMakeLists.txt @@ -84,6 +84,7 @@ add_obs_plugin( PLATFORMS WINDOWS LINUX ARCHITECTURES x64 x86_64 ) +add_obs_plugin(obs-shaderfilter) add_obs_plugin(obs-text PLATFORMS WINDOWS) add_obs_plugin(obs-transitions) add_obs_plugin( diff --git a/obs-studio/plugins/obs-browser/.clang-format b/obs-studio/plugins/obs-browser/.clang-format new file mode 100644 index 0000000..73c5b0f --- /dev/null +++ b/obs-studio/plugins/obs-browser/.clang-format @@ -0,0 +1,209 @@ +# please use clang-format version 16 or later + +Standard: c++17 +AccessModifierOffset: -8 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: false # apparently unpredictable +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +FixNamespaceComments: true +ForEachMacros: + - 'json_object_foreach' + - 'json_object_foreach_safe' + - 'json_array_foreach' + - 'HASH_ITER' +IncludeBlocks: Preserve +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SkipMacroDefinitionBody: true +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +StatementMacros: + - 'Q_OBJECT' +TabWidth: 8 +TypenameMacros: + - 'DARRAY' +UseTab: ForContinuationAndIndentation +--- +Language: ObjC +AccessModifierOffset: 2 +AlignArrayOfStructures: Right +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true +AllowShortBlocksOnASingleLine: Never +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AttributeMacros: ['__unused', '__autoreleasing', '_Nonnull', '__bridge'] +BitFieldColonSpacing: Both +#BreakBeforeBraces: Webkit +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Never + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakArrays: false +BreakBeforeConceptDeclarations: Allowed +BreakBeforeInlineASMColon: OnlyMultiline +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterComma +ColumnLimit: 120 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: Indent +IndentGotoLabels: false +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertBraces: false +InsertNewlineAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +NamespaceIndentation: All +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: false +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PPIndentWidth: -1 +PackConstructorInitializers: NextLine +QualifierAlignment: Leave +ReferenceAlignment: Right +RemoveSemicolon: false +RequiresClausePosition: WithPreceding +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: false +#SortUsingDeclarations: LexicographicNumeric +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInConditionalStatement: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +Standard: c++17 +TabWidth: 4 +UseTab: Never diff --git a/obs-studio/plugins/obs-browser/.cmake-format.json b/obs-studio/plugins/obs-browser/.cmake-format.json new file mode 100644 index 0000000..20610be --- /dev/null +++ b/obs-studio/plugins/obs-browser/.cmake-format.json @@ -0,0 +1,46 @@ +{ + "format": { + "line_width": 120, + "tab_size": 2, + "enable_sort": true, + "autosort": true + }, + "additional_commands": { + "find_qt": { + "flags": [], + "kwargs": { + "COMPONENTS": "+", + "COMPONENTS_WIN": "+", + "COMPONENTS_MACOS": "+", + "COMPONENTS_LINUX": "+" + } + }, + "set_target_properties_obs": { + "pargs": 1, + "flags": [], + "kwargs": { + "PROPERTIES": { + "kwargs": { + "PREFIX": 1, + "OUTPUT_NAME": 1, + "FOLDER": 1, + "VERSION": 1, + "SOVERSION": 1, + "FRAMEWORK": 1, + "BUNDLE": 1, + "AUTOMOC": 1, + "AUTOUIC": 1, + "AUTORCC": 1, + "AUTOUIC_SEARCH_PATHS": 1, + "BUILD_RPATH": 1, + "INSTALL_RPATH": 1, + "XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC": 1, + "XCODE_ATTRIBUTE_CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION": 1, + "XCODE_ATTRIBUTE_GCC_WARN_SHADOW":1 , + "LIBRARY_OUTPUT_DIRECTORY": 1 + } + } + } + } + } +} diff --git a/obs-studio/plugins/obs-browser/.github/actions/check-changes/action.yaml b/obs-studio/plugins/obs-browser/.github/actions/check-changes/action.yaml new file mode 100644 index 0000000..e5690b8 --- /dev/null +++ b/obs-studio/plugins/obs-browser/.github/actions/check-changes/action.yaml @@ -0,0 +1,82 @@ +name: Check For Changed Files +description: Checks for changed files compared to specific git reference and glob expression +inputs: + baseRef: + description: Git reference to check against + required: false + ref: + description: Git reference to check with + required: false + default: HEAD + checkGlob: + description: Glob expression to limit check to specific files + required: false + useFallback: + description: Use fallback compare against prior commit + required: false + default: 'true' + diffFilter: + description: git diff-filter string to use + required: false + default: '' +outputs: + hasChangedFiles: + value: ${{ steps.checks.outputs.hasChangedFiles }} + description: True if specified files were changed in comparison to specified git reference + changedFiles: + value: ${{ steps.checks.outputs.changedFiles }} + description: List of changed files +runs: + using: composite + steps: + - name: Check For Changed Files ✅ + shell: bash + id: checks + env: + GIT_BASE_REF: ${{ inputs.baseRef }} + GIT_REF: ${{ inputs.ref }} + GITHUB_EVENT_FORCED: ${{ github.event.forced }} + GITHUB_REF_BEFORE: ${{ github.event.before }} + USE_FALLBACK: ${{ inputs.useFallback }} + DIFF_FILTER: ${{ inputs.diffFilter }} + run: | + : Check for Changed Files ✅ + if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi + shopt -s extglob + shopt -s dotglob + + # 4b825dc642cb6eb9a060e54bf8d69288fbee4904 is a "hidden" sha1 hash of + # the "empty tree", retrived via 'git hash-object -t tree /dev/null', + # and used here as a last-resort fallback to always provide a valid + # git ref. + + if [[ "${GIT_BASE_REF}" ]]; then + if ! git cat-file -e "${GIT_BASE_REF}" &> /dev/null; then + echo "::warning::Provided base reference ${GIT_BASE_REF} is invalid" + if [[ "${USE_FALLBACK}" == 'true' ]]; then + GIT_BASE_REF='HEAD~1' + fi + fi + else + if ! git cat-file -e ${GITHUB_REF_BEFORE} &> /dev/null; then + GITHUB_REF_BEFORE='4b825dc642cb6eb9a060e54bf8d69288fbee4904' + fi + + GIT_BASE_REF='HEAD~1' + case "${GITHUB_EVENT_NAME}" in + pull_request) GIT_BASE_REF="origin/${GITHUB_BASE_REF}" ;; + push) if [[ "${GITHUB_EVENT_FORCED}" != 'true' ]]; then GIT_BASE_REF="${GITHUB_REF_BEFORE}"; fi ;; + *) ;; + esac + fi + + changes=($(git diff --name-only --diff-filter="${DIFF_FILTER}" ${GIT_BASE_REF} ${GIT_REF} -- ${{ inputs.checkGlob }})) + + if (( ${#changes[@]} )); then + file_string="${changes[*]}" + echo "hasChangedFiles=true" >> $GITHUB_OUTPUT + echo "changedFiles=[\"${file_string// /\",\"}\"]" >> $GITHUB_OUTPUT + else + echo "hasChangedFiles=false" >> $GITHUB_OUTPUT + echo "changedFiles=[]" >> GITHUB_OUTPUT + fi diff --git a/obs-studio/plugins/obs-browser/.github/actions/run-clang-format/action.yaml b/obs-studio/plugins/obs-browser/.github/actions/run-clang-format/action.yaml new file mode 100644 index 0000000..669fe8e --- /dev/null +++ b/obs-studio/plugins/obs-browser/.github/actions/run-clang-format/action.yaml @@ -0,0 +1,60 @@ +name: Run clang-format +description: Runs clang-format and checks for any changes introduced by it +inputs: + failCondition: + description: Controls whether failed checks also fail the workflow run + required: false + default: never + workingDirectory: + description: Working directory for checks + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Check Runner Operating System 🏃‍♂️ + if: runner.os == 'Windows' + shell: bash + run: | + : Check Runner Operating System 🏃‍♂️ + echo "::notice::run-clang-format action requires a macOS-based or Linux-based runner." + exit 2 + + - name: Check for Changed Files ✅ + uses: ./.github/actions/check-changes + id: checks + with: + checkGlob: "'*.c' '*.h' '*.cpp' '*.hpp' '*.m' '*.mm'" + diffFilter: 'ACM' + + - name: Install Dependencies 🛍️ + if: runner.os == 'Linux' && fromJSON(steps.checks.outputs.hasChangedFiles) + shell: bash + run: | + : Install Dependencies 🛍️ + echo ::group::Install Dependencies + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH + echo "/home/linuxbrew/.linuxbrew/opt/clang-format@19/bin" >> $GITHUB_PATH + brew install --quiet zsh + echo ::endgroup:: + + - name: Run clang-format 🐉 + if: fromJSON(steps.checks.outputs.hasChangedFiles) + id: result + shell: zsh --no-rcs --errexit --pipefail {0} + working-directory: ${{ inputs.workingDirectory }} + env: + CHANGED_FILES: ${{ steps.checks.outputs.changedFiles }} + run: | + : Run clang-format 🐉 + if (( ${+RUNNER_DEBUG} )) setopt XTRACE + + print ::group::Install clang-format-19 + brew install --quiet obsproject/tools/clang-format@19 + print ::endgroup:: + + print ::group::Run clang-format-19 + local -a changes=(${(s:,:)CHANGED_FILES//[\[\]\'\"]/}) + ./build-aux/run-clang-format --fail-${{ inputs.failCondition }} --check ${changes} + print ::endgroup:: diff --git a/obs-studio/plugins/obs-browser/.github/workflows/check-format.yaml b/obs-studio/plugins/obs-browser/.github/workflows/check-format.yaml new file mode 100644 index 0000000..0b9a367 --- /dev/null +++ b/obs-studio/plugins/obs-browser/.github/workflows/check-format.yaml @@ -0,0 +1,15 @@ +name: Check Code Formatting 🛠️ +on: + workflow_call: +jobs: + clang-format: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: clang-format Check 🐉 + id: clang-format + uses: ./.github/actions/run-clang-format + with: + failCondition: error diff --git a/obs-studio/plugins/obs-browser/.github/workflows/pr-pull.yaml b/obs-studio/plugins/obs-browser/.github/workflows/pr-pull.yaml new file mode 100644 index 0000000..b2ffa34 --- /dev/null +++ b/obs-studio/plugins/obs-browser/.github/workflows/pr-pull.yaml @@ -0,0 +1,20 @@ +name: Pull +run-name: ${{ github.event.pull_request.title }} pull request run 🚀 +on: + workflow_dispatch: + pull_request: + paths-ignore: + - '**.md' + branches: [master] + types: [ opened, synchronize, reopened ] +permissions: + contents: read +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true +jobs: + check-format: + name: Format 🔍 + uses: ./.github/workflows/check-format.yaml + permissions: + contents: read diff --git a/obs-studio/plugins/obs-browser/.github/workflows/push.yaml b/obs-studio/plugins/obs-browser/.github/workflows/push.yaml new file mode 100644 index 0000000..c6f8521 --- /dev/null +++ b/obs-studio/plugins/obs-browser/.github/workflows/push.yaml @@ -0,0 +1,20 @@ +name: Push +run-name: ${{ github.ref_name }} push run 🚀 +on: + push: + paths-ignore: + - '**.md' + branches: + - master + - 'release/**' + tags: + - '*' +permissions: + contents: write +jobs: + check-format: + name: Format 🔍 + if: github.ref_name == 'master' + uses: ./.github/workflows/check-format.yaml + permissions: + contents: read diff --git a/obs-studio/plugins/obs-browser/.gitignore b/obs-studio/plugins/obs-browser/.gitignore new file mode 100644 index 0000000..6d5e884 --- /dev/null +++ b/obs-studio/plugins/obs-browser/.gitignore @@ -0,0 +1,85 @@ +#binaries +*.exe +*.dll +*.dylib +*.so + +#cmake +/cmbuild/ +/build/ +/build32/ +/build64/ +/release/ +/release32/ +/release64/ +/debug/ +/debug32/ +/debug64/ +/builds/ +*.o.d +*.ninja +.ninja* +.dirstamp + +#xcode +*.xcodeproj/ + +#other stuff (windows stuff, qt moc stuff, etc) +Release_MD/ +Release/ +Debug/ +x64/ +ipch/ +GeneratedFiles/ +.moc/ + +/other/ + +#make stuff +configure +depcomp +install-sh +Makefile.in +Makefile + +#random useless file stuff +*.dmg +*.app +.DS_Store +.directory +.hg +.depend +tags +*.trace +*.vsp +*.psess +*.swp +*.dat +*.clbin +*.log +*.tlog +*.sdf +*.opensdf +*.xml +*.ipch +*.css +*.xslt +*.aps +*.suo +*.ncb +*.user +*.lo +*.ilk +*.la +*.o +*.obj +*.pdb +*.res +*.dep +*.zip +*.lnk +*.chm +*~ +.DS_Store +*/.DS_Store +*/**/.DS_Store diff --git a/obs-studio/plugins/obs-browser/CMakeLists.txt b/obs-studio/plugins/obs-browser/CMakeLists.txt new file mode 100644 index 0000000..ba56c5a --- /dev/null +++ b/obs-studio/plugins/obs-browser/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.16...3.25) + +option(ENABLE_BROWSER "Enable browser source plugin (required Chromium Embedded Framework)" OFF) + +if(NOT ENABLE_BROWSER) + target_disable(obs-browser) + target_disable_feature(obs-browser "Browser sources are not enabled by default (set CEF_ROOT_DIR and ENABLE_BROWSER)") + + return() +endif() + +find_package(CEF 95 REQUIRED) +find_package(nlohmann_json 3.11 REQUIRED) + +add_library(obs-browser MODULE) +add_library(OBS::browser ALIAS obs-browser) + +option(ENABLE_BROWSER_PANELS "Enable Qt web browser panel support" ON) +mark_as_advanced(ENABLE_BROWSER_PANELS) + +target_sources( + obs-browser + PRIVATE # cmake-format: sortable + browser-app.cpp + browser-app.hpp + browser-client.cpp + browser-client.hpp + browser-dummy-client.cpp + browser-dummy-client.hpp + browser-scheme.cpp + browser-scheme.hpp + browser-version.h + cef-headers.hpp + deps/base64/base64.cpp + deps/base64/base64.hpp + deps/ip-string.hpp + deps/signal-restore.cpp + deps/signal-restore.hpp + deps/wide-string.cpp + deps/wide-string.hpp + obs-browser-plugin.cpp + obs-browser-source.cpp + obs-browser-source.hpp) + +target_include_directories(obs-browser PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/deps") + +target_compile_features(obs-browser PRIVATE cxx_std_20) +target_link_libraries(obs-browser PRIVATE OBS::libobs OBS::frontend-api OBS::websocket-api nlohmann_json::nlohmann_json) + +if(OS_WINDOWS) + include(cmake/os-windows.cmake) +elseif(OS_MACOS) + include(cmake/os-macos.cmake) +elseif(OS_LINUX) + include(cmake/os-linux.cmake) +endif() + +if(ENABLE_BROWSER_PANELS) + include(cmake/feature-panels.cmake) +endif() + +set_target_properties_obs(obs-browser PROPERTIES FOLDER plugins/obs-browser PREFIX "") diff --git a/obs-studio/plugins/obs-browser/COPYING b/obs-studio/plugins/obs-browser/COPYING new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/obs-studio/plugins/obs-browser/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General +Public License instead of this License. diff --git a/obs-studio/plugins/obs-browser/README.md b/obs-studio/plugins/obs-browser/README.md new file mode 100644 index 0000000..3e17328 --- /dev/null +++ b/obs-studio/plugins/obs-browser/README.md @@ -0,0 +1,377 @@ +# obs-browser + +obs-browser introduces a cross-platform Browser Source, powered by CEF ([Chromium Embedded Framework](https://bitbucket.org/chromiumembedded/cef/src/master/README.md)), to OBS Studio. A Browser Source allows the user to integrate web-based overlays into their scenes, with complete access to modern web APIs. + +Additionally, obs-browser enables Service Integration (linking third party services) and Browser Docks (webpages loaded into the interface itself) on all supported platforms, except for Wayland (Linux). + +**This plugin is included by default** on official packages on Windows, macOS, the Ubuntu PPA and the official [Flatpak](https://flathub.org/apps/details/com.obsproject.Studio) (most Linux distributions). + +## JS Bindings + +obs-browser provides a global object that allows access to some OBS-specific functionality from JavaScript. This can be used to create an overlay that adapts dynamically to changes in OBS. + +### TypeScript Type Definitions + +If you're using TypeScript, type definitions for the obs-browser bindings are available through npm and yarn. + +```sh +# npm +npm install --save-dev @types/obs-studio + +# yarn +yarn add --dev @types/obs-studio +``` + +### Get Browser Plugin Version + +```js +/** + * @returns {string} OBS Browser plugin version + */ +window.obsstudio.pluginVersion +// => 2.17.0 +``` + +### Register for event callbacks + +```js +/** + * @callback EventListener + * @param {CustomEvent} event + */ + +/** + * @param {string} type + * @param {EventListener} listener + */ +window.addEventListener('obsSceneChanged', function(event) { + var t = document.createTextNode(event.detail.name) + document.body.appendChild(t) +}) +``` + +#### Available events + +Descriptions for these events can be [found here](https://obsproject.com/docs/reference-frontend-api.html?highlight=paused#c.obs_frontend_event). + +* obsSceneChanged +* obsSceneListChanged +* obsTransitionChanged +* obsTransitionListChanged +* obsSourceVisibleChanged +* obsSourceActiveChanged +* obsStreamingStarting +* obsStreamingStarted +* obsStreamingStopping +* obsStreamingStopped +* obsRecordingStarting +* obsRecordingStarted +* obsRecordingPaused +* obsRecordingUnpaused +* obsRecordingStopping +* obsRecordingStopped +* obsReplaybufferStarting +* obsReplaybufferStarted +* obsReplaybufferSaved +* obsReplaybufferStopping +* obsReplaybufferStopped +* obsVirtualcamStarted +* obsVirtualcamStopped +* obsExit +* [Any custom event emitted via obs-websocket vendor requests] + + +### Control OBS +#### Get webpage control permissions +Permissions required: NONE +```js +/** + * @typedef {number} Level - The level of permissions. 0 for NONE, 1 for READ_OBS (OBS data), 2 for READ_USER (User data), 3 for BASIC, 4 for ADVANCED and 5 for ALL + */ + +/** + * @callback LevelCallback + * @param {Level} level + */ + +/** + * @param {LevelCallback} cb - The callback that receives the current control level. + */ +window.obsstudio.getControlLevel(function (level) { + console.log(level) +}) +``` + +#### Get OBS output status +Permissions required: READ_OBS +```js +/** + * @typedef {Object} Status + * @property {boolean} recording - not affected by pause state + * @property {boolean} recordingPaused + * @property {boolean} streaming + * @property {boolean} replaybuffer + * @property {boolean} virtualcam + */ + +/** + * @callback StatusCallback + * @param {Status} status + */ + +/** + * @param {StatusCallback} cb - The callback that receives the current output status of OBS. + */ +window.obsstudio.getStatus(function (status) { + console.log(status) +}) +``` + +#### Get the current scene +Permissions required: READ_USER +```js +/** + * @typedef {Object} Scene + * @property {string} name - name of the scene + * @property {number} width - width of the scene + * @property {number} height - height of the scene + */ + +/** + * @callback SceneCallback + * @param {Scene} scene + */ + +/** + * @param {SceneCallback} cb - The callback that receives the current scene in OBS. + */ +window.obsstudio.getCurrentScene(function(scene) { + console.log(scene) +}) +``` + +#### Get scenes +Permissions required: READ_USER +```js +/** + * @callback ScenesCallback + * @param {string[]} scenes + */ + +/** + * @param {ScenesCallback} cb - The callback that receives the scenes. + */ +window.obsstudio.getScenes(function (scenes) { + console.log(scenes) +}) +``` + +#### Get transitions +Permissions required: READ_USER +```js +/** + * @callback TransitionsCallback + * @param {string[]} transitions + */ + +/** + * @param {TransitionsCallback} cb - The callback that receives the transitions. + */ +window.obsstudio.getTransitions(function (transitions) { + console.log(transitions) +}) +``` + +#### Get current transition +Permissions required: READ_USER +```js +/** + * @callback TransitionCallback + * @param {string} transition + */ + +/** + * @param {TransitionCallback} cb - The callback that receives the transition currently set. + */ +window.obsstudio.getCurrentTransition(function (transition) { + console.log(transition) +}) +``` + +#### Save the Replay Buffer +Permissions required: BASIC +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.saveReplayBuffer() +``` + +#### Start the Replay Buffer +Permissions required: ADVANCED +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.startReplayBuffer() +``` + +#### Stop the Replay Buffer +Permissions required: ADVANCED +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.stopReplayBuffer() +``` + +#### Change scene +Permissions required: ADVANCED +```js +/** + * @param {string} name - Name of the scene + */ +window.obsstudio.setCurrentScene(name) +``` + +#### Set the current transition +Permissions required: ADVANCED +```js +/** + * @param {string} name - Name of the transition + */ +window.obsstudio.setCurrentTransition(name) +``` + +#### Start streaming +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.startStreaming() +``` + +#### Stop streaming +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.stopStreaming() +``` + +#### Start recording +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.startRecording() +``` + +#### Stop recording +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.stopRecording() +``` + +#### Pause recording +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.pauseRecording() +``` + +#### Unpause recording +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.unpauseRecording() +``` + +#### Start the Virtual Camera +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.startVirtualcam() +``` + +#### Stop the Virtual Camera +Permissions required: ALL +```js +/** + * Does not accept any parameters and does not return anything + */ +window.obsstudio.stopVirtualcam() +``` + + +### Register for visibility callbacks + +**This method is legacy. Register an event listener instead.** + +```js +/** + * onVisibilityChange gets callbacks when the visibility of the browser source changes in OBS + * + * @deprecated + * @see obsSourceVisibleChanged + * @param {boolean} visibility - True -> visible, False -> hidden + */ +window.obsstudio.onVisibilityChange = function(visibility) { + +}; +``` + +### Register for active/inactive callbacks + +**This method is legacy. Register an event listener instead.** + +```js +/** + * onActiveChange gets callbacks when the active/inactive state of the browser source changes in OBS + * + * @deprecated + * @see obsSourceActiveChanged + * @param {bool} True -> active, False -> inactive + */ +window.obsstudio.onActiveChange = function(active) { + +}; +``` + +### obs-websocket Vendor +obs-browser includes integration with obs-websocket's Vendor requests. The vendor name to use is `obs-browser`, and available requests are: + +- `emit_event` - Takes `event_name` and ?`event_data` parameters. Emits a custom event to all browser sources. To subscribe to events, see [here](#register-for-event-callbacks) + - See [#340](https://github.com/obsproject/obs-browser/pull/340) for example usage. + +There are no available vendor events at this time. + +## Building + +OBS Browser cannot be built standalone. It is built as part of OBS Studio. + +By following the instructions, this will enable Browser Source & Custom Browser Docks on all three platforms. Both `BUILD_BROWSER` and `CEF_ROOT_DIR` are required. + +### On Windows + +Follow the [build instructions](https://obsproject.com/wiki/Install-Instructions#windows-build-directions) and be sure to download the **CEF Wrapper** and set `CEF_ROOT_DIR` in CMake to point to the extracted wrapper. + +### On macOS + +Use the [macOS Full Build Script](https://obsproject.com/wiki/Install-Instructions#macos-build-directions). This will automatically download & enable OBS Browser. + +### On Linux + +Follow the [build instructions](https://obsproject.com/wiki/Install-Instructions#linux-build-directions) and choose the "If building with browser source" option. This includes steps to download/extract the CEF Wrapper, and set the required CMake variables. diff --git a/obs-studio/plugins/obs-browser/browser-app.cpp b/obs-studio/plugins/obs-browser/browser-app.cpp new file mode 100644 index 0000000..f24350e --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-app.cpp @@ -0,0 +1,498 @@ +/****************************************************************************** + Copyright (C) 2014 by John R. Bradley + Copyright (C) 2023 by Lain Bailey + + 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, see . + ******************************************************************************/ + +#include "browser-app.hpp" +#include "browser-version.h" +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef ENABLE_BROWSER_QT_LOOP +#include +#include +#include +#include +#endif + +#ifndef UNUSED_PARAMETER +#define UNUSED_PARAMETER(x) \ + { \ + (void)x; \ + } +#endif + +#ifdef _WIN32 +constexpr bool kIsPlatformWindows = true; +#else +constexpr bool kIsPlatformWindows = false; +#endif + +CefRefPtr BrowserApp::GetRenderProcessHandler() +{ + return this; +} + +CefRefPtr BrowserApp::GetBrowserProcessHandler() +{ + return this; +} + +CefRefPtr BrowserApp::GetDefaultClient() +{ + return GetDummy(); +} + +void BrowserApp::OnRegisterCustomSchemes(CefRawPtr registrar) +{ + registrar->AddCustomScheme("http", CEF_SCHEME_OPTION_STANDARD | CEF_SCHEME_OPTION_CORS_ENABLED); +} + +void BrowserApp::OnContextInitialized() +{ + // Without a default client, CefBrowser is unmanaged, allowing full-blown Chromium windows outside of our control. + // We don't actually want those, so define a dummy client which will automatically close any such windows. + dummy = new BrowserDummyClient(); + + CefRefPtr requestContext = CefRequestContext::GetGlobalContext(); + CefString errorMessage; + CefRefPtr optionValue = CefValue::Create(); + + constexpr std::array kBrowserFeaturesToDisable{ + "autofill.credit_card_enabled", + "autofill.enabled", + "autofill.iban_enabled", + "autofill.payment_card_benefits", + "autofill.payment_cvc_storage", + "autofill.profile_enabled", + "autologin.enabled", + "browser_labs_enabled", + "credentials_enable_autosignin", + "credentials_enable_service", + "payments.can_make_payment_enabled", + "printing.enabled", + "search.suggest_enabled", + "shopping_list_enabled", + "side_panel.google_search_side_panel_enabled", + "side_search.enabled", + "signin.allowed", + "signin.allowed_on_next_startup", + "translate", + "url_keyed_anonymized_data_collection.enabled"}; + + constexpr std::array kBrowserFeaturesToEnable{"extensions.block_external_extensions", + "extensions.disabled"}; + + optionValue->SetBool(false); + for (std::string_view feature : kBrowserFeaturesToDisable) { + requestContext->SetPreference(feature.data(), optionValue.get(), errorMessage); + } + + optionValue->SetBool(true); + for (std::string_view feature : kBrowserFeaturesToEnable) { + requestContext->SetPreference(feature.data(), optionValue.get(), errorMessage); + } +} + +void BrowserApp::OnBeforeChildProcessLaunch(CefRefPtr command_line) +{ +#ifdef _WIN32 + std::string pid = std::to_string(GetCurrentProcessId()); + command_line->AppendSwitchWithValue("parent_pid", pid); +#else + (void)command_line; +#endif +} + +void BrowserApp::OnBeforeCommandLineProcessing(const CefString &, CefRefPtr command_line) +{ + if (!shared_texture_available) { + bool enableGPU = command_line->HasSwitch("enable-gpu"); + CefString type = command_line->GetSwitchValue("type"); + + if (!enableGPU && type.empty()) { + command_line->AppendSwitch("disable-gpu-compositing"); + } + } + + // See https://github.com/chromiumembedded/cef/issues/3966 for 'StorageNotificationService' requirement. + constexpr std::string_view kDefaultDisabledFeatures{ + "CalculateNativeWinOcclusion,GlobalShortcutsPortal,HardwareMediaKeyHandling,LiveCaption," + "LocalNetworkAccessChecksWebSockets,MediaRouter,StorageNotificationService,WebBluetooth," + "EnableWindowsGamingInputDataFetcher"}; + + std::string disableFeatures{}; + + if constexpr (kIsPlatformWindows) { + disableFeatures.append(kDefaultDisabledFeatures); + } else { + constexpr std::string_view kWindowsFeature{",EnableWindowsGamingInputDataFetcher"}; + constexpr size_t kNonWindowsLength = kDefaultDisabledFeatures.size() - kWindowsFeature.size(); + disableFeatures.append(kDefaultDisabledFeatures.substr(0, kNonWindowsLength)); + } + + if (command_line->HasSwitch("disable-features")) { + // Don't override existing, as this can break OSR + disableFeatures.append(","); + disableFeatures.append(command_line->GetSwitchValue("disable-features")); + } + + if (command_line->HasSwitch("disable-blink-features")) { + std::string disableBlinkFeatures = command_line->GetSwitchValue("disable-blink-features"); + disableBlinkFeatures += ",DocumentPictureInPictureAPI"; + command_line->AppendSwitchWithValue("disable-blink-features", disableBlinkFeatures); + } else { + command_line->AppendSwitchWithValue("disable-blink-features", "DocumentPictureInPictureAPI"); + } + + command_line->AppendSwitchWithValue("autoplay-policy", "no-user-gesture-required"); + command_line->AppendSwitch("disable-extensions"); + command_line->AppendSwitchWithValue("disable-features", disableFeatures); + command_line->AppendSwitch("hide-crash-restore-bubble"); +#ifdef __APPLE__ + command_line->AppendSwitch("use-mock-keychain"); +#elif !defined(_WIN32) + command_line->AppendSwitchWithValue("ozone-platform", wayland ? "wayland" : "x11"); +#endif +} + +std::vector exposedFunctions = {"getControlLevel", "getCurrentScene", "getStatus", + "startRecording", "stopRecording", "startStreaming", + "stopStreaming", "pauseRecording", "unpauseRecording", + "startReplayBuffer", "stopReplayBuffer", "saveReplayBuffer", + "startVirtualcam", "stopVirtualcam", "getScenes", + "setCurrentScene", "getTransitions", "getCurrentTransition", + "setCurrentTransition"}; + +void BrowserApp::OnContextCreated(CefRefPtr browser, CefRefPtr, CefRefPtr context) +{ + CefRefPtr globalObj = context->GetGlobal(); + + CefRefPtr obsStudioObj = CefV8Value::CreateObject(nullptr, nullptr); + globalObj->SetValue("obsstudio", obsStudioObj, V8_PROPERTY_ATTRIBUTE_NONE); + + CefRefPtr pluginVersion = CefV8Value::CreateString(OBS_BROWSER_VERSION_STRING); + obsStudioObj->SetValue("pluginVersion", pluginVersion, V8_PROPERTY_ATTRIBUTE_NONE); + + for (std::string name : exposedFunctions) { + CefRefPtr func = CefV8Value::CreateFunction(name, this); + obsStudioObj->SetValue(name, func, V8_PROPERTY_ATTRIBUTE_NONE); + } + + UNUSED_PARAMETER(browser); +} + +void BrowserApp::ExecuteJSFunction(CefRefPtr browser, const char *functionName, CefV8ValueList arguments) +{ + std::vector names; + browser->GetFrameNames(names); + for (auto &name : names) { + CefRefPtr frame = +#if CHROME_VERSION_BUILD >= 6261 + browser->GetFrameByName(name); +#else + browser->GetFrame(name); +#endif + CefRefPtr context = frame->GetV8Context(); + + context->Enter(); + + CefRefPtr globalObj = context->GetGlobal(); + CefRefPtr obsStudioObj = globalObj->GetValue("obsstudio"); + CefRefPtr jsFunction = obsStudioObj->GetValue(functionName); + + if (jsFunction && jsFunction->IsFunction()) + jsFunction->ExecuteFunction(nullptr, arguments); + + context->Exit(); + } +} + +CefRefPtr CefValueToCefV8Value(CefRefPtr value) +{ + CefRefPtr result; + switch (value->GetType()) { + case VTYPE_INVALID: + result = CefV8Value::CreateNull(); + break; + case VTYPE_NULL: + result = CefV8Value::CreateNull(); + break; + case VTYPE_BOOL: + result = CefV8Value::CreateBool(value->GetBool()); + break; + case VTYPE_INT: + result = CefV8Value::CreateInt(value->GetInt()); + break; + case VTYPE_DOUBLE: + result = CefV8Value::CreateDouble(value->GetDouble()); + break; + case VTYPE_STRING: + result = CefV8Value::CreateString(value->GetString()); + break; + case VTYPE_BINARY: + result = CefV8Value::CreateNull(); + break; + case VTYPE_DICTIONARY: { + result = CefV8Value::CreateObject(nullptr, nullptr); + CefRefPtr dict = value->GetDictionary(); + CefDictionaryValue::KeyList keys; + dict->GetKeys(keys); + for (unsigned int i = 0; i < keys.size(); i++) { + CefString key = keys[i]; + result->SetValue(key, CefValueToCefV8Value(dict->GetValue(key)), V8_PROPERTY_ATTRIBUTE_NONE); + } + } break; + case VTYPE_LIST: { + CefRefPtr list = value->GetList(); + size_t size = list->GetSize(); + result = CefV8Value::CreateArray((int)size); + for (size_t i = 0; i < size; i++) { + result->SetValue((int)i, CefValueToCefV8Value(list->GetValue(i))); + } + } break; +#if !defined(_WIN32) && CHROME_VERSION_BUILD >= 6943 + case VTYPE_NUM_VALUES: + result = CefV8Value::CreateNull(); + break; +#endif + } + return result; +} + +bool BrowserApp::OnProcessMessageReceived(CefRefPtr browser, CefRefPtr frame, + CefProcessId source_process, CefRefPtr message) +{ + UNUSED_PARAMETER(frame); + DCHECK(source_process == PID_BROWSER); + + CefRefPtr args = message->GetArgumentList(); + + if (message->GetName() == "Visibility") { + CefV8ValueList arguments; + arguments.push_back(CefV8Value::CreateBool(args->GetBool(0))); + + ExecuteJSFunction(browser, "onVisibilityChange", arguments); + + } else if (message->GetName() == "Active") { + CefV8ValueList arguments; + arguments.push_back(CefV8Value::CreateBool(args->GetBool(0))); + + ExecuteJSFunction(browser, "onActiveChange", arguments); + + } else if (message->GetName() == "DispatchJSEvent") { + nlohmann::json payloadJson = nlohmann::json::parse(args->GetString(1).ToString(), nullptr, false); + + nlohmann::json wrapperJson; + if (args->GetSize() > 1) + wrapperJson["detail"] = payloadJson; + std::string wrapperJsonString = wrapperJson.dump(); + std::string script; + + script += "new CustomEvent('"; + script += args->GetString(0).ToString(); + script += "', "; + script += wrapperJsonString; + script += ");"; + + std::vector names; + browser->GetFrameNames(names); + for (auto &name : names) { + CefRefPtr frame = +#if CHROME_VERSION_BUILD >= 6261 + browser->GetFrameByName(name); +#else + browser->GetFrame(name); +#endif + CefRefPtr context = frame->GetV8Context(); + + context->Enter(); + + CefRefPtr globalObj = context->GetGlobal(); + + CefRefPtr returnValue; + CefRefPtr exception; + + /* Create the CustomEvent object + * We have to use eval to invoke the new operator */ + context->Eval(script, browser->GetMainFrame()->GetURL(), 0, returnValue, exception); + + CefV8ValueList arguments; + arguments.push_back(returnValue); + + CefRefPtr dispatchEvent = globalObj->GetValue("dispatchEvent"); + dispatchEvent->ExecuteFunction(nullptr, arguments); + + context->Exit(); + } + + } else if (message->GetName() == "executeCallback") { + CefRefPtr context = browser->GetMainFrame()->GetV8Context(); + + context->Enter(); + + CefRefPtr arguments = message->GetArgumentList(); + int callbackID = arguments->GetInt(0); + CefString jsonString = arguments->GetString(1); + + CefRefPtr json = CefParseJSON(arguments->GetString(1).ToString(), {}); + + CefRefPtr callback = callbackMap[callbackID]; + CefV8ValueList args; + + CefRefPtr retval = CefValueToCefV8Value(json); + + args.push_back(retval); + + if (callback) + callback->ExecuteFunction(nullptr, args); + + context->Exit(); + + callbackMap.erase(callbackID); + + } else { + return false; + } + + return true; +} + +bool IsValidFunction(std::string function) +{ + std::vector::iterator iterator; + iterator = std::find(exposedFunctions.begin(), exposedFunctions.end(), function); + return iterator != exposedFunctions.end(); +} + +bool BrowserApp::Execute(const CefString &name, CefRefPtr, const CefV8ValueList &arguments, + CefRefPtr &, CefString &) +{ + if (IsValidFunction(name.ToString())) { + if (arguments.size() >= 1 && arguments[0]->IsFunction()) { + callbackId++; + callbackMap[callbackId] = arguments[0]; + } + + CefRefPtr msg = CefProcessMessage::Create(name); + CefRefPtr args = msg->GetArgumentList(); + args->SetInt(0, callbackId); + + /* Pass on arguments */ + for (u_long l = 0; l < arguments.size(); l++) { + u_long pos; + if (arguments[0]->IsFunction()) + pos = l; + else + pos = l + 1; + + if (arguments[l]->IsString()) + args->SetString(pos, arguments[l]->GetStringValue()); + else if (arguments[l]->IsInt()) + args->SetInt(pos, arguments[l]->GetIntValue()); + else if (arguments[l]->IsBool()) + args->SetBool(pos, arguments[l]->GetBoolValue()); + else if (arguments[l]->IsDouble()) + args->SetDouble(pos, arguments[l]->GetDoubleValue()); + } + + CefRefPtr browser = CefV8Context::GetCurrentContext()->GetBrowser(); + SendBrowserProcessMessage(browser, PID_BROWSER, msg); + + } else { + /* Function does not exist. */ + return false; + } + + return true; +} + +#ifdef ENABLE_BROWSER_QT_LOOP +Q_DECLARE_METATYPE(MessageTask); +MessageObject messageObject; + +void QueueBrowserTask(CefRefPtr browser, BrowserFunc func) +{ + std::lock_guard lock(messageObject.browserTaskMutex); + messageObject.browserTasks.emplace_back(browser, func); + + QMetaObject::invokeMethod(&messageObject, "ExecuteNextBrowserTask", Qt::QueuedConnection); +} + +bool MessageObject::ExecuteNextBrowserTask() +{ + Task nextTask; + { + std::lock_guard lock(browserTaskMutex); + if (!browserTasks.size()) + return false; + + nextTask = browserTasks[0]; + browserTasks.pop_front(); + } + + nextTask.func(nextTask.browser); + return true; +} + +void MessageObject::ExecuteTask(MessageTask task) +{ + task(); +} + +void MessageObject::DoCefMessageLoop(int ms) +{ + if (ms) + QTimer::singleShot((int)ms + 2, []() { CefDoMessageLoopWork(); }); + else + CefDoMessageLoopWork(); +} + +void MessageObject::Process() +{ + CefDoMessageLoopWork(); +} + +void ProcessCef() +{ + QMetaObject::invokeMethod(&messageObject, "DoCefMessageLoop", Qt::QueuedConnection, Q_ARG(int, (int)0)); +} + +#define MAX_DELAY (1000 / 30) + +#if CHROME_VERSION_BUILD < 5938 +void BrowserApp::OnScheduleMessagePumpWork(int64 delay_ms) +#else +void BrowserApp::OnScheduleMessagePumpWork(int64_t delay_ms) +#endif +{ + if (delay_ms < 0) + delay_ms = 0; + else if (delay_ms > MAX_DELAY) + delay_ms = MAX_DELAY; + + if (!frameTimer.isActive()) { + QObject::connect(&frameTimer, &QTimer::timeout, &messageObject, &MessageObject::Process); + frameTimer.setSingleShot(false); + frameTimer.start(33); + } + + QMetaObject::invokeMethod(&messageObject, "DoCefMessageLoop", Qt::QueuedConnection, Q_ARG(int, (int)delay_ms)); +} +#endif diff --git a/obs-studio/plugins/obs-browser/browser-app.hpp b/obs-studio/plugins/obs-browser/browser-app.hpp new file mode 100644 index 0000000..0694d0c --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-app.hpp @@ -0,0 +1,117 @@ +/****************************************************************************** + Copyright (C) 2014 by John R. Bradley + Copyright (C) 2023 by Lain Bailey + + 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, see . + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include "cef-headers.hpp" +#include "browser-dummy-client.hpp" + +typedef std::function)> BrowserFunc; + +#ifdef ENABLE_BROWSER_QT_LOOP +#include +#include +#include +#include + +typedef std::function MessageTask; + +class MessageObject : public QObject { + Q_OBJECT + + friend void QueueBrowserTask(CefRefPtr browser, BrowserFunc func); + + struct Task { + CefRefPtr browser; + BrowserFunc func; + + inline Task() {} + inline Task(CefRefPtr browser_, BrowserFunc func_) : browser(browser_), func(func_) {} + }; + + std::mutex browserTaskMutex; + std::deque browserTasks; + +public slots: + bool ExecuteNextBrowserTask(); + void ExecuteTask(MessageTask task); + void DoCefMessageLoop(int ms); + void Process(); +}; + +extern void QueueBrowserTask(CefRefPtr browser, BrowserFunc func); +#endif + +class BrowserApp : public CefApp, public CefRenderProcessHandler, public CefBrowserProcessHandler, public CefV8Handler { + + void ExecuteJSFunction(CefRefPtr browser, const char *functionName, CefV8ValueList arguments); + + typedef std::map> CallbackMap; + + bool shared_texture_available; + CallbackMap callbackMap; + int callbackId; +#if !defined(__APPLE__) && !defined(_WIN32) + bool wayland; +#endif + +public: +#if defined(__APPLE__) || defined(_WIN32) + inline BrowserApp(bool shared_texture_available_ = false) : shared_texture_available(shared_texture_available_) +#else + inline BrowserApp(bool shared_texture_available_ = false, bool wayland_ = false) + : shared_texture_available(shared_texture_available_), + wayland(wayland_) +#endif + { + } + + virtual CefRefPtr GetRenderProcessHandler() override; + virtual CefRefPtr GetBrowserProcessHandler() override; + virtual CefRefPtr GetDefaultClient() override; + virtual void OnContextInitialized() override; + virtual void OnBeforeChildProcessLaunch(CefRefPtr command_line) override; + virtual void OnRegisterCustomSchemes(CefRawPtr registrar) override; + virtual void OnBeforeCommandLineProcessing(const CefString &process_type, + CefRefPtr command_line) override; + virtual void OnContextCreated(CefRefPtr browser, CefRefPtr frame, + CefRefPtr context) override; + virtual bool OnProcessMessageReceived(CefRefPtr browser, CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) override; + virtual bool Execute(const CefString &name, CefRefPtr object, const CefV8ValueList &arguments, + CefRefPtr &retval, CefString &exception) override; + +#ifdef ENABLE_BROWSER_QT_LOOP +#if CHROME_VERSION_BUILD < 5938 + virtual void OnScheduleMessagePumpWork(int64 delay_ms) override; +#else + virtual void OnScheduleMessagePumpWork(int64_t delay_ms) override; +#endif + QTimer frameTimer; +#endif + + CefRefPtr dummy = nullptr; + + BrowserDummyClient *GetDummy() const { return dummy.get(); }; + + IMPLEMENT_REFCOUNTING(BrowserApp); +}; diff --git a/obs-studio/plugins/obs-browser/browser-client.cpp b/obs-studio/plugins/obs-browser/browser-client.cpp new file mode 100644 index 0000000..1083793 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-client.cpp @@ -0,0 +1,666 @@ +/****************************************************************************** + Copyright (C) 2014 by John R. Bradley + Copyright (C) 2023 by Lain Bailey + + 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, see . + ******************************************************************************/ + +#include "browser-client.hpp" +#include "obs-browser-source.hpp" +#include "base64/base64.hpp" +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) && CHROME_VERSION_BUILD > 4430 +#include +#endif + +#if !defined(_WIN32) && !defined(__APPLE__) +#include + +#include "drm-format.hpp" +#endif + +inline bool BrowserClient::valid() const +{ + return !!bs && !bs->destroying; +} + +CefRefPtr BrowserClient::GetLoadHandler() +{ + return this; +} + +CefRefPtr BrowserClient::GetRenderHandler() +{ + return this; +} + +CefRefPtr BrowserClient::GetDisplayHandler() +{ + return this; +} + +CefRefPtr BrowserClient::GetLifeSpanHandler() +{ + return this; +} + +CefRefPtr BrowserClient::GetContextMenuHandler() +{ + return this; +} + +CefRefPtr BrowserClient::GetAudioHandler() +{ + return reroute_audio ? this : nullptr; +} + +CefRefPtr BrowserClient::GetRequestHandler() +{ + return this; +} + +CefRefPtr BrowserClient::GetResourceRequestHandler(CefRefPtr, + CefRefPtr, + CefRefPtr request, bool, bool, + const CefString &, bool &) +{ + if (request->GetHeaderByName("origin") == "null") { + return this; + } + + return nullptr; +} + +void BrowserClient::OnRenderProcessTerminated(CefRefPtr, TerminationStatus +#if CHROME_VERSION_BUILD >= 6367 + , + int, const CefString &error_string +#endif +) +{ + if (!valid()) + return; + +#if CHROME_VERSION_BUILD >= 6367 + std::string str_text = error_string; +#else + std::string str_text = ""; +#endif + + const char *sourceName = ""; + + if (bs && bs->source) + sourceName = obs_source_get_name(bs->source); + + blog(LOG_ERROR, "[obs-browser: '%s'] Webpage has crashed unexpectedly! Reason: '%s'", sourceName, + str_text.c_str()); +} + +CefResourceRequestHandler::ReturnValue BrowserClient::OnBeforeResourceLoad(CefRefPtr, CefRefPtr, + CefRefPtr, + CefRefPtr) +{ + return RV_CONTINUE; +} + +bool BrowserClient::OnBeforePopup(CefRefPtr, CefRefPtr, +#if CHROME_VERSION_BUILD >= 6834 + int, +#endif + const CefString &, const CefString &, cef_window_open_disposition_t, bool, + const CefPopupFeatures &, CefWindowInfo &, CefRefPtr &, + CefBrowserSettings &, CefRefPtr &, bool *) +{ + /* block popups */ + return true; +} + +void BrowserClient::OnBeforeContextMenu(CefRefPtr, CefRefPtr, CefRefPtr, + CefRefPtr model) +{ + /* remove all context menu contributions */ + model->Clear(); +} + +bool BrowserClient::OnProcessMessageReceived(CefRefPtr browser, CefRefPtr, CefProcessId, + CefRefPtr message) +{ + const std::string &name = message->GetName(); + CefRefPtr input_args = message->GetArgumentList(); + nlohmann::json json; + + if (!valid()) { + return false; + } + + // Fall-through switch, so that higher levels also have lower-level rights + switch (webpage_control_level) { + case ControlLevel::All: + if (name == "startRecording") { + obs_frontend_recording_start(); + } else if (name == "stopRecording") { + obs_frontend_recording_stop(); + } else if (name == "startStreaming") { + obs_frontend_streaming_start(); + } else if (name == "stopStreaming") { + obs_frontend_streaming_stop(); + } else if (name == "pauseRecording") { + obs_frontend_recording_pause(true); + } else if (name == "unpauseRecording") { + obs_frontend_recording_pause(false); + } else if (name == "startVirtualcam") { + obs_frontend_start_virtualcam(); + } else if (name == "stopVirtualcam") { + obs_frontend_stop_virtualcam(); + } + [[fallthrough]]; + case ControlLevel::Advanced: + if (name == "startReplayBuffer") { + obs_frontend_replay_buffer_start(); + } else if (name == "stopReplayBuffer") { + obs_frontend_replay_buffer_stop(); + } else if (name == "setCurrentScene") { + const std::string scene_name = input_args->GetString(1).ToString(); + OBSSourceAutoRelease source = obs_get_source_by_name(scene_name.c_str()); + if (!source) { + blog(LOG_WARNING, + "Browser source '%s' tried to switch to scene '%s' which doesn't exist", + obs_source_get_name(bs->source), scene_name.c_str()); + } else if (!obs_source_is_scene(source)) { + blog(LOG_WARNING, "Browser source '%s' tried to switch to '%s' which isn't a scene", + obs_source_get_name(bs->source), scene_name.c_str()); + } else { + obs_frontend_set_current_scene(source); + } + } else if (name == "setCurrentTransition") { + const std::string transition_name = input_args->GetString(1).ToString(); + obs_frontend_source_list transitions = {}; + obs_frontend_get_transitions(&transitions); + + OBSSourceAutoRelease transition; + for (size_t i = 0; i < transitions.sources.num; i++) { + obs_source_t *source = transitions.sources.array[i]; + if (obs_source_get_name(source) == transition_name) { + transition = obs_source_get_ref(source); + break; + } + } + + obs_frontend_source_list_free(&transitions); + + if (transition) + obs_frontend_set_current_transition(transition); + else + blog(LOG_WARNING, + "Browser source '%s' tried to change the current transition to '%s' which doesn't exist", + obs_source_get_name(bs->source), transition_name.c_str()); + } + [[fallthrough]]; + case ControlLevel::Basic: + if (name == "saveReplayBuffer") { + obs_frontend_replay_buffer_save(); + } + [[fallthrough]]; + case ControlLevel::ReadUser: + if (name == "getScenes") { + struct obs_frontend_source_list list = {}; + obs_frontend_get_scenes(&list); + std::vector scenes_vector; + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + scenes_vector.push_back(obs_source_get_name(source)); + } + json = scenes_vector; + obs_frontend_source_list_free(&list); + } else if (name == "getCurrentScene") { + OBSSourceAutoRelease current_scene = obs_frontend_get_current_scene(); + + if (!current_scene) + return false; + + const char *name = obs_source_get_name(current_scene); + if (!name) + return false; + + json = {{"name", name}, + {"width", obs_source_get_width(current_scene)}, + {"height", obs_source_get_height(current_scene)}}; + } else if (name == "getTransitions") { + struct obs_frontend_source_list list = {}; + obs_frontend_get_transitions(&list); + std::vector transitions_vector; + for (size_t i = 0; i < list.sources.num; i++) { + obs_source_t *source = list.sources.array[i]; + transitions_vector.push_back(obs_source_get_name(source)); + } + json = transitions_vector; + obs_frontend_source_list_free(&list); + } else if (name == "getCurrentTransition") { + OBSSourceAutoRelease source = obs_frontend_get_current_transition(); + json = obs_source_get_name(source); + } + [[fallthrough]]; + case ControlLevel::ReadObs: + if (name == "getStatus") { + json = {{"recording", obs_frontend_recording_active()}, + {"streaming", obs_frontend_streaming_active()}, + {"recordingPaused", obs_frontend_recording_paused()}, + {"replaybuffer", obs_frontend_replay_buffer_active()}, + {"virtualcam", obs_frontend_virtualcam_active()}}; + } + [[fallthrough]]; + case ControlLevel::None: + if (name == "getControlLevel") { + json = (int)webpage_control_level; + } + } + + CefRefPtr msg = CefProcessMessage::Create("executeCallback"); + + CefRefPtr execute_args = msg->GetArgumentList(); + execute_args->SetInt(0, input_args->GetInt(0)); + execute_args->SetString(1, json.dump()); + + SendBrowserProcessMessage(browser, PID_RENDERER, msg); + + return true; +} + +void BrowserClient::GetViewRect(CefRefPtr, CefRect &rect) +{ + if (!valid()) { + rect.Set(0, 0, 16, 16); + return; + } + + rect.Set(0, 0, bs->width < 1 ? 1 : bs->width, bs->height < 1 ? 1 : bs->height); +} + +bool BrowserClient::OnTooltip(CefRefPtr, CefString &text) +{ + std::string str_text = text; + QMetaObject::invokeMethod(QCoreApplication::instance()->thread(), + [str_text]() { QToolTip::showText(QCursor::pos(), str_text.c_str()); }); + return true; +} + +void BrowserClient::OnPaint(CefRefPtr, PaintElementType type, const RectList &, const void *buffer, + int width, int height) +{ + if (type != PET_VIEW) { + // TODO Overlay texture on top of bs->texture + return; + } + +#ifdef ENABLE_BROWSER_SHARED_TEXTURE + if (sharing_available) { + return; + } +#endif + + if (!valid()) { + return; + } + + if (bs->width != width || bs->height != height) { + obs_enter_graphics(); + bs->DestroyTextures(); + obs_leave_graphics(); + } + + if (!bs->texture && width && height) { + obs_enter_graphics(); + bs->texture = gs_texture_create(width, height, GS_BGRA, 1, (const uint8_t **)&buffer, GS_DYNAMIC); + bs->width = width; + bs->height = height; + obs_leave_graphics(); + } else { + obs_enter_graphics(); + gs_texture_set_image(bs->texture, (const uint8_t *)buffer, width * 4, false); + obs_leave_graphics(); + } +} + +#ifdef ENABLE_BROWSER_SHARED_TEXTURE +void BrowserClient::UpdateExtraTexture() +{ + if (bs->texture) { + const uint32_t cx = gs_texture_get_width(bs->texture); + const uint32_t cy = gs_texture_get_height(bs->texture); + const gs_color_format format = gs_texture_get_color_format(bs->texture); + const gs_color_format linear_format = gs_generalize_format(format); + + if (linear_format != format) { + if (!bs->extra_texture || bs->last_format != linear_format || bs->last_cx != cx || + bs->last_cy != cy) { + if (bs->extra_texture) { + gs_texture_destroy(bs->extra_texture); + bs->extra_texture = nullptr; + } + bs->extra_texture = gs_texture_create(cx, cy, linear_format, 1, nullptr, 0); + bs->last_cx = cx; + bs->last_cy = cy; + bs->last_format = linear_format; + } + } else if (bs->extra_texture) { + gs_texture_destroy(bs->extra_texture); + bs->extra_texture = nullptr; + bs->last_cx = 0; + bs->last_cy = 0; + bs->last_format = GS_UNKNOWN; + } + } +} + +void BrowserClient::OnAcceleratedPaint(CefRefPtr, PaintElementType type, const RectList &, +#if CHROME_VERSION_BUILD >= 6367 + const CefAcceleratedPaintInfo &info) +#else + void *shared_handle) +#endif +{ + if (type != PET_VIEW) { + // TODO Overlay texture on top of bs->texture + return; + } + + if (!valid()) { + return; + } + +#if !defined(_WIN32) && !defined(__APPLE__) + if (info.plane_count == 0) + return; + + struct obs_cef_video_format format = obs_cef_format_from_cef_type(info.format); + uint64_t modifier = info.modifier; + + if (format.gs_format == GS_UNKNOWN) + return; + + uint32_t *strides = (uint32_t *)alloca(info.plane_count * sizeof(uint32_t)); + uint32_t *offsets = (uint32_t *)alloca(info.plane_count * sizeof(uint32_t)); + uint64_t *modifiers = (uint64_t *)alloca(info.plane_count * sizeof(uint64_t)); + int *fds = (int *)alloca(info.plane_count * sizeof(int)); + + /* NOTE: This a workaround under X11 where the modifier is always invalid where it can mean "no modifier" in + * Chromium's code. */ + if (obs_get_nix_platform() == OBS_NIX_PLATFORM_X11_EGL && modifier == DRM_FORMAT_MOD_INVALID) + modifier = DRM_FORMAT_MOD_LINEAR; + + for (size_t i = 0; i < kAcceleratedPaintMaxPlanes; i++) { + auto *plane = &info.planes[i]; + + strides[i] = plane->stride; + offsets[i] = plane->offset; + fds[i] = plane->fd; + + modifiers[i] = modifier; + } +#endif + +#if !defined(_WIN32) && CHROME_VERSION_BUILD < 6367 + if (shared_handle == bs->last_handle) + return; +#endif + + obs_enter_graphics(); + + if (bs->texture) { +#ifdef _WIN32 + //gs_texture_release_sync(bs->texture, 0); +#endif + gs_texture_destroy(bs->texture); + bs->texture = nullptr; + } + +#if defined(__APPLE__) && CHROME_VERSION_BUILD > 6367 + bs->texture = gs_texture_create_from_iosurface((IOSurfaceRef)(uintptr_t)info.shared_texture_io_surface); +#elif defined(__APPLE__) && CHROME_VERSION_BUILD > 4183 + bs->texture = gs_texture_create_from_iosurface((IOSurfaceRef)(uintptr_t)shared_handle); +#elif defined(_WIN32) && CHROME_VERSION_BUILD > 4183 + bs->texture = +#if CHROME_VERSION_BUILD >= 6367 + gs_texture_open_nt_shared((uint32_t)(uintptr_t)info.shared_texture_handle); +#else + gs_texture_open_nt_shared((uint32_t)(uintptr_t)shared_handle); +#endif + //if (bs->texture) + // gs_texture_acquire_sync(bs->texture, 1, INFINITE); + +#elif defined(_WIN32) + bs->texture = gs_texture_open_shared((uint32_t)(uintptr_t)shared_handle); +#else + bs->texture = gs_texture_create_from_dmabuf(info.extra.coded_size.width, info.extra.coded_size.height, + format.drm_format, format.gs_format, info.plane_count, fds, strides, + offsets, modifier != DRM_FORMAT_MOD_INVALID ? modifiers : NULL); +#endif + UpdateExtraTexture(); + obs_leave_graphics(); + +#if defined(__APPLE__) && CHROME_VERSION_BUILD >= 6367 + bs->last_handle = info.shared_texture_io_surface; +#elif defined(_WIN32) && CHROME_VERSION_BUILD >= 6367 + bs->last_handle = info.shared_texture_handle; +#elif defined(__APPLE__) || defined(_WIN32) + bs->last_handle = shared_handle; +#endif +} + +#ifdef CEF_ON_ACCELERATED_PAINT2 +void BrowserClient::OnAcceleratedPaint2(CefRefPtr, PaintElementType type, const RectList &, + void *shared_handle, bool new_texture) +{ + if (type != PET_VIEW) { + // TODO Overlay texture on top of bs->texture + return; + } + + if (!valid()) { + return; + } + + if (!new_texture) { + return; + } + + obs_enter_graphics(); + + if (bs->texture) { + gs_texture_destroy(bs->texture); + bs->texture = nullptr; + } + +#if defined(__APPLE__) && CHROME_VERSION_BUILD > 4183 + bs->texture = gs_texture_create_from_iosurface((IOSurfaceRef)(uintptr_t)shared_handle); +#elif defined(_WIN32) && CHROME_VERSION_BUILD > 4183 + bs->texture = gs_texture_open_nt_shared((uint32_t)(uintptr_t)shared_handle); + +#else + bs->texture = gs_texture_open_shared((uint32_t)(uintptr_t)shared_handle); +#endif + UpdateExtraTexture(); + obs_leave_graphics(); +} +#endif +#endif + +static speaker_layout GetSpeakerLayout(CefAudioHandler::ChannelLayout cefLayout) +{ + switch (cefLayout) { + case CEF_CHANNEL_LAYOUT_MONO: + return SPEAKERS_MONO; /**< Channels: MONO */ + case CEF_CHANNEL_LAYOUT_STEREO: + return SPEAKERS_STEREO; /**< Channels: FL, FR */ + case CEF_CHANNEL_LAYOUT_2POINT1: + case CEF_CHANNEL_LAYOUT_2_1: + case CEF_CHANNEL_LAYOUT_SURROUND: + return SPEAKERS_2POINT1; /**< Channels: FL, FR, LFE */ + case CEF_CHANNEL_LAYOUT_2_2: + case CEF_CHANNEL_LAYOUT_QUAD: + case CEF_CHANNEL_LAYOUT_4_0: + return SPEAKERS_4POINT0; /**< Channels: FL, FR, FC, RC */ + case CEF_CHANNEL_LAYOUT_4_1: + case CEF_CHANNEL_LAYOUT_5_0: + case CEF_CHANNEL_LAYOUT_5_0_BACK: + return SPEAKERS_4POINT1; /**< Channels: FL, FR, FC, LFE, RC */ + case CEF_CHANNEL_LAYOUT_5_1: + case CEF_CHANNEL_LAYOUT_5_1_BACK: + return SPEAKERS_5POINT1; /**< Channels: FL, FR, FC, LFE, RL, RR */ + case CEF_CHANNEL_LAYOUT_7_1: + case CEF_CHANNEL_LAYOUT_7_1_WIDE_BACK: + case CEF_CHANNEL_LAYOUT_7_1_WIDE: + return SPEAKERS_7POINT1; /**< Channels: FL, FR, FC, LFE, RL, RR, SL, SR */ + default: + return SPEAKERS_UNKNOWN; + } +} + +void BrowserClient::OnAudioStreamStarted(CefRefPtr browser, const CefAudioParameters ¶ms_, + int channels_) +{ + UNUSED_PARAMETER(browser); + channels = channels_; + channel_layout = (ChannelLayout)params_.channel_layout; + sample_rate = params_.sample_rate; + frames_per_buffer = params_.frames_per_buffer; +} + +void BrowserClient::OnAudioStreamPacket(CefRefPtr browser, const float **data, int frames, int64_t pts) +{ + UNUSED_PARAMETER(browser); + if (!valid()) { + return; + } + struct obs_source_audio audio = {}; + const uint8_t **pcm = (const uint8_t **)data; + speaker_layout speakers = GetSpeakerLayout(channel_layout); + int speaker_count = get_audio_channels(speakers); + for (int i = 0; i < speaker_count; i++) + audio.data[i] = pcm[i]; + audio.samples_per_sec = sample_rate; + audio.frames = frames; + audio.format = AUDIO_FORMAT_FLOAT_PLANAR; + audio.speakers = speakers; + audio.timestamp = (uint64_t)pts * 1000000LLU; + obs_source_output_audio(bs->source, &audio); +} + +void BrowserClient::OnAudioStreamStopped(CefRefPtr browser) +{ + UNUSED_PARAMETER(browser); +} + +void BrowserClient::OnAudioStreamError(CefRefPtr browser, const CefString &message) +{ + UNUSED_PARAMETER(browser); + UNUSED_PARAMETER(message); +} + +static CefAudioHandler::ChannelLayout Convert2CEFSpeakerLayout(int channels) +{ + switch (channels) { + case 1: + return CEF_CHANNEL_LAYOUT_MONO; + case 2: + return CEF_CHANNEL_LAYOUT_STEREO; + case 3: + return CEF_CHANNEL_LAYOUT_2_1; + case 4: + return CEF_CHANNEL_LAYOUT_4_0; + case 5: + return CEF_CHANNEL_LAYOUT_4_1; + case 6: + return CEF_CHANNEL_LAYOUT_5_1; + case 8: + return CEF_CHANNEL_LAYOUT_7_1; + default: + return CEF_CHANNEL_LAYOUT_UNSUPPORTED; + } +} + +bool BrowserClient::GetAudioParameters(CefRefPtr browser, CefAudioParameters ¶ms) +{ + UNUSED_PARAMETER(browser); + int channels = (int)audio_output_get_channels(obs_get_audio()); + params.channel_layout = Convert2CEFSpeakerLayout(channels); + params.sample_rate = (int)audio_output_get_sample_rate(obs_get_audio()); + params.frames_per_buffer = kFramesPerBuffer; + return true; +} + +void BrowserClient::OnLoadEnd(CefRefPtr, CefRefPtr frame, int) +{ + if (!valid()) { + return; + } + + if (frame->IsMain() && bs->css.length()) { + std::string uriEncodedCSS = CefURIEncode(bs->css, false).ToString(); + + std::string script; + script += "const obsCSS = document.createElement('style');"; + script += "obsCSS.appendChild(document.createTextNode(" + "decodeURIComponent(\"" + + uriEncodedCSS + "\")));"; + script += "document.querySelector('head').appendChild(obsCSS);"; + + frame->ExecuteJavaScript(script, "", 0); + } +} + +void BrowserClient::OnLoadError(CefRefPtr, [[maybe_unused]] CefRefPtr frame, + CefLoadHandler::ErrorCode, const CefString &, const CefString &) +{ +#if CHROME_VERSION_BUILD > 6533 + // CEF doesn't currently provide a way to properly disable/override the default Chrome error page. + // https://github.com/obsproject/obs-studio/issues/13499 + // FIXME: https://github.com/chromiumembedded/cef/issues/3852 + frame->LoadURL("about:blank"); +#endif +} + +bool BrowserClient::OnConsoleMessage(CefRefPtr, cef_log_severity_t level, const CefString &message, + const CefString &source, int line) +{ + int errorLevel = LOG_INFO; + const char *code = "Info"; + switch (level) { + case LOGSEVERITY_ERROR: + errorLevel = LOG_WARNING; + code = "Error"; + break; + case LOGSEVERITY_FATAL: + errorLevel = LOG_ERROR; + code = "Fatal"; + break; + default: + return false; + } + + const char *sourceName = ""; + + if (bs && bs->source) + sourceName = obs_source_get_name(bs->source); + + blog(errorLevel, "[obs-browser: '%s'] %s: %s (%s:%d)", sourceName, code, message.ToString().c_str(), + source.ToString().c_str(), line); + return false; +} diff --git a/obs-studio/plugins/obs-browser/browser-client.hpp b/obs-studio/plugins/obs-browser/browser-client.hpp new file mode 100644 index 0000000..069c3ff --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-client.hpp @@ -0,0 +1,154 @@ +/****************************************************************************** + Copyright (C) 2014 by John R. Bradley + Copyright (C) 2023 by Lain Bailey + + 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, see . + ******************************************************************************/ + +#pragma once + +#include +#include +#include "cef-headers.hpp" +#include "obs-browser-source.hpp" + +struct BrowserSource; + +class BrowserClient : public CefClient, + public CefDisplayHandler, + public CefLifeSpanHandler, + public CefRequestHandler, + public CefResourceRequestHandler, + public CefContextMenuHandler, + public CefRenderHandler, + public CefAudioHandler, + public CefLoadHandler { + + bool sharing_available = false; + bool reroute_audio = true; + ControlLevel webpage_control_level = DEFAULT_CONTROL_LEVEL; + + inline bool valid() const; + + void UpdateExtraTexture(); + +public: + BrowserSource *bs; + CefRect popupRect; + CefRect originalPopupRect; + + int sample_rate; + int channels; + ChannelLayout channel_layout; + int frames_per_buffer; + + inline BrowserClient(BrowserSource *bs_, bool sharing_avail, bool reroute_audio_, + ControlLevel webpage_control_level_) + : sharing_available(sharing_avail), + reroute_audio(reroute_audio_), + webpage_control_level(webpage_control_level_), + bs(bs_) + { + } + + /* CefClient */ + virtual CefRefPtr GetLoadHandler() override; + virtual CefRefPtr GetRenderHandler() override; + virtual CefRefPtr GetDisplayHandler() override; + virtual CefRefPtr GetLifeSpanHandler() override; + virtual CefRefPtr GetRequestHandler() override; + virtual CefRefPtr GetContextMenuHandler() override; + virtual CefRefPtr GetAudioHandler() override; + + virtual bool OnProcessMessageReceived(CefRefPtr browser, CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) override; + + /* CefDisplayHandler */ + virtual bool OnConsoleMessage(CefRefPtr browser, cef_log_severity_t level, const CefString &message, + const CefString &source, int line) override; + virtual bool OnTooltip(CefRefPtr browser, CefString &text) override; + + /* CefLifeSpanHandler */ + virtual bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, +#if CHROME_VERSION_BUILD >= 6834 + int popup_id, +#endif + const CefString &target_url, const CefString &target_frame_name, + cef_window_open_disposition_t target_disposition, bool user_gesture, + const CefPopupFeatures &popupFeatures, CefWindowInfo &windowInfo, + CefRefPtr &client, CefBrowserSettings &settings, + CefRefPtr &extra_info, bool *no_javascript_access) override; + + /* CefRequestHandler */ + virtual CefRefPtr + GetResourceRequestHandler(CefRefPtr browser, CefRefPtr frame, + CefRefPtr request, bool is_navigation, bool is_download, + const CefString &request_initiator, bool &disable_default_handling) override; + virtual void OnRenderProcessTerminated(CefRefPtr browser, TerminationStatus status +#if CHROME_VERSION_BUILD >= 6367 + , + int error_code, const CefString &error_string +#endif + ) override; + + /* CefResourceRequestHandler */ + virtual CefResourceRequestHandler::ReturnValue OnBeforeResourceLoad(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr callback) override; + + /* CefContextMenuHandler */ + virtual void OnBeforeContextMenu(CefRefPtr browser, CefRefPtr frame, + CefRefPtr params, + CefRefPtr model) override; + + /* CefRenderHandler */ + virtual void GetViewRect(CefRefPtr browser, CefRect &rect) override; + virtual void OnPaint(CefRefPtr browser, PaintElementType type, const RectList &dirtyRects, + const void *buffer, int width, int height) override; +#ifdef ENABLE_BROWSER_SHARED_TEXTURE + virtual void OnAcceleratedPaint(CefRefPtr browser, PaintElementType type, + const RectList &dirtyRects, +#if CHROME_VERSION_BUILD >= 6367 + const CefAcceleratedPaintInfo &info) override; +#else + void *shared_handle) override; +#endif +#ifdef CEF_ON_ACCELERATED_PAINT2 + virtual void OnAcceleratedPaint2(CefRefPtr browser, PaintElementType type, + const RectList &dirtyRects, void *shared_handle, bool new_texture) override; +#endif +#endif + + virtual void OnAudioStreamPacket(CefRefPtr browser, const float **data, int frames, + int64_t pts) override; + + virtual void OnAudioStreamStopped(CefRefPtr browser) override; + + virtual void OnAudioStreamStarted(CefRefPtr browser, const CefAudioParameters ¶ms, + int channels) override; + virtual void OnAudioStreamError(CefRefPtr browser, const CefString &message) override; + const int kFramesPerBuffer = 1024; + virtual bool GetAudioParameters(CefRefPtr browser, CefAudioParameters ¶ms) override; + + /* CefLoadHandler */ + virtual void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) override; + + virtual void OnLoadError(CefRefPtr browser, CefRefPtr frame, + CefLoadHandler::ErrorCode errorCode, const CefString &errorText, + const CefString &failedUrl) override; + + IMPLEMENT_REFCOUNTING(BrowserClient); +}; diff --git a/obs-studio/plugins/obs-browser/browser-config.h.in b/obs-studio/plugins/obs-browser/browser-config.h.in new file mode 100644 index 0000000..9427ec4 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-config.h.in @@ -0,0 +1,21 @@ +#pragma once + +#ifndef ON +#define ON true +#endif + +#ifndef OFF +#define OFF false +#endif + +#ifndef TRUE +#define TRUE true +#endif + +#ifndef FALSE +#define FALSE false +#endif + +#ifdef __APPLE__ +#define CEF_LIBRARY "@CEF_LIBRARY@" +#endif diff --git a/obs-studio/plugins/obs-browser/browser-dummy-client.cpp b/obs-studio/plugins/obs-browser/browser-dummy-client.cpp new file mode 100644 index 0000000..e891ca7 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-dummy-client.cpp @@ -0,0 +1,87 @@ +/****************************************************************************** + Copyright (C) 2026 by Matt Gajownik + + 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, see . +******************************************************************************/ + +#include "browser-dummy-client.hpp" + +CefRefPtr BrowserDummyClient::GetCommandHandler() +{ + return this; +} + +CefRefPtr BrowserDummyClient::GetRequestHandler() +{ + return this; +} + +CefRefPtr BrowserDummyClient::GetLifeSpanHandler() +{ + return this; +} + +bool BrowserDummyClient::OnChromeCommand(CefRefPtr, int, cef_window_open_disposition_t) +{ + return true; +} + +bool BrowserDummyClient::IsChromeAppMenuItemVisible(CefRefPtr, int) +{ + return false; +} + +bool BrowserDummyClient::IsChromeToolbarButtonVisible(cef_chrome_toolbar_button_type_t) +{ + return false; +} + +bool BrowserDummyClient::IsChromePageActionIconVisible(cef_chrome_page_action_icon_type_t) +{ + return false; +} + +bool BrowserDummyClient::IsChromeAppMenuItemEnabled(CefRefPtr, int) +{ + return false; +} + +bool BrowserDummyClient::OnBeforePopup(CefRefPtr, CefRefPtr, +#if CHROME_VERSION_BUILD >= 6834 + int, +#endif + const CefString &, const CefString &, cef_window_open_disposition_t, bool, + const CefPopupFeatures &, CefWindowInfo &, CefRefPtr &, + CefBrowserSettings &, CefRefPtr &, bool *) +{ + return true; +} + +void BrowserDummyClient::OnAfterCreated(CefRefPtr browser) +{ + if (browser && browser->GetHost()) { + browser->GetHost()->CloseBrowser(false); + } +} + +bool BrowserDummyClient::OnOpenURLFromTab(CefRefPtr, CefRefPtr, const CefString &, + CefRequestHandler::WindowOpenDisposition, bool) +{ + return true; +} + +bool BrowserDummyClient::OnBeforeBrowse(CefRefPtr, CefRefPtr, CefRefPtr, bool, bool) +{ + return true; +} diff --git a/obs-studio/plugins/obs-browser/browser-dummy-client.hpp b/obs-studio/plugins/obs-browser/browser-dummy-client.hpp new file mode 100644 index 0000000..12cf1a5 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-dummy-client.hpp @@ -0,0 +1,64 @@ +/****************************************************************************** + Copyright (C) 2026 by Matt Gajownik + + 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, see . +******************************************************************************/ + +#pragma once + +#include "cef-headers.hpp" + +class BrowserDummyClient : public CefClient, + public CefCommandHandler, + public CefRequestHandler, + public CefLifeSpanHandler { +public: + virtual CefRefPtr GetCommandHandler() override; + virtual CefRefPtr GetRequestHandler() override; + virtual CefRefPtr GetLifeSpanHandler() override; + + /* CefCommandHandler */ + virtual bool OnChromeCommand(CefRefPtr browser, int command_id, + cef_window_open_disposition_t disposition) override; + virtual bool IsChromeAppMenuItemVisible(CefRefPtr browser, int command_id) override; + virtual bool IsChromeToolbarButtonVisible(cef_chrome_toolbar_button_type_t button_type) override; + + virtual bool IsChromePageActionIconVisible(cef_chrome_page_action_icon_type_t icon_type) override; + + virtual bool IsChromeAppMenuItemEnabled(CefRefPtr browser, int command_id) override; + + /* CefLifeSpanHandler */ + virtual bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, +#if CHROME_VERSION_BUILD >= 6834 + int, +#endif + const CefString &target_url, const CefString &target_frame_name, + cef_window_open_disposition_t target_disposition, bool user_gesture, + const CefPopupFeatures &popupFeatures, CefWindowInfo &windowInfo, + CefRefPtr &client, CefBrowserSettings &settings, + CefRefPtr &extra_info, bool *no_javascript_access) override; + + virtual void OnAfterCreated(CefRefPtr browser) override; + + /* CefRequestHandler */ + virtual bool OnOpenURLFromTab(CefRefPtr browser, CefRefPtr frame, + const CefString &target_url, + CefRequestHandler::WindowOpenDisposition target_disposition, + bool user_gesture) override; + + virtual bool OnBeforeBrowse(CefRefPtr browser, CefRefPtr frame, + CefRefPtr request, bool user_gesture, bool is_redirect) override; + + IMPLEMENT_REFCOUNTING(BrowserDummyClient); +}; diff --git a/obs-studio/plugins/obs-browser/browser-scheme.cpp b/obs-studio/plugins/obs-browser/browser-scheme.cpp new file mode 100644 index 0000000..4b3df23 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-scheme.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + Copyright (C) 2014 by John R. Bradley + Copyright (C) 2023 by Lain Bailey + + 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, see . + ******************************************************************************/ + +#include "browser-scheme.hpp" +#include "ip-string.hpp" +#include "wide-string.hpp" +#include + +CefRefPtr BrowserSchemeHandlerFactory::Create(CefRefPtr browser, CefRefPtr, + const CefString &, CefRefPtr request) +{ + if (!browser || !request) + return nullptr; + + CefURLParts parts; + CefParseURL(request->GetURL(), parts); + + std::string path = CefString(&parts.path); + + path = CefURIDecode(path, true, cef_uri_unescape_rule_t::UU_SPACES); + path = CefURIDecode(path, true, cef_uri_unescape_rule_t::UU_URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS); + + std::string fileExtension = path.substr(path.find_last_of(".") + 1); + + for (char &ch : fileExtension) + ch = (char)tolower(ch); + if (fileExtension.compare("woff2") == 0) + fileExtension = "woff"; + + std::string filePath = path.substr(1); + + std::string checkString = path.substr(path.find_first_not_of("/")); + checkString = checkString.substr(0, checkString.find_first_of("/")); + + // An IP address should never be a valid path for CreateForFile normally, but in some cases an OS + // can resolve one as such. As an extra safeguard, we prevent any IP addresses in the path. + if (checkForIpv4String(checkString)) { + return nullptr; + } + + if (checkForIpv6String(checkString)) { + return nullptr; + } + + CefRefPtr stream = CefStreamReader::CreateForFile(filePath); + if (!stream) { + return nullptr; + } + + CefString mimeType = CefGetMimeType(fileExtension); + if (mimeType.empty()) { + mimeType = "application/octet-stream"; + } + + return new CefStreamResourceHandler(mimeType, stream); +} diff --git a/obs-studio/plugins/obs-browser/browser-scheme.hpp b/obs-studio/plugins/obs-browser/browser-scheme.hpp new file mode 100644 index 0000000..323e947 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-scheme.hpp @@ -0,0 +1,31 @@ +/****************************************************************************** + Copyright (C) 2014 by John R. Bradley + Copyright (C) 2023 by Lain Bailey + + 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, see . + ******************************************************************************/ + +#pragma once + +#include "cef-headers.hpp" +#include +#include + +class BrowserSchemeHandlerFactory : public CefSchemeHandlerFactory { +public: + virtual CefRefPtr Create(CefRefPtr browser, CefRefPtr, + const CefString &, CefRefPtr request) override; + + IMPLEMENT_REFCOUNTING(BrowserSchemeHandlerFactory); +}; diff --git a/obs-studio/plugins/obs-browser/browser-version.h b/obs-studio/plugins/obs-browser/browser-version.h new file mode 100644 index 0000000..90f4d77 --- /dev/null +++ b/obs-studio/plugins/obs-browser/browser-version.h @@ -0,0 +1,19 @@ +#pragma once + +#define OBS_BROWSER_VERSION_MAJOR 2 +#define OBS_BROWSER_VERSION_MINOR 27 +#define OBS_BROWSER_VERSION_PATCH 0 + +#ifndef MAKE_SEMANTIC_VERSION +#define MAKE_SEMANTIC_VERSION(major, minor, patch) ((major << 24) | (minor << 16) | patch) +#endif + +#define OBS_BROWSER_VERSION_INT \ + MAKE_SEMANTIC_VERSION(OBS_BROWSER_VERSION_MAJOR, OBS_BROWSER_VERSION_MINOR, OBS_BROWSER_VERSION_PATCH) + +#define OBS_BROWSER_MACRO_STR_(x) #x +#define OBS_BROWSER_MACRO_STR(x) OBS_BROWSER_MACRO_STR_(x) + +#define OBS_BROWSER_VERSION_STRING \ + OBS_BROWSER_MACRO_STR(OBS_BROWSER_VERSION_MAJOR) \ + "." OBS_BROWSER_MACRO_STR(OBS_BROWSER_VERSION_MINOR) "." OBS_BROWSER_MACRO_STR(OBS_BROWSER_VERSION_PATCH) diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_debug b/obs-studio/plugins/obs-browser/build-aux/.functions/log_debug new file mode 100644 index 0000000..6a477a1 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_debug @@ -0,0 +1,3 @@ +if (( ! ${+_loglevel} )) typeset -g _loglevel=1 + +if (( _loglevel > 2 )) print -PR -e -- "${CI:+::debug::}%F{220}DEBUG: ${@}%f" diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_error b/obs-studio/plugins/obs-browser/build-aux/.functions/log_error new file mode 100644 index 0000000..f1c7b43 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_error @@ -0,0 +1,3 @@ +local icon=' ✖︎ ' + +print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}" diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_group b/obs-studio/plugins/obs-browser/build-aux/.functions/log_group new file mode 100644 index 0000000..7b6aca9 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_group @@ -0,0 +1,16 @@ +autoload -Uz log_info + +if (( ! ${+_log_group} )) typeset -g _log_group=0 + +if (( ${+CI} )) { + if (( _log_group )) { + print "::endgroup::" + typeset -g _log_group=0 + } + if (( # )) { + print "::group::${@}" + typeset -g _log_group=1 + } +} else { + if (( # )) log_info ${@} +} diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_info b/obs-studio/plugins/obs-browser/build-aux/.functions/log_info new file mode 100644 index 0000000..d437c29 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_info @@ -0,0 +1,7 @@ +if (( ! ${+_loglevel} )) typeset -g _loglevel=1 + +if (( _loglevel > 0 )) { + local icon=' =>' + + print -PR "%F{4} ${(r:5:)icon}%f %B${@}%b" +} diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_output b/obs-studio/plugins/obs-browser/build-aux/.functions/log_output new file mode 100644 index 0000000..4d9b52f --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_output @@ -0,0 +1,7 @@ +if (( ! ${+_loglevel} )) typeset -g _loglevel=1 + +if (( _loglevel > 0 )) { + local icon='' + + print -PR " ${(r:5:)icon} ${@}" +} diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_status b/obs-studio/plugins/obs-browser/build-aux/.functions/log_status new file mode 100644 index 0000000..950e681 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_status @@ -0,0 +1,7 @@ +if (( ! ${+_loglevel} )) typeset -g _loglevel=1 + +if (( _loglevel > 0 )) { + local icon=' >' + + print -PR "%F{2} ${(r:5:)icon}%f ${@}" +} diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/log_warning b/obs-studio/plugins/obs-browser/build-aux/.functions/log_warning new file mode 100644 index 0000000..ceff982 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/log_warning @@ -0,0 +1,7 @@ +if (( ! ${+_loglevel} )) typeset -g _loglevel=1 + +if (( _loglevel > 0 )) { + local icon=' =>' + + print -PR "${CI:+::warning::}%F{3} ${(r:5:)icon} ${@}%f" +} diff --git a/obs-studio/plugins/obs-browser/build-aux/.functions/set_loglevel b/obs-studio/plugins/obs-browser/build-aux/.functions/set_loglevel new file mode 100644 index 0000000..e32f4bb --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.functions/set_loglevel @@ -0,0 +1,17 @@ +autoload -Uz log_debug log_error + +local -r _usage="Usage: %B${0}%b + +Set log level, following levels are supported: 0 (quiet), 1 (normal), 2 (verbose), 3 (debug)" + +if (( ! # )); then + log_error 'Called without arguments.' + log_output ${_usage} + return 2 +elif (( ${1} >= 4 )); then + log_error 'Called with loglevel > 3.' + log_output ${_usage} +fi + +typeset -g -i -r _loglevel=${1} +log_debug "Log level set to '${1}'" diff --git a/obs-studio/plugins/obs-browser/build-aux/.run-format.zsh b/obs-studio/plugins/obs-browser/build-aux/.run-format.zsh new file mode 100644 index 0000000..ab74d28 --- /dev/null +++ b/obs-studio/plugins/obs-browser/build-aux/.run-format.zsh @@ -0,0 +1,276 @@ +#!/usr/bin/env zsh + +builtin emulate -L zsh +setopt EXTENDED_GLOB +setopt PUSHD_SILENT +setopt ERR_EXIT +setopt ERR_RETURN +setopt NO_UNSET +setopt PIPE_FAIL +setopt NO_AUTO_PUSHD +setopt NO_PUSHD_IGNORE_DUPS +setopt FUNCTION_ARGZERO + +## Enable for script debugging +# setopt WARN_CREATE_GLOBAL +# setopt WARN_NESTED_VAR +# setopt XTRACE + +autoload -Uz is-at-least && if ! is-at-least 5.2; then + print -u2 -PR "%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade zsh to fix this issue." + exit 1 +fi + +invoke_formatter() { + if (( # < 1 )) { + log_error "Usage invoke_formatter [formatter_name]" + exit 2 + } + + local formatter="${1}" + shift + local -a source_files=(${@}) + + case ${formatter} { + clang) + if (( ${+commands[clang-format-19]} )) { + local formatter=clang-format-19 + } elif (( ${+commands[clang-format]} )) { + local formatter=clang-format + } else { + log_error "No viable clang-format version found (required 19.1.1)" + exit 2 + } + + local -a formatter_version=($(${formatter} --version)) + + if ! is-at-least 19.1.1 ${formatter_version[-1]}; then + log_error "clang-format is not version 19.1.1 or above (found ${formatter_version[-1]}." + exit 2 + fi + + if ! is-at-least ${formatter_version[-1]} 19.1.1; then + log_error "clang-format is more recent than version 19.1.1 (found ${formatter_version[-1]})." + exit 2 + fi + + if (( ! #source_files )) source_files=((libobs|libobs-*|frontend|plugins|deps|shared|test)/**/*.(c|cpp|h|hpp|m|mm)(.N)) + + source_files=(${source_files:#*/(obs-websocket/deps|decklink/*/decklink-sdk|mac-syphon/syphon-framework|libdshowcapture)/*}) + + local -a format_args=(-style=file -fallback-style=none) + if (( _loglevel > 2 )) format_args+=(--verbose) + + check_files() { + local -i num_failures=0 + local -a source_files=($@) + local file + local -a format_args=(-style=file -fallback-style=none) + if (( _loglevel > 2 )) format_args+=(--verbose) + + local -a command=(${formatter} ${format_args}) + + for file (${source_files}) { + if ! ${command} "${file}" | diff -q "${file}" - &> /dev/null; then + log_error "${file} requires formatting changes." + if (( fail_on_error == 2 )) return 2; + num_failures=$(( num_failures + 1 )) + fi + } + if (( num_failures && fail_on_error == 1 )) return 2 + } + + format_files() { + local -a source_files=($@) + + if (( ${#source_files} )) { + local -a format_args=(-style=file -fallback-style=none -i) + if (( _loglevel > 2 )) format_args+=(--verbose) + + "${formatter}" ${format_args} ${source_files} + } + } + ;; + gersemi) + local formatter=gersemi + if (( ${+commands[gersemi]} )) { + local gersemi_version=($(gersemi --version)) + + if ! is-at-least 0.21.0 ${gersemi_version[2]}; then + log_error "gersemi is not version 0.21.0 or above (found ${gersemi_version[2]}." + exit 2 + fi + } + + if (( ! #source_files )) source_files=(CMakeLists.txt (libobs|libobs-*|frontend|plugins|deps|shared|cmake|test)/**/(CMakeLists.txt|*.cmake)(.N)) + + source_files=(${source_files:#*/(jansson|decklink/*/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)/*}) + source_files=(${source_files:#(cmake/Modules/*|*/legacy.cmake)}) + + check_files() { + local -i num_failures=0 + local -a source_files=($@) + local file + local -a command=(${formatter} -c --no-cache ${source_files}) + + if (( ${#source_files} )) { + while read -r line; do + local -a line_tokens=(${(z)line}) + if (( #line_tokens )) { + file=${line_tokens[1]//*${project_root}\//} + + log_error "${file} requires formatting changes." + } else { + log_error "${line}" + } + + if (( fail_on_error == 2 )) return 2 + num_failures=$(( num_failures + 1 )) + done < <(${command} 2>&1) + + if (( num_failures && fail_on_error == 1 )) return 2 + } + } + + format_files() { + local -a source_files=($@) + + if (( ${#source_files} )) { + "${formatter}" -i ${source_files} + } + } + ;; + swift) + local formatter=swift-format + if (( ${+commands[swift-format]} )) { + local swift_format_version=$(swift-format --version) + + if ! is-at-least 508.0.0 ${swift_format_version}; then + log_error "swift-format is not version 508.0.0 or above (found ${swift_format_version})." + exit 2 + fi + } else { + log_error "No viable swift-format version found (required 508.0.0)" + exit 2 + } + + if (( ! #source_files )) source_files=((libobs|libobs-*|frontend|plugins)/**/*.swift(.N)) + + check_files() { + local -i num_failures=0 + local -a source_files=($@) + local file + local -a format_args=() + + local -a command=(${formatter} ${format_args}) + + for file (${source_files}) { + if ! "${command}" "${file}" | diff -q "${file}" - &> /dev/null; then + log_error "${file} requires formatting changes." + if (( fail_on_error == 2 )) return 2; + num_failures=$(( num_failures + 1 )) + fi + } + if (( num_failures && fail_on_error == 1 )) return 2 + } + + format_files() { + local -a source_files=($@) + + if (( ${#source_files} )) { + local -a format_args=(-i) + + "${formatter}" ${format_args} ${source_files} + } + } + ;; + *) log_error "Invalid formatter specified: ${1}. Valid options are clang-format, gersemi, and swift-format."; exit 2 ;; + } + + local file + local -i num_failures=0 + if (( check_only )) { + if (( ${+functions[check_files]} )) { + check_files ${source_files} + } else { + log_error "No format check function defined for formatter '${formatter}'" + exit 2 + } + } else { + if (( ${+functions[format_files]} )) { + format_files ${source_files} + } else { + log_error "No format function defined for formatter '${formatter}'" + exit 2 + } + } +} + +run_format() { + if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h} + if (( ! ${+FORMATTER_NAME} )) typeset -g FORMATTER_NAME=${${(s:-:)ZSH_ARGZERO:t:r}[2]} + local project_root=${SCRIPT_HOME:A:h} + + typeset -g host_os=${${(L)$(uname -s)}//darwin/macos} + local -i fail_on_error=0 + local -i check_only=0 + local -i verbosity=1 + local -r _version='1.0.0' + + fpath=("${SCRIPT_HOME}/.functions" ${fpath}) + autoload -Uz set_loglevel log_info log_error log_output log_status log_warning + + local -r _usage=" +Usage: %B${functrace[1]%:*}%b