CAIIC 定制版 v1.0.0:英文默认界面、2K/4K 支持、集成抠图插件、更换品牌图标

界面与品牌
- 默认 UI 语言固定为英文,不再跟随系统区域设置自动切换(frontend/OBSApp.cpp),用户仍可在设置中手动切换
- 程序图标更换为 CAIIC logo:exe 图标 obs-studio.ico 及界面/托盘/About 图标 obs.png
- About 窗口精简为 logo + 名称 + 版本号,移除项目介绍、赞助名单、Authors/License 标签页
- 版本号定为 1.0.0(构建参数 OBS_VERSION_OVERRIDE=1.0.0)

分辨率支持
- 移除首次启动将画布强制限制为 1920x1080 的逻辑,默认使用显示器原生分辨率
- 设置-视频 与自动配置向导新增 3840x2160 / 2560x1440 预设
- 自动配置向导性能测试上限放宽至 4K60(软硬件编码数据率上限及起始分辨率钳制)

插件
- 集成 obs-shaderfilter 2.6.0(绿幕抠图/自定义着色器滤镜),并适配新版 libobs 的 gs_image_file_ex API
- 补齐缺失的源码:obs-browser、obs-websocket、libdshowcapture(含 external/capture-device-support)

工程
- 新增 .gitignore,排除 deploy/ 发布目录与构建日志
This commit is contained in:
evan.liu 2026-09-18 20:41:13 +08:00
parent 438b2bada3
commit d0074858bf
643 changed files with 83639 additions and 408 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/deploy/
/build_log.txt
/configure_log.txt
/icon_preview.png

BIN
caiic_logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

@ -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

View File

@ -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
*~

View File

@ -0,0 +1,3 @@
[submodule "external/capture-device-support"]
path = external/capture-device-support
url = https://github.com/elgatosf/capture-device-support

View File

@ -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
$<$<CONFIG:DEBUG>:DEBUG>
$<$<CONFIG:DEBUG>:_DEBUG>
$<$<BOOL:${BUILD_SHARED_LIBS}>:DSHOWCAPTURE_EXPORTS>
_UP_WINDOWS=1
)
target_link_libraries(
libdshowcapture
PRIVATE setupapi strmiids ksuser winmm wmcodecdspuuid
)

View File

@ -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.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@ -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.

View File

@ -0,0 +1,306 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <vector>
#include <string>
#include <functional>
#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<void(const VideoConfig &config, unsigned char *data,
size_t size, long long startTime, long long stopTime,
long rotation)>
VideoProc;
typedef std::function<void(const AudioConfig &config, unsigned char *data,
size_t size, long long startTime, long long stopTime)>
AudioProc;
typedef std::function<void()> 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<VideoInfo> caps;
};
struct AudioDevice : DeviceId {
std::vector<AudioInfo> 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<VideoDevice> &devices);
static bool EnumAudioDevices(std::vector<AudioDevice> &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<DeviceId> &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);
};

View File

@ -0,0 +1,3 @@
Language: Cpp
SortIncludes: false
DisableFormat: true

View File

@ -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/

View File

@ -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()

View File

@ -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.

View File

@ -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 <vector>
#include <mutex>
#include <thread>
//==============================================================================
// # 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();
};

View File

@ -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 <vector>
#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<uint8_t>& 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<uint8_t>& inMessage, int inReportID) = 0;
};
//! @brief Platform specific factory method
std::shared_ptr<EGAVHIDInterface> CreateEGAVHIDInterface();

View File

@ -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 <winerror.h> // 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;
}

View File

@ -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 <string>
#if _UP_WINDOWS
// FMB NOTE: For some strange reason <WinSock2.h> must be before <Windows.h>.
// This is only necessary because contents of <WinSock2.h> are required in other code files.
// However, if <WinSock2.h> is include somewhere it must be guaranteded that <Windows.h> was never include before.
#define NOMINMAX
#include <WinSock2.h> // for sockaddr_in
#include <Windows.h>
#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

View File

@ -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<EGAVDeviceID> GetElgatoUVCDeviceIDs()
{
std::vector<EGAVDeviceID> 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<EGAVHIDInterface> 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<std::recursive_mutex> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<std::recursive_mutex> lock(mHIDMutex);
EGAVResult res = EGAVResult::ErrUnknown;
if (mNewDeviceType)
{
const uint8_t writeLen = 1 + inLength /* +1 for byte register address*/, reportLen = 4 + writeLen;
std::vector<uint8_t> 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<uint8_t> 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<std::recursive_mutex> 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<std::recursive_mutex> 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;
}

View File

@ -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 <memory>
#include <vector>
#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<EGAVDeviceID> GetElgatoUVCDeviceIDs();
//! @return true for new devices with new USB chipset
bool IsNewDeviceType(const EGAVDeviceID& inDeviceID);
//==============================================================================
// # Class ElgatoUVCDevice
//==============================================================================
class ElgatoUVCDevice
{
public:
ElgatoUVCDevice(std::shared_ptr<EGAVHIDInterface> 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<EGAVHIDInterface> mHIDImpl;
std::recursive_mutex mHIDMutex;
};

View File

@ -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 <string>
#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);
}

View File

@ -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 <IOKit/IOKitLib.h>
#include <thread>
#include <chrono>
//------------------------------------------------------------------------------
// PREPROCESSOR SWITCHES
//------------------------------------------------------------------------------
std::shared_ptr<EGAVHIDInterface> CreateEGAVHIDInterface()
{
return std::make_shared<EGAVHID>();
}
//==============================================================================
// # 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<EGAVHID*>(inContext)->DeviceAdded(inIOHIDDeviceRef);
}
static void HIDDeviceRemovalCallback(void* inContext,
IOReturn /*inResult*/,
void* /*inSender*/,
IOHIDDeviceRef inIOHIDDeviceRef)
{
reinterpret_cast<EGAVHID*>(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<uint8_t>& outMessage, int inReportId, int inReadBufferSize /*= 0*/)
{
EGAVResult_CheckPointer(mHIDDevice);
EGAVResult res = EGAVResult::Ok;
std::vector<uint8_t> 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<uint8_t>& inMessage, int inReportID)
{
// dbgFunctionI();
EGAVResult_CheckPointer(mHIDDevice);
std::vector<uint8_t> 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;
}

View File

@ -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 <memory>
#include <thread>
// macOS
#include <IOKit/hid/IOHIDManager.h>
#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<uint8_t>& 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<uint8_t>& inMessage, int inReportID) override;
private:
int mLocationID = 0;
int mInputReportSize = 0, mOutputReportSize = 0;
std::atomic<IOHIDDeviceRef> mHIDDevice = nullptr;
std::atomic<CFRunLoopRef> mRunLoop = nullptr;
std::thread mWorker; //!< background worker for HID device discovery
bool mWorkerCreated = false;
};

View File

@ -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 <sstream> // for ostringstream
// Windows headers; for HID interface
#include <hidsdi.h>
#pragma comment(lib,"hid.lib")
#include <SetupAPI.h>
#include <atlstr.h>
#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<EGAVHIDInterface> CreateEGAVHIDInterface()
{
return std::make_shared<EGAVHID>();
}
//==============================================================================
// ## 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<uint8_t>& outMessage, int inReportID, int inReadBufferSize/* = 0*/)
{
if (outMessage.size() >= mHIDCaps->InputReportByteLength)
return EGAVResult::ErrInvalidParameter;
std::vector<uint8_t> 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<uint8_t>& inMessage, int inReportID)
{
if (!mHIDCaps)
return EGAVResult::ErrInvalidState;
if (inMessage.size() > mHIDCaps->OutputReportByteLength-1)
return EGAVResult::ErrInvalidParameter;
std::vector<uint8_t> 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;
}

View File

@ -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 <memory>
#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<uint8_t>& 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<uint8_t>& inMessage, int inReportID) override;
HANDLE GetHIDHandle() { return mHIDHandle; }
private:
HANDLE mHIDHandle = nullptr;
std::unique_ptr<_HIDP_CAPS> mHIDCaps;
};

View File

@ -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, &param, 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, &param, 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;
}

View File

@ -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 <atlcomcli.h>
#include <strmif.h>
#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<IKsPropertySet> mICustomPropertySet;
};

View File

@ -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 <iostream>
#include <memory>
#include <thread>
#include <chrono>
#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<EGAVHIDInterface> hid = std::make_shared<EGAVHID>();
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;
}

View File

@ -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.

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<typename T> 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;
}
};

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 T> 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<T> &c) : ptr(c.ptr)
{
if (ptr)
ptr->AddRef();
}
inline ComPtr(ComPtr<T> &&c) noexcept : ptr(c.ptr) { c.ptr = nullptr; }
inline ~ComPtr() { Kill(); }
inline void Clear()
{
if (ptr) {
ptr->Release();
ptr = nullptr;
}
}
inline ComPtr<T> &operator=(T *p)
{
Replace(p);
return *this;
}
inline ComPtr<T> &operator=(const ComPtr<T> &c)
{
Replace(c.ptr);
return *this;
}
inline ComPtr<T> &operator=(ComPtr<T> &&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 T> class ComQIPtr : public ComPtr<T> {
public:
inline ComQIPtr(IUnknown *unk)
{
this->ptr = nullptr;
unk->QueryInterface(__uuidof(T), (void **)&this->ptr);
}
inline ComPtr<T> &operator=(IUnknown *unk)
{
ComPtr<T>::Clear();
unk->QueryInterface(__uuidof(T), (void **)&this->ptr);
return *this;
}
};

View File

@ -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. <morris.pan@avermedia.com>
*
* 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;
};

View File

@ -0,0 +1,696 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 */

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<void(IMediaSample *sample)> callback;
GUID expectedMajorType{};
GUID expectedSubType{};
};
class CapturePin : public IPin, public IMemInputPin {
friend class CaptureEnumMediaTypes;
volatile long refCount;
PinCaptureInfo captureInfo;
ComPtr<IPin> 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<IFilterGraph> graph;
ComPtr<CapturePin> pin;
ComPtr<IAMFilterMiscFlags> 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<CaptureFilter> 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<CapturePin> 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 */

View File

@ -0,0 +1,204 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <ElgatoUVCDevice.h>
#include "../external/capture-device-support/SampleCode/DriverInterface.h"
#include "device.hpp"
#include "log.hpp"
#include <cinttypes>
#include <vidcap.h>
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<IKsTopologyInfo> 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<IKsControl> 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<EGAVHIDInterface> 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<IKsPropertySet> propertySet =
ComQIPtr<IKsPropertySet>(filter);
if (propertySet) {
SetTonemapperAvermedia(propertySet, enable);
SetTonemapperAvermedia2(filter, enable);
SetTonemapperElgato(propertySet, enable);
}
}
}
} /* namespace DShow */

View File

@ -0,0 +1,875 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<IAMCameraControl> cc(videoFilter);
if (cc) {
long ccf = 0;
cc->Get(CameraControl_Roll, &roll, &ccf);
}
}
if (isVideo && videoConfig.reactivateCallback) {
ComQIPtr<IKsPropertySet> 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<WAVEFORMATEX *>(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<IPin> 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<IEnumMediaTypes> 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<IPin> 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<IAMStreamConfig> 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<IBaseFilter> 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<IKsPropertySet> propertySet = ComQIPtr<IKsPropertySet>(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<IEnumMediaTypes> 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<IPin> 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<IAMStreamConfig> 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<IBaseFilter> 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<IBaseFilter> 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<IPin> 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<IBaseFilter> crossbar;
ComPtr<IPin> filterPin;
ComPtr<IPin> 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<IPin> pin;
bool success = GetFilterPin(audioFilter, MEDIATYPE_Audio,
PIN_CATEGORY_CAPTURE, PINDIR_OUTPUT, &pin);
if (!success)
return;
ComQIPtr<IAMStreamConfig> config(pin);
if (!config)
return;
ComQIPtr<IAMBufferNegotiation> 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<IEnumFilters> filterEnum;
HRESULT hr;
if (!graph)
return;
hr = graph->EnumFilters(&filterEnum);
if (FAILED(hr))
return;
ComPtr<IBaseFilter> 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 */

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <string>
#include <vector>
using namespace std;
namespace DShow {
struct EncodedData {
long long lastStartTime = 0;
long long lastStopTime = 0;
vector<unsigned char> bytes;
};
struct EncodedDevice {
VideoFormat videoFormat;
ULONG videoPacketID;
long width;
long height;
long long frameInterval;
AudioFormat audioFormat;
ULONG audioPacketID;
DWORD samplesPerSec;
};
struct HDevice {
ComPtr<IGraphBuilder> graph;
ComPtr<ICaptureGraphBuilder2> builder;
ComPtr<IMediaControl> control;
ComPtr<IBaseFilter> videoFilter;
ComPtr<IBaseFilter> audioFilter;
ComPtr<CaptureFilter> videoCapture;
ComPtr<CaptureFilter> audioCapture;
ComPtr<IBaseFilter> audioOutput;
ComPtr<IBaseFilter> 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 */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <windows.h>
#include <dshow.h>
#include <ks.h>
#include <ksmedia.h>
#include <ksproxy.h>
#include <Amaudio.h>
#include <Dvdmedia.h>
#include "ComPtr.hpp"
#include "CoTaskMemPtr.hpp"
#include <string>
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 */

View File

@ -0,0 +1,156 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<IMpeg2Demultiplexer> demuxer(demuxFilter);
if (!demuxer) {
Warning(L"CreateDemuxVideoPin: Failed to get "
L"IMpeg2Demultiplexer from filter");
return false;
}
ComPtr<IPin> pin;
HRESULT hr;
VIDEOINFOHEADER *vih = mt.AllocFormat<VIDEOINFOHEADER>();
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<IMpeg2Demultiplexer> demuxer(demuxFilter);
if (!demuxer) {
Warning(L"CreateDemuxAudioPin: Failed to get "
L"IMpeg2Demultiplexer from filter");
return false;
}
ComPtr<IPin> pin;
HRESULT hr;
WAVEFORMATEX *wfex = mt.AllocFormat<WAVEFORMATEX>();
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 */

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 */

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 */

View File

@ -0,0 +1,260 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<IPin> inputPin;
ComPtr<IPin> 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<IPin> 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<IKsPropertySet> 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<IBaseFilter> crossbar;
ComPtr<IBaseFilter> encoder;
ComPtr<IBaseFilter> 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 */

View File

@ -0,0 +1,573 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <stdlib.h>
#include <mutex>
#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<IEnumMediaTypes> 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<IAMStreamConfig> config(pin);
int count, size;
if (config == NULL)
return false;
hr = config->GetNumberOfCapabilities(&count, &size);
if (SUCCEEDED(hr)) {
vector<BYTE> 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<const VIDEO_STREAM_CONFIG_CAPS *>(data);
viHeader = reinterpret_cast<const VIDEOINFOHEADER *>(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<const AUDIO_STREAM_CONFIG_CAPS *>(data);
wfex = reinterpret_cast<const WAVEFORMATEX *>(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<IPin> 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<IPin> 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<VideoInfo> &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<VideoInfo> &caps)
{
return EnumPinCaps(pin, EnumCapsCallback(EnumVideoCap), &caps);
}
static bool EnumAudioCap(vector<AudioInfo> &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<AudioInfo> &caps)
{
return EnumPinCaps(pin, EnumCapsCallback(EnumAudioCap), &caps);
}
static bool decklinkVideoPresent = false;
static bool EnumDevice(const GUID &type, IMoniker *deviceInfo,
EnumDeviceCallback callback, void *param)
{
ComPtr<IPropertyBag> propertyData;
ComPtr<IBaseFilter> 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<IBaseFilter> 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<recursive_mutex> lock(enumMutex);
ComPtr<ICreateDevEnum> deviceEnum;
ComPtr<IEnumMoniker> enumMoniker;
ComPtr<IMoniker> 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 */

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <vector>
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<VideoInfo> &caps);
bool EnumAudioCaps(IPin *pin, vector<AudioInfo> &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 */

View File

@ -0,0 +1,335 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 */

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <wmcodecdsp.h>
#include <mmreg.h>
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 */

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<VIDEOINFOHEADER *>(mt.pbFormat);
return &vih->bmiHeader;
} else if (mt.formattype == FORMAT_VideoInfo2) {
VIDEOINFOHEADER2 *vih;
vih = reinterpret_cast<VIDEOINFOHEADER2 *>(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<const VIDEOINFOHEADER *>(mt.pbFormat);
return &vih->bmiHeader;
} else if (mt.formattype == FORMAT_VideoInfo2) {
const VIDEOINFOHEADER2 *vih;
vih = reinterpret_cast<const VIDEOINFOHEADER2 *>(mt.pbFormat);
return &vih->bmiHeader;
}
return NULL;
}
}; /* namespace DShow */

View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<typename T> 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 */

View File

@ -0,0 +1,313 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <vector>
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<ISpecifyPropertyPages> 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<IUnknown> 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<IAMCrossbar> xbar(ptr);
ComQIPtr<IBaseFilter> 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<VideoDevice> &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<VideoDevice> &devices,
IBaseFilter *filter,
const wchar_t *deviceName,
const wchar_t *devicePath)
{
ComPtr<IPin> 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<VideoDevice> &devices,
IBaseFilter *filter, const wchar_t *deviceName,
const wchar_t *devicePath)
{
ComPtr<IPin> pin;
ComPtr<IPin> audioPin;
ComPtr<IBaseFilter> 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<VideoDevice> &devices)
{
devices.clear();
return EnumDevices(CLSID_VideoInputDeviceCategory,
EnumDeviceCallback(EnumVideoDevice), &devices);
}
static bool EnumAudioDevice(vector<AudioDevice> &devices, IBaseFilter *filter,
const wchar_t *deviceName,
const wchar_t *devicePath)
{
ComPtr<IPin> 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<AudioDevice> &devices)
{
devices.clear();
return EnumDevices(CLSID_AudioInputDeviceCategory,
EnumDeviceCallback(EnumAudioDevice), &devices);
}
}; /* namespace DShow */

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<DeviceId> &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<DeviceId> &encoders)
{
encoders.clear();
return EnumDevices(KSCATEGORY_ENCODER,
EnumDeviceCallback(EnumVideoEncoder), &encoders);
}
};

View File

@ -0,0 +1,411 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<IEnumFilters> 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<IPin> deviceIn;
ComPtr<IPin> deviceOut;
ComPtr<IPin> encoderIn;
ComPtr<IPin> 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<IEnumMediaTypes> 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<IBaseFilter> crossbar;
ComPtr<IPin> 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<IBaseFilter> deviceFilter;
ComPtr<IPin> inputPin;
ComPtr<IPin> 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, &params,
sizeof(params), &params, sizeof(params));
}
bool SetAvermediaEncoderConfig(IBaseFilter *encoder, VideoEncoderConfig &config)
{
HRESULT hr;
ComQIPtr<IKsPropertySet> 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<IBaseFilter> filter;
ComPtr<IBaseFilter> 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;
}
};

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <string>
#include <vector>
#include <deque>
#include <mutex>
using namespace std;
struct EncodedData {
vector<unsigned char> 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<IGraphBuilder> graph;
ComPtr<ICaptureGraphBuilder2> builder;
ComPtr<IMediaControl> control;
ComPtr<IBaseFilter> encoder;
ComPtr<IBaseFilter> device;
ComPtr<OutputFilter> output;
ComPtr<CaptureFilter> capture;
VideoEncoderConfig config;
mutex packetMutex;
deque<EncodedData> packets;
EncodedData curPacket;
deque<long long> 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);
};
};

View File

@ -0,0 +1,3 @@
Language: Cpp
SortIncludes: false
DisableFormat: true

View File

@ -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;
};

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 */

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 */

View File

@ -0,0 +1,992 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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 <strsafe.h>
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<IEnumMediaTypes> 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<decltype(vih)>(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<decltype(vih)>(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<IMemInputPin> 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<decltype(vih)>(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<VIDEOINFOHEADER>();
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<IMemInputPin> 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<IMemInputPin> 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 */

View File

@ -0,0 +1,262 @@
/*
* Copyright (C) 2023 Lain Bailey <lain@obsproject.com>
*
* 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<MediaType> mtList;
MediaType mt;
VideoFormat curVFormat;
long long curInterval = 0;
int curCX = 0;
int curCY = 0;
bool setSampleMediaType = false;
ComPtr<IPin> connectedPin;
OutputFilter *filter;
volatile bool flushing = false;
ComPtr<IMemAllocator> allocator;
ComPtr<IMediaSample> 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<OutputPin> pin;
ComPtr<IAMFilterMiscFlags> misc;
protected:
ComPtr<IReferenceClock> 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<OutputFilter> 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<OutputPin> 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 */

View File

View File

@ -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

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{FFF52519-38BB-4155-851D-4209EC67ACA7}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>dshowcapture</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v120</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;DSHOWCAPTURE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>strmiids.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\..\source\capture-filter.cpp" />
<ClCompile Include="..\..\..\source\device.cpp" />
<ClCompile Include="..\..\..\source\dshow-base.cpp" />
<ClCompile Include="..\..\..\source\dshow-demux.cpp" />
<ClCompile Include="..\..\..\source\dshow-encoded-device.cpp" />
<ClCompile Include="..\..\..\source\dshow-enum.cpp" />
<ClCompile Include="..\..\..\source\dshow-formats.cpp" />
<ClCompile Include="..\..\..\source\dshow-media-type.cpp" />
<ClCompile Include="..\..\..\source\dshowcapture.cpp" />
<ClCompile Include="..\..\..\source\dshowencode.cpp" />
<ClCompile Include="..\..\..\source\encoder.cpp" />
<ClCompile Include="..\..\..\source\log.cpp" />
<ClCompile Include="..\..\..\source\output-filter.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\dshowcapture.hpp" />
<ClInclude Include="..\..\..\source\capture-filter.hpp" />
<ClInclude Include="..\..\..\source\ComPtr.hpp" />
<ClInclude Include="..\..\..\source\device.hpp" />
<ClInclude Include="..\..\..\source\dshow-base.hpp" />
<ClInclude Include="..\..\..\source\dshow-demux.hpp" />
<ClInclude Include="..\..\..\source\dshow-device-defs.hpp" />
<ClInclude Include="..\..\..\source\dshow-enum.hpp" />
<ClInclude Include="..\..\..\source\dshow-formats.hpp" />
<ClInclude Include="..\..\..\source\dshow-media-type.hpp" />
<ClInclude Include="..\..\..\source\encoder.hpp" />
<ClInclude Include="..\..\..\source\IVideoCaptureFilter.h" />
<ClInclude Include="..\..\..\source\log.hpp" />
<ClInclude Include="..\..\..\source\output-filter.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\source\capture-filter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\device.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshow-base.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshow-enum.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshow-formats.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshow-media-type.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshowcapture.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\log.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshow-demux.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshow-encoded-device.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\output-filter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\dshowencode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\source\encoder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\source\capture-filter.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\device.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\dshow-base.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\dshow-enum.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\dshow-formats.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\dshow-media-type.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\log.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\dshowcapture.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\dshow-demux.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\IVideoCaptureFilter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\dshow-device-defs.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\output-filter.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\encoder.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\source\ComPtr.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -748,34 +748,7 @@ bool OBSApp::InitLocale()
} }
if (!userLocale && defaultLang) { if (!userLocale && defaultLang) {
for (auto &locale_ : GetPreferredLocales()) { // Default to English UI; ignore system preferred locales.
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;
}
return true; return true;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -1,18 +1,9 @@
#include "OBSAbout.hpp" #include "OBSAbout.hpp"
#include <widgets/OBSBasic.hpp>
#include <utility/RemoteTextThread.hpp>
#include <qt-wrappers.hpp> #include <qt-wrappers.hpp>
#include <json11.hpp>
#include "moc_OBSAbout.cpp" #include "moc_OBSAbout.cpp"
using namespace json11;
extern bool steam;
OBSAbout::OBSAbout(QWidget *parent) : QDialog(parent), ui(new Ui::OBSAbout) OBSAbout::OBSAbout(QWidget *parent) : QDialog(parent), ui(new Ui::OBSAbout)
{ {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -31,142 +22,6 @@ OBSAbout::OBSAbout(QWidget *parent) : QDialog(parent), ui(new Ui::OBSAbout)
ui->version->setText(ver + bitness); ui->version->setText(ver + bitness);
ui->contribute->setText(QTStr("About.Contribute"));
if (steam) {
delete ui->donate;
} else {
ui->donate->setText("&nbsp;&nbsp;<a href='https://obsproject.com/contribute'>" + QTStr("About.Donate") +
"</a>");
ui->donate->setTextInteractionFlags(Qt::TextBrowserInteraction);
ui->donate->setOpenExternalLinks(true);
}
ui->getInvolved->setText("&nbsp;&nbsp;<a href='https://obsproject.com/developer-contributing'>" +
QTStr("About.GetInvolved") + "</a>");
ui->getInvolved->setTextInteractionFlags(Qt::TextBrowserInteraction);
ui->getInvolved->setOpenExternalLinks(true);
ui->about->setText("<a href='#'>" + QTStr("About") + "</a>");
ui->authors->setText("<a href='#'>" + QTStr("About.Authors") + "</a>");
ui->license->setText("<a href='#'>" + QTStr("About.License") + "</a>");
ui->name->setProperty("class", "text-heading"); ui->name->setProperty("class", "text-heading");
ui->version->setProperty("class", "text-large"); 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<OBSAbout> 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 += "<h1>Top Patreon contributors:</h1>";
text += "<p style=\"font-size:16px;\">";
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 += "</p>";
top = false;
} else if (!first) {
text += "<br/>";
}
if (!link.empty()) {
text += "<a href=\"";
text += QT_UTF8(link.c_str()).toHtmlEscaped();
text += "\">";
}
text += QT_UTF8(name.c_str()).toHtmlEscaped();
if (!link.empty()) {
text += "</a>";
}
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<char> 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<char> text = os_quick_read_utf8_file(path.c_str());
if (!text || !*text) {
ui->textBrowser->setPlainText(error);
return;
}
ui->textBrowser->setPlainText(QT_UTF8(text));
} }

View File

@ -11,9 +11,4 @@ public:
explicit OBSAbout(QWidget *parent = 0); explicit OBSAbout(QWidget *parent = 0);
std::unique_ptr<Ui::OBSAbout> ui; std::unique_ptr<Ui::OBSAbout> ui;
private slots:
void ShowAbout();
void ShowAuthors();
void ShowLicense();
}; };

View File

@ -6,246 +6,90 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>840</width> <width>520</width>
<height>519</height> <height>300</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>About</string> <string>About</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>30</number>
</property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>30</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>0</number> <number>30</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>0</number> <number>30</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>30</number>
</property> </property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <widget class="QLabel" name="icon">
<property name="minimumSize">
<size>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>:res/images/obs.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing"> <property name="spacing">
<number>30</number> <number>0</number>
</property>
<property name="leftMargin">
<number>30</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property> </property>
<item> <item>
<widget class="QLabel" name="icon"> <widget class="QLabel" name="name">
<property name="minimumSize">
<size>
<width>256</width>
<height>256</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>256</width>
<height>256</height>
</size>
</property>
<property name="text"> <property name="text">
<string/> <string notr="true">OBS Studio</string>
</property>
<property name="pixmap">
<pixmap>:res/images/obs.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <widget class="QLabel" name="version">
<property name="spacing"> <property name="text">
<number>0</number> <string notr="true">Version</string>
</property> </property>
<item> </widget>
<widget class="QLabel" name="name"> </item>
<property name="text"> <item>
<string notr="true">OBS Studio</string> <spacer name="verticalSpacer">
</property> <property name="orientation">
</widget> <enum>Qt::Orientation::Vertical</enum>
</item> </property>
<item> <property name="sizeHint" stdset="0">
<widget class="QLabel" name="version"> <size>
<property name="text"> <width>20</width>
<string notr="true">Version</string> <height>40</height>
</property> </size>
</widget> </property>
</item> </spacer>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="info">
<property name="text">
<string>About.Info</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="contribute">
<property name="text">
<string notr="true">Contribute</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="donate">
<property name="text">
<string notr="true">Donate</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="getInvolved">
<property name="text">
<string notr="true">Get Involved</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QFrame" name="widget">
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ClickableLabel" name="about">
<property name="text">
<string>About</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="ClickableLabel" name="authors">
<property name="text">
<string>Authors</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="ClickableLabel" name="license">
<property name="text">
<string>License</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>components/ClickableLabel.hpp</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1654,6 +1654,8 @@ void OBSBasicSettings::LoadResolutionLists()
addRes(as_width, as_height); addRes(as_width, as_height);
} }
addRes(3840, 2160);
addRes(2560, 1440);
addRes(1920, 1080); addRes(1920, 1080);
addRes(1280, 720); addRes(1280, 720);

View File

@ -622,16 +622,6 @@ bool OBSBasic::InitBasicConfigDefaults()
cx *= devicePixelRatioF(); cx *= devicePixelRatioF();
cy *= 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; bool changed = false;
/* ----------------------------------------------------- */ /* ----------------------------------------------------- */

View File

@ -467,14 +467,14 @@ struct Result {
static void CalcBaseRes(int &baseCX, int &baseCY) static void CalcBaseRes(int &baseCX, int &baseCY)
{ {
const int maxBaseArea = 1920 * 1200; const int maxBaseArea = 3840 * 2160;
const int clipResArea = 1920 * 1080; const int clipResArea = 3840 * 2160;
/* if base resolution unusually high, recalculate to a more reasonable /* 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 * for 16:9 resolutions this will always change the starting value to
* 1920x1080 */ * 3840x2160 */
if ((baseCX * baseCY) > maxBaseArea) { if ((baseCX * baseCY) > maxBaseArea) {
long double xyAspect = (long double)baseCX / (long double)baseCY; long double xyAspect = (long double)baseCX / (long double)baseCY;
baseCY = (int)sqrt((long double)clipResArea / xyAspect); baseCY = (int)sqrt((long double)clipResArea / xyAspect);
@ -559,15 +559,15 @@ bool AutoConfigTestPage::TestSoftwareEncoding()
int maxDataRate; int maxDataRate;
if (lcores > 8 || pcores > 4) { if (lcores > 8 || pcores > 4) {
/* superb */ /* superb */
maxDataRate = 1920 * 1200 * 60 + 1000; maxDataRate = 3840 * 2160 * 60 + 1000;
} else if (lcores > 4 && pcores == 4) { } else if (lcores > 4 && pcores == 4) {
/* great */ /* great */
maxDataRate = 1920 * 1080 * 60 + 1000; maxDataRate = 2560 * 1440 * 60 + 1000;
} else if (pcores == 4) { } else if (pcores == 4) {
/* okay */ /* okay */
maxDataRate = 1920 * 1080 * 30 + 1000; maxDataRate = 1920 * 1080 * 60 + 1000;
} else { } else {
/* toaster */ /* toaster */
@ -779,7 +779,7 @@ void AutoConfigTestPage::FindIdealHardwareResolution()
int pcores = os_get_physical_cores(); int pcores = os_get_physical_cores();
int maxDataRate; int maxDataRate;
if (pcores >= 4) { if (pcores >= 4) {
maxDataRate = 1920 * 1200 * 60 + 1000; maxDataRate = 3840 * 2160 * 60 + 1000;
} else { } else {
maxDataRate = 1280 * 720 * 30 + 1000; maxDataRate = 1280 * 720 * 30 + 1000;
} }

View File

@ -76,6 +76,8 @@ AutoConfigVideoPage::AutoConfigVideoPage(QWidget *parent) : QWizardPage(parent),
ui->canvasRes->addItem(str, encRes); ui->canvasRes->addItem(str, encRes);
}; };
addRes(3840, 2160);
addRes(2560, 1440);
addRes(1920, 1080); addRes(1920, 1080);
addRes(1280, 720); addRes(1280, 720);

View File

@ -84,6 +84,7 @@ add_obs_plugin(
PLATFORMS WINDOWS LINUX PLATFORMS WINDOWS LINUX
ARCHITECTURES x64 x86_64 ARCHITECTURES x64 x86_64
) )
add_obs_plugin(obs-shaderfilter)
add_obs_plugin(obs-text PLATFORMS WINDOWS) add_obs_plugin(obs-text PLATFORMS WINDOWS)
add_obs_plugin(obs-transitions) add_obs_plugin(obs-transitions)
add_obs_plugin( add_obs_plugin(

View File

@ -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

View File

@ -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
}
}
}
}
}
}

View File

@ -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

View File

@ -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::

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 "")

View File

@ -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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@ -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.

View File

@ -0,0 +1,498 @@
/******************************************************************************
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "browser-app.hpp"
#include "browser-version.h"
#include <nlohmann/json.hpp>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef ENABLE_BROWSER_QT_LOOP
#include <util/base.h>
#include <util/platform.h>
#include <util/threading.h>
#include <QTimer>
#endif
#ifndef UNUSED_PARAMETER
#define UNUSED_PARAMETER(x) \
{ \
(void)x; \
}
#endif
#ifdef _WIN32
constexpr bool kIsPlatformWindows = true;
#else
constexpr bool kIsPlatformWindows = false;
#endif
CefRefPtr<CefRenderProcessHandler> BrowserApp::GetRenderProcessHandler()
{
return this;
}
CefRefPtr<CefBrowserProcessHandler> BrowserApp::GetBrowserProcessHandler()
{
return this;
}
CefRefPtr<CefClient> BrowserApp::GetDefaultClient()
{
return GetDummy();
}
void BrowserApp::OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> 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<CefRequestContext> requestContext = CefRequestContext::GetGlobalContext();
CefString errorMessage;
CefRefPtr<CefValue> optionValue = CefValue::Create();
constexpr std::array<std::string_view, 20> 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<std::string_view, 2> 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<CefCommandLine> 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<CefCommandLine> 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<std::string> exposedFunctions = {"getControlLevel", "getCurrentScene", "getStatus",
"startRecording", "stopRecording", "startStreaming",
"stopStreaming", "pauseRecording", "unpauseRecording",
"startReplayBuffer", "stopReplayBuffer", "saveReplayBuffer",
"startVirtualcam", "stopVirtualcam", "getScenes",
"setCurrentScene", "getTransitions", "getCurrentTransition",
"setCurrentTransition"};
void BrowserApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame>, CefRefPtr<CefV8Context> context)
{
CefRefPtr<CefV8Value> globalObj = context->GetGlobal();
CefRefPtr<CefV8Value> obsStudioObj = CefV8Value::CreateObject(nullptr, nullptr);
globalObj->SetValue("obsstudio", obsStudioObj, V8_PROPERTY_ATTRIBUTE_NONE);
CefRefPtr<CefV8Value> pluginVersion = CefV8Value::CreateString(OBS_BROWSER_VERSION_STRING);
obsStudioObj->SetValue("pluginVersion", pluginVersion, V8_PROPERTY_ATTRIBUTE_NONE);
for (std::string name : exposedFunctions) {
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction(name, this);
obsStudioObj->SetValue(name, func, V8_PROPERTY_ATTRIBUTE_NONE);
}
UNUSED_PARAMETER(browser);
}
void BrowserApp::ExecuteJSFunction(CefRefPtr<CefBrowser> browser, const char *functionName, CefV8ValueList arguments)
{
std::vector<CefString> names;
browser->GetFrameNames(names);
for (auto &name : names) {
CefRefPtr<CefFrame> frame =
#if CHROME_VERSION_BUILD >= 6261
browser->GetFrameByName(name);
#else
browser->GetFrame(name);
#endif
CefRefPtr<CefV8Context> context = frame->GetV8Context();
context->Enter();
CefRefPtr<CefV8Value> globalObj = context->GetGlobal();
CefRefPtr<CefV8Value> obsStudioObj = globalObj->GetValue("obsstudio");
CefRefPtr<CefV8Value> jsFunction = obsStudioObj->GetValue(functionName);
if (jsFunction && jsFunction->IsFunction())
jsFunction->ExecuteFunction(nullptr, arguments);
context->Exit();
}
}
CefRefPtr<CefV8Value> CefValueToCefV8Value(CefRefPtr<CefValue> value)
{
CefRefPtr<CefV8Value> 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<CefDictionaryValue> 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<CefListValue> 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<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
UNUSED_PARAMETER(frame);
DCHECK(source_process == PID_BROWSER);
CefRefPtr<CefListValue> 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<CefString> names;
browser->GetFrameNames(names);
for (auto &name : names) {
CefRefPtr<CefFrame> frame =
#if CHROME_VERSION_BUILD >= 6261
browser->GetFrameByName(name);
#else
browser->GetFrame(name);
#endif
CefRefPtr<CefV8Context> context = frame->GetV8Context();
context->Enter();
CefRefPtr<CefV8Value> globalObj = context->GetGlobal();
CefRefPtr<CefV8Value> returnValue;
CefRefPtr<CefV8Exception> 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<CefV8Value> dispatchEvent = globalObj->GetValue("dispatchEvent");
dispatchEvent->ExecuteFunction(nullptr, arguments);
context->Exit();
}
} else if (message->GetName() == "executeCallback") {
CefRefPtr<CefV8Context> context = browser->GetMainFrame()->GetV8Context();
context->Enter();
CefRefPtr<CefListValue> arguments = message->GetArgumentList();
int callbackID = arguments->GetInt(0);
CefString jsonString = arguments->GetString(1);
CefRefPtr<CefValue> json = CefParseJSON(arguments->GetString(1).ToString(), {});
CefRefPtr<CefV8Value> callback = callbackMap[callbackID];
CefV8ValueList args;
CefRefPtr<CefV8Value> 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<std::string>::iterator iterator;
iterator = std::find(exposedFunctions.begin(), exposedFunctions.end(), function);
return iterator != exposedFunctions.end();
}
bool BrowserApp::Execute(const CefString &name, CefRefPtr<CefV8Value>, const CefV8ValueList &arguments,
CefRefPtr<CefV8Value> &, CefString &)
{
if (IsValidFunction(name.ToString())) {
if (arguments.size() >= 1 && arguments[0]->IsFunction()) {
callbackId++;
callbackMap[callbackId] = arguments[0];
}
CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(name);
CefRefPtr<CefListValue> 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<CefBrowser> 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<CefBrowser> browser, BrowserFunc func)
{
std::lock_guard<std::mutex> lock(messageObject.browserTaskMutex);
messageObject.browserTasks.emplace_back(browser, func);
QMetaObject::invokeMethod(&messageObject, "ExecuteNextBrowserTask", Qt::QueuedConnection);
}
bool MessageObject::ExecuteNextBrowserTask()
{
Task nextTask;
{
std::lock_guard<std::mutex> 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

View File

@ -0,0 +1,117 @@
/******************************************************************************
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <map>
#include <unordered_map>
#include <functional>
#include "cef-headers.hpp"
#include "browser-dummy-client.hpp"
typedef std::function<void(CefRefPtr<CefBrowser>)> BrowserFunc;
#ifdef ENABLE_BROWSER_QT_LOOP
#include <QObject>
#include <QTimer>
#include <mutex>
#include <deque>
typedef std::function<void()> MessageTask;
class MessageObject : public QObject {
Q_OBJECT
friend void QueueBrowserTask(CefRefPtr<CefBrowser> browser, BrowserFunc func);
struct Task {
CefRefPtr<CefBrowser> browser;
BrowserFunc func;
inline Task() {}
inline Task(CefRefPtr<CefBrowser> browser_, BrowserFunc func_) : browser(browser_), func(func_) {}
};
std::mutex browserTaskMutex;
std::deque<Task> browserTasks;
public slots:
bool ExecuteNextBrowserTask();
void ExecuteTask(MessageTask task);
void DoCefMessageLoop(int ms);
void Process();
};
extern void QueueBrowserTask(CefRefPtr<CefBrowser> browser, BrowserFunc func);
#endif
class BrowserApp : public CefApp, public CefRenderProcessHandler, public CefBrowserProcessHandler, public CefV8Handler {
void ExecuteJSFunction(CefRefPtr<CefBrowser> browser, const char *functionName, CefV8ValueList arguments);
typedef std::map<int, CefRefPtr<CefV8Value>> 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<CefRenderProcessHandler> GetRenderProcessHandler() override;
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override;
virtual CefRefPtr<CefClient> GetDefaultClient() override;
virtual void OnContextInitialized() override;
virtual void OnBeforeChildProcessLaunch(CefRefPtr<CefCommandLine> command_line) override;
virtual void OnRegisterCustomSchemes(CefRawPtr<CefSchemeRegistrar> registrar) override;
virtual void OnBeforeCommandLineProcessing(const CefString &process_type,
CefRefPtr<CefCommandLine> command_line) override;
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) override;
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) override;
virtual bool Execute(const CefString &name, CefRefPtr<CefV8Value> object, const CefV8ValueList &arguments,
CefRefPtr<CefV8Value> &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<BrowserDummyClient> dummy = nullptr;
BrowserDummyClient *GetDummy() const { return dummy.get(); };
IMPLEMENT_REFCOUNTING(BrowserApp);
};

View File

@ -0,0 +1,666 @@
/******************************************************************************
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "browser-client.hpp"
#include "obs-browser-source.hpp"
#include "base64/base64.hpp"
#include <nlohmann/json.hpp>
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <util/platform.h>
#include <QApplication>
#include <QThread>
#include <QToolTip>
#if defined(__APPLE__) && CHROME_VERSION_BUILD > 4430
#include <IOSurface/IOSurface.h>
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
#include <obs-nix-platform.h>
#include "drm-format.hpp"
#endif
inline bool BrowserClient::valid() const
{
return !!bs && !bs->destroying;
}
CefRefPtr<CefLoadHandler> BrowserClient::GetLoadHandler()
{
return this;
}
CefRefPtr<CefRenderHandler> BrowserClient::GetRenderHandler()
{
return this;
}
CefRefPtr<CefDisplayHandler> BrowserClient::GetDisplayHandler()
{
return this;
}
CefRefPtr<CefLifeSpanHandler> BrowserClient::GetLifeSpanHandler()
{
return this;
}
CefRefPtr<CefContextMenuHandler> BrowserClient::GetContextMenuHandler()
{
return this;
}
CefRefPtr<CefAudioHandler> BrowserClient::GetAudioHandler()
{
return reroute_audio ? this : nullptr;
}
CefRefPtr<CefRequestHandler> BrowserClient::GetRequestHandler()
{
return this;
}
CefRefPtr<CefResourceRequestHandler> BrowserClient::GetResourceRequestHandler(CefRefPtr<CefBrowser>,
CefRefPtr<CefFrame>,
CefRefPtr<CefRequest> request, bool, bool,
const CefString &, bool &)
{
if (request->GetHeaderByName("origin") == "null") {
return this;
}
return nullptr;
}
void BrowserClient::OnRenderProcessTerminated(CefRefPtr<CefBrowser>, 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 = "<unknown>";
#endif
const char *sourceName = "<unknown>";
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<CefBrowser>, CefRefPtr<CefFrame>,
CefRefPtr<CefRequest>,
CefRefPtr<CefCallback>)
{
return RV_CONTINUE;
}
bool BrowserClient::OnBeforePopup(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>,
#if CHROME_VERSION_BUILD >= 6834
int,
#endif
const CefString &, const CefString &, cef_window_open_disposition_t, bool,
const CefPopupFeatures &, CefWindowInfo &, CefRefPtr<CefClient> &,
CefBrowserSettings &, CefRefPtr<CefDictionaryValue> &, bool *)
{
/* block popups */
return true;
}
void BrowserClient::OnBeforeContextMenu(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, CefRefPtr<CefContextMenuParams>,
CefRefPtr<CefMenuModel> model)
{
/* remove all context menu contributions */
model->Clear();
}
bool BrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame>, CefProcessId,
CefRefPtr<CefProcessMessage> message)
{
const std::string &name = message->GetName();
CefRefPtr<CefListValue> 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<nlohmann::json> 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<nlohmann::json> 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<CefProcessMessage> msg = CefProcessMessage::Create("executeCallback");
CefRefPtr<CefListValue> 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<CefBrowser>, 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<CefBrowser>, 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<CefBrowser>, 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<CefBrowser>, 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<CefBrowser>, 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<CefBrowser> browser, const CefAudioParameters &params_,
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<CefBrowser> 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<CefBrowser> browser)
{
UNUSED_PARAMETER(browser);
}
void BrowserClient::OnAudioStreamError(CefRefPtr<CefBrowser> 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<CefBrowser> browser, CefAudioParameters &params)
{
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<CefBrowser>, CefRefPtr<CefFrame> 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<CefBrowser>, [[maybe_unused]] CefRefPtr<CefFrame> 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<CefBrowser>, 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 = "<unknown>";
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;
}

View File

@ -0,0 +1,154 @@
/******************************************************************************
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <graphics/graphics.h>
#include <util/threading.h>
#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<CefLoadHandler> GetLoadHandler() override;
virtual CefRefPtr<CefRenderHandler> GetRenderHandler() override;
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override;
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override;
virtual CefRefPtr<CefRequestHandler> GetRequestHandler() override;
virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() override;
virtual CefRefPtr<CefAudioHandler> GetAudioHandler() override;
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) override;
/* CefDisplayHandler */
virtual bool OnConsoleMessage(CefRefPtr<CefBrowser> browser, cef_log_severity_t level, const CefString &message,
const CefString &source, int line) override;
virtual bool OnTooltip(CefRefPtr<CefBrowser> browser, CefString &text) override;
/* CefLifeSpanHandler */
virtual bool OnBeforePopup(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> 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<CefClient> &client, CefBrowserSettings &settings,
CefRefPtr<CefDictionaryValue> &extra_info, bool *no_javascript_access) override;
/* CefRequestHandler */
virtual CefRefPtr<CefResourceRequestHandler>
GetResourceRequestHandler(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request, bool is_navigation, bool is_download,
const CefString &request_initiator, bool &disable_default_handling) override;
virtual void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser, TerminationStatus status
#if CHROME_VERSION_BUILD >= 6367
,
int error_code, const CefString &error_string
#endif
) override;
/* CefResourceRequestHandler */
virtual CefResourceRequestHandler::ReturnValue OnBeforeResourceLoad(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefCallback> callback) override;
/* CefContextMenuHandler */
virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefContextMenuParams> params,
CefRefPtr<CefMenuModel> model) override;
/* CefRenderHandler */
virtual void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) override;
virtual void OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects,
const void *buffer, int width, int height) override;
#ifdef ENABLE_BROWSER_SHARED_TEXTURE
virtual void OnAcceleratedPaint(CefRefPtr<CefBrowser> 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<CefBrowser> browser, PaintElementType type,
const RectList &dirtyRects, void *shared_handle, bool new_texture) override;
#endif
#endif
virtual void OnAudioStreamPacket(CefRefPtr<CefBrowser> browser, const float **data, int frames,
int64_t pts) override;
virtual void OnAudioStreamStopped(CefRefPtr<CefBrowser> browser) override;
virtual void OnAudioStreamStarted(CefRefPtr<CefBrowser> browser, const CefAudioParameters &params,
int channels) override;
virtual void OnAudioStreamError(CefRefPtr<CefBrowser> browser, const CefString &message) override;
const int kFramesPerBuffer = 1024;
virtual bool GetAudioParameters(CefRefPtr<CefBrowser> browser, CefAudioParameters &params) override;
/* CefLoadHandler */
virtual void OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode) override;
virtual void OnLoadError(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefLoadHandler::ErrorCode errorCode, const CefString &errorText,
const CefString &failedUrl) override;
IMPLEMENT_REFCOUNTING(BrowserClient);
};

View File

@ -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

View File

@ -0,0 +1,87 @@
/******************************************************************************
Copyright (C) 2026 by Matt Gajownik <matt@volunteer.obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "browser-dummy-client.hpp"
CefRefPtr<CefCommandHandler> BrowserDummyClient::GetCommandHandler()
{
return this;
}
CefRefPtr<CefRequestHandler> BrowserDummyClient::GetRequestHandler()
{
return this;
}
CefRefPtr<CefLifeSpanHandler> BrowserDummyClient::GetLifeSpanHandler()
{
return this;
}
bool BrowserDummyClient::OnChromeCommand(CefRefPtr<CefBrowser>, int, cef_window_open_disposition_t)
{
return true;
}
bool BrowserDummyClient::IsChromeAppMenuItemVisible(CefRefPtr<CefBrowser>, 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<CefBrowser>, int)
{
return false;
}
bool BrowserDummyClient::OnBeforePopup(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>,
#if CHROME_VERSION_BUILD >= 6834
int,
#endif
const CefString &, const CefString &, cef_window_open_disposition_t, bool,
const CefPopupFeatures &, CefWindowInfo &, CefRefPtr<CefClient> &,
CefBrowserSettings &, CefRefPtr<CefDictionaryValue> &, bool *)
{
return true;
}
void BrowserDummyClient::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
if (browser && browser->GetHost()) {
browser->GetHost()->CloseBrowser(false);
}
}
bool BrowserDummyClient::OnOpenURLFromTab(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, const CefString &,
CefRequestHandler::WindowOpenDisposition, bool)
{
return true;
}
bool BrowserDummyClient::OnBeforeBrowse(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, CefRefPtr<CefRequest>, bool, bool)
{
return true;
}

View File

@ -0,0 +1,64 @@
/******************************************************************************
Copyright (C) 2026 by Matt Gajownik <matt@volunteer.obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include "cef-headers.hpp"
class BrowserDummyClient : public CefClient,
public CefCommandHandler,
public CefRequestHandler,
public CefLifeSpanHandler {
public:
virtual CefRefPtr<CefCommandHandler> GetCommandHandler() override;
virtual CefRefPtr<CefRequestHandler> GetRequestHandler() override;
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override;
/* CefCommandHandler */
virtual bool OnChromeCommand(CefRefPtr<CefBrowser> browser, int command_id,
cef_window_open_disposition_t disposition) override;
virtual bool IsChromeAppMenuItemVisible(CefRefPtr<CefBrowser> 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<CefBrowser> browser, int command_id) override;
/* CefLifeSpanHandler */
virtual bool OnBeforePopup(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> 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<CefClient> &client, CefBrowserSettings &settings,
CefRefPtr<CefDictionaryValue> &extra_info, bool *no_javascript_access) override;
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) override;
/* CefRequestHandler */
virtual bool OnOpenURLFromTab(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
const CefString &target_url,
CefRequestHandler::WindowOpenDisposition target_disposition,
bool user_gesture) override;
virtual bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request, bool user_gesture, bool is_redirect) override;
IMPLEMENT_REFCOUNTING(BrowserDummyClient);
};

View File

@ -0,0 +1,71 @@
/******************************************************************************
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "browser-scheme.hpp"
#include "ip-string.hpp"
#include "wide-string.hpp"
#include <include/wrapper/cef_stream_resource_handler.h>
CefRefPtr<CefResourceHandler> BrowserSchemeHandlerFactory::Create(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame>,
const CefString &, CefRefPtr<CefRequest> 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<CefStreamReader> stream = CefStreamReader::CreateForFile(filePath);
if (!stream) {
return nullptr;
}
CefString mimeType = CefGetMimeType(fileExtension);
if (mimeType.empty()) {
mimeType = "application/octet-stream";
}
return new CefStreamResourceHandler(mimeType, stream);
}

View File

@ -0,0 +1,31 @@
/******************************************************************************
Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include "cef-headers.hpp"
#include <string>
#include <fstream>
class BrowserSchemeHandlerFactory : public CefSchemeHandlerFactory {
public:
virtual CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame>,
const CefString &, CefRefPtr<CefRequest> request) override;
IMPLEMENT_REFCOUNTING(BrowserSchemeHandlerFactory);
};

View File

@ -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)

View File

@ -0,0 +1,3 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 2 )) print -PR -e -- "${CI:+::debug::}%F{220}DEBUG: ${@}%f"

View File

@ -0,0 +1,3 @@
local icon=' ✖︎ '
print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}"

View File

@ -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 ${@}
}

View File

@ -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"
}

View File

@ -0,0 +1,7 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 0 )) {
local icon=''
print -PR " ${(r:5:)icon} ${@}"
}

View File

@ -0,0 +1,7 @@
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
if (( _loglevel > 0 )) {
local icon=' >'
print -PR "%F{2} ${(r:5:)icon}%f ${@}"
}

Some files were not shown because too many files have changed in this diff Show More