From 673ce425f2607cee1b0a46206f61d96b19c52842 Mon Sep 17 00:00:00 2001 From: Gonzalo Exequiel Pedone Date: Tue, 2 Jun 2020 12:37:45 -0300 Subject: [PATCH] Code forked from Webcamoid's virtual camera. --- .gitignore | 152 +- CONTRIBUTING.md | 81 + LICENSE => COPYING | 8 +- ISSUE_TEMPLATE.md | 58 + Manager/Manager.pro | 80 + Manager/main.cpp | 25 + PULL_REQUEST_TEMPLATE.md | 35 + SECURITY.md | 10 + VCamUtils/VCamUtils.pri | 24 + VCamUtils/VCamUtils.pro | 65 + VCamUtils/src/fraction.cpp | 178 ++ VCamUtils/src/fraction.h | 58 + VCamUtils/src/image/color.h | 56 + VCamUtils/src/image/videoformat.cpp | 430 ++++ VCamUtils/src/image/videoformat.h | 80 + VCamUtils/src/image/videoformattypes.h | 59 + VCamUtils/src/image/videoframe.cpp | 1724 ++++++++++++++ VCamUtils/src/image/videoframe.h | 82 + VCamUtils/src/image/videoframetypes.h | 39 + VCamUtils/src/ipcbridge.h | 236 ++ VCamUtils/src/logger/logger.cpp | 143 ++ VCamUtils/src/logger/logger.h | 67 + VCamUtils/src/timer.cpp | 98 + VCamUtils/src/timer.h | 50 + VCamUtils/src/utils.cpp | 158 ++ VCamUtils/src/utils.h | 158 ++ akvirtualcamera.pro | 25 + cmio/Assistant/Assistant.pro | 59 + cmio/Assistant/src/assistant.cpp | 1469 ++++++++++++ cmio/Assistant/src/assistant.h | 45 + cmio/Assistant/src/assistantglobals.h | 69 + cmio/Assistant/src/main.cpp | 74 + cmio/VCamIPC/VCamIPC.pro | 55 + cmio/VCamIPC/src/ipcbridge.mm | 1748 ++++++++++++++ cmio/VirtualCamera/Info.plist | 46 + cmio/VirtualCamera/VirtualCamera.pro | 93 + cmio/VirtualCamera/src/clock.cpp | 73 + cmio/VirtualCamera/src/clock.h | 53 + cmio/VirtualCamera/src/device.cpp | 339 +++ cmio/VirtualCamera/src/device.h | 77 + cmio/VirtualCamera/src/object.cpp | 142 ++ cmio/VirtualCamera/src/object.h | 65 + cmio/VirtualCamera/src/objectinterface.cpp | 174 ++ cmio/VirtualCamera/src/objectinterface.h | 81 + cmio/VirtualCamera/src/objectproperties.cpp | 743 ++++++ cmio/VirtualCamera/src/objectproperties.h | 129 + cmio/VirtualCamera/src/plugin.cpp | 42 + cmio/VirtualCamera/src/plugin.h | 25 + cmio/VirtualCamera/src/plugininterface.cpp | 972 ++++++++ cmio/VirtualCamera/src/plugininterface.h | 89 + cmio/VirtualCamera/src/queue.h | 101 + cmio/VirtualCamera/src/stream.cpp | 618 +++++ cmio/VirtualCamera/src/stream.h | 78 + cmio/VirtualCamera/src/utils.cpp | 85 + cmio/VirtualCamera/src/utils.h | 37 + cmio/cmio.pri | 44 + cmio/cmio.pro | 25 + cmio/pspec.json | 4 + commons.pri | 189 ++ dshow/Assistant/Assistant.pro | 70 + dshow/Assistant/src/main.cpp | 67 + dshow/Assistant/src/service.cpp | 925 +++++++ dshow/Assistant/src/service.h | 42 + dshow/PlatformUtils/PlatformUtils.pro | 63 + dshow/PlatformUtils/src/messagecommons.h | 211 ++ dshow/PlatformUtils/src/messageserver.cpp | 455 ++++ dshow/PlatformUtils/src/messageserver.h | 104 + dshow/PlatformUtils/src/mutex.cpp | 110 + dshow/PlatformUtils/src/mutex.h | 47 + dshow/PlatformUtils/src/sharedmemory.cpp | 204 ++ dshow/PlatformUtils/src/sharedmemory.h | 60 + dshow/PlatformUtils/src/utils.cpp | 1140 +++++++++ dshow/PlatformUtils/src/utils.h | 97 + dshow/VCamIPC/VCamIPC.pro | 65 + dshow/VCamIPC/src/ipcbridge.cpp | 2120 +++++++++++++++++ dshow/VirtualCamera/VirtualCamera.def | 26 + dshow/VirtualCamera/VirtualCamera.pro | 130 + dshow/VirtualCamera/src/basefilter.cpp | 524 ++++ dshow/VirtualCamera/src/basefilter.h | 73 + dshow/VirtualCamera/src/classfactory.cpp | 121 + dshow/VirtualCamera/src/classfactory.h | 56 + dshow/VirtualCamera/src/cunknown.cpp | 118 + dshow/VirtualCamera/src/cunknown.h | 108 + dshow/VirtualCamera/src/enummediatypes.cpp | 181 ++ dshow/VirtualCamera/src/enummediatypes.h | 63 + dshow/VirtualCamera/src/enumpins.cpp | 186 ++ dshow/VirtualCamera/src/enumpins.h | 61 + dshow/VirtualCamera/src/filtermiscflags.cpp | 42 + dshow/VirtualCamera/src/filtermiscflags.h | 44 + dshow/VirtualCamera/src/latency.cpp | 78 + dshow/VirtualCamera/src/latency.h | 61 + dshow/VirtualCamera/src/mediafilter.cpp | 191 ++ dshow/VirtualCamera/src/mediafilter.h | 115 + dshow/VirtualCamera/src/mediasample.cpp | 275 +++ dshow/VirtualCamera/src/mediasample.h | 164 ++ dshow/VirtualCamera/src/mediasample2.cpp | 123 + dshow/VirtualCamera/src/mediasample2.h | 51 + dshow/VirtualCamera/src/memallocator.cpp | 229 ++ dshow/VirtualCamera/src/memallocator.h | 58 + dshow/VirtualCamera/src/persist.cpp | 57 + dshow/VirtualCamera/src/persist.h | 59 + .../VirtualCamera/src/persistpropertybag.cpp | 134 ++ dshow/VirtualCamera/src/persistpropertybag.h | 108 + dshow/VirtualCamera/src/pin.cpp | 1102 +++++++++ dshow/VirtualCamera/src/pin.h | 95 + dshow/VirtualCamera/src/plugin.cpp | 151 ++ dshow/VirtualCamera/src/plugin.h | 32 + dshow/VirtualCamera/src/plugininterface.cpp | 386 +++ dshow/VirtualCamera/src/plugininterface.h | 61 + dshow/VirtualCamera/src/propertyset.cpp | 109 + dshow/VirtualCamera/src/propertyset.h | 59 + dshow/VirtualCamera/src/pushsource.cpp | 86 + dshow/VirtualCamera/src/pushsource.h | 47 + dshow/VirtualCamera/src/qualitycontrol.cpp | 59 + dshow/VirtualCamera/src/qualitycontrol.h | 45 + dshow/VirtualCamera/src/referenceclock.cpp | 306 +++ dshow/VirtualCamera/src/referenceclock.h | 59 + .../src/specifypropertypages.cpp | 91 + .../VirtualCamera/src/specifypropertypages.h | 49 + dshow/VirtualCamera/src/streamconfig.cpp | 268 +++ dshow/VirtualCamera/src/streamconfig.h | 88 + dshow/VirtualCamera/src/videocontrol.cpp | 308 +++ dshow/VirtualCamera/src/videocontrol.h | 63 + dshow/VirtualCamera/src/videoprocamp.cpp | 159 ++ dshow/VirtualCamera/src/videoprocamp.h | 65 + dshow/dshow.pri | 58 + dshow/dshow.pro | 26 + dshow/pspec.json | 4 + share/TestFrame/TestFrame.bmp | Bin 0 -> 921654 bytes share/TestFrame/TestFrame.py | 39 + share/TestFrame/TestFrame.qml | 194 ++ share/TestFrame/webcamoid.png | Bin 0 -> 18934 bytes 132 files changed, 25205 insertions(+), 42 deletions(-) create mode 100644 CONTRIBUTING.md rename LICENSE => COPYING (99%) create mode 100644 ISSUE_TEMPLATE.md create mode 100644 Manager/Manager.pro create mode 100644 Manager/main.cpp create mode 100644 PULL_REQUEST_TEMPLATE.md create mode 100644 SECURITY.md create mode 100644 VCamUtils/VCamUtils.pri create mode 100644 VCamUtils/VCamUtils.pro create mode 100644 VCamUtils/src/fraction.cpp create mode 100644 VCamUtils/src/fraction.h create mode 100644 VCamUtils/src/image/color.h create mode 100644 VCamUtils/src/image/videoformat.cpp create mode 100644 VCamUtils/src/image/videoformat.h create mode 100644 VCamUtils/src/image/videoformattypes.h create mode 100644 VCamUtils/src/image/videoframe.cpp create mode 100644 VCamUtils/src/image/videoframe.h create mode 100644 VCamUtils/src/image/videoframetypes.h create mode 100644 VCamUtils/src/ipcbridge.h create mode 100644 VCamUtils/src/logger/logger.cpp create mode 100644 VCamUtils/src/logger/logger.h create mode 100644 VCamUtils/src/timer.cpp create mode 100644 VCamUtils/src/timer.h create mode 100644 VCamUtils/src/utils.cpp create mode 100644 VCamUtils/src/utils.h create mode 100644 akvirtualcamera.pro create mode 100644 cmio/Assistant/Assistant.pro create mode 100644 cmio/Assistant/src/assistant.cpp create mode 100644 cmio/Assistant/src/assistant.h create mode 100644 cmio/Assistant/src/assistantglobals.h create mode 100644 cmio/Assistant/src/main.cpp create mode 100644 cmio/VCamIPC/VCamIPC.pro create mode 100644 cmio/VCamIPC/src/ipcbridge.mm create mode 100644 cmio/VirtualCamera/Info.plist create mode 100644 cmio/VirtualCamera/VirtualCamera.pro create mode 100644 cmio/VirtualCamera/src/clock.cpp create mode 100644 cmio/VirtualCamera/src/clock.h create mode 100644 cmio/VirtualCamera/src/device.cpp create mode 100644 cmio/VirtualCamera/src/device.h create mode 100644 cmio/VirtualCamera/src/object.cpp create mode 100644 cmio/VirtualCamera/src/object.h create mode 100644 cmio/VirtualCamera/src/objectinterface.cpp create mode 100644 cmio/VirtualCamera/src/objectinterface.h create mode 100644 cmio/VirtualCamera/src/objectproperties.cpp create mode 100644 cmio/VirtualCamera/src/objectproperties.h create mode 100644 cmio/VirtualCamera/src/plugin.cpp create mode 100644 cmio/VirtualCamera/src/plugin.h create mode 100644 cmio/VirtualCamera/src/plugininterface.cpp create mode 100644 cmio/VirtualCamera/src/plugininterface.h create mode 100644 cmio/VirtualCamera/src/queue.h create mode 100644 cmio/VirtualCamera/src/stream.cpp create mode 100644 cmio/VirtualCamera/src/stream.h create mode 100644 cmio/VirtualCamera/src/utils.cpp create mode 100644 cmio/VirtualCamera/src/utils.h create mode 100644 cmio/cmio.pri create mode 100644 cmio/cmio.pro create mode 100644 cmio/pspec.json create mode 100644 commons.pri create mode 100644 dshow/Assistant/Assistant.pro create mode 100644 dshow/Assistant/src/main.cpp create mode 100644 dshow/Assistant/src/service.cpp create mode 100644 dshow/Assistant/src/service.h create mode 100644 dshow/PlatformUtils/PlatformUtils.pro create mode 100644 dshow/PlatformUtils/src/messagecommons.h create mode 100644 dshow/PlatformUtils/src/messageserver.cpp create mode 100644 dshow/PlatformUtils/src/messageserver.h create mode 100644 dshow/PlatformUtils/src/mutex.cpp create mode 100644 dshow/PlatformUtils/src/mutex.h create mode 100644 dshow/PlatformUtils/src/sharedmemory.cpp create mode 100644 dshow/PlatformUtils/src/sharedmemory.h create mode 100644 dshow/PlatformUtils/src/utils.cpp create mode 100644 dshow/PlatformUtils/src/utils.h create mode 100644 dshow/VCamIPC/VCamIPC.pro create mode 100644 dshow/VCamIPC/src/ipcbridge.cpp create mode 100644 dshow/VirtualCamera/VirtualCamera.def create mode 100644 dshow/VirtualCamera/VirtualCamera.pro create mode 100644 dshow/VirtualCamera/src/basefilter.cpp create mode 100644 dshow/VirtualCamera/src/basefilter.h create mode 100644 dshow/VirtualCamera/src/classfactory.cpp create mode 100644 dshow/VirtualCamera/src/classfactory.h create mode 100644 dshow/VirtualCamera/src/cunknown.cpp create mode 100644 dshow/VirtualCamera/src/cunknown.h create mode 100644 dshow/VirtualCamera/src/enummediatypes.cpp create mode 100644 dshow/VirtualCamera/src/enummediatypes.h create mode 100644 dshow/VirtualCamera/src/enumpins.cpp create mode 100644 dshow/VirtualCamera/src/enumpins.h create mode 100644 dshow/VirtualCamera/src/filtermiscflags.cpp create mode 100644 dshow/VirtualCamera/src/filtermiscflags.h create mode 100644 dshow/VirtualCamera/src/latency.cpp create mode 100644 dshow/VirtualCamera/src/latency.h create mode 100644 dshow/VirtualCamera/src/mediafilter.cpp create mode 100644 dshow/VirtualCamera/src/mediafilter.h create mode 100644 dshow/VirtualCamera/src/mediasample.cpp create mode 100644 dshow/VirtualCamera/src/mediasample.h create mode 100644 dshow/VirtualCamera/src/mediasample2.cpp create mode 100644 dshow/VirtualCamera/src/mediasample2.h create mode 100644 dshow/VirtualCamera/src/memallocator.cpp create mode 100644 dshow/VirtualCamera/src/memallocator.h create mode 100644 dshow/VirtualCamera/src/persist.cpp create mode 100644 dshow/VirtualCamera/src/persist.h create mode 100644 dshow/VirtualCamera/src/persistpropertybag.cpp create mode 100644 dshow/VirtualCamera/src/persistpropertybag.h create mode 100644 dshow/VirtualCamera/src/pin.cpp create mode 100644 dshow/VirtualCamera/src/pin.h create mode 100644 dshow/VirtualCamera/src/plugin.cpp create mode 100644 dshow/VirtualCamera/src/plugin.h create mode 100644 dshow/VirtualCamera/src/plugininterface.cpp create mode 100644 dshow/VirtualCamera/src/plugininterface.h create mode 100644 dshow/VirtualCamera/src/propertyset.cpp create mode 100644 dshow/VirtualCamera/src/propertyset.h create mode 100644 dshow/VirtualCamera/src/pushsource.cpp create mode 100644 dshow/VirtualCamera/src/pushsource.h create mode 100644 dshow/VirtualCamera/src/qualitycontrol.cpp create mode 100644 dshow/VirtualCamera/src/qualitycontrol.h create mode 100644 dshow/VirtualCamera/src/referenceclock.cpp create mode 100644 dshow/VirtualCamera/src/referenceclock.h create mode 100644 dshow/VirtualCamera/src/specifypropertypages.cpp create mode 100644 dshow/VirtualCamera/src/specifypropertypages.h create mode 100644 dshow/VirtualCamera/src/streamconfig.cpp create mode 100644 dshow/VirtualCamera/src/streamconfig.h create mode 100644 dshow/VirtualCamera/src/videocontrol.cpp create mode 100644 dshow/VirtualCamera/src/videocontrol.h create mode 100644 dshow/VirtualCamera/src/videoprocamp.cpp create mode 100644 dshow/VirtualCamera/src/videoprocamp.h create mode 100644 dshow/dshow.pri create mode 100644 dshow/dshow.pro create mode 100644 dshow/pspec.json create mode 100644 share/TestFrame/TestFrame.bmp create mode 100755 share/TestFrame/TestFrame.py create mode 100644 share/TestFrame/TestFrame.qml create mode 100644 share/TestFrame/webcamoid.png diff --git a/.gitignore b/.gitignore index f147edf..d40d484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,128 @@ -# C++ objects and libs -*.slo -*.lo +# Compiled source # +################### +*.com +*.class +*.dll +*.exe *.o -*.a -*.la -*.lai *.so *.so.* -*.dll -*.dylib +*.so.debug +*.run +*.AppImage +*.framework -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp -/.qmake.cache -/.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* +# Packages # +############ +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.xz + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite +vgcore.* + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db +*~ +*_resource.rc + +# Ignore files generated by Qt # +################################ +*.a *.moc moc_*.cpp -moc_*.h -qrc_*.cpp ui_*.h -*.qmlc -*.jsc +*.obj +*.dylib Makefile* -*build-* -*.qm *.prl - -# Qt unit tests -target_wrapper.* - -# QtCreator +*.app +*.pro.user* +*.qmlproject.user* *.autosave +*.qmlc +.qmake.stash +.qmake.cache +callgrind.out.* +*.debug +*.plugin +*_qmlcache.qrc +test.o-* +object_script.* -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Ignore files generated by Python # +#################################### +__pycache__ +*.pyc -# QtCreator CMake -CMakeLists.txt.user* +# Android files # +################# +android-build +android-*-deployment-settings.json -# QtCreator 4.8< compilation database -compile_commands.json +# VIM temporary files # +####################### +.*.swp -# QtCreator local machine specific files for imported projects -*creator.user* +# Kate temporary files # +####################### +.*.kate-swp + +# KDevelop generated files # +############################ +.kdev4 +*.kdev4 + +# MinGW files # +############### +*.Debug +*.Release + +# MSVC files # +############## +*.exp +*.ilk +*.lib +*.pdb + +# Failed patch # +################ +*.orig +*.rej + +# Project Files # +################# +AkVCamAssistant + +# Ignore Directories # +###################### +build +debug +release + +# Ignore Auto Generated Files # +############################### +*_auto* + +# Ignore Private Files and Folders # +#################################### +*_priv +*_priv.* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b0b0333 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# akvcam Individual Contributor License Agreement # + +Thank you for your interest in contributing to akvcam ("We" or "Us"). + +This contributor agreement ("Agreement") documents the rights granted by contributors to Us. To make this document effective, please sign it and send it to Us by email, following the instructions at CONTRIBUTING.md. This is a legally binding document, so please read it carefully before agreeing to it. The Agreement may cover more than one software project managed by Us. + +You are accepting this agreement by making a pull request to the repository. + +## 1. Definitions ## + +"You" means the individual who Submits a Contribution to Us. + +"Contribution" means any work of authorship that is Submitted by You to Us in which You own or assert ownership of the Copyright. If You do not own the Copyright in the entire work of authorship, please follow the instructions in CONTRIBUTING.md. + +"Copyright" means all rights protecting works of authorship owned or controlled by You, including copyright, moral and neighboring rights, as appropriate, for the full term of their existence including any extensions by You. + +"Material" means the work of authorship which is made available by Us to third parties. When this Agreement covers more than one software project, the Material means the work of authorship to which the Contribution was Submitted. After You Submit the Contribution, it may be included in the Material. + +"Submit" means any form of electronic, verbal, or written communication sent to Us or our representatives, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Us for the purpose of discussing and improving the Material, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +"Submission Date" means the date on which You Submit a Contribution to Us. + +"Effective Date" means the date You execute this Agreement or the date You first Submit a Contribution to Us, whichever is earlier. + +"Media" means any portion of a Contribution which is not software. + +## 2. Grant of Rights ## + +### 2.1 Copyright License ### + +(a) You retain ownership of the Copyright in Your Contribution and have the same rights to use or license the Contribution which You would have had without entering into the Agreement. + +(b) To the maximum extent permitted by the relevant law, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable license under the Copyright covering the Contribution, with the right to sublicense such rights through multiple tiers of sublicensees, to reproduce, modify, display, perform and distribute the Contribution as part of the Material; provided that this license is conditioned upon compliance with Section 2.3. + +### 2.2 Patent License ### + +For patent claims including, without limitation, method, process, and apparatus claims which You own, control or have the right to grant, now or in the future, You grant to Us a perpetual, worldwide, non-exclusive, transferable, royalty-free, irrevocable patent license, with the right to sublicense these rights to multiple tiers of sublicensees, to make, have made, use, sell, offer for sale, import and otherwise transfer the Contribution and the Contribution in combination with the Material (and portions of such combination). This license is granted only to the extent that the exercise of the licensed rights infringes such patent claims; and provided that this license is conditioned upon compliance with Section 2.3. + +### 2.3 Outbound License ### + +As a condition on the grant of rights in Sections 2.1 and 2.2, We agree to license the Contribution only under the terms of the license or licenses which We are using on the Submission Date for the Material or any licenses on the Free Software Foundation's list of "Recommended copyleft licenses" on or after the Effective Date, whether or not such licenses are subsequently disapproved (including any right to adopt any future version of a license if permitted). + +In addition, We may use the following licenses for Media in the Contribution: Creative Commons Attribution Share Alike 3.0 (including any right to adopt any future version of a license if permitted). + +**2.4 Moral Rights.** If moral rights apply to the Contribution, to the maximum extent permitted by law, You waive and agree not to assert such moral rights against Us or our successors in interest, or any of our licensees, either direct or indirect. + +**2.5 Our Rights.** You acknowledge that We are not obligated to use Your Contribution as part of the Material and may decide to include any Contribution We consider appropriate. + +**2.6 Reservation of Rights.** Any rights not expressly licensed under this section are expressly reserved by You. + +## 3. Agreement ## + +You confirm that: + +(a) You have the legal authority to enter into this Agreement. + +(b) You own the Copyright and patent claims covering the Contribution which are required to grant the rights under Section 2. + +(c) The grant of rights under Section 2 does not violate any grant of rights which You have made to third parties, including Your employer. If You are an employee, You have had Your employer approve this Agreement or sign the Entity version of this document. If You are less than eighteen years old, please have Your parents or guardian sign the Agreement. + +(d) You have followed the instructions in CONTRIBUTING.md, if You do not own the Copyright in the entire work of authorship Submitted. + +## 4. Disclaimer ## + +EXCEPT FOR THE EXPRESS WARRANTIES IN SECTION 3, THE CONTRIBUTION IS PROVIDED "AS IS". MORE PARTICULARLY, ALL EXPRESS OR IMPLIED WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE EXPRESSLY DISCLAIMED BY YOU TO US. TO THE EXTENT THAT ANY SUCH WARRANTIES CANNOT BE DISCLAIMED, SUCH WARRANTY IS LIMITED IN DURATION TO THE MINIMUM PERIOD PERMITTED BY LAW. + +## 5. Consequential Damage Waiver ## + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL YOU BE LIABLE FOR ANY LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF DATA, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL AND EXEMPLARY DAMAGES ARISING OUT OF THIS AGREEMENT REGARDLESS OF THE LEGAL OR EQUITABLE THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED. + +## 6. Miscellaneous ## + +6.1 This Agreement will be governed by and construed in accordance with the laws of Argentina excluding its conflicts of law provisions. Under certain circumstances, the governing law in this section might be superseded by the United Nations Convention on Contracts for the International Sale of Goods ("UN Convention") and the parties intend to avoid the application of the UN Convention to this Agreement and, thus, exclude the application of the UN Convention in its entirety to this Agreement. + +6.2 This Agreement sets out the entire agreement between You and Us for Your Contributions to Us and overrides all other agreements or understandings. + +6.3 If You or We assign the rights or obligations received through this Agreement to a third party, as a condition of the assignment, that third party must agree in writing to abide by all the rights and obligations in the Agreement. + +6.4 The failure of either party to require performance by the other party of any provision of this Agreement in one situation shall not affect the right of a party to require such performance at any time in the future. A waiver of performance under a provision in one situation shall not be considered a waiver of the performance of the provision in the future or a waiver of the provision in its entirety. + +6.5 If any provision of this Agreement is found void and unenforceable, such provision will be replaced to the extent possible with a provision that comes closest to the meaning of the original provision and which is enforceable. The terms and conditions set forth in this Agreement shall apply notwithstanding any failure of essential purpose of this Agreement or any limited remedy to the maximum extent possible under law. diff --git a/LICENSE b/COPYING similarity index 99% rename from LICENSE rename to COPYING index f288702..94a9ed0 100644 --- a/LICENSE +++ b/COPYING @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found. 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 . + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. @@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see -. +. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..23d2e24 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,58 @@ +# README + +Some considerations before doing anything: + +* Search the [issues list](https://github.com/webcamoid/akvirtualcamera/issues) for similar topics before opening a new one. +* If you know how to fix the problem, consider doing a [pull request](https://github.com/webcamoid/akvirtualcamera/pulls) instead of opening a new issue. +* Only report problems in [latest](https://github.com/webcamoid/akvirtualcamera/releases) and [development](https://github.com/webcamoid/akvirtualcamera/) version. Reporting issues related to older versions will be rejected. +* Use [gist](https://gist.github.com/) to post logs longer than 1024 characters. +* akvirtualcamera as project, is not affiliated or endorsed to any distribution, report packaging problems in their respective issue tracker. +* Respect the templates, we need as much information as possible. +* Don't open an issue and disappear, we need you at least the first week to clear up missing information. +* Missing information makes useless and unsolvable an issue report. +* You are our debbuger, eyes and hands, if an issue is not reproducible then you will be the only person able to solve it. +* Take a seat and wait for your turn, as many others that has their issues open, or much better try fixing it your self and collaborate with the solution :smile: + +Choose one of the templates bellow that fit better your issue. + +# Reporting a problem? + +## Summary + +Write here a brief description of the problem. + +## Current Behavior + +Describe the problem the best as you can, don't omit information. + +## Expected Behavior + +How it should have work? + +## Steps to Reproduce + +If akvirtualcamera crashed, try to reproduce the crash several times to be sure where is the problem. Write the steps to reproduce the issue bellow: + +1. Open this +2. Click that +3. Drag those +4. ... + +## Suggestions and tips + +How would you solve the problem? + +## Your Environment + +* akvirtualcamera inormation: (version) (architecture) +* Operating System information: (name) (numeric version and codename if applied) (architecture) +* Any other useful information: (logs, gdb backtrace, valgrind logs, screenshots, hardware, etc.) + +# Want a new feature? + +Describe your idea the best as you can, include sketches, mockups and diagrams if required. +Be patient, take in mind that there may be other priorities. Your idea will be accepted if fit in project goals. + +# Questinons and other matters? + +Go ahead! diff --git a/Manager/Manager.pro b/Manager/Manager.pro new file mode 100644 index 0000000..3a21c44 --- /dev/null +++ b/Manager/Manager.pro @@ -0,0 +1,80 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../commons.pri) { + include(../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +win32: include(../dshow/dshow.pri) +macx: include(../cmio/cmio.pri) +include(../VCamUtils/VCamUtils.pri) + +TEMPLATE = app +CONFIG += console link_prl +CONFIG -= app_bundle +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = manager + +SOURCES = \ + main.cpp + +INCLUDEPATH += \ + .. \ + ../.. + +win32: LIBS += \ + -L$${OUT_PWD}/../dshow/VCamIPC/$${BIN_DIR} -lVCamIPC \ + -L$${OUT_PWD}/../dshow/PlatformUtils/$${BIN_DIR} -lPlatformUtils \ + -ladvapi32 \ + -lgdi32 \ + -lstrmiids \ + -luuid \ + -lole32 \ + -loleaut32 \ + -lshell32 +macx: LIBS += \ + -L$${OUT_PWD}/../cmio/VCamIPC/$${BIN_DIR} -lVCamIPC \ + -framework CoreFoundation \ + -framework CoreMedia \ + -framework CoreMediaIO \ + -framework CoreVideo \ + -framework Foundation \ + -framework IOKit \ + -framework IOSurface +LIBS += \ + -L$${OUT_PWD}/VCamUtils/$${BIN_DIR} -lVCamUtils + +isEmpty(STATIC_BUILD) | isEqual(STATIC_BUILD, 0) { + win32-g++: QMAKE_LFLAGS = -static -static-libgcc -static-libstdc++ +} + +win32: QMAKE_POST_LINK = \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/../dshow/VirtualCamera/$${DSHOW_PLUGIN_NAME}.plugin/$$normalizedArch(TARGET_ARCH))) $${CMD_SEP} \ + $(COPY) $$shell_path($${OUT_PWD}/$${BIN_DIR}/$${TARGET}.exe) $$shell_path($${OUT_PWD}/../dshow/VirtualCamera/$${DSHOW_PLUGIN_NAME}.plugin/$$normalizedArch(TARGET_ARCH)) +macx: QMAKE_POST_LINK = \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/../cmio/VirtualCamera/$${CMIO_PLUGIN_NAME}.plugin/Contents/Resources)) $${CMD_SEP} \ + $(COPY) $$shell_path($${OUT_PWD}/$${BIN_DIR}/$${TARGET}) $$shell_path($${OUT_PWD}/../cmio/VirtualCamera/$${CMIO_PLUGIN_NAME}.plugin/Contents/Resources) diff --git a/Manager/main.cpp b/Manager/main.cpp new file mode 100644 index 0000000..a05477b --- /dev/null +++ b/Manager/main.cpp @@ -0,0 +1,25 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +int main(int argc, char **argv) +{ + return 0; +} diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..16b498d --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,35 @@ +# README + +Before contributing, please read [the contributing document](https://github.com/webcamoid/akvirtualcamera/blob/master/CONTRIBUTING.md), and the [coding style and conventions](https://github.com/webcamoid/webcamoid/wiki/Coding-style-and-conventions) guide. +Search the [pull request list](https://github.com/webcamoid/akvirtualcamera/pulls) for similar pulls before opening a new one. +Check your code doesn't throw any warning or error message while compiling, and doesn't give any warning in Clang static analyzer. Make sure your code is GCC, Clang, MinGW and MSVC compliant (use AppVeyor and Travis for that). +Check your code using GDB, Valgrind and similar tools to remove all possible memory leaks and segfaults. + +# Pull request + +## Type of change + +Is your pull request a bug fix, new feature, code refactor, breaking change, etc.? +If your change is too big consider [discussing it](https://github.com/webcamoid/akvirtualcamera/issues) before pulling. + +## Summary + +Describe your pull request the best as you can. + +## Related Issue + +Is this pull request related to some [issue](https://github.com/webcamoid/akvirtualcamera/issues)? Cite the issue as #NNN, where NNN is the number of issue. + +## More info + +Provide screenshots, logs, etc. if required. + +## Added dependencies + +Does your pull request add more dependencies to the project? This may require some discussion. Minimal dependencies is a requirement. + +## Target Environment + +Is this pull request specific to a target operating system? + +* Operating System information: (name) (numeric version and codename if applied) (architecture) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..bec73b3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +# Security Policy + +## Supported Versions + +Only report vulnerabilities in the [latest stable](https://github.com/webcamoid/akvirtualcamera/releases) version and the [master repository](https://github.com/webcamoid/akvirtualcamera) (that includes [daily build](https://bintray.com/webcamoid/webcamoid/akvirtualcamera/daily/link)). + +## Reporting a Vulnerability + +Report all vulnerabilities at the [issues section](https://github.com/webcamoid/akvirtualcamera/issues). +​ \ No newline at end of file diff --git a/VCamUtils/VCamUtils.pri b/VCamUtils/VCamUtils.pri new file mode 100644 index 0000000..bbb9d76 --- /dev/null +++ b/VCamUtils/VCamUtils.pri @@ -0,0 +1,24 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +DEFINES += \ + QT_NAMESPACE=AkVCam + +CONFIG(debug, debug|release) { + DEFINES += QT_DEBUG +} diff --git a/VCamUtils/VCamUtils.pro b/VCamUtils/VCamUtils.pro new file mode 100644 index 0000000..94b9ffb --- /dev/null +++ b/VCamUtils/VCamUtils.pro @@ -0,0 +1,65 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../commons.pri) { + include(../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(VCamUtils.pri) + +CONFIG += \ + staticlib \ + create_prl \ + no_install_prl +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = VCamUtils + +TEMPLATE = lib + +SOURCES += \ + src/fraction.cpp \ + src/image/videoformat.cpp \ + src/image/videoframe.cpp \ + src/logger/logger.cpp \ + src/timer.cpp \ + src/utils.cpp + +HEADERS += \ + src/fraction.h \ + src/image/color.h \ + src/image/videoformat.h \ + src/image/videoframe.h \ + src/image/videoframetypes.h \ + src/image/videoformattypes.h \ + src/ipcbridge.h \ + src/logger/logger.h \ + src/timer.h \ + src/utils.h + +isEmpty(STATIC_BUILD) | isEqual(STATIC_BUILD, 0) { + win32-g++: QMAKE_LFLAGS = -static -static-libgcc -static-libstdc++ +} diff --git a/VCamUtils/src/fraction.cpp b/VCamUtils/src/fraction.cpp new file mode 100644 index 0000000..3088132 --- /dev/null +++ b/VCamUtils/src/fraction.cpp @@ -0,0 +1,178 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "fraction.h" +#include "utils.h" + +namespace AkVCam +{ + class FractionPrivate + { + public: + int64_t m_num; + int64_t m_den; + }; +} + +AkVCam::Fraction::Fraction() +{ + this->d = new FractionPrivate; + this->d->m_num = 0; + this->d->m_den = 0; +} + +AkVCam::Fraction::Fraction(int64_t num, int64_t den) +{ + this->d = new FractionPrivate; + this->d->m_num = num; + this->d->m_den = den; +} + +AkVCam::Fraction::Fraction(const std::string &str) +{ + this->d = new FractionPrivate; + this->d->m_num = 0; + this->d->m_den = 1; + auto pos = str.find('/'); + + if (pos == std::string::npos) { + auto strCpy = trimmed(str); + this->d->m_num = uint32_t(strtol(strCpy.c_str(), nullptr, 10)); + } else { + auto numStr = trimmed(str.substr(0, pos)); + auto denStr = trimmed(str.substr(pos + 1)); + + this->d->m_num = uint32_t(strtol(numStr.c_str(), nullptr, 10)); + this->d->m_den = uint32_t(strtol(denStr.c_str(), nullptr, 10)); + + if (this->d->m_den < 1) { + this->d->m_num = 0; + this->d->m_den = 1; + } + } +} + +AkVCam::Fraction::Fraction(const std::wstring &str) +{ + this->d = new FractionPrivate; + this->d->m_num = 0; + this->d->m_den = 1; + auto pos = str.find(L'/'); + + if (pos == std::wstring::npos) { + auto strCpy = trimmed(str); + + this->d->m_num = uint32_t(wcstol(strCpy.c_str(), nullptr, 10)); + } else { + auto numStr = trimmed(str.substr(0, pos)); + auto denStr = trimmed(str.substr(pos + 1)); + + this->d->m_num = uint32_t(wcstol(numStr.c_str(), nullptr, 10)); + this->d->m_den = uint32_t(wcstol(denStr.c_str(), nullptr, 10)); + + if (this->d->m_den < 1) { + this->d->m_num = 0; + this->d->m_den = 1; + } + } +} + +AkVCam::Fraction::Fraction(const Fraction &other) +{ + this->d = new FractionPrivate; + this->d->m_num = other.d->m_num; + this->d->m_den = other.d->m_den; +} + +AkVCam::Fraction::~Fraction() +{ + delete this->d; +} + +AkVCam::Fraction &AkVCam::Fraction::operator =(const Fraction &other) +{ + if (this != &other) { + this->d->m_num = other.d->m_num; + this->d->m_den = other.d->m_den; + } + + return *this; +} + +bool AkVCam::Fraction::operator ==(const Fraction &other) const +{ + if (this->d->m_den == 0 && other.d->m_den != 0) + return false; + + if (this->d->m_den != 0 && other.d->m_den == 0) + return false; + + return this->d->m_num * other.d->m_den == this->d->m_den * other.d->m_num; +} + +bool AkVCam::Fraction::operator <(const Fraction &other) const +{ + return this->d->m_num * other.d->m_den < this->d->m_den * other.d->m_num; +} + +int64_t AkVCam::Fraction::num() const +{ + return this->d->m_num; +} + +int64_t &AkVCam::Fraction::num() +{ + return this->d->m_num; +} + +int64_t AkVCam::Fraction::den() const +{ + return this->d->m_den; +} + +int64_t &AkVCam::Fraction::den() +{ + return this->d->m_den; +} + +double AkVCam::Fraction::value() const +{ + return double(this->d->m_num) / this->d->m_den; +} + +std::string AkVCam::Fraction::toString() const +{ + std::stringstream ss; + ss << this->d->m_num << '/' << this->d->m_den; + + return ss.str(); +} + +std::wstring AkVCam::Fraction::toWString() const +{ + std::wstringstream ss; + ss << this->d->m_num << L'/' << this->d->m_den; + + return ss.str(); +} diff --git a/VCamUtils/src/fraction.h b/VCamUtils/src/fraction.h new file mode 100644 index 0000000..01ee4a7 --- /dev/null +++ b/VCamUtils/src/fraction.h @@ -0,0 +1,58 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef FRACTION_H +#define FRACTION_H + +#include +#include + +namespace AkVCam +{ + class Fraction; + class FractionPrivate; + using FractionRange = std::pair; + + class Fraction + { + public: + Fraction(); + Fraction(int64_t num, int64_t den); + Fraction(const std::string &str); + Fraction(const std::wstring &str); + Fraction(const Fraction &other); + virtual ~Fraction(); + Fraction &operator =(const Fraction &other); + bool operator ==(const Fraction &other) const; + bool operator <(const Fraction &other) const; + + int64_t num() const; + int64_t &num(); + int64_t den() const; + int64_t &den(); + double value() const; + std::string toString() const; + std::wstring toWString() const; + + private: + FractionPrivate *d; + }; +} + +#endif // FRACTION_H diff --git a/VCamUtils/src/image/color.h b/VCamUtils/src/image/color.h new file mode 100644 index 0000000..8f42e04 --- /dev/null +++ b/VCamUtils/src/image/color.h @@ -0,0 +1,56 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef AKVCAMUTILS_COLOR_H +#define AKVCAMUTILS_COLOR_H + +#include + +namespace AkVCam +{ + namespace Color + { + inline uint32_t rgb(uint32_t r, uint32_t g, uint32_t b, uint32_t a) + { + return (a << 24) | (r << 16) | (g << 8) | b; + } + + inline uint32_t red(uint32_t rgba) + { + return (rgba >> 16) & 0xff; + } + + inline uint32_t green(uint32_t rgba) + { + return (rgba >> 8) & 0xff; + } + + inline uint32_t blue(uint32_t rgba) + { + return rgba & 0xff; + } + + inline uint32_t alpha(uint32_t rgba) + { + return rgba >> 24; + } + } +} + +#endif // AKVCAMUTILS_COLOR_H diff --git a/VCamUtils/src/image/videoformat.cpp b/VCamUtils/src/image/videoformat.cpp new file mode 100644 index 0000000..1497ab5 --- /dev/null +++ b/VCamUtils/src/image/videoformat.cpp @@ -0,0 +1,430 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include + +#include "videoformat.h" +#include "../utils.h" + +namespace AkVCam +{ + class VideoFormatPrivate + { + public: + FourCC m_fourcc {0}; + int m_width {0}; + int m_height {0}; + std::vector m_frameRates; + + VideoFormatPrivate() = default; + VideoFormatPrivate(FourCC fourcc, + int width, + int height, + const std::vector &frameRates); + }; + + using PlaneOffsetFunc = size_t (*)(size_t plane, size_t width, size_t height); + using ByplFunc = size_t (*)(size_t plane, size_t width); + + class VideoFormatGlobals + { + public: + PixelFormat format; + size_t bpp; + size_t planes; + PlaneOffsetFunc planeOffset; + ByplFunc bypl; + std::string str; + + inline static const std::vector &formats(); + static inline const VideoFormatGlobals *byPixelFormat(PixelFormat pixelFormat); + static inline const VideoFormatGlobals *byStr(const std::string &str); + static size_t offsetNV(size_t plane, size_t width, size_t height); + static size_t byplNV(size_t plane, size_t width); + + template + static inline T alignUp(const T &value, const T &align) + { + return (value + align - 1) & ~(align - 1); + } + + template + static inline T align32(const T &value) + { + return alignUp(value, 32); + } + }; +} + +AkVCam::VideoFormat::VideoFormat() +{ + this->d = new VideoFormatPrivate; +} + +AkVCam::VideoFormat::VideoFormat(FourCC fourcc, + int width, + int height, + const std::vector &frameRates) +{ + this->d = new VideoFormatPrivate(fourcc, width, height, frameRates); +} + +AkVCam::VideoFormat::VideoFormat(const VideoFormat &other) +{ + this->d = new VideoFormatPrivate(other.d->m_fourcc, + other.d->m_width, + other.d->m_height, + other.d->m_frameRates); +} + +AkVCam::VideoFormat::~VideoFormat() +{ + delete this->d; +} + +AkVCam::VideoFormat &AkVCam::VideoFormat::operator =(const VideoFormat &other) +{ + if (this != &other) { + this->d->m_fourcc = other.d->m_fourcc; + this->d->m_width = other.d->m_width; + this->d->m_height = other.d->m_height; + this->d->m_frameRates = other.d->m_frameRates; + } + + return *this; +} + +bool AkVCam::VideoFormat::operator ==(const AkVCam::VideoFormat &other) const +{ + return this->d->m_fourcc == other.d->m_fourcc + && this->d->m_width == other.d->m_width + && this->d->m_height == other.d->m_height + && this->d->m_frameRates == other.d->m_frameRates; +} + +bool AkVCam::VideoFormat::operator !=(const AkVCam::VideoFormat &other) const +{ + return this->d->m_fourcc != other.d->m_fourcc + || this->d->m_width != other.d->m_width + || this->d->m_height != other.d->m_height + || this->d->m_frameRates != other.d->m_frameRates; +} + +AkVCam::VideoFormat::operator bool() const +{ + return this->isValid(); +} + +AkVCam::FourCC AkVCam::VideoFormat::fourcc() const +{ + return this->d->m_fourcc; +} + +AkVCam::FourCC &AkVCam::VideoFormat::fourcc() +{ + return this->d->m_fourcc; +} + +int AkVCam::VideoFormat::width() const +{ + return this->d->m_width; +} + +int &AkVCam::VideoFormat::width() +{ + return this->d->m_width; +} + +int AkVCam::VideoFormat::height() const +{ + return this->d->m_height; +} + +int &AkVCam::VideoFormat::height() +{ + return this->d->m_height; +} + +std::vector AkVCam::VideoFormat::frameRates() const +{ + return this->d->m_frameRates; +} + +std::vector &AkVCam::VideoFormat::frameRates() +{ + return this->d->m_frameRates; +} + +std::vector AkVCam::VideoFormat::frameRateRanges() const +{ + std::vector ranges; + + if (!this->d->m_frameRates.empty()) { + auto min = *std::min_element(this->d->m_frameRates.begin(), + this->d->m_frameRates.end()); + auto max = *std::max_element(this->d->m_frameRates.begin(), + this->d->m_frameRates.end()); + ranges.emplace_back(FractionRange {min, max}); + } + + return ranges; +} + +AkVCam::Fraction AkVCam::VideoFormat::minimumFrameRate() const +{ + if (this->d->m_frameRates.empty()) + return {0, 0}; + + return *std::min_element(this->d->m_frameRates.begin(), + this->d->m_frameRates.end()); +} + +size_t AkVCam::VideoFormat::bpp() const +{ + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc)); + + return vf? vf->bpp: 0; +} + +size_t AkVCam::VideoFormat::bypl(size_t plane) const +{ + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc)); + + if (!vf) + return 0; + + if (vf->bypl) + return vf->bypl(plane, size_t(this->d->m_width)); + + return VideoFormatGlobals::align32(size_t(this->d->m_width) * vf->bpp) / 8; +} + +size_t AkVCam::VideoFormat::size() const +{ + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc)); + + if (!vf) + return 0; + + if (vf->planeOffset) + return vf->planeOffset(vf->planes, + size_t(this->d->m_width), + size_t(this->d->m_height)); + + return size_t(this->d->m_height) + * VideoFormatGlobals::align32(size_t(this->d->m_width) + * vf->bpp) / 8; +} + +size_t AkVCam::VideoFormat::planes() const +{ + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc)); + + return vf? vf->planes: 0; +} + +size_t AkVCam::VideoFormat::offset(size_t plane) const +{ + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc)); + + if (!vf) + return 0; + + if (vf->planeOffset) + return vf->planeOffset(plane, + size_t(this->d->m_width), + size_t(this->d->m_height)); + + return 0; +} + +size_t AkVCam::VideoFormat::planeSize(size_t plane) const +{ + return size_t(this->d->m_height) * this->bypl(plane); +} + +bool AkVCam::VideoFormat::isValid() const +{ + if (this->size() <= 0) + return false; + + if (this->d->m_frameRates.empty()) + return false; + + for (auto &fps: this->d->m_frameRates) + if (fps.num() < 1 || fps.den() < 1) + return false; + + return true; +} + +void AkVCam::VideoFormat::clear() +{ + this->d->m_fourcc = 0; + this->d->m_width = 0; + this->d->m_height = 0; + this->d->m_frameRates.clear(); +} + +AkVCam::VideoFormat AkVCam::VideoFormat::nearest(const std::vector &formats) const +{ + VideoFormat nearestFormat; + auto q = std::numeric_limits::max(); + auto svf = VideoFormatGlobals::byPixelFormat(PixelFormat(this->d->m_fourcc)); + + for (auto &format: formats) { + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(format.d->m_fourcc)); + uint64_t diffFourcc = format.d->m_fourcc == this->d->m_fourcc? 0: 1; + auto diffWidth = format.d->m_width - this->d->m_width; + auto diffHeight = format.d->m_height - this->d->m_height; + auto diffBpp = vf->bpp - svf->bpp; + auto diffPlanes = vf->planes - svf->planes; + + uint64_t k = diffFourcc + + uint64_t(diffWidth * diffWidth) + + uint64_t(diffHeight * diffHeight) + + diffBpp * diffBpp + + diffPlanes * diffPlanes; + + if (k < q) { + nearestFormat = format; + q = k; + } + } + + return nearestFormat; +} + +void AkVCam::VideoFormat::roundNearest(int width, int height, + int *owidth, int *oheight, + int align) +{ + /* Explanation: + * + * When 'align' is a power of 2, the left most bit will be 1 (the pivot), + * while all other bits be 0, if destination width is multiple of 'align' + * all bits after pivot position will be 0, then we create a mask + * substracting 1 to the align, so all bits after pivot position in the + * mask will 1. + * Then we negate all bits in the mask so all bits from pivot to the left + * will be 1, and then we use that mask to get a width multiple of align. + * This give us the lower (floor) width nearest to the original 'width' and + * multiple of align. To get the rounded nearest value we add align / 2 to + * 'width'. + * This is the equivalent of: + * + * align * round(width / align) + */ + *owidth = (width + (align >> 1)) & ~(align - 1); + + /* Find the nearest width: + * + * round(height * owidth / width) + */ + *oheight = (2 * height * *owidth + width) / (2 * width); +} + +AkVCam::FourCC AkVCam::VideoFormat::fourccFromString(const std::string &fourccStr) +{ + auto vf = VideoFormatGlobals::byStr(fourccStr); + + return vf? vf->format: 0; +} + +std::string AkVCam::VideoFormat::stringFromFourcc(AkVCam::FourCC fourcc) +{ + auto vf = VideoFormatGlobals::byPixelFormat(PixelFormat(fourcc)); + + return vf? vf->str: std::string(); +} + +std::wstring AkVCam::VideoFormat::wstringFromFourcc(AkVCam::FourCC fourcc) +{ + auto str = stringFromFourcc(fourcc); + + return std::wstring(str.begin(), str.end()); +} + +AkVCam::VideoFormatPrivate::VideoFormatPrivate(FourCC fourcc, + int width, + int height, + const std::vector &frameRates): + m_fourcc(fourcc), + m_width(width), + m_height(height), + m_frameRates(frameRates) +{ +} + +const std::vector &AkVCam::VideoFormatGlobals::formats() +{ + static const std::vector formats { + {PixelFormatRGB32, 32, 1, nullptr, nullptr, "RGB32"}, + {PixelFormatRGB24, 24, 1, nullptr, nullptr, "RGB24"}, + {PixelFormatRGB16, 16, 1, nullptr, nullptr, "RGB16"}, + {PixelFormatRGB15, 16, 1, nullptr, nullptr, "RGB15"}, + {PixelFormatBGR32, 32, 1, nullptr, nullptr, "BGR32"}, + {PixelFormatBGR24, 24, 1, nullptr, nullptr, "BGR24"}, + {PixelFormatBGR16, 16, 1, nullptr, nullptr, "BGR16"}, + {PixelFormatBGR15, 16, 1, nullptr, nullptr, "BGR15"}, + {PixelFormatUYVY , 16, 1, nullptr, nullptr, "UYVY"}, + {PixelFormatYUY2 , 16, 1, nullptr, nullptr, "YUY2"}, + {PixelFormatNV12 , 12, 2, offsetNV, byplNV, "NV12"}, + {PixelFormatNV21 , 12, 2, offsetNV, byplNV, "NV21"} + }; + + return formats; +} + +const AkVCam::VideoFormatGlobals *AkVCam::VideoFormatGlobals::byPixelFormat(PixelFormat pixelFormat) +{ + for (auto &format: formats()) + if (format.format == pixelFormat) + return &format; + + return nullptr; +} + +const AkVCam::VideoFormatGlobals *AkVCam::VideoFormatGlobals::byStr(const std::string &str) +{ + for (auto &format: formats()) + if (format.str == str) + return &format; + + return nullptr; +} + +size_t AkVCam::VideoFormatGlobals::offsetNV(size_t plane, size_t width, size_t height) +{ + size_t offset[] = { + 0, + align32(size_t(width)) * height, + 5 * align32(size_t(width)) * height / 4 + }; + + return offset[plane]; +} + +size_t AkVCam::VideoFormatGlobals::byplNV(size_t plane, size_t width) +{ + UNUSED(plane) + + return align32(size_t(width)); +} diff --git a/VCamUtils/src/image/videoformat.h b/VCamUtils/src/image/videoformat.h new file mode 100644 index 0000000..ac78ed3 --- /dev/null +++ b/VCamUtils/src/image/videoformat.h @@ -0,0 +1,80 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef AKVCAMUTILS_VIDEOFORMAT_H +#define AKVCAMUTILS_VIDEOFORMAT_H + +#include +#include + +#include "videoformattypes.h" +#include "../fraction.h" + +namespace AkVCam +{ + class VideoFormatPrivate; + + class VideoFormat + { + public: + VideoFormat(); + VideoFormat(FourCC fourcc, + int width, + int height, + const std::vector &frameRates={}); + VideoFormat(const VideoFormat &other); + ~VideoFormat(); + VideoFormat &operator =(const VideoFormat &other); + bool operator ==(const VideoFormat &other) const; + bool operator !=(const VideoFormat &other) const; + operator bool() const; + + FourCC fourcc() const; + FourCC &fourcc(); + int width() const; + int &width(); + int height() const; + int &height(); + std::vector frameRates() const; + std::vector &frameRates(); + std::vector frameRateRanges() const; + Fraction minimumFrameRate() const; + size_t bpp() const; + size_t bypl(size_t plane) const; + size_t size() const; + size_t planes() const; + size_t offset(size_t plane) const; + size_t planeSize(size_t plane) const; + bool isValid() const; + void clear(); + VideoFormat nearest(const std::vector &formats) const; + + static void roundNearest(int width, int height, + int *owidth, int *oheight, + int align=32); + static FourCC fourccFromString(const std::string &fourccStr); + static std::string stringFromFourcc(FourCC fourcc); + static std::wstring wstringFromFourcc(FourCC fourcc); + + private: + VideoFormatPrivate *d; + }; +} + +#endif // AKVCAMUTILS_VIDEOFORMAT_H diff --git a/VCamUtils/src/image/videoformattypes.h b/VCamUtils/src/image/videoformattypes.h new file mode 100644 index 0000000..d9327bc --- /dev/null +++ b/VCamUtils/src/image/videoformattypes.h @@ -0,0 +1,59 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef VIDEOFORMATTYPES_H +#define VIDEOFORMATTYPES_H + +#include + +#define MKFOURCC(a, b, c, d) \ + (((uint32_t(a) & 0xff) << 24) \ + | ((uint32_t(b) & 0xff) << 16) \ + | ((uint32_t(c) & 0xff) << 8) \ + | (uint32_t(d) & 0xff)) + +namespace AkVCam +{ + using FourCC = uint32_t; + + enum PixelFormat + { + // RGB formats + PixelFormatRGB32 = MKFOURCC('R', 'G', 'B', 32), + PixelFormatRGB24 = MKFOURCC('R', 'G', 'B', 24), + PixelFormatRGB16 = MKFOURCC('R', 'G', 'B', 16), + PixelFormatRGB15 = MKFOURCC('R', 'G', 'B', 15), + + // BGR formats + PixelFormatBGR32 = MKFOURCC('B', 'G', 'R', 32), + PixelFormatBGR24 = MKFOURCC('B', 'G', 'R', 24), + PixelFormatBGR16 = MKFOURCC('B', 'G', 'R', 16), + PixelFormatBGR15 = MKFOURCC('B', 'G', 'R', 15), + + // Luminance+Chrominance formats + PixelFormatUYVY = MKFOURCC('U', 'Y', 'V', 'Y'), + PixelFormatYUY2 = MKFOURCC('Y', 'U', 'Y', '2'), + + // two planes -- one Y, one Cr + Cb interleaved + PixelFormatNV12 = MKFOURCC('N', 'V', '1', '2'), + PixelFormatNV21 = MKFOURCC('N', 'V', '2', '1') + }; +} + +#endif // VIDEOFORMATTYPES_H diff --git a/VCamUtils/src/image/videoframe.cpp b/VCamUtils/src/image/videoframe.cpp new file mode 100644 index 0000000..f8a9a84 --- /dev/null +++ b/VCamUtils/src/image/videoframe.cpp @@ -0,0 +1,1724 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "videoframe.h" +#include "videoformat.h" +#include "../utils.h" + +namespace AkVCam +{ + struct RGB32 + { + uint8_t x; + uint8_t b; + uint8_t g; + uint8_t r; + }; + + struct RGB24 + { + uint8_t b; + uint8_t g; + uint8_t r; + }; + + struct RGB16 + { + uint16_t b: 5; + uint16_t g: 6; + uint16_t r: 5; + }; + + struct RGB15 + { + uint16_t b: 5; + uint16_t g: 5; + uint16_t r: 5; + uint16_t x: 1; + }; + + struct BGR32 + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t x; + }; + + struct BGR24 + { + uint8_t r; + uint8_t g; + uint8_t b; + }; + + struct BGR16 + { + uint16_t r: 5; + uint16_t g: 6; + uint16_t b: 5; + }; + + struct BGR15 + { + uint16_t r: 5; + uint16_t g: 5; + uint16_t b: 5; + uint16_t x: 1; + }; + + struct UYVY + { + uint8_t v0; + uint8_t y0; + uint8_t u0; + uint8_t y1; + }; + + struct YUY2 + { + uint8_t y0; + uint8_t v0; + uint8_t y1; + uint8_t u0; + }; + + struct UV + { + uint8_t u; + uint8_t v; + }; + + struct VU + { + uint8_t v; + uint8_t u; + }; + + using VideoConvertFuntion = VideoFrame (*)(const VideoFrame *src); + + struct VideoConvert + { + FourCC from; + FourCC to; + VideoConvertFuntion convert; + }; + + class VideoFramePrivate + { + public: + VideoFrame *self; + VideoFormat m_format; + VideoData m_data; + std::vector m_convert; + std::vector m_adjustFormats; + + explicit VideoFramePrivate(VideoFrame *self): + self(self) + { + this->m_convert = { + {PixelFormatBGR24, PixelFormatRGB32, bgr24_to_rgb32}, + {PixelFormatBGR24, PixelFormatRGB24, bgr24_to_rgb24}, + {PixelFormatBGR24, PixelFormatRGB16, bgr24_to_rgb16}, + {PixelFormatBGR24, PixelFormatRGB15, bgr24_to_rgb15}, + {PixelFormatBGR24, PixelFormatBGR32, bgr24_to_bgr32}, + {PixelFormatBGR24, PixelFormatBGR16, bgr24_to_bgr16}, + {PixelFormatBGR24, PixelFormatBGR15, bgr24_to_bgr15}, + {PixelFormatBGR24, PixelFormatUYVY , bgr24_to_uyvy }, + {PixelFormatBGR24, PixelFormatYUY2 , bgr24_to_yuy2 }, + {PixelFormatBGR24, PixelFormatNV12 , bgr24_to_nv12 }, + {PixelFormatBGR24, PixelFormatNV21 , bgr24_to_nv21 }, + + {PixelFormatRGB24, PixelFormatRGB32, rgb24_to_rgb32}, + {PixelFormatRGB24, PixelFormatRGB16, rgb24_to_rgb16}, + {PixelFormatRGB24, PixelFormatRGB15, rgb24_to_rgb15}, + {PixelFormatRGB24, PixelFormatBGR32, rgb24_to_bgr32}, + {PixelFormatRGB24, PixelFormatBGR24, rgb24_to_bgr24}, + {PixelFormatRGB24, PixelFormatBGR16, rgb24_to_bgr16}, + {PixelFormatRGB24, PixelFormatBGR15, rgb24_to_bgr15}, + {PixelFormatRGB24, PixelFormatUYVY , rgb24_to_uyvy }, + {PixelFormatRGB24, PixelFormatYUY2 , rgb24_to_yuy2 }, + {PixelFormatRGB24, PixelFormatNV12 , rgb24_to_nv12 }, + {PixelFormatRGB24, PixelFormatNV21 , rgb24_to_nv21 } + }; + + this->m_adjustFormats = { + PixelFormatBGR24, + PixelFormatRGB24 + }; + } + + template + static inline T bound(T min, T value, T max) + { + return value < min? min: value > max? max: value; + } + + template + inline T mod(T value, T mod) + { + return (value % mod + mod) % mod; + } + + inline int grayval(int r, int g, int b); + + // YUV utility functions + inline static uint8_t rgb_y(int r, int g, int b); + inline static uint8_t rgb_u(int r, int g, int b); + inline static uint8_t rgb_v(int r, int g, int b); + inline static uint8_t yuv_r(int y, int u, int v); + inline static uint8_t yuv_g(int y, int u, int v); + inline static uint8_t yuv_b(int y, int u, int v); + + // BGR to RGB formats + static VideoFrame bgr24_to_rgb32(const VideoFrame *src); + static VideoFrame bgr24_to_rgb24(const VideoFrame *src); + static VideoFrame bgr24_to_rgb16(const VideoFrame *src); + static VideoFrame bgr24_to_rgb15(const VideoFrame *src); + + // BGR to BGR formats + static VideoFrame bgr24_to_bgr32(const VideoFrame *src); + static VideoFrame bgr24_to_bgr16(const VideoFrame *src); + static VideoFrame bgr24_to_bgr15(const VideoFrame *src); + + // BGR to Luminance+Chrominance formats + static VideoFrame bgr24_to_uyvy(const VideoFrame *src); + static VideoFrame bgr24_to_yuy2(const VideoFrame *src); + + // BGR to two planes -- one Y, one Cr + Cb interleaved + static VideoFrame bgr24_to_nv12(const VideoFrame *src); + static VideoFrame bgr24_to_nv21(const VideoFrame *src); + + // RGB to RGB formats + static VideoFrame rgb24_to_rgb32(const VideoFrame *src); + static VideoFrame rgb24_to_rgb16(const VideoFrame *src); + static VideoFrame rgb24_to_rgb15(const VideoFrame *src); + + // RGB to BGR formats + static VideoFrame rgb24_to_bgr32(const VideoFrame *src); + static VideoFrame rgb24_to_bgr24(const VideoFrame *src); + static VideoFrame rgb24_to_bgr16(const VideoFrame *src); + static VideoFrame rgb24_to_bgr15(const VideoFrame *src); + + // RGB to Luminance+Chrominance formats + static VideoFrame rgb24_to_uyvy(const VideoFrame *src); + static VideoFrame rgb24_to_yuy2(const VideoFrame *src); + + // RGB to two planes -- one Y, one Cr + Cb interleaved + static VideoFrame rgb24_to_nv12(const VideoFrame *src); + static VideoFrame rgb24_to_nv21(const VideoFrame *src); + + inline static void extrapolateUp(int dstCoord, + int num, int den, int s, + int *srcCoordMin, int *srcCoordMax, + int *kNum, int *kDen); + inline static void extrapolateDown(int dstCoord, + int num, int den, int s, + int *srcCoordMin, int *srcCoordMax, + int *kNum, int *kDen); + inline uint8_t extrapolateComponent(uint8_t min, uint8_t max, + int kNum, int kDen) const; + inline RGB24 extrapolateColor(const RGB24 &colorMin, + const RGB24 &colorMax, + int kNum, + int kDen) const; + inline RGB24 extrapolateColor(int xMin, int xMax, + int kNumX, int kDenX, + int yMin, int yMax, + int kNumY, int kDenY) const; + inline void rgbToHsl(int r, int g, int b, int *h, int *s, int *l); + inline void hslToRgb(int h, int s, int l, int *r, int *g, int *b); + }; + + std::vector initGammaTable(); + + inline std::vector *gammaTable() { + static auto gammaTable = initGammaTable(); + + return &gammaTable; + } + + std::vector initContrastTable(); + + inline std::vector *contrastTable() { + static auto contrastTable = initContrastTable(); + + return &contrastTable; + } + + struct BmpHeader + { + uint32_t size; + uint16_t reserved1; + uint16_t reserved2; + uint32_t offBits; + }; + + struct BmpImageHeader + { + uint32_t size; + uint32_t width; + uint32_t height; + uint16_t planes; + uint16_t bitCount; + uint32_t compression; + uint32_t sizeImage; + uint32_t xPelsPerMeter; + uint32_t yPelsPerMeter; + uint32_t clrUsed; + uint32_t clrImportant; + }; +} + +AkVCam::VideoFrame::VideoFrame() +{ + this->d = new VideoFramePrivate(this); +} + +AkVCam::VideoFrame::VideoFrame(const std::string &fileName) +{ + this->d = new VideoFramePrivate(this); + this->load(fileName); +} + +AkVCam::VideoFrame::VideoFrame(const AkVCam::VideoFormat &format) +{ + this->d = new VideoFramePrivate(this); + this->d->m_format = format; + + if (format.size() > 0) + this->d->m_data.resize(format.size()); +} + +AkVCam::VideoFrame::VideoFrame(const AkVCam::VideoFrame &other) +{ + this->d = new VideoFramePrivate(this); + this->d->m_format = other.d->m_format; + this->d->m_data = other.d->m_data; +} + +AkVCam::VideoFrame &AkVCam::VideoFrame::operator =(const AkVCam::VideoFrame &other) +{ + if (this != &other) { + this->d->m_format = other.d->m_format; + this->d->m_data = other.d->m_data; + } + + return *this; +} + +AkVCam::VideoFrame::~VideoFrame() +{ + delete this->d; +} + +// http://www.dragonwins.com/domains/getteched/bmp/bmpfileformat.htm +bool AkVCam::VideoFrame::load(const std::string &fileName) +{ + if (fileName.empty()) + return false; + + std::ifstream stream(fileName); + + if (!stream.is_open()) + return false; + + char type[2]; + stream.read(type, 2); + + if (memcmp(type, "BM", 2) != 0) + return false; + + BmpHeader header {}; + stream.read(reinterpret_cast(&header), sizeof(BmpHeader)); + + BmpImageHeader imageHeader {}; + stream.read(reinterpret_cast(&imageHeader), sizeof(BmpImageHeader)); + VideoFormat format(PixelFormatRGB24, + int(imageHeader.width), + int(imageHeader.height)); + + if (format.size() < 1) + return false; + + stream.seekg(header.offBits, std::ios_base::beg); + this->d->m_format = format; + this->d->m_data.resize(format.size()); + + VideoData data(imageHeader.sizeImage); + stream.read(reinterpret_cast(data.data()), + imageHeader.sizeImage); + + switch (imageHeader.bitCount) { + case 24: { + VideoFormat bmpFormat(PixelFormatBGR24, + int(imageHeader.width), + int(imageHeader.height)); + + for (uint32_t y = 0; y < imageHeader.height; y++) { + auto srcLine = reinterpret_cast + (data.data() + y * bmpFormat.bypl(0)); + auto dstLine = reinterpret_cast + (this->line(0, size_t(imageHeader.height - y - 1))); + + for (uint32_t x = 0; x < imageHeader.width; x++) { + dstLine[x].r = srcLine[x].r; + dstLine[x].g = srcLine[x].g; + dstLine[x].b = srcLine[x].b; + } + } + + break; + } + + case 32: { + VideoFormat bmpFormat(PixelFormatBGR32, + int(imageHeader.width), + int(imageHeader.height)); + + for (uint32_t y = 0; y < imageHeader.height; y++) { + auto srcLine = reinterpret_cast + (data.data() + y * bmpFormat.bypl(0)); + auto dstLine = reinterpret_cast + (this->line(0, size_t(imageHeader.height - y - 1))); + + for (uint32_t x = 0; x < imageHeader.width; x++) { + dstLine[x].r = srcLine[x].r; + dstLine[x].g = srcLine[x].g; + dstLine[x].b = srcLine[x].b; + } + } + + break; + } + + default: + this->d->m_format.clear(); + this->d->m_data.clear(); + + return false; + } + + return true; +} + +AkVCam::VideoFormat AkVCam::VideoFrame::format() const +{ + return this->d->m_format; +} + +AkVCam::VideoFormat &AkVCam::VideoFrame::format() +{ + return this->d->m_format; +} + +AkVCam::VideoData AkVCam::VideoFrame::data() const +{ + return this->d->m_data; +} + +AkVCam::VideoData &AkVCam::VideoFrame::data() +{ + return this->d->m_data; +} + +uint8_t *AkVCam::VideoFrame::line(size_t plane, size_t y) const +{ + return this->d->m_data.data() + + this->d->m_format.offset(plane) + + y * this->d->m_format.bypl(plane); +} + +void AkVCam::VideoFrame::clear() +{ + this->d->m_format.clear(); + this->d->m_data.clear(); +} + +AkVCam::VideoFrame AkVCam::VideoFrame::mirror(bool horizontalMirror, + bool verticalMirror) const +{ + if (!horizontalMirror && !verticalMirror) + return *this; + + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + int width = this->d->m_format.width(); + int height = this->d->m_format.height(); + + if (horizontalMirror && verticalMirror) { + for (int y = 0; y < height; y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(height - y - 1))); + auto dstLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) + dstLine[x] = srcLine[width - x - 1]; + } + } else if (horizontalMirror) { + for (int y = 0; y < height; y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto dstLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) + dstLine[x] = srcLine[width - x - 1]; + } + } else if (verticalMirror) { + for (int y = 0; y < height; y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(height - y - 1))); + auto dstLine = reinterpret_cast(dst.line(0, size_t(y))); + memcpy(dstLine, srcLine, size_t(width) * sizeof(RGB24)); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::scaled(int width, + int height, + Scaling mode, + AspectRatio aspectRatio) const +{ + if (this->d->m_format.width() == width + && this->d->m_format.height() == height) + return *this; + + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + int xDstMin = 0; + int yDstMin = 0; + int xDstMax = width; + int yDstMax = height; + + if (aspectRatio == AspectRatioKeep) { + if (width * this->d->m_format.height() + > this->d->m_format.width() * height) { + // Right and left black bars + xDstMin = (width * this->d->m_format.height() + - this->d->m_format.width() * height) + / (2 * this->d->m_format.height()); + xDstMax = (width * this->d->m_format.height() + + this->d->m_format.width() * height) + / (2 * this->d->m_format.height()); + } else if (width * this->d->m_format.height() + < this->d->m_format.width() * height) { + // Top and bottom black bars + yDstMin = (this->d->m_format.width() * height + - width * this->d->m_format.height()) + / (2 * this->d->m_format.width()); + yDstMax = (this->d->m_format.width() * height + + width * this->d->m_format.height()) + / (2 * this->d->m_format.width()); + } + } + + int iWidth = this->d->m_format.width() - 1; + int iHeight = this->d->m_format.height() - 1; + int oWidth = xDstMax - xDstMin - 1; + int oHeight = yDstMax - yDstMin - 1; + int xNum = iWidth; + int xDen = oWidth; + int xs = 0; + int yNum = iHeight; + int yDen = oHeight; + int ys = 0; + + if (aspectRatio == AspectRatioExpanding) { + if (mode == ScalingLinear) { + iWidth--; + iHeight--; + oWidth--; + oHeight--; + } + + if (width * this->d->m_format.height() + < this->d->m_format.width() * height) { + // Right and left cut + xNum = 2 * iHeight; + xDen = 2 * oHeight; + xs = iWidth * oHeight - oWidth * iHeight; + } else if (width * this->d->m_format.height() + > this->d->m_format.width() * height) { + // Top and bottom cut + yNum = 2 * iWidth; + yDen = 2 * oWidth; + ys = oWidth * iHeight - iWidth * oHeight; + } + } + + auto format = this->d->m_format; + format.width() = width; + format.height() = height; + VideoFrame dst(format); + + switch (mode) { + case ScalingFast: + for (int y = yDstMin; y < yDstMax; y++) { + auto srcY = (yNum * (y - yDstMin) + ys) / yDen; + auto srcLine = reinterpret_cast(this->line(0, size_t(srcY))); + auto dstLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = xDstMin; x < xDstMax; x++) { + auto srcX = (xNum * (x - xDstMin) + xs) / xDen; + dstLine[x] = srcLine[srcX]; + } + } + + return dst; + + case ScalingLinear: { + auto extrapolateX = + this->d->m_format.width() < width? + &VideoFramePrivate::extrapolateUp: + &VideoFramePrivate::extrapolateDown; + auto extrapolateY = + this->d->m_format.height() < height? + &VideoFramePrivate::extrapolateUp: + &VideoFramePrivate::extrapolateDown; + + for (int y = yDstMin; y < yDstMax; y++) { + auto dstLine = reinterpret_cast(dst.line(0, size_t(y))); + int yMin; + int yMax; + int kNumY; + int kDenY; + extrapolateY(y - yDstMin, + yNum, yDen, ys, + &yMin, &yMax, + &kNumY, &kDenY); + + for (int x = xDstMin; x < xDstMax; x++) { + int xMin; + int xMax; + int kNumX; + int kDenX; + extrapolateX(x - xDstMin, + xNum, xDen, xs, + &xMin, &xMax, + &kNumX, &kDenX); + + dstLine[x] = + this->d->extrapolateColor(xMin, xMax, + kNumX, kDenX, + yMin, yMax, + kNumY, kDenY); + } + } + + return dst; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::scaled(size_t maxArea, + AkVCam::Scaling mode, + int align) const +{ + auto width = int(sqrt(maxArea + * size_t(this->d->m_format.width()) + / size_t(this->d->m_format.height()))); + auto height = int(sqrt(maxArea + * size_t(this->d->m_format.height()) + / size_t(this->d->m_format.width()))); + int owidth = align * int(width / align); + int oheight = height * owidth / width; + + return this->scaled(owidth, oheight, mode); +} + +AkVCam::VideoFrame AkVCam::VideoFrame::swapRgb(bool swap) const +{ + if (swap) + return this->swapRgb(); + + return *this; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::swapRgb() const +{ + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + + for (int y = 0; y < this->d->m_format.height(); y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto destLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < this->d->m_format.width(); x++) { + destLine[x].r = srcLine[x].b; + destLine[x].g = srcLine[x].g; + destLine[x].b = srcLine[x].r; + } + } + + return dst; +} + +bool AkVCam::VideoFrame::canConvert(FourCC input, FourCC output) const +{ + if (input == output) + return true; + + for (auto &convert: this->d->m_convert) + if (convert.from == input + && convert.to == output) { + return true; + } + + return false; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::convert(AkVCam::FourCC fourcc) const +{ + if (this->d->m_format.fourcc() == fourcc) + return *this; + + VideoConvert *converter = nullptr; + + for (auto &convert: this->d->m_convert) + if (convert.from == this->d->m_format.fourcc() + && convert.to == fourcc) { + converter = &convert; + + break; + } + + if (!converter) + return {}; + + return converter->convert(this); +} + +AkVCam::VideoFrame AkVCam::VideoFrame::adjustHsl(int hue, + int saturation, + int luminance) +{ + if (hue == 0 && saturation == 0 && luminance == 0) + return *this; + + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + + for (int y = 0; y < this->d->m_format.height(); y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto destLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < this->d->m_format.width(); x++) { + int h; + int s; + int l; + this->d->rgbToHsl(srcLine[x].r, srcLine[x].g, srcLine[x].b, + &h, &s, &l); + + h = this->d->mod(h + hue, 360); + s = VideoFramePrivate::bound(0, s + saturation, 255); + l = VideoFramePrivate::bound(0, l + luminance, 255); + + int r; + int g; + int b; + this->d->hslToRgb(h, s, l, &r, &g, &b); + + destLine[x].r = uint8_t(r); + destLine[x].g = uint8_t(g); + destLine[x].b = uint8_t(b); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::adjustGamma(int gamma) +{ + if (gamma == 0) + return *this; + + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + auto dataGt = gammaTable()->data(); + gamma = VideoFramePrivate::bound(-255, gamma, 255); + size_t gammaOffset = size_t(gamma + 255) << 8; + + for (int y = 0; y < this->d->m_format.height(); y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto destLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < this->d->m_format.width(); x++) { + destLine[x].r = dataGt[gammaOffset | srcLine[x].r]; + destLine[x].g = dataGt[gammaOffset | srcLine[x].g]; + destLine[x].b = dataGt[gammaOffset | srcLine[x].b]; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::adjustContrast(int contrast) +{ + if (contrast == 0) + return *this; + + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + auto dataCt = contrastTable()->data(); + contrast = VideoFramePrivate::bound(-255, contrast, 255); + size_t contrastOffset = size_t(contrast + 255) << 8; + + for (int y = 0; y < this->d->m_format.height(); y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto destLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < this->d->m_format.width(); x++) { + destLine[x].r = dataCt[contrastOffset | srcLine[x].r]; + destLine[x].g = dataCt[contrastOffset | srcLine[x].g]; + destLine[x].b = dataCt[contrastOffset | srcLine[x].b]; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::toGrayScale() +{ + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + + for (int y = 0; y < this->d->m_format.height(); y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto destLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < this->d->m_format.width(); x++) { + int luma = this->d->grayval(srcLine[x].r, + srcLine[x].g, + srcLine[x].b); + + destLine[x].r = uint8_t(luma); + destLine[x].g = uint8_t(luma); + destLine[x].b = uint8_t(luma); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFrame::adjust(int hue, + int saturation, + int luminance, + int gamma, + int contrast, + bool gray) +{ + if (hue == 0 + && saturation == 0 + && luminance == 0 + && gamma == 0 + && contrast == 0 + && !gray) + return *this; + + auto it = std::find(this->d->m_adjustFormats.begin(), + this->d->m_adjustFormats.end(), + this->d->m_format.fourcc()); + + if (it == this->d->m_adjustFormats.end()) + return {}; + + VideoFrame dst(this->d->m_format); + auto dataGt = gammaTable()->data(); + auto dataCt = contrastTable()->data(); + + gamma = VideoFramePrivate::bound(-255, gamma, 255); + size_t gammaOffset = size_t(gamma + 255) << 8; + + contrast = VideoFramePrivate::bound(-255, contrast, 255); + size_t contrastOffset = size_t(contrast + 255) << 8; + + for (int y = 0; y < this->d->m_format.height(); y++) { + auto srcLine = reinterpret_cast(this->line(0, size_t(y))); + auto destLine = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < this->d->m_format.width(); x++) { + int r = srcLine[x].r; + int g = srcLine[x].g; + int b = srcLine[x].b; + + if (hue != 0 || saturation != 0 || luminance != 0) { + int h; + int s; + int l; + this->d->rgbToHsl(r, g, b, &h, &s, &l); + + h = this->d->mod(h + hue, 360); + s = VideoFramePrivate::bound(0, s + saturation, 255); + l = VideoFramePrivate::bound(0, l + luminance, 255); + this->d->hslToRgb(h, s, l, &r, &g, &b); + } + + if (gamma != 0) { + r = dataGt[gammaOffset | size_t(r)]; + g = dataGt[gammaOffset | size_t(g)]; + b = dataGt[gammaOffset | size_t(b)]; + } + + if (contrast != 0) { + r = dataCt[contrastOffset | size_t(r)]; + g = dataCt[contrastOffset | size_t(g)]; + b = dataCt[contrastOffset | size_t(b)]; + } + + if (gray) { + int luma = this->d->grayval(r, g, b); + + r = luma; + g = luma; + b = luma; + } + + destLine[x].r = uint8_t(r); + destLine[x].g = uint8_t(g); + destLine[x].b = uint8_t(b); + } + } + + return dst; +} + +int AkVCam::VideoFramePrivate::grayval(int r, int g, int b) +{ + return (11 * r + 16 * g + 5 * b) >> 5; +} + +uint8_t AkVCam::VideoFramePrivate::rgb_y(int r, int g, int b) +{ + return uint8_t(((66 * r + 129 * g + 25 * b + 128) >> 8) + 16); +} + +uint8_t AkVCam::VideoFramePrivate::rgb_u(int r, int g, int b) +{ + return uint8_t(((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128); +} + +uint8_t AkVCam::VideoFramePrivate::rgb_v(int r, int g, int b) +{ + return uint8_t(((112 * r - 94 * g - 18 * b + 128) >> 8) + 128); +} + +uint8_t AkVCam::VideoFramePrivate::yuv_r(int y, int u, int v) +{ + UNUSED(u) + int r = (298 * (y - 16) + 409 * (v - 128) + 128) >> 8; + + return uint8_t(bound(0, r, 255)); +} + +uint8_t AkVCam::VideoFramePrivate::yuv_g(int y, int u, int v) +{ + int g = (298 * (y - 16) - 100 * (u - 128) - 208 * (v - 128) + 128) >> 8; + + return uint8_t(bound(0, g, 255)); +} + +uint8_t AkVCam::VideoFramePrivate::yuv_b(int y, int u, int v) +{ + UNUSED(v) + int b = (298 * (y - 16) + 516 * (u - 128) + 128) >> 8; + + return uint8_t(bound(0, b, 255)); +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_rgb32(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB32; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 255; + dst_line[x].r = src_line[x].r; + dst_line[x].g = src_line[x].g; + dst_line[x].b = src_line[x].b; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_rgb24(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB24; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].r = src_line[x].r; + dst_line[x].g = src_line[x].g; + dst_line[x].b = src_line[x].b; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_rgb16(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB16; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 2; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_rgb15(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB15; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 1; + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 3; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_bgr32(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR32; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 255; + dst_line[x].r = src_line[x].r; + dst_line[x].g = src_line[x].g; + dst_line[x].b = src_line[x].b; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_bgr16(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR16; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 2; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_bgr15(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR15; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 1; + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 3; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_uyvy(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatUYVY; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + auto x_yuv = x / 2; + + auto r0 = src_line[x].r; + auto g0 = src_line[x].g; + auto b0 = src_line[x].b; + + x++; + + int r1 = src_line[x].r; + int g1 = src_line[x].g; + int b1 = src_line[x].b; + + dst_line[x_yuv].u0 = rgb_u(r0, g0, b0); + dst_line[x_yuv].y0 = rgb_y(r0, g0, b0); + dst_line[x_yuv].v0 = rgb_v(r0, g0, b0); + dst_line[x_yuv].y1 = rgb_y(r1, g1, b1); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_yuy2(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatYUY2; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + auto x_yuv = x / 2; + + auto r0 = src_line[x].r; + auto g0 = src_line[x].g; + auto b0 = src_line[x].b; + + x++; + + auto r1 = src_line[x].r; + auto g1 = src_line[x].g; + auto b1 = src_line[x].b; + + dst_line[x_yuv].y0 = rgb_y(r0, g0, b0); + dst_line[x_yuv].u0 = rgb_u(r0, g0, b0); + dst_line[x_yuv].y1 = rgb_y(r1, g1, b1); + dst_line[x_yuv].v0 = rgb_v(r0, g0, b0); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_nv12(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatNV12; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line_y = dst.line(0, size_t(y)); + auto dst_line_vu = reinterpret_cast(dst.line(1, size_t(y) / 2)); + + for (int x = 0; x < width; x++) { + auto r = src_line[x].r; + auto g = src_line[x].g; + auto b = src_line[x].b; + + dst_line_y[y] = rgb_y(r, g, b); + + if (!(x & 0x1) && !(y & 0x1)) { + dst_line_vu[x / 2].v = rgb_v(r, g, b); + dst_line_vu[x / 2].u = rgb_u(r, g, b); + } + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::bgr24_to_nv21(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatNV21; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line_y = dst.line(0, size_t(y)); + auto dst_line_vu = reinterpret_cast(dst.line(1, size_t(y) / 2)); + + for (int x = 0; x < width; x++) { + auto r = src_line[x].r; + auto g = src_line[x].g; + auto b = src_line[x].b; + + dst_line_y[y] = rgb_y(r, g, b); + + if (!(x & 0x1) && !(y & 0x1)) { + dst_line_vu[x / 2].v = rgb_v(r, g, b); + dst_line_vu[x / 2].u = rgb_u(r, g, b); + } + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_rgb32(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB32; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 255; + dst_line[x].r = src_line[x].r; + dst_line[x].g = src_line[x].g; + dst_line[x].b = src_line[x].b; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_rgb16(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB16; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 2; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_rgb15(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatRGB15; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 1; + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 3; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_bgr32(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR32; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 255; + dst_line[x].r = src_line[x].r; + dst_line[x].g = src_line[x].g; + dst_line[x].b = src_line[x].b; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_bgr24(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR24; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].r = src_line[x].r; + dst_line[x].g = src_line[x].g; + dst_line[x].b = src_line[x].b; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_bgr16(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR16; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 2; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_bgr15(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatBGR15; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + dst_line[x].x = 1; + dst_line[x].r = src_line[x].r >> 3; + dst_line[x].g = src_line[x].g >> 3; + dst_line[x].b = src_line[x].b >> 3; + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_uyvy(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatUYVY; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + auto x_yuv = x / 2; + + auto r0 = src_line[x].r; + auto g0 = src_line[x].g; + auto b0 = src_line[x].b; + + x++; + + int r1 = src_line[x].r; + int g1 = src_line[x].g; + int b1 = src_line[x].b; + + dst_line[x_yuv].u0 = rgb_u(r0, g0, b0); + dst_line[x_yuv].y0 = rgb_y(r0, g0, b0); + dst_line[x_yuv].v0 = rgb_v(r0, g0, b0); + dst_line[x_yuv].y1 = rgb_y(r1, g1, b1); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_yuy2(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatYUY2; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line = reinterpret_cast(dst.line(0, size_t(y))); + + for (int x = 0; x < width; x++) { + auto x_yuv = x / 2; + + auto r0 = src_line[x].r; + auto g0 = src_line[x].g; + auto b0 = src_line[x].b; + + x++; + + auto r1 = src_line[x].r; + auto g1 = src_line[x].g; + auto b1 = src_line[x].b; + + dst_line[x_yuv].y0 = rgb_y(r0, g0, b0); + dst_line[x_yuv].u0 = rgb_u(r0, g0, b0); + dst_line[x_yuv].y1 = rgb_y(r1, g1, b1); + dst_line[x_yuv].v0 = rgb_v(r0, g0, b0); + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_nv12(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatNV12; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line_y = dst.line(0, size_t(y)); + auto dst_line_vu = reinterpret_cast(dst.line(1, size_t(y) / 2)); + + for (int x = 0; x < width; x++) { + auto r = src_line[x].r; + auto g = src_line[x].g; + auto b = src_line[x].b; + + dst_line_y[y] = rgb_y(r, g, b); + + if (!(x & 0x1) && !(y & 0x1)) { + dst_line_vu[x / 2].v = rgb_v(r, g, b); + dst_line_vu[x / 2].u = rgb_u(r, g, b); + } + } + } + + return dst; +} + +AkVCam::VideoFrame AkVCam::VideoFramePrivate::rgb24_to_nv21(const VideoFrame *src) +{ + auto format = src->format(); + format.fourcc() = PixelFormatNV21; + VideoFrame dst(format); + auto width = src->format().width(); + auto height = src->format().height(); + + for (int y = 0; y < height; y++) { + auto src_line = reinterpret_cast(src->line(0, size_t(y))); + auto dst_line_y = dst.line(0, size_t(y)); + auto dst_line_vu = reinterpret_cast(dst.line(1, size_t(y) / 2)); + + for (int x = 0; x < width; x++) { + auto r = src_line[x].r; + auto g = src_line[x].g; + auto b = src_line[x].b; + + dst_line_y[y] = rgb_y(r, g, b); + + if (!(x & 0x1) && !(y & 0x1)) { + dst_line_vu[x / 2].v = rgb_v(r, g, b); + dst_line_vu[x / 2].u = rgb_u(r, g, b); + } + } + } + + return dst; +} + +void AkVCam::VideoFramePrivate::extrapolateUp(int dstCoord, + int num, int den, int s, + int *srcCoordMin, int *srcCoordMax, + int *kNum, int *kDen) +{ + *srcCoordMin = (num * dstCoord + s) / den; + *srcCoordMax = *srcCoordMin + 1; + int dstCoordMin = (den * *srcCoordMin - s) / num; + int dstCoordMax = (den * *srcCoordMax - s) / num; + *kNum = dstCoord - dstCoordMin; + *kDen = dstCoordMax - dstCoordMin; +} + +void AkVCam::VideoFramePrivate::extrapolateDown(int dstCoord, + int num, int den, int s, + int *srcCoordMin, int *srcCoordMax, + int *kNum, int *kDen) +{ + *srcCoordMin = (num * dstCoord + s) / den; + *srcCoordMax = *srcCoordMin; + *kNum = 0; + *kDen = 1; +} + +uint8_t AkVCam::VideoFramePrivate::extrapolateComponent(uint8_t min, uint8_t max, + int kNum, int kDen) const +{ + return uint8_t((kNum * (max - min) + kDen * min) / kDen); +} + +AkVCam::RGB24 AkVCam::VideoFramePrivate::extrapolateColor(const RGB24 &colorMin, + const RGB24 &colorMax, + int kNum, + int kDen) const +{ + return RGB24 { + extrapolateComponent(colorMin.b, colorMax.b, kNum, kDen), + extrapolateComponent(colorMin.g, colorMax.g, kNum, kDen), + extrapolateComponent(colorMin.r, colorMax.r, kNum, kDen) + }; +} + +AkVCam::RGB24 AkVCam::VideoFramePrivate::extrapolateColor(int xMin, int xMax, + int kNumX, int kDenX, + int yMin, int yMax, + int kNumY, int kDenY) const +{ + auto minLine = reinterpret_cast(this->self->line(0, size_t(yMin))); + auto maxLine = reinterpret_cast(this->self->line(0, size_t(yMax))); + auto colorMin = extrapolateColor(minLine[xMin], minLine[xMax], kNumX, kDenX); + auto colorMax = extrapolateColor(maxLine[xMin], maxLine[xMax], kNumX, kDenX); + + return extrapolateColor(colorMin, colorMax, kNumY, kDenY); +} + +// https://en.wikipedia.org/wiki/HSL_and_HSV +void AkVCam::VideoFramePrivate::rgbToHsl(int r, int g, int b, int *h, int *s, int *l) +{ + int max = std::max(r, std::max(g, b)); + int min = std::min(r, std::min(g, b)); + int c = max - min; + + *l = (max + min) / 2; + + if (!c) { + *h = 0; + *s = 0; + } else { + if (max == r) + *h = this->mod(g - b, 6 * c); + else if (max == g) + *h = b - r + 2 * c; + else + *h = r - g + 4 * c; + + *h = 60 * (*h) / c; + *s = 255 * c / (255 - abs(max + min - 255)); + } +} + +void AkVCam::VideoFramePrivate::hslToRgb(int h, int s, int l, int *r, int *g, int *b) +{ + int c = s * (255 - abs(2 * l - 255)) / 255; + int x = c * (60 - abs((h % 120) - 60)) / 60; + + if (h >= 0 && h < 60) { + *r = c; + *g = x; + *b = 0; + } else if (h >= 60 && h < 120) { + *r = x; + *g = c; + *b = 0; + } else if (h >= 120 && h < 180) { + *r = 0; + *g = c; + *b = x; + } else if (h >= 180 && h < 240) { + *r = 0; + *g = x; + *b = c; + } else if (h >= 240 && h < 300) { + *r = x; + *g = 0; + *b = c; + } else if (h >= 300 && h < 360) { + *r = c; + *g = 0; + *b = x; + } else { + *r = 0; + *g = 0; + *b = 0; + } + + int m = 2 * l - c; + + *r = (2 * (*r) + m) / 2; + *g = (2 * (*g) + m) / 2; + *b = (2 * (*b) + m) / 2; +} + +std::vector AkVCam::initGammaTable() +{ + std::vector gammaTable; + + for (int i = 0; i < 256; i++) { + auto ig = uint8_t(255. * pow(i / 255., 255)); + gammaTable.push_back(ig); + } + + for (int gamma = -254; gamma < 256; gamma++) { + double k = 255. / (gamma + 255); + + for (int i = 0; i < 256; i++) { + auto ig = uint8_t(255. * pow(i / 255., k)); + gammaTable.push_back(ig); + } + } + + return gammaTable; +} + +std::vector AkVCam::initContrastTable() +{ + std::vector contrastTable; + + for (int contrast = -255; contrast < 256; contrast++) { + double f = 259. * (255 + contrast) / (255. * (259 - contrast)); + + for (int i = 0; i < 256; i++) { + int ic = int(f * (i - 128) + 128.); + contrastTable.push_back(uint8_t(VideoFramePrivate::bound(0, ic, 255))); + } + } + + return contrastTable; +} diff --git a/VCamUtils/src/image/videoframe.h b/VCamUtils/src/image/videoframe.h new file mode 100644 index 0000000..f0f142f --- /dev/null +++ b/VCamUtils/src/image/videoframe.h @@ -0,0 +1,82 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef VIDEOFRAME_H +#define VIDEOFRAME_H + +#include +#include +#include + +#include "videoframetypes.h" +#include "videoformattypes.h" + +namespace AkVCam +{ + class VideoFramePrivate; + class VideoFormat; + using VideoData = std::vector; + + class VideoFrame + { + public: + VideoFrame(); + VideoFrame(const std::string &fileName); + VideoFrame(const VideoFormat &format); + VideoFrame(const VideoFrame &other); + VideoFrame &operator =(const VideoFrame &other); + ~VideoFrame(); + + bool load(const std::string &fileName); + VideoFormat format() const; + VideoFormat &format(); + VideoData data() const; + VideoData &data(); + uint8_t *line(size_t plane, size_t y) const; + void clear(); + + VideoFrame mirror(bool horizontalMirror, bool verticalMirror) const; + VideoFrame scaled(int width, + int height, + Scaling mode=ScalingFast, + AspectRatio aspectRatio=AspectRatioIgnore) const; + VideoFrame scaled(size_t maxArea, + Scaling mode=ScalingFast, + int align=32) const; + VideoFrame swapRgb(bool swap) const; + VideoFrame swapRgb() const; + bool canConvert(FourCC input, FourCC output) const; + VideoFrame convert(FourCC fourcc) const; + VideoFrame adjustHsl(int hue, int saturation, int luminance); + VideoFrame adjustGamma(int gamma); + VideoFrame adjustContrast(int contrast); + VideoFrame toGrayScale(); + VideoFrame adjust(int hue, + int saturation, + int luminance, + int gamma, + int contrast, + bool gray); + + private: + VideoFramePrivate *d; + }; +} + +#endif // VIDEOFRAME_H diff --git a/VCamUtils/src/image/videoframetypes.h b/VCamUtils/src/image/videoframetypes.h new file mode 100644 index 0000000..d67aa65 --- /dev/null +++ b/VCamUtils/src/image/videoframetypes.h @@ -0,0 +1,39 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef VIDEOFRAMETYPES_H +#define VIDEOFRAMETYPES_H + +namespace AkVCam +{ + enum Scaling + { + ScalingFast, + ScalingLinear + }; + + enum AspectRatio + { + AspectRatioIgnore, + AspectRatioKeep, + AspectRatioExpanding + }; +} + +#endif // VIDEOFRAMETYPES_H diff --git a/VCamUtils/src/ipcbridge.h b/VCamUtils/src/ipcbridge.h new file mode 100644 index 0000000..884c283 --- /dev/null +++ b/VCamUtils/src/ipcbridge.h @@ -0,0 +1,236 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef IPCBRIDGE_H +#define IPCBRIDGE_H + +#include +#include + +#include "image/videoformattypes.h" +#include "image/videoframetypes.h" +#include "utils.h" + +namespace AkVCam +{ + class IpcBridgePrivate; + class VideoFormat; + class VideoFrame; + + class IpcBridge + { + public: + enum ServerState + { + ServerStateAvailable, + ServerStateGone + }; + + enum Operation + { + OperationCreate, + OperationEdit, + OperationDestroy, + OperationDestroyAll + }; + + AKVCAM_SIGNAL(ServerStateChanged, + ServerState state) + AKVCAM_SIGNAL(FrameReady, + const std::string &deviceId, + const VideoFrame &frame) + AKVCAM_SIGNAL(DeviceAdded, + const std::string &deviceId) + AKVCAM_SIGNAL(DeviceRemoved, + const std::string &deviceId) + AKVCAM_SIGNAL(ListenerAdded, + const std::string &deviceId, + const std::string &listener) + AKVCAM_SIGNAL(ListenerRemoved, + const std::string &deviceId, + const std::string &listener) + AKVCAM_SIGNAL(BroadcastingChanged, + const std::string &deviceId, + const std::string &broadcaster) + AKVCAM_SIGNAL(MirrorChanged, + const std::string &deviceId, + bool horizontalMirror, + bool verticalMirror) + AKVCAM_SIGNAL(ScalingChanged, + const std::string &deviceId, + Scaling scaling) + AKVCAM_SIGNAL(AspectRatioChanged, + const std::string &deviceId, + AspectRatio aspectRatio) + AKVCAM_SIGNAL(SwapRgbChanged, + const std::string &deviceId, + bool swap) + + public: + IpcBridge(); + ~IpcBridge(); + + /* Server & Client */ + + // Get the last error message. + std::wstring errorMessage() const; + + // Pass extra options to the bridge. + void setOption(const std::string &key, const std::string &value); + + // Driver search paths. + std::vector driverPaths() const; + + // Set driver search paths. + void setDriverPaths(const std::vector &driverPaths); + + // Driver configuration. + std::vector availableDrivers() const; + std::string driver() const; + bool setDriver(const std::string &driver); + + // Configure method to be used for executing commands with elevated + // privileges. + std::vector availableRootMethods() const; + std::string rootMethod() const; + bool setRootMethod(const std::string &rootMethod); + + // Manage main service connection. + void connectService(bool asClient); + void disconnectService(); + + // Register the peer to the global server. + bool registerPeer(bool asClient); + + // Unregister the peer to the global server. + void unregisterPeer(); + + // List available devices. + std::vector listDevices() const; + + // Return human readable description of the device. + std::wstring description(const std::string &deviceId) const; + + // Output pixel formats supported by the driver. + std::vector supportedOutputPixelFormats() const; + + // Default output pixel format of the driver. + PixelFormat defaultOutputPixelFormat() const; + + // Return supported formats for the device. + std::vector formats(const std::string &deviceId) const; + + // Return return the status of the device. + std::string broadcaster(const std::string &deviceId) const; + + // Device is horizontal mirrored, + bool isHorizontalMirrored(const std::string &deviceId); + + // Device is vertical mirrored, + bool isVerticalMirrored(const std::string &deviceId); + + // Scaling mode for frames shown in clients. + Scaling scalingMode(const std::string &deviceId); + + // Aspect ratio mode for frames shown in clients. + AspectRatio aspectRatioMode(const std::string &deviceId); + + // Check if red and blue channels are swapped. + bool swapRgb(const std::string &deviceId); + + // Returns the clients that are capturing from a virtual camera. + std::vector listeners(const std::string &deviceId); + + // Returns clients PIDs using the virtual devices. + std::vector clientsPids() const; + + // Returns client executable from PID. + std::string clientExe(uint64_t pid) const; + + // Returns 'true' if the application needs to be restarted before + // creating, editing, or removing the virtual devices. + bool needsRestart(Operation operation) const; + + // Can create, edit, or remove virtual devices? + bool canApply(Operation operation) const; + + /* Server */ + + // Create a device definition. + std::string deviceCreate(const std::wstring &description, + const std::vector &formats); + + // Edit a device definition. + bool deviceEdit(const std::string &deviceId, + const std::wstring &description, + const std::vector &formats); + + // Change device description. + bool changeDescription(const std::string &deviceId, + const std::wstring &description); + + // Remove a device definition. + bool deviceDestroy(const std::string &deviceId); + + // Remove all device definitions. + bool destroyAllDevices(); + + // Start frame transfer to the device. + bool deviceStart(const std::string &deviceId, + const VideoFormat &format); + + // Stop frame transfer to the device. + void deviceStop(const std::string &deviceId); + + // Transfer a frame to the device. + bool write(const std::string &deviceId, + const VideoFrame &frame); + + // Set mirroring options for device, + void setMirroring(const std::string &deviceId, + bool horizontalMirrored, + bool verticalMirrored); + + // Set scaling options for device. + void setScaling(const std::string &deviceId, + Scaling scaling); + + // Set aspect ratio options for device. + void setAspectRatio(const std::string &deviceId, + AspectRatio aspectRatio); + + // Swap red and blue channels. + void setSwapRgb(const std::string &deviceId, bool swap); + + /* Client */ + + // Increment the count of device listeners + bool addListener(const std::string &deviceId); + + // Decrement the count of device listeners + bool removeListener(const std::string &deviceId); + + private: + IpcBridgePrivate *d; + + friend class IpcBridgePrivate; + }; +} + +#endif // IPCBRIDGE_H diff --git a/VCamUtils/src/logger/logger.cpp b/VCamUtils/src/logger/logger.cpp new file mode 100644 index 0000000..01c87ca --- /dev/null +++ b/VCamUtils/src/logger/logger.cpp @@ -0,0 +1,143 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include + +#include "logger.h" +#include "../utils.h" + +#ifdef QT_DEBUG + +namespace AkVCam +{ + class LoggerPrivate: public std::streambuf + { + public: + std::ostream *outs; + std::string fileName; + std::fstream logFile; + Logger::LogCallback logCallback; + + LoggerPrivate(); + ~LoggerPrivate() override; + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) override; + }; + + inline LoggerPrivate *loggerPrivate() + { + static LoggerPrivate logger; + + return &logger; + } +} + +void AkVCam::Logger::start(const std::string &fileName, + const std::string &extension) +{ + stop(); + loggerPrivate()->fileName = + fileName + "-" + timeStamp() + "." + extension; +} + +void AkVCam::Logger::start(LogCallback callback) +{ + stop(); + loggerPrivate()->logCallback = callback; +} + +std::string AkVCam::Logger::header() +{ + auto now = std::chrono::system_clock::now(); + auto nowMSecs = std::chrono::duration_cast(now.time_since_epoch()); + char timeStamp[256]; + auto time = std::chrono::system_clock::to_time_t(now); + strftime(timeStamp, 256, "%Y-%m-%d %H:%M:%S", std::localtime(&time)); + std::stringstream ss; + ss << "[" + << timeStamp + << "." << nowMSecs.count() % 1000 << ", " << std::this_thread::get_id() << "] "; + + return ss.str(); +} + +std::ostream &AkVCam::Logger::out() +{ + if (loggerPrivate()->logCallback.second) + return *loggerPrivate()->outs; + + if (loggerPrivate()->fileName.empty()) + return std::cout; + + if (!loggerPrivate()->logFile.is_open()) + loggerPrivate()->logFile.open(loggerPrivate()->fileName, + std::ios_base::out + | std::ios_base::app); + + if (!loggerPrivate()->logFile.is_open()) + return std::cout; + + return loggerPrivate()->logFile; +} + +void AkVCam::Logger::log() +{ + tlog(header()); +} + +void AkVCam::Logger::tlog() +{ + out() << std::endl; +} + +void AkVCam::Logger::stop() +{ + loggerPrivate()->fileName = {}; + + if (loggerPrivate()->logFile.is_open()) + loggerPrivate()->logFile.close(); + + loggerPrivate()->logCallback = {nullptr, nullptr}; +} + +AkVCam::LoggerPrivate::LoggerPrivate(): + outs(new std::ostream(this)), + logCallback({nullptr, nullptr}) +{ +} + +AkVCam::LoggerPrivate::~LoggerPrivate() +{ + delete this->outs; +} + +std::streamsize AkVCam::LoggerPrivate::xsputn(const char *s, std::streamsize n) +{ + if (this->logCallback.second) + this->logCallback.second(this->logCallback.first, s, size_t(n)); + + return std::streambuf::xsputn(s, n); +} + +#endif diff --git a/VCamUtils/src/logger/logger.h b/VCamUtils/src/logger/logger.h new file mode 100644 index 0000000..89963d2 --- /dev/null +++ b/VCamUtils/src/logger/logger.h @@ -0,0 +1,67 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef AKVCAMUTILS_LOGGER_H +#define AKVCAMUTILS_LOGGER_H + +#include + +#include "../utils.h" + +#ifdef QT_DEBUG + #define AkLoggerStart(...) AkVCam::Logger::start(__VA_ARGS__) + #define AkLoggerLog(...) AkVCam::Logger::log(__VA_ARGS__) + #define AkLoggerStop() AkVCam::Logger::stop() + + namespace AkVCam + { + namespace Logger + { + AKVCAM_CALLBACK(Log, const char *data, size_t size) + + void start(const std::string &fileName=std::string(), + const std::string &extension=std::string()); + void start(LogCallback callback); + std::string header(); + std::ostream &out(); + void log(); + void tlog(); + void stop(); + + template + void tlog(const First &first, const Next &... next) + { + out() << first; + tlog(next...); + } + + template + void log(const Param &... param) + { + tlog(header(), " ", param...); + } + } + } +#else + #define AkLoggerStart(...) + #define AkLoggerLog(...) + #define AkLoggerStop() +#endif + +#endif // AKVCAMUTILS_LOGGER_H diff --git a/VCamUtils/src/timer.cpp b/VCamUtils/src/timer.cpp new file mode 100644 index 0000000..c4bc16b --- /dev/null +++ b/VCamUtils/src/timer.cpp @@ -0,0 +1,98 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include + +#include "timer.h" + +namespace AkVCam +{ + class TimerPrivate + { + public: + Timer *self; + std::thread m_thread; + int m_interval; + bool m_running; + + explicit TimerPrivate(Timer *self); + void timerLoop(); + }; +} + +AkVCam::Timer::Timer() +{ + this->d = new TimerPrivate(this); +} + +AkVCam::Timer::~Timer() +{ + this->stop(); + delete this->d; +} + +int AkVCam::Timer::interval() const +{ + return this->d->m_interval; +} + +int &AkVCam::Timer::interval() +{ + return this->d->m_interval; +} + +void AkVCam::Timer::setInterval(int msec) +{ + this->d->m_interval = msec; +} + +void AkVCam::Timer::start() +{ + this->stop(); + this->d->m_running = true; + this->d->m_thread = std::thread(&TimerPrivate::timerLoop, this->d); +} + +void AkVCam::Timer::stop() +{ + if (!this->d->m_running) + return; + + this->d->m_running = false; + this->d->m_thread.join(); +} + +AkVCam::TimerPrivate::TimerPrivate(AkVCam::Timer *self): + self(self), + m_interval(0), + m_running(false) +{ + +} + +void AkVCam::TimerPrivate::timerLoop() +{ + while (this->m_running) { + if (this->m_interval) + std::this_thread::sleep_for(std::chrono::milliseconds(this->m_interval)); + + AKVCAM_EMIT_NOARGS(this->self, Timeout); + } +} diff --git a/VCamUtils/src/timer.h b/VCamUtils/src/timer.h new file mode 100644 index 0000000..9db17e1 --- /dev/null +++ b/VCamUtils/src/timer.h @@ -0,0 +1,50 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef TIMER_H +#define TIMER_H + +#include "utils.h" + +namespace AkVCam +{ + class TimerPrivate; + + class Timer + { + AKVCAM_SIGNAL_NOARGS(Timeout) + + public: + Timer(); + Timer(const Timer &other) = delete; + ~Timer(); + + int interval() const; + int &interval(); + void setInterval(int msec); + void start(); + void stop(); + + private: + TimerPrivate *d; + friend class TimerPrivate; + }; +} + +#endif // TIMER_H diff --git a/VCamUtils/src/utils.cpp b/VCamUtils/src/utils.cpp new file mode 100644 index 0000000..89ed214 --- /dev/null +++ b/VCamUtils/src/utils.cpp @@ -0,0 +1,158 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include + +#include "utils.h" + +uint64_t AkVCam::id() +{ + static uint64_t id = 0; + + return id++; +} + +std::string AkVCam::timeStamp() +{ + char ts[256]; + auto time = std::time(nullptr); + strftime(ts, 256, "%Y%m%d%H%M%S", std::localtime(&time)); + + return std::string(ts); +} + +std::string AkVCam::replace(const std::string &str, + const std::string &from, + const std::string &to) +{ + auto newStr = str; + + for (auto pos = newStr.find(from); + pos != std::string::npos; + pos = newStr.find(from)) + newStr.replace(pos, from.size(), to); + + return newStr; +} + +std::wstring AkVCam::replace(const std::wstring &str, + const std::wstring &from, + const std::wstring &to) +{ + auto newStr = str; + + for (auto pos = newStr.find(from); + pos != std::wstring::npos; + pos = newStr.find(from)) + newStr.replace(pos, from.size(), to); + + return newStr; +} + +bool AkVCam::isEqualFile(const std::wstring &file1, const std::wstring &file2) +{ + if (file1 == file2) + return true; + + std::fstream f1; + std::fstream f2; + f1.open(std::string(file1.begin(), file1.end()), std::ios_base::in); + f2.open(std::string(file2.begin(), file2.end()), std::ios_base::in); + + if (!f1.is_open() || !f2.is_open()) + return false; + + const size_t bufferSize = 1024; + char buffer1[bufferSize]; + char buffer2[bufferSize]; + memset(buffer1, 0, bufferSize); + memset(buffer2, 0, bufferSize); + + while (!f1.eof() && !f2.eof()) { + f1.read(buffer1, bufferSize); + f2.read(buffer2, bufferSize); + + if (memcmp(buffer1, buffer2, bufferSize) != 0) + return false; + } + + return true; +} + +std::string AkVCam::trimmed(const std::string &str) +{ + auto left = uint64_t(str.size()); + auto right = uint64_t(str.size()); + + for (size_t i = 0; i < str.size(); i++) + if (!isspace(str[i])) { + left = uint64_t(i); + + break; + } + + auto strippedLen = str.size(); + + if (left == str.size()) { + strippedLen = 0; + } else { + for (int64_t i = str.size() - 1; i >= 0; i--) + if (!isspace(str[size_t(i)])) { + right = uint64_t(i); + + break; + } + + strippedLen = size_t(right - left + 1); + } + + return str.substr(size_t(left), strippedLen); +} + +std::wstring AkVCam::trimmed(const std::wstring &str) +{ + auto left = uint64_t(str.size()); + auto right = uint64_t(str.size()); + + for (size_t i = 0; i < str.size(); i++) + if (!iswspace(str[i])) { + left = uint64_t(i); + + break; + } + + auto strippedLen = str.size(); + + if (left == str.size()) { + strippedLen = 0; + } else { + for (int64_t i = str.size() - 1; i >= 0; i--) + if (!iswspace(str[size_t(i)])) { + right = uint64_t(i); + + break; + } + + strippedLen = size_t(right - left + 1); + } + + return str.substr(size_t(left), strippedLen); +} diff --git a/VCamUtils/src/utils.h b/VCamUtils/src/utils.h new file mode 100644 index 0000000..730f40d --- /dev/null +++ b/VCamUtils/src/utils.h @@ -0,0 +1,158 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef AKVCAMUTILS_UTILS_H +#define AKVCAMUTILS_UTILS_H + +#include +#include +#include + +#ifndef UNUSED + #define UNUSED(x) (void)(x); +#endif + +#define GLOBAL_STATIC(type, variableName) \ + type *variableName() \ + { \ + static type _##variableName; \ + \ + return &_##variableName; \ + } + +#define GLOBAL_STATIC_WITH_ARGS(type, variableName, ...) \ + type *variableName() \ + { \ + static type _##variableName {__VA_ARGS__}; \ + \ + return &_##variableName; \ + } + +#define AKVCAM_CALLBACK(CallbackName, ...) \ + using CallbackName##CallbackT = void (*)(void *userData, __VA_ARGS__); \ + using CallbackName##Callback = std::pair; + +#define AKVCAM_CALLBACK_NOARGS(CallbackName) \ + using CallbackName##CallbackT = void (*)(void *userData); \ + using CallbackName##Callback = std::pair; + +#define AKVCAM_SIGNAL(CallbackName, ...) \ + public: \ + using CallbackName##CallbackT = void (*)(void *userData, __VA_ARGS__); \ + using CallbackName##Callback = std::pair; \ + \ + void connect##CallbackName(void *userData, \ + CallbackName##CallbackT callback) \ + { \ + if (!callback) \ + return; \ + \ + for (auto &func: this->m_##CallbackName##Callback) \ + if (func.first == userData \ + && func.second == callback) \ + return; \ + \ + this->m_##CallbackName##Callback.push_back({userData, callback});\ + } \ + \ + void disconnect##CallbackName(void *userData, \ + CallbackName##CallbackT callback) \ + { \ + if (!callback) \ + return; \ + \ + for (auto it = this->m_##CallbackName##Callback.begin(); \ + it != this->m_##CallbackName##Callback.end(); \ + it++) \ + if (it->first == userData \ + && it->second == callback) { \ + this->m_##CallbackName##Callback.erase(it); \ + \ + return; \ + } \ + } \ + \ + private: \ + std::vector m_##CallbackName##Callback; + +#define AKVCAM_SIGNAL_NOARGS(CallbackName) \ + public: \ + using CallbackName##CallbackT = void (*)(void *userData); \ + using CallbackName##Callback = std::pair; \ + \ + void connect##CallbackName(void *userData, \ + CallbackName##CallbackT callback) \ + { \ + if (!callback) \ + return; \ + \ + for (auto &func: this->m_##CallbackName##Callback) \ + if (func.first == userData \ + && func.second == callback) \ + return; \ + \ + this->m_##CallbackName##Callback.push_back({userData, callback});\ + } \ + \ + void disconnect##CallbackName(void *userData, \ + CallbackName##CallbackT callback) \ + { \ + if (!callback) \ + return; \ + \ + for (auto it = this->m_##CallbackName##Callback.begin(); \ + it != this->m_##CallbackName##Callback.end(); \ + it++) \ + if (it->first == userData \ + && it->second == callback) { \ + this->m_##CallbackName##Callback.erase(it); \ + \ + return; \ + } \ + } \ + \ + private: \ + std::vector m_##CallbackName##Callback; + +#define AKVCAM_EMIT(owner, CallbackName, ...) \ + for (auto &callback: owner->m_##CallbackName##Callback) \ + if (callback.second) \ + callback.second(callback.first, __VA_ARGS__); \ + +#define AKVCAM_EMIT_NOARGS(owner, CallbackName) \ + for (auto &callback: owner->m_##CallbackName##Callback) \ + if (callback.second) \ + callback.second(callback.first); \ + +namespace AkVCam +{ + uint64_t id(); + std::string timeStamp(); + std::string replace(const std::string &str, + const std::string &from, + const std::string &to); + std::wstring replace(const std::wstring &str, + const std::wstring &from, + const std::wstring &to); + bool isEqualFile(const std::wstring &file1, const std::wstring &file2); + std::string trimmed(const std::string &str); + std::wstring trimmed(const std::wstring &str); +} + +#endif // AKVCAMUTILS_UTILS_H diff --git a/akvirtualcamera.pro b/akvirtualcamera.pro new file mode 100644 index 0000000..95833a7 --- /dev/null +++ b/akvirtualcamera.pro @@ -0,0 +1,25 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = VCamUtils +macx: SUBDIRS += cmio +win32: SUBDIRS += dshow +SUBDIRS += Manager diff --git a/cmio/Assistant/Assistant.pro b/cmio/Assistant/Assistant.pro new file mode 100644 index 0000000..9ebd991 --- /dev/null +++ b/cmio/Assistant/Assistant.pro @@ -0,0 +1,59 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../cmio.pri) +include(../../VCamUtils/VCamUtils.pri) + +CONFIG += console link_prl +CONFIG -= app_bundle +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = $${CMIO_PLUGIN_ASSISTANT_NAME} + +TEMPLATE = app + +SOURCES += \ + src/main.cpp \ + src/assistant.cpp + +LIBS += \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -framework CoreFoundation + +HEADERS += \ + src/assistantglobals.h \ + src/assistant.h + +INCLUDEPATH += \ + ../.. + +QMAKE_POST_LINK = \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/../VirtualCamera/$${CMIO_PLUGIN_NAME}.plugin/Contents/Resources)) $${CMD_SEP} \ + $(COPY) $$shell_path($${OUT_PWD}/$${BIN_DIR}/$${CMIO_PLUGIN_ASSISTANT_NAME}) $$shell_path($${OUT_PWD}/../VirtualCamera/$${CMIO_PLUGIN_NAME}.plugin/Contents/Resources) diff --git a/cmio/Assistant/src/assistant.cpp b/cmio/Assistant/src/assistant.cpp new file mode 100644 index 0000000..9b7b141 --- /dev/null +++ b/cmio/Assistant/src/assistant.cpp @@ -0,0 +1,1469 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include + +#include "assistant.h" +#include "assistantglobals.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/logger/logger.h" + +#define AkAssistantLogMethod() \ + AkLoggerLog("Assistant::", __FUNCTION__, "()") + +#define AkAssistantPrivateLogMethod() \ + AkLoggerLog("ServicePrivate::", __FUNCTION__, "()") + +#define AKVCAM_BIND_FUNC(member) \ + std::bind(&member, this, std::placeholders::_1, std::placeholders::_2) + +#define PREFERENCES_ID CFSTR(AKVCAM_ASSISTANT_NAME) + +namespace AkVCam +{ + struct AssistantDevice + { + std::wstring description; + std::vector formats; + std::string broadcaster; + std::vector listeners; + bool horizontalMirror {false}; + bool verticalMirror {false}; + Scaling scaling {ScalingFast}; + AspectRatio aspectRatio {AspectRatioIgnore}; + bool swapRgb {false}; + }; + + using AssistantPeers = std::map; + using DeviceConfigs = std::map; + + class AssistantPrivate + { + public: + AssistantPeers m_servers; + AssistantPeers m_clients; + DeviceConfigs m_deviceConfigs; + std::map m_messageHandlers; + CFRunLoopTimerRef m_timer {nullptr}; + double m_timeout {0.0}; + + AssistantPrivate(); + ~AssistantPrivate(); + inline static uint64_t id(); + bool startTimer(); + void stopTimer(); + static void timerTimeout(CFRunLoopTimerRef timer, void *info); + std::shared_ptr cfTypeFromStd(const std::string &str) const; + std::shared_ptr cfTypeFromStd(const std::wstring &str) const; + std::shared_ptr cfTypeFromStd(int num) const; + std::shared_ptr cfTypeFromStd(double num) const; + std::string stringFromCFType(CFTypeRef cfType) const; + std::wstring wstringFromCFType(CFTypeRef cfType) const; + std::vector preferencesKeys() const; + void preferencesWrite(const std::string &key, + const std::shared_ptr &value) const; + void preferencesWrite(const std::string &key, + const std::string &value) const; + void preferencesWrite(const std::string &key, + const std::wstring &value) const; + void preferencesWrite(const std::string &key, int value) const; + void preferencesWrite(const std::string &key, double value) const; + std::shared_ptr preferencesRead(const std::string &key) const; + std::string preferencesReadString(const std::string &key) const; + std::wstring preferencesReadWString(const std::string &key) const; + int preferencesReadInt(const std::string &key) const; + double preferencesReadDouble(const std::string &key) const; + void preferencesDelete(const std::string &key) const; + void preferencesDeleteAll(const std::string &key) const; + void preferencesMove(const std::string &keyFrom, + const std::string &keyTo) const; + void preferencesMoveAll(const std::string &keyFrom, + const std::string &keyTo) const; + void preferencesSync() const; + std::string preferencesAddCamera(const std::wstring &description, + const std::vector &formats); + std::string preferencesAddCamera(const std::string &path, + const std::wstring &description, + const std::vector &formats); + void preferencesRemoveCamera(const std::string &path); + size_t camerasCount() const; + std::string createDevicePath() const; + int cameraFromPath(const std::string &path) const; + bool cameraExists(const std::string &path) const; + std::wstring cameraDescription(size_t cameraIndex) const; + std::string cameraPath(size_t cameraIndex) const; + size_t formatsCount(size_t cameraIndex) const; + VideoFormat cameraFormat(size_t cameraIndex, size_t formatIndex) const; + std::vector cameraFormats(size_t cameraIndex) const; + void loadCameras(); + void releaseDevicesFromPeer(const std::string &portName); + void peerDied(); + void requestPort(xpc_connection_t client, xpc_object_t event); + void addPort(xpc_connection_t client, xpc_object_t event); + void removePortByName(const std::string &portName); + void removePort(xpc_connection_t client, xpc_object_t event); + void deviceCreate(xpc_connection_t client, xpc_object_t event); + void deviceDestroyById(const std::string &deviceId); + void deviceDestroy(xpc_connection_t client, xpc_object_t event); + void setBroadcasting(xpc_connection_t client, xpc_object_t event); + void setMirroring(xpc_connection_t client, xpc_object_t event); + void setScaling(xpc_connection_t client, xpc_object_t event); + void setAspectRatio(xpc_connection_t client, xpc_object_t event); + void setSwapRgb(xpc_connection_t client, xpc_object_t event); + void frameReady(xpc_connection_t client, xpc_object_t event); + void listeners(xpc_connection_t client, xpc_object_t event); + void listener(xpc_connection_t client, xpc_object_t event); + void devices(xpc_connection_t client, xpc_object_t event); + void description(xpc_connection_t client, xpc_object_t event); + void formats(xpc_connection_t client, xpc_object_t event); + void broadcasting(xpc_connection_t client, xpc_object_t event); + void mirroring(xpc_connection_t client, xpc_object_t event); + void scaling(xpc_connection_t client, xpc_object_t event); + void aspectRatio(xpc_connection_t client, xpc_object_t event); + void swapRgb(xpc_connection_t client, xpc_object_t event); + void listenerAdd(xpc_connection_t client, xpc_object_t event); + void listenerRemove(xpc_connection_t client, xpc_object_t event); + }; +} + +AkVCam::Assistant::Assistant() +{ + this->d = new AssistantPrivate; +} + +AkVCam::Assistant::~Assistant() +{ + delete this->d; +} + +void AkVCam::Assistant::setTimeout(double timeout) +{ + this->d->m_timeout = timeout; +} + +void AkVCam::Assistant::messageReceived(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantLogMethod(); + auto type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) { + if (event == XPC_ERROR_CONNECTION_INVALID) { + this->d->peerDied(); + } else { + auto description = xpc_copy_description(event); + AkLoggerLog("ERROR: ", description); + free(description); + } + } else if (type == XPC_TYPE_DICTIONARY) { + int64_t message = xpc_dictionary_get_int64(event, "message"); + + if (this->d->m_messageHandlers.count(message)) + this->d->m_messageHandlers[message](client, event); + } +} + +AkVCam::AssistantPrivate::AssistantPrivate() +{ + this->m_messageHandlers = { + {AKVCAM_ASSISTANT_MSG_FRAME_READY , AKVCAM_BIND_FUNC(AssistantPrivate::frameReady) }, + {AKVCAM_ASSISTANT_MSG_REQUEST_PORT , AKVCAM_BIND_FUNC(AssistantPrivate::requestPort) }, + {AKVCAM_ASSISTANT_MSG_ADD_PORT , AKVCAM_BIND_FUNC(AssistantPrivate::addPort) }, + {AKVCAM_ASSISTANT_MSG_REMOVE_PORT , AKVCAM_BIND_FUNC(AssistantPrivate::removePort) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_CREATE , AKVCAM_BIND_FUNC(AssistantPrivate::deviceCreate) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY , AKVCAM_BIND_FUNC(AssistantPrivate::deviceDestroy) }, + {AKVCAM_ASSISTANT_MSG_DEVICES , AKVCAM_BIND_FUNC(AssistantPrivate::devices) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_DESCRIPTION , AKVCAM_BIND_FUNC(AssistantPrivate::description) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_FORMATS , AKVCAM_BIND_FUNC(AssistantPrivate::formats) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD , AKVCAM_BIND_FUNC(AssistantPrivate::listenerAdd) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE, AKVCAM_BIND_FUNC(AssistantPrivate::listenerRemove) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENERS , AKVCAM_BIND_FUNC(AssistantPrivate::listeners) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER , AKVCAM_BIND_FUNC(AssistantPrivate::listener) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING , AKVCAM_BIND_FUNC(AssistantPrivate::broadcasting) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING, AKVCAM_BIND_FUNC(AssistantPrivate::setBroadcasting)}, + {AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING , AKVCAM_BIND_FUNC(AssistantPrivate::mirroring) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING , AKVCAM_BIND_FUNC(AssistantPrivate::setMirroring) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SCALING , AKVCAM_BIND_FUNC(AssistantPrivate::scaling) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING , AKVCAM_BIND_FUNC(AssistantPrivate::setScaling) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_ASPECTRATIO , AKVCAM_BIND_FUNC(AssistantPrivate::aspectRatio) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO , AKVCAM_BIND_FUNC(AssistantPrivate::setAspectRatio) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB , AKVCAM_BIND_FUNC(AssistantPrivate::swapRgb) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB , AKVCAM_BIND_FUNC(AssistantPrivate::setSwapRgb) }, + }; + + this->loadCameras(); + this->startTimer(); +} + +AkVCam::AssistantPrivate::~AssistantPrivate() +{ + std::vector allPeers { + &this->m_clients, + &this->m_servers + }; + + for (auto &device: this->m_deviceConfigs) { + auto notification = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_int64(notification, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY); + xpc_dictionary_set_string(notification, "device", device.first.c_str()); + + for (auto peers: allPeers) + for (auto &peer: *peers) + xpc_connection_send_message(peer.second, notification); + + xpc_release(notification); + } +} + +uint64_t AkVCam::AssistantPrivate::id() +{ + static uint64_t id = 0; + + return id++; +} + +bool AkVCam::AssistantPrivate::startTimer() +{ + AkAssistantPrivateLogMethod(); + + if (this->m_timer || this->m_timeout <= 0.0) + return false; + + // If no peer has been connected for 5 minutes shutdown the assistant. + CFRunLoopTimerContext context {0, this, nullptr, nullptr, nullptr}; + this->m_timer = + CFRunLoopTimerCreate(kCFAllocatorDefault, + CFAbsoluteTimeGetCurrent() + this->m_timeout, + 0, + 0, + 0, + AssistantPrivate::timerTimeout, + &context); + + if (!this->m_timer) + return false; + + CFRunLoopAddTimer(CFRunLoopGetMain(), + this->m_timer, + kCFRunLoopCommonModes); + + return true; +} + +void AkVCam::AssistantPrivate::stopTimer() +{ + AkAssistantPrivateLogMethod(); + + if (!this->m_timer) + return; + + CFRunLoopTimerInvalidate(this->m_timer); + CFRunLoopRemoveTimer(CFRunLoopGetMain(), + this->m_timer, + kCFRunLoopCommonModes); + CFRelease(this->m_timer); + this->m_timer = nullptr; +} + +void AkVCam::AssistantPrivate::timerTimeout(CFRunLoopTimerRef timer, void *info) +{ + UNUSED(timer) + UNUSED(info) + AkAssistantPrivateLogMethod(); + + CFRunLoopStop(CFRunLoopGetMain()); +} + +std::shared_ptr AkVCam::AssistantPrivate::cfTypeFromStd(const std::string &str) const +{ + auto ref = + new CFTypeRef(CFStringCreateWithCString(kCFAllocatorDefault, + str.c_str(), + kCFStringEncodingUTF8)); + + return std::shared_ptr(ref, [] (CFTypeRef *ptr) { + CFRelease(*ptr); + delete ptr; + }); +} + +std::shared_ptr AkVCam::AssistantPrivate::cfTypeFromStd(const std::wstring &str) const +{ + auto ref = + new CFTypeRef(CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast(str.c_str()), + CFIndex(str.size() * sizeof(wchar_t)), + kCFStringEncodingUTF32LE, + false)); + + return std::shared_ptr(ref, [] (CFTypeRef *ptr) { + CFRelease(*ptr); + delete ptr; + }); +} + +std::shared_ptr AkVCam::AssistantPrivate::cfTypeFromStd(int num) const +{ + auto ref = + new CFTypeRef(CFNumberCreate(kCFAllocatorDefault, + kCFNumberIntType, + &num)); + + return std::shared_ptr(ref, [] (CFTypeRef *ptr) { + CFRelease(*ptr); + delete ptr; + }); +} + +std::shared_ptr AkVCam::AssistantPrivate::cfTypeFromStd(double num) const +{ + auto ref = + new CFTypeRef(CFNumberCreate(kCFAllocatorDefault, + kCFNumberDoubleType, + &num)); + + return std::shared_ptr(ref, [] (CFTypeRef *ptr) { + CFRelease(*ptr); + delete ptr; + }); +} + +std::string AkVCam::AssistantPrivate::stringFromCFType(CFTypeRef cfType) const +{ + auto len = size_t(CFStringGetLength(CFStringRef(cfType))); + auto data = CFStringGetCStringPtr(CFStringRef(cfType), kCFStringEncodingUTF8); + + if (data) + return std::string(data, len); + + auto cstr = new char[len]; + CFStringGetCString(CFStringRef(cfType), cstr, CFIndex(len), kCFStringEncodingUTF8); + std::string str(cstr, len); + delete [] cstr; + + return str; +} + +std::wstring AkVCam::AssistantPrivate::wstringFromCFType(CFTypeRef cfType) const +{ + auto len = CFStringGetLength(CFStringRef(cfType)); + auto range = CFRangeMake(0, len); + CFIndex bufferLen = 0; + auto converted = CFStringGetBytes(CFStringRef(cfType), + range, + kCFStringEncodingUTF32LE, + 0, + false, + nullptr, + 0, + &bufferLen); + + if (converted < 1 || bufferLen < 1) + return {}; + + wchar_t cstr[bufferLen]; + + converted = CFStringGetBytes(CFStringRef(cfType), + range, + kCFStringEncodingUTF32LE, + 0, + false, + reinterpret_cast(cstr), + bufferLen, + nullptr); + + if (converted < 1) + return {}; + + return std::wstring(cstr, size_t(len)); +} + +std::vector AkVCam::AssistantPrivate::preferencesKeys() const +{ + AkAssistantPrivateLogMethod(); + std::vector keys; + + auto cfKeys = CFPreferencesCopyKeyList(PREFERENCES_ID, + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + + if (cfKeys) { + auto size = CFArrayGetCount(cfKeys); + + for (CFIndex i = 0; i < size; i++) { + auto key = CFStringRef(CFArrayGetValueAtIndex(cfKeys, i)); + keys.push_back(this->stringFromCFType(key)); + } + + CFRelease(cfKeys); + } + +#ifdef QT_DEBUG + AkLoggerLog("Keys: ", keys.size()); + std::sort(keys.begin(), keys.end()); + + for (auto &key: keys) + AkLoggerLog(" ", key); +#endif + + return keys; +} + +void AkVCam::AssistantPrivate::preferencesWrite(const std::string &key, + const std::shared_ptr &value) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Writing: ", key, " = ", *value); + auto cfKey = cfTypeFromStd(key); + CFPreferencesSetAppValue(CFStringRef(*cfKey), *value, PREFERENCES_ID); +} + +void AkVCam::AssistantPrivate::preferencesWrite(const std::string &key, + const std::string &value) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Writing: ", key, " = ", value); + auto cfKey = cfTypeFromStd(key); + auto cfValue = cfTypeFromStd(value); + CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID); +} + +void AkVCam::AssistantPrivate::preferencesWrite(const std::string &key, + const std::wstring &value) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Writing: ", key, " = ", std::string(value.begin(), + value.end())); + auto cfKey = cfTypeFromStd(key); + auto cfValue = cfTypeFromStd(value); + CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID); +} + +void AkVCam::AssistantPrivate::preferencesWrite(const std::string &key, + int value) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Writing: ", key, " = ", value); + auto cfKey = cfTypeFromStd(key); + auto cfValue = cfTypeFromStd(value); + CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID); +} + +void AkVCam::AssistantPrivate::preferencesWrite(const std::string &key, + double value) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Writing: ", key, " = ", value); + auto cfKey = cfTypeFromStd(key); + auto cfValue = cfTypeFromStd(value); + CFPreferencesSetAppValue(CFStringRef(*cfKey), *cfValue, PREFERENCES_ID); +} + +std::shared_ptr AkVCam::AssistantPrivate::preferencesRead(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + auto cfKey = cfTypeFromStd(key); + auto cfValue = CFTypeRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey), + PREFERENCES_ID)); + + if (!cfValue) + return {}; + + return std::shared_ptr(new CFTypeRef(cfValue), + [] (CFTypeRef *ptr) { + CFRelease(*ptr); + delete ptr; + }); +} + +std::string AkVCam::AssistantPrivate::preferencesReadString(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + auto cfKey = cfTypeFromStd(key); + auto cfValue = + CFStringRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey), + PREFERENCES_ID)); + std::string value; + + if (cfValue) { + value = this->stringFromCFType(cfValue); + CFRelease(cfValue); + } + + return value; +} + +std::wstring AkVCam::AssistantPrivate::preferencesReadWString(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + auto cfKey = cfTypeFromStd(key); + auto cfValue = + CFStringRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey), + PREFERENCES_ID)); + std::wstring value; + + if (cfValue) { + value = this->wstringFromCFType(cfValue); + CFRelease(cfValue); + } + + return value; +} + +int AkVCam::AssistantPrivate::preferencesReadInt(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + auto cfKey = cfTypeFromStd(key); + auto cfValue = + CFNumberRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey), + PREFERENCES_ID)); + int value = 0; + + if (cfValue) { + CFNumberGetValue(cfValue, kCFNumberIntType, &value); + CFRelease(cfValue); + } + + return value; +} + +double AkVCam::AssistantPrivate::preferencesReadDouble(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + auto cfKey = cfTypeFromStd(key); + auto cfValue = + CFNumberRef(CFPreferencesCopyAppValue(CFStringRef(*cfKey), + PREFERENCES_ID)); + double value = 0; + + if (cfValue) { + CFNumberGetValue(cfValue, kCFNumberDoubleType, &value); + CFRelease(cfValue); + } + + return value; +} + +void AkVCam::AssistantPrivate::preferencesDelete(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Deleting ", key); + auto cfKey = cfTypeFromStd(key); + CFPreferencesSetAppValue(CFStringRef(*cfKey), nullptr, PREFERENCES_ID); +} + +void AkVCam::AssistantPrivate::preferencesDeleteAll(const std::string &key) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Key: ", key); + + for (auto &key_: this->preferencesKeys()) + if (key_.size() >= key.size() && key_.substr(0, key.size()) == key) + this->preferencesDelete(key_); +} + +void AkVCam::AssistantPrivate::preferencesMove(const std::string &keyFrom, + const std::string &keyTo) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("From: ", keyFrom); + AkLoggerLog("To: ", keyTo); + auto value = this->preferencesRead(keyFrom); + + if (!value) + return; + + this->preferencesWrite(keyTo, value); + this->preferencesDelete(keyFrom); +} + +void AkVCam::AssistantPrivate::preferencesMoveAll(const std::string &keyFrom, + const std::string &keyTo) const +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("From: ", keyFrom); + AkLoggerLog("to: ", keyTo); + + for (auto &key: this->preferencesKeys()) + if (key.size() >= keyFrom.size() + && key.substr(0, keyFrom.size()) == keyFrom) { + if (key.size() == keyFrom.size()) + this->preferencesMove(key, keyTo); + else + this->preferencesMove(key, keyTo + key.substr(keyFrom.size())); + } +} + +void AkVCam::AssistantPrivate::preferencesSync() const +{ + AkAssistantPrivateLogMethod(); + CFPreferencesAppSynchronize(PREFERENCES_ID); +} + +std::string AkVCam::AssistantPrivate::preferencesAddCamera(const std::wstring &description, + const std::vector &formats) +{ + return this->preferencesAddCamera("", description, formats); +} + +std::string AkVCam::AssistantPrivate::preferencesAddCamera(const std::string &path, + const std::wstring &description, + const std::vector &formats) +{ + AkAssistantPrivateLogMethod(); + + if (!path.empty() && this->cameraExists(path)) + return {}; + + auto path_ = path.empty()? this->createDevicePath(): path; + int cameraIndex = this->preferencesReadInt("cameras"); + this->preferencesWrite("cameras", cameraIndex + 1); + + this->preferencesWrite("cameras." + + std::to_string(cameraIndex) + + ".description", + description); + this->preferencesWrite("cameras." + + std::to_string(cameraIndex) + + ".path", + path_); + this->preferencesWrite("cameras." + + std::to_string(cameraIndex) + + ".formats", + int(formats.size())); + + for (size_t i = 0; i < formats.size(); i++) { + auto &format = formats[i]; + auto prefix = "cameras." + + std::to_string(cameraIndex) + + ".formats." + + std::to_string(i); + auto formatStr = VideoFormat::stringFromFourcc(format.fourcc()); + this->preferencesWrite(prefix + ".format", formatStr); + this->preferencesWrite(prefix + ".width", format.width()); + this->preferencesWrite(prefix + ".height", format.height()); + this->preferencesWrite(prefix + ".fps", format.minimumFrameRate().toString()); + } + + this->preferencesSync(); + + return path_; +} + +void AkVCam::AssistantPrivate::preferencesRemoveCamera(const std::string &path) +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Device: ", path); + int cameraIndex = this->cameraFromPath(path); + + if (cameraIndex < 0) + return; + + auto nCameras = this->camerasCount(); + this->preferencesDeleteAll("cameras." + std::to_string(cameraIndex)); + + for (auto i = size_t(cameraIndex + 1); i < nCameras; i++) + this->preferencesMoveAll("cameras." + std::to_string(i), + "cameras." + std::to_string(i - 1)); + + if (nCameras > 1) + this->preferencesWrite("cameras", int(nCameras - 1)); + else + this->preferencesDelete("cameras"); + + this->preferencesSync(); +} + +size_t AkVCam::AssistantPrivate::camerasCount() const +{ + AkAssistantPrivateLogMethod(); + int nCameras = this->preferencesReadInt("cameras"); + AkLoggerLog("Cameras: ", nCameras); + + return size_t(nCameras); +} + +std::string AkVCam::AssistantPrivate::createDevicePath() const +{ + AkAssistantPrivateLogMethod(); + + // List device paths in use. + std::vector cameraPaths; + + for (size_t i = 0; i < this->camerasCount(); i++) + cameraPaths.push_back(this->cameraPath(i)); + + const int maxId = 64; + + for (int i = 0; i < maxId; i++) { + /* There are no rules for device paths in Windows. Just append an + * incremental index to a common prefix. + */ + auto path = CMIO_PLUGIN_DEVICE_PREFIX + std::to_string(i); + + // Check if the path is being used, if not return it. + if (std::find(cameraPaths.begin(), + cameraPaths.end(), + path) == cameraPaths.end()) + return path; + } + + return {}; +} + +int AkVCam::AssistantPrivate::cameraFromPath(const std::string &path) const +{ + for (size_t i = 0; i < this->camerasCount(); i++) + if (this->cameraPath(i) == path && !this->cameraFormats(i).empty()) + return int(i); + + return -1; +} + +bool AkVCam::AssistantPrivate::cameraExists(const std::string &path) const +{ + for (size_t i = 0; i < this->camerasCount(); i++) + if (this->cameraPath(i) == path) + return true; + + return false; +} + +std::wstring AkVCam::AssistantPrivate::cameraDescription(size_t cameraIndex) const +{ + return this->preferencesReadWString("cameras." + + std::to_string(cameraIndex) + + ".description"); +} + +std::string AkVCam::AssistantPrivate::cameraPath(size_t cameraIndex) const +{ + return this->preferencesReadString("cameras." + + std::to_string(cameraIndex) + + ".path"); +} + +size_t AkVCam::AssistantPrivate::formatsCount(size_t cameraIndex) const +{ + return size_t(this->preferencesReadInt("cameras." + + std::to_string(cameraIndex) + + ".formats")); +} + +AkVCam::VideoFormat AkVCam::AssistantPrivate::cameraFormat(size_t cameraIndex, + size_t formatIndex) const +{ + AkAssistantPrivateLogMethod(); + auto prefix = "cameras." + + std::to_string(cameraIndex) + + ".formats." + + std::to_string(formatIndex); + auto format = this->preferencesReadString(prefix + ".format"); + auto fourcc = VideoFormat::fourccFromString(format); + int width = this->preferencesReadInt(prefix + ".width"); + int height = this->preferencesReadInt(prefix + ".height"); + auto fps = Fraction(this->preferencesReadString(prefix + ".fps")); + + return VideoFormat(fourcc, width, height, {fps}); +} + +std::vector AkVCam::AssistantPrivate::cameraFormats(size_t cameraIndex) const +{ + AkAssistantPrivateLogMethod(); + std::vector formats; + + for (size_t i = 0; i < this->formatsCount(cameraIndex); i++) { + auto videoFormat = this->cameraFormat(cameraIndex, i); + + if (videoFormat) + formats.push_back(videoFormat); + } + + return formats; +} + +void AkVCam::AssistantPrivate::loadCameras() +{ + AkAssistantPrivateLogMethod(); + + for (size_t i = 0; i < this->camerasCount(); i++) { + this->m_deviceConfigs[this->cameraPath(i)] = {}; + this->m_deviceConfigs[this->cameraPath(i)].description = this->cameraDescription(i); + this->m_deviceConfigs[this->cameraPath(i)].formats = this->cameraFormats(i); + } +} + +void AkVCam::AssistantPrivate::releaseDevicesFromPeer(const std::string &portName) +{ + AkAssistantPrivateLogMethod(); + + for (auto &config: this->m_deviceConfigs) + if (config.second.broadcaster == portName) { + config.second.broadcaster.clear(); + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING); + xpc_dictionary_set_string(dictionary, "device", config.first.c_str()); + xpc_dictionary_set_string(dictionary, "broadcaster", ""); + + for (auto &client: this->m_clients) { + auto reply = + xpc_connection_send_message_with_reply_sync(client.second, + dictionary); + xpc_release(reply); + } + + xpc_release(dictionary); + } else { + auto it = std::find(config.second.listeners.begin(), + config.second.listeners.end(), + portName); + + if (it != config.second.listeners.end()) + config.second.listeners.erase(it); + } +} + +void AkVCam::AssistantPrivate::peerDied() +{ + AkAssistantPrivateLogMethod(); + + std::vector allPeers { + &this->m_clients, + &this->m_servers + }; + + for (auto peers: allPeers) { + for (auto &peer: *peers) { + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_ISALIVE); + auto reply = xpc_connection_send_message_with_reply_sync(peer.second, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + bool alive = false; + + if (replyType == XPC_TYPE_DICTIONARY) + alive = xpc_dictionary_get_bool(reply, "alive"); + + xpc_release(reply); + + if (!alive) + this->removePortByName(peer.first); + } + } +} + +void AkVCam::AssistantPrivate::requestPort(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + + bool asClient = xpc_dictionary_get_bool(event, "client"); + std::string portName = asClient? + AKVCAM_ASSISTANT_CLIENT_NAME: + AKVCAM_ASSISTANT_SERVER_NAME; + portName += std::to_string(this->id()); + + AkLoggerLog("Returning Port: ", portName); + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_string(reply, "port", portName.c_str()); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::addPort(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + + std::string portName = xpc_dictionary_get_string(event, "port"); + auto endpoint = xpc_dictionary_get_value(event, "connection"); + auto connection = xpc_connection_create_from_endpoint(reinterpret_cast(endpoint)); + xpc_connection_set_event_handler(connection, ^(xpc_object_t) {}); + xpc_connection_resume(connection); + bool ok = true; + AssistantPeers *peers; + + if (portName.find(AKVCAM_ASSISTANT_CLIENT_NAME) != std::string::npos) + peers = &this->m_clients; + else + peers = &this->m_servers; + + for (auto &peer: *peers) + if (peer.first == portName) { + ok = false; + + break; + } + + if (ok) { + AkLoggerLog("Adding Peer: ", portName); + (*peers)[portName] = connection; + this->stopTimer(); + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::removePortByName(const std::string &portName) +{ + AkAssistantPrivateLogMethod(); + AkLoggerLog("Port: ", portName); + + std::vector allPeers { + &this->m_clients, + &this->m_servers + }; + + bool breakLoop = false; + + for (auto peers: allPeers) { + for (auto &peer: *peers) + if (peer.first == portName) { + xpc_release(peer.second); + peers->erase(portName); + breakLoop = true; + + break; + } + + if (breakLoop) + break; + } + + bool peersEmpty = this->m_servers.empty() && this->m_clients.empty(); + + if (peersEmpty) + this->startTimer(); + + this->releaseDevicesFromPeer(portName); +} + +void AkVCam::AssistantPrivate::removePort(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkAssistantPrivateLogMethod(); + + this->removePortByName(xpc_dictionary_get_string(event, "port")); +} + +void AkVCam::AssistantPrivate::deviceCreate(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string portName = xpc_dictionary_get_string(event, "port"); + AkLoggerLog("Port Name: ", portName); + size_t len = 0; + auto data = reinterpret_cast(xpc_dictionary_get_data(event, + "description", + &len)); + std::wstring description(data, len / sizeof(wchar_t)); + auto formatsArray = xpc_dictionary_get_array(event, "formats"); + std::vector formats; + + for (size_t i = 0; i < xpc_array_get_count(formatsArray); i++) { + auto format = xpc_array_get_dictionary(formatsArray, i); + auto fourcc = FourCC(xpc_dictionary_get_uint64(format, "fourcc")); + auto width = int(xpc_dictionary_get_int64(format, "width")); + auto height = int(xpc_dictionary_get_int64(format, "height")); + auto frameRate = Fraction(xpc_dictionary_get_string(format, "fps")); + formats.push_back(VideoFormat {fourcc, width, height, {frameRate}}); + } + + auto deviceId = this->preferencesAddCamera(description, formats); + this->m_deviceConfigs[deviceId] = {}; + this->m_deviceConfigs[deviceId].description = description; + this->m_deviceConfigs[deviceId].formats = formats; + + auto notification = xpc_copy(event); + xpc_dictionary_set_string(notification, "device", deviceId.c_str()); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + AkLoggerLog("Device created: ", deviceId); + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_string(reply, "device", deviceId.c_str()); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::deviceDestroyById(const std::string &deviceId) +{ + AkAssistantPrivateLogMethod(); + auto it = this->m_deviceConfigs.find(deviceId); + + if (it != this->m_deviceConfigs.end()) { + this->m_deviceConfigs.erase(it); + + auto notification = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(notification, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY); + xpc_dictionary_set_string(notification, "device", deviceId.c_str()); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + } +} + +void AkVCam::AssistantPrivate::deviceDestroy(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkAssistantPrivateLogMethod(); + + std::string deviceId = xpc_dictionary_get_string(event, "device"); + this->deviceDestroyById(deviceId); + this->preferencesRemoveCamera(deviceId); +} + +void AkVCam::AssistantPrivate::setBroadcasting(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::string broadcaster = xpc_dictionary_get_string(event, "broadcaster"); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + if (this->m_deviceConfigs[deviceId].broadcaster != broadcaster) { + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Broadcaster: ", broadcaster); + this->m_deviceConfigs[deviceId].broadcaster = broadcaster; + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::setMirroring(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + bool horizontalMirror = xpc_dictionary_get_bool(event, "hmirror"); + bool verticalMirror = xpc_dictionary_get_bool(event, "vmirror"); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + if (this->m_deviceConfigs[deviceId].horizontalMirror != horizontalMirror + || this->m_deviceConfigs[deviceId].verticalMirror != verticalMirror) { + this->m_deviceConfigs[deviceId].horizontalMirror = horizontalMirror; + this->m_deviceConfigs[deviceId].verticalMirror = verticalMirror; + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::setScaling(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto scaling = Scaling(xpc_dictionary_get_int64(event, "scaling")); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + if (this->m_deviceConfigs[deviceId].scaling != scaling) { + this->m_deviceConfigs[deviceId].scaling = scaling; + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::setAspectRatio(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto aspectRatio = AspectRatio(xpc_dictionary_get_int64(event, "aspect")); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + if (this->m_deviceConfigs[deviceId].aspectRatio != aspectRatio) { + this->m_deviceConfigs[deviceId].aspectRatio = aspectRatio; + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::setSwapRgb(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto swapRgb = xpc_dictionary_get_bool(event, "swap"); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + if (this->m_deviceConfigs[deviceId].swapRgb != swapRgb) { + this->m_deviceConfigs[deviceId].swapRgb = swapRgb; + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::frameReady(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkAssistantPrivateLogMethod(); + auto reply = xpc_dictionary_create_reply(event); + bool ok = true; + + for (auto &client: this->m_clients) { + auto reply = xpc_connection_send_message_with_reply_sync(client.second, + event); + auto replyType = xpc_get_type(reply); + bool isOk = false; + + if (replyType == XPC_TYPE_DICTIONARY) + isOk = xpc_dictionary_get_bool(reply, "status"); + + ok &= isOk; + xpc_release(reply); + } + + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::listeners(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto listeners = xpc_array_create(nullptr, 0); + + if (this->m_deviceConfigs.count(deviceId) > 0) + for (auto &listener: this->m_deviceConfigs[deviceId].listeners) { + auto listenerObj = xpc_string_create(listener.c_str()); + xpc_array_append_value(listeners, listenerObj); + } + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Listeners: ", xpc_array_get_count(listeners)); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_value(reply, "listeners", listeners); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::listener(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto index = xpc_dictionary_get_uint64(event, "index"); + std::string listener; + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + if (index < this->m_deviceConfigs[deviceId].listeners.size()) { + listener = this->m_deviceConfigs[deviceId].listeners[index]; + ok = true; + } + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Listener: ", listener); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_string(reply, "listener", listener.c_str()); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::devices(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + auto devices = xpc_array_create(nullptr, 0); + + for (auto &device: this->m_deviceConfigs) { + auto deviceObj = xpc_string_create(device.first.c_str()); + xpc_array_append_value(devices, deviceObj); + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_value(reply, "devices", devices); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::description(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::wstring description; + + if (this->m_deviceConfigs.count(deviceId) > 0) + description = this->m_deviceConfigs[deviceId].description; + + AkLoggerLog("Description for device ", deviceId, ": ", + std::string(description.begin(), description.end())); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_data(reply, + "description", + description.c_str(), + description.size() * sizeof(wchar_t)); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::formats(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + auto formats = xpc_array_create(nullptr, 0); + + if (this->m_deviceConfigs.count(deviceId) > 0) + for (auto &format: this->m_deviceConfigs[deviceId].formats) { + auto dictFormat = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_uint64(dictFormat, "fourcc", format.fourcc()); + xpc_dictionary_set_int64(dictFormat, "width", format.width()); + xpc_dictionary_set_int64(dictFormat, "height", format.height()); + xpc_dictionary_set_string(dictFormat, "fps", format.minimumFrameRate().toString().c_str()); + xpc_array_append_value(formats, dictFormat); + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_value(reply, "formats", formats); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::broadcasting(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::string broadcaster; + + if (this->m_deviceConfigs.count(deviceId) > 0) + broadcaster = this->m_deviceConfigs[deviceId].broadcaster; + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Broadcaster: ", broadcaster); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_string(reply, "broadcaster", broadcaster.c_str()); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::mirroring(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + bool horizontalMirror = false; + bool verticalMirror = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) { + horizontalMirror = this->m_deviceConfigs[deviceId].horizontalMirror; + verticalMirror = this->m_deviceConfigs[deviceId].verticalMirror; + } + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Horizontal mirror: ", horizontalMirror); + AkLoggerLog("Vertical mirror: ", verticalMirror); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "hmirror", horizontalMirror); + xpc_dictionary_set_bool(reply, "vmirror", verticalMirror); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::scaling(xpc_connection_t client, xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + Scaling scaling = ScalingFast; + + if (this->m_deviceConfigs.count(deviceId) > 0) + scaling = this->m_deviceConfigs[deviceId].scaling; + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Scaling: ", scaling); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_int64(reply, "scaling", scaling); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::aspectRatio(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + AspectRatio aspectRatio = AspectRatioIgnore; + + if (this->m_deviceConfigs.count(deviceId) > 0) + aspectRatio = this->m_deviceConfigs[deviceId].aspectRatio; + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Aspect ratio: ", aspectRatio); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_int64(reply, "aspect", aspectRatio); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::swapRgb(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + bool swapRgb = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) + swapRgb = this->m_deviceConfigs[deviceId].swapRgb; + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Swap RGB: ", swapRgb); + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "swap", swapRgb); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::listenerAdd(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::string listener = xpc_dictionary_get_string(event, "listener"); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) { + auto &listeners = this->m_deviceConfigs[deviceId].listeners; + auto it = std::find(listeners.begin(), listeners.end(), listener); + + if (it == listeners.end()) { + listeners.push_back(listener); + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::AssistantPrivate::listenerRemove(xpc_connection_t client, + xpc_object_t event) +{ + AkAssistantPrivateLogMethod(); + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::string listener = xpc_dictionary_get_string(event, "listener"); + bool ok = false; + + if (this->m_deviceConfigs.count(deviceId) > 0) { + auto &listeners = this->m_deviceConfigs[deviceId].listeners; + auto it = std::find(listeners.begin(), listeners.end(), listener); + + if (it != listeners.end()) { + listeners.erase(it); + auto notification = xpc_copy(event); + + for (auto &client: this->m_clients) + xpc_connection_send_message(client.second, notification); + + xpc_release(notification); + ok = true; + } + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", ok); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} diff --git a/cmio/Assistant/src/assistant.h b/cmio/Assistant/src/assistant.h new file mode 100644 index 0000000..0a18831 --- /dev/null +++ b/cmio/Assistant/src/assistant.h @@ -0,0 +1,45 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef ASSISTANT_H +#define ASSISTANT_H + +#include +#include + +namespace AkVCam +{ + class AssistantPrivate; + + class Assistant + { + public: + Assistant(); + Assistant(const Assistant &other) = delete; + ~Assistant(); + + void setTimeout(double timeout); + void messageReceived(xpc_connection_t client, xpc_object_t event); + + private: + AssistantPrivate *d; + }; +} + +#endif // ASSISTANT_H diff --git a/cmio/Assistant/src/assistantglobals.h b/cmio/Assistant/src/assistantglobals.h new file mode 100644 index 0000000..8ce2363 --- /dev/null +++ b/cmio/Assistant/src/assistantglobals.h @@ -0,0 +1,69 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef ASSISTANTGLOBALS_H +#define ASSISTANTGLOBALS_H + +#include +#include + +#define AKVCAM_ASSISTANT_NAME "org.webcamoid.cmio.AkVCam.Assistant" +#define AKVCAM_ASSISTANT_CLIENT_NAME "org.webcamoid.cmio.AkVCam.Client" +#define AKVCAM_ASSISTANT_SERVER_NAME "org.webcamoid.cmio.AkVCam.Server" + +// General messages +#define AKVCAM_ASSISTANT_MSG_ISALIVE 0x000 +#define AKVCAM_ASSISTANT_MSG_FRAME_READY 0x001 + +// Assistant messages +#define AKVCAM_ASSISTANT_MSG_REQUEST_PORT 0x100 +#define AKVCAM_ASSISTANT_MSG_ADD_PORT 0x101 +#define AKVCAM_ASSISTANT_MSG_REMOVE_PORT 0x102 + +// Device control and information +#define AKVCAM_ASSISTANT_MSG_DEVICES 0x200 +#define AKVCAM_ASSISTANT_MSG_DEVICE_CREATE 0x201 +#define AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY 0x202 +#define AKVCAM_ASSISTANT_MSG_DEVICE_DESCRIPTION 0x203 +#define AKVCAM_ASSISTANT_MSG_DEVICE_FORMATS 0x204 + +// Device listeners controls +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENERS 0x300 +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER 0x301 +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD 0x302 +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE 0x303 + +// Device dynamic properties +#define AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING 0x400 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING 0x401 +#define AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING 0x402 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING 0x403 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SCALING 0x404 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING 0x405 +#define AKVCAM_ASSISTANT_MSG_DEVICE_ASPECTRATIO 0x406 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO 0x407 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB 0x408 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB 0x409 + +namespace AkVCam +{ + using XpcMessage = std::function; +} + +#endif // ASSISTANTGLOBALS_H diff --git a/cmio/Assistant/src/main.cpp b/cmio/Assistant/src/main.cpp new file mode 100644 index 0000000..87195c7 --- /dev/null +++ b/cmio/Assistant/src/main.cpp @@ -0,0 +1,74 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include + +#include "assistant.h" +#include "assistantglobals.h" +#include "VCamUtils/src/logger/logger.h" + +GLOBAL_STATIC(AkVCam::Assistant, assistant) + +int main(int argc, char **argv) +{ + auto server = + xpc_connection_create_mach_service(AKVCAM_ASSISTANT_NAME, + NULL, + XPC_CONNECTION_MACH_SERVICE_LISTENER); + + if (!server) + return EXIT_FAILURE; + + for (int i = 0; i < argc; i++) + if (strcmp(argv[i], "--timeout") == 0 && i + 1 < argc) { + auto timeout = strtod(argv[i + 1], nullptr); + AkLoggerLog("Set timeout: ", timeout); + assistant()->setTimeout(timeout); + + break; + } + + xpc_connection_set_event_handler(server, ^(xpc_object_t event) { + auto type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) { + auto description = xpc_copy_description(event); + AkLoggerLog("ERROR: ", description); + free(description); + + return; + } + + auto client = reinterpret_cast(event); + + xpc_connection_set_event_handler(client, ^(xpc_object_t event) { + assistant()->messageReceived(client, event); + }); + + xpc_connection_resume(client); + }); + + xpc_connection_resume(server); + CFRunLoopRun(); + xpc_release(server); + + return EXIT_SUCCESS; +} diff --git a/cmio/VCamIPC/VCamIPC.pro b/cmio/VCamIPC/VCamIPC.pro new file mode 100644 index 0000000..edac20b --- /dev/null +++ b/cmio/VCamIPC/VCamIPC.pro @@ -0,0 +1,55 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../cmio.pri) + +CONFIG += \ + staticlib \ + create_prl \ + no_install_prl +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = VCamIPC + +TEMPLATE = lib + +LIBS = \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -framework Foundation + +OBJECTIVE_SOURCES = \ + src/ipcbridge.mm + +HEADERS = \ + ../../ipcbridge.h + +INCLUDEPATH += \ + .. \ + ../.. diff --git a/cmio/VCamIPC/src/ipcbridge.mm b/cmio/VCamIPC/src/ipcbridge.mm new file mode 100644 index 0000000..b475afe --- /dev/null +++ b/cmio/VCamIPC/src/ipcbridge.mm @@ -0,0 +1,1748 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Assistant/src/assistantglobals.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/ipcbridge.h" +#include "VCamUtils/src/logger/logger.h" +#include "VCamUtils/src/utils.h" + +#define AkIpcBridgeLogMethod() \ + AkLoggerLog("IpcBridge::", __FUNCTION__, "()") + +#define AkIpcBridgePrivateLogMethod() \ + AkLoggerLog("IpcBridgePrivate::", __FUNCTION__, "()") + +#define AKVCAM_BIND_FUNC(member) \ + std::bind(&member, this, std::placeholders::_1, std::placeholders::_2) + +namespace AkVCam +{ + class IpcBridgePrivate + { + public: + IpcBridge *self; + std::string m_portName; + xpc_connection_t m_messagePort; + xpc_connection_t m_serverMessagePort; + std::map m_messageHandlers; + std::vector m_broadcasting; + std::map m_options; + std::wstring m_error; + bool m_asClient; + bool m_uninstall; + + IpcBridgePrivate(IpcBridge *self=nullptr); + ~IpcBridgePrivate(); + + static inline std::vector *driverPaths(); + inline void add(IpcBridge *bridge); + void remove(IpcBridge *bridge); + inline std::vector &bridges(); + + // Message handling methods + void isAlive(xpc_connection_t client, xpc_object_t event); + void deviceCreate(xpc_connection_t client, xpc_object_t event); + void deviceDestroy(xpc_connection_t client, xpc_object_t event); + void frameReady(xpc_connection_t client, xpc_object_t event); + void setBroadcasting(xpc_connection_t client, + xpc_object_t event); + void setMirror(xpc_connection_t client, xpc_object_t event); + void setScaling(xpc_connection_t client, xpc_object_t event); + void setAspectRatio(xpc_connection_t client, xpc_object_t event); + void setSwapRgb(xpc_connection_t client, xpc_object_t event); + void listenerAdd(xpc_connection_t client, xpc_object_t event); + void listenerRemove(xpc_connection_t client, xpc_object_t event); + void messageReceived(xpc_connection_t client, xpc_object_t event); + void connectionInterrupted(); + + // Utility methods + std::string homePath() const; + bool fileExists(const std::wstring &path) const; + bool fileExists(const std::string &path) const; + std::wstring fileName(const std::wstring &path) const; + bool mkpath(const std::string &path) const; + bool rm(const std::string &path) const; + bool createDaemonPlist(const std::string &fileName) const; + bool loadDaemon(); + void unloadDaemon() const; + bool checkDaemon(); + void uninstallPlugin(); + std::wstring locateDriverPath() const; + + // Execute commands with elevated privileges. + int sudo(const std::vector ¶meters); + + private: + std::vector m_bridges; + }; + + inline IpcBridgePrivate &ipcBridgePrivate() + { + static IpcBridgePrivate ipcBridgePrivate; + + return ipcBridgePrivate; + } +} + +AkVCam::IpcBridge::IpcBridge() +{ + AkIpcBridgeLogMethod(); + this->d = new IpcBridgePrivate(this); + ipcBridgePrivate().add(this); +} + +AkVCam::IpcBridge::~IpcBridge() +{ + this->unregisterPeer(); + ipcBridgePrivate().remove(this); + delete this->d; +} + +std::wstring AkVCam::IpcBridge::errorMessage() const +{ + return this->d->m_error; +} + +void AkVCam::IpcBridge::setOption(const std::string &key, + const std::string &value) +{ + AkIpcBridgeLogMethod(); + + if (value.empty()) + this->d->m_options.erase(key); + else + this->d->m_options[key] = value; +} + +std::vector AkVCam::IpcBridge::driverPaths() const +{ + AkIpcBridgeLogMethod(); + + return *this->d->driverPaths(); +} + +void AkVCam::IpcBridge::setDriverPaths(const std::vector &driverPaths) +{ + AkIpcBridgeLogMethod(); + *this->d->driverPaths() = driverPaths; +} + +std::vector AkVCam::IpcBridge::availableDrivers() const +{ + return {"AkVirtualCamera"}; +} + +std::string AkVCam::IpcBridge::driver() const +{ + return {"AkVirtualCamera"}; +} + +bool AkVCam::IpcBridge::setDriver(const std::string &driver) +{ + return driver == "AkVirtualCamera"; +} + +std::vector AkVCam::IpcBridge::availableRootMethods() const +{ + return {"osascript"}; +} + +std::string AkVCam::IpcBridge::rootMethod() const +{ + return {"osascript"}; +} + +bool AkVCam::IpcBridge::setRootMethod(const std::string &rootMethod) +{ + return rootMethod == "osascript"; +} + +void AkVCam::IpcBridge::connectService(bool asClient) +{ + AkIpcBridgeLogMethod(); + this->d->m_asClient = asClient; + this->registerPeer(asClient); +} + +void AkVCam::IpcBridge::disconnectService() +{ + AkIpcBridgeLogMethod(); + this->unregisterPeer(); + this->d->m_asClient = false; +} + +bool AkVCam::IpcBridge::registerPeer(bool asClient) +{ + AkIpcBridgeLogMethod(); + + if (!asClient) { + std::string plistFile = + CMIO_DAEMONS_PATH "/" AKVCAM_ASSISTANT_NAME ".plist"; + + auto daemon = replace(plistFile, "~", this->d->homePath()); + + if (!this->d->fileExists(daemon)) + return false; + } + + if (this->d->m_serverMessagePort) + return true; + + xpc_object_t dictionary = nullptr; + xpc_object_t reply = nullptr; + std::string portName; + xpc_connection_t messagePort = nullptr; + xpc_type_t replyType; + bool status = false; + + auto serverMessagePort = + xpc_connection_create_mach_service(AKVCAM_ASSISTANT_NAME, + nullptr, + 0); + + if (!serverMessagePort) + goto registerEndPoint_failed; + + xpc_connection_set_event_handler(serverMessagePort, ^(xpc_object_t event) { + if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { + ipcBridgePrivate().connectionInterrupted(); + } + }); + xpc_connection_resume(serverMessagePort); + + dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_REQUEST_PORT); + xpc_dictionary_set_bool(dictionary, "client", asClient); + reply = xpc_connection_send_message_with_reply_sync(serverMessagePort, + dictionary); + xpc_release(dictionary); + replyType = xpc_get_type(reply); + + if (replyType == XPC_TYPE_DICTIONARY) + portName = xpc_dictionary_get_string(reply, "port"); + + xpc_release(reply); + + if (replyType != XPC_TYPE_DICTIONARY) + goto registerEndPoint_failed; + + messagePort = xpc_connection_create(nullptr, nullptr); + + if (!messagePort) + goto registerEndPoint_failed; + + xpc_connection_set_event_handler(messagePort, ^(xpc_object_t event) { + auto type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) + return; + + auto client = reinterpret_cast(event); + + xpc_connection_set_event_handler(client, ^(xpc_object_t event) { + ipcBridgePrivate().messageReceived(client, event); + }); + + xpc_connection_resume(client); + }); + + xpc_connection_resume(messagePort); + + dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_ADD_PORT); + xpc_dictionary_set_string(dictionary, "port", portName.c_str()); + xpc_dictionary_set_connection(dictionary, "connection", messagePort); + reply = xpc_connection_send_message_with_reply_sync(serverMessagePort, + dictionary); + xpc_release(dictionary); + replyType = xpc_get_type(reply); + + if (replyType == XPC_TYPE_DICTIONARY) + status = xpc_dictionary_get_bool(reply, "status"); + + xpc_release(reply); + + if (replyType != XPC_TYPE_DICTIONARY || !status) + goto registerEndPoint_failed; + + this->d->m_portName = portName; + this->d->m_messagePort = messagePort; + this->d->m_serverMessagePort = serverMessagePort; + + AkLoggerLog("SUCCESSFUL"); + + return true; + +registerEndPoint_failed: + if (messagePort) + xpc_release(messagePort); + + if (serverMessagePort) + xpc_release(serverMessagePort); + + AkLoggerLog("FAILED"); + + return false; +} + +void AkVCam::IpcBridge::unregisterPeer() +{ + AkIpcBridgeLogMethod(); + + if (this->d->m_messagePort) { + xpc_release(this->d->m_messagePort); + this->d->m_messagePort = nullptr; + } + + if (this->d->m_serverMessagePort) { + if (!this->d->m_portName.empty()) { + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_REMOVE_PORT); + xpc_dictionary_set_string(dictionary, "port", this->d->m_portName.c_str()); + xpc_connection_send_message(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + } + + xpc_release(this->d->m_serverMessagePort); + this->d->m_serverMessagePort = nullptr; + } + + this->d->m_portName.clear(); +} + +std::vector AkVCam::IpcBridge::listDevices() const +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return {}; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICES); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return {}; + } + + auto devicesList = xpc_dictionary_get_array(reply, "devices"); + std::vector devices; + + for (size_t i = 0; i < xpc_array_get_count(devicesList); i++) + devices.push_back(xpc_array_get_string(devicesList, i)); + + xpc_release(reply); + +#ifdef QT_DEBUG + AkLoggerLog("Devices:"); + + for (auto &device: devices) + AkLoggerLog(" ", device); +#endif + + return devices; +} + +std::wstring AkVCam::IpcBridge::description(const std::string &deviceId) const +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return {}; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESCRIPTION); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return {}; + } + + size_t len = 0; + auto data = reinterpret_cast(xpc_dictionary_get_data(reply, + "description", + &len)); + std::wstring description(data, len / sizeof(wchar_t)); + xpc_release(reply); + + return description; +} + +std::vector AkVCam::IpcBridge::supportedOutputPixelFormats() const +{ + return { + PixelFormatRGB32, + PixelFormatRGB24, + PixelFormatUYVY, + PixelFormatYUY2 + }; +} + +AkVCam::PixelFormat AkVCam::IpcBridge::defaultOutputPixelFormat() const +{ + return PixelFormatYUY2; +} + +std::vector AkVCam::IpcBridge::formats(const std::string &deviceId) const +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return {}; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_FORMATS); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return {}; + } + + auto formatsList = xpc_dictionary_get_array(reply, "formats"); + std::vector formats; + + for (size_t i = 0; i < xpc_array_get_count(formatsList); i++) { + auto format = xpc_array_get_dictionary(formatsList, i); + auto fourcc = FourCC(xpc_dictionary_get_uint64(format, "fourcc")); + auto width = int(xpc_dictionary_get_int64(format, "width")); + auto height = int(xpc_dictionary_get_int64(format, "height")); + auto fps = Fraction(xpc_dictionary_get_string(format, "fps")); + + formats.push_back(VideoFormat(fourcc, width, height, {fps})); + } + + xpc_release(reply); + + return formats; +} + +std::string AkVCam::IpcBridge::broadcaster(const std::string &deviceId) const +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return {}; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return {}; + } + + std::string broadcaster = xpc_dictionary_get_string(reply, "broadcaster"); + xpc_release(reply); + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Broadcaster: ", broadcaster); + + return broadcaster; +} + +bool AkVCam::IpcBridge::isHorizontalMirrored(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return false; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return false; + } + + bool horizontalMirror = xpc_dictionary_get_bool(reply, "hmirror"); + xpc_release(reply); + + return horizontalMirror; +} + +bool AkVCam::IpcBridge::isVerticalMirrored(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return false; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return false; + } + + bool verticalMirror = xpc_dictionary_get_bool(reply, "vmirror"); + xpc_release(reply); + + return verticalMirror; +} + +AkVCam::Scaling AkVCam::IpcBridge::scalingMode(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return ScalingFast; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SCALING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return ScalingFast; + } + + auto scaling = Scaling(xpc_dictionary_get_int64(reply, "scaling")); + xpc_release(reply); + + return scaling; +} + +AkVCam::AspectRatio AkVCam::IpcBridge::aspectRatioMode(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return AspectRatioIgnore; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_ASPECTRATIO); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return AspectRatioIgnore; + } + + auto aspectRatio = AspectRatio(xpc_dictionary_get_int64(reply, "aspect")); + xpc_release(reply); + + return aspectRatio; +} + +bool AkVCam::IpcBridge::swapRgb(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return false; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return false; + } + + auto swap = xpc_dictionary_get_bool(reply, "swap"); + xpc_release(reply); + + return swap; +} + +std::vector AkVCam::IpcBridge::listeners(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return {}; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_LISTENERS); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return {}; + } + + auto listenersList = xpc_dictionary_get_array(reply, "listeners"); + std::vector listeners; + + for (size_t i = 0; i < xpc_array_get_count(listenersList); i++) + listeners.push_back(xpc_array_get_string(listenersList, i)); + + xpc_release(reply); + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Listeners: ", listeners.size()); + + return listeners; +} + +std::vector AkVCam::IpcBridge::clientsPids() const +{ + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) + return {}; + + auto plugin = this->d->fileName(driverPath); + std::wstring pluginPath = + CMIO_PLUGINS_DAL_PATH_L L"/" + + plugin + + L"/Contents/MacOS/" CMIO_PLUGIN_NAME_L; + std::string path(pluginPath.begin(), pluginPath.end()); + auto npids = proc_listpidspath(PROC_ALL_PIDS, + 0, + path.c_str(), + 0, + nullptr, + 0); + pid_t pidsvec[npids]; + memset(pidsvec, 0, npids * sizeof(pid_t)); + proc_listpidspath(PROC_ALL_PIDS, + 0, + path.c_str(), + 0, + pidsvec, + npids * sizeof(pid_t)); + auto currentPid = getpid(); + std::vector pids; + + for (int i = 0; i < npids; i++) { + auto it = std::find(pids.begin(), pids.end(), pidsvec[i]); + + if (pidsvec[i] > 0 && it == pids.end() && pidsvec[i] != currentPid) + pids.push_back(pidsvec[i]); + } + + return pids; +} + +std::string AkVCam::IpcBridge::clientExe(uint64_t pid) const +{ + char path[4096]; + memset(path, 0, 4096); + proc_pidpath(pid, path, 4096); + + return {path}; +} + +bool AkVCam::IpcBridge::needsRestart(Operation operation) const +{ + return operation == OperationDestroyAll + || ((operation == OperationDestroy || operation == OperationEdit) + && this->listDevices().size() == 1); +} + +bool AkVCam::IpcBridge::canApply(AkVCam::IpcBridge::Operation operation) const +{ + return this->clientsPids().empty() && !this->needsRestart(operation); +} + +std::string AkVCam::IpcBridge::deviceCreate(const std::wstring &description, + const std::vector &formats) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationCreate)) { + this->d->m_error = L"The driver is in use"; + + return {}; + } + + if (!this->d->checkDaemon()) + return {}; + + this->registerPeer(false); + + if (!this->d->m_serverMessagePort || !this->d->m_messagePort) { + this->d->m_error = L"Can't register peer"; + + return {}; + } + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_CREATE); + xpc_dictionary_set_string(dictionary, "port", this->d->m_portName.c_str()); + xpc_dictionary_set_data(dictionary, + "description", + description.c_str(), + description.size() * sizeof(wchar_t)); + auto formatsList = xpc_array_create(nullptr, 0); + + for (auto &format: formats) { + auto dictFormat = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_uint64(dictFormat, "fourcc", format.fourcc()); + xpc_dictionary_set_int64(dictFormat, "width", format.width()); + xpc_dictionary_set_int64(dictFormat, "height", format.height()); + xpc_dictionary_set_string(dictFormat, "fps", format.minimumFrameRate().toString().c_str()); + xpc_array_append_value(formatsList, dictFormat); + } + + xpc_dictionary_set_value(dictionary, "formats", formatsList); + + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + this->d->m_error = L"Can't set virtual camera formats"; + xpc_release(reply); + + return {}; + } + + std::string deviceId(xpc_dictionary_get_string(reply, "device")); + xpc_release(reply); + + return deviceId; +} + +bool AkVCam::IpcBridge::deviceEdit(const std::string &deviceId, + const std::wstring &description, + const std::vector &formats) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationEdit)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + this->d->m_uninstall = false; + this->deviceDestroy(deviceId); + this->d->m_uninstall = true; + + if (this->deviceCreate(description.empty()? + L"AvKys Virtual Camera": + description, + formats).empty()) + return false; + + return true; +} + +bool AkVCam::IpcBridge::changeDescription(const std::string &deviceId, + const std::wstring &description) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationEdit)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + auto formats = this->formats(deviceId); + + if (formats.empty()) + return false; + + this->d->m_uninstall = false; + this->deviceDestroy(deviceId); + this->d->m_uninstall = true; + + if (this->deviceCreate(description.empty()? + L"AvKys Virtual Camera": + description, + formats).empty()) + return false; + + return true; +} + +bool AkVCam::IpcBridge::deviceDestroy(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationDestroy)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + if (!this->d->m_serverMessagePort) + return false; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_connection_send_message(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + + // If no devices are registered + if (this->d->m_uninstall && listDevices().empty()) + this->d->uninstallPlugin(); + + return true; +} + +bool AkVCam::IpcBridge::destroyAllDevices() +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationDestroyAll)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + for (auto &device: this->listDevices()) + this->deviceDestroy(device); + + return true; +} + +bool AkVCam::IpcBridge::deviceStart(const std::string &deviceId, + const VideoFormat &format) +{ + UNUSED(format) + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return false; + + auto it = std::find(this->d->m_broadcasting.begin(), + this->d->m_broadcasting.end(), + deviceId); + + if (it != this->d->m_broadcasting.end()) + return false; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_string(dictionary, "broadcaster", this->d->m_portName.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return false; + } + + bool status = xpc_dictionary_get_bool(reply, "status"); + xpc_release(reply); + this->d->m_broadcasting.push_back(deviceId); + + return status; +} + +void AkVCam::IpcBridge::deviceStop(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return; + + auto it = std::find(this->d->m_broadcasting.begin(), + this->d->m_broadcasting.end(), + deviceId); + + if (it == this->d->m_broadcasting.end()) + return; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_string(dictionary, "broadcaster", ""); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + xpc_release(reply); + this->d->m_broadcasting.erase(it); +} + +bool AkVCam::IpcBridge::write(const std::string &deviceId, + const VideoFrame &frame) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return false; + + auto it = std::find(this->d->m_broadcasting.begin(), + this->d->m_broadcasting.end(), + deviceId); + + if (it == this->d->m_broadcasting.end()) + return false; + + std::vector keys { + kIOSurfacePixelFormat, + kIOSurfaceWidth, + kIOSurfaceHeight, + kIOSurfaceAllocSize + }; + + auto fourcc = frame.format().fourcc(); + auto width = frame.format().width(); + auto height = frame.format().height(); + auto dataSize = int64_t(frame.data().size()); + + std::vector values { + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fourcc), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataSize) + }; + + auto surfaceProperties = + CFDictionaryCreate(kCFAllocatorDefault, + reinterpret_cast(keys.data()), + reinterpret_cast(values.data()), + CFIndex(values.size()), + nullptr, + nullptr); + auto surface = IOSurfaceCreate(surfaceProperties); + + for (auto &value: values) + CFRelease(value); + + CFRelease(surfaceProperties); + + if (!surface) + return false; + + uint32_t surfaceSeed = 0; + IOSurfaceLock(surface, 0, &surfaceSeed); + auto data = IOSurfaceGetBaseAddress(surface); + memcpy(data, frame.data().data(), frame.data().size()); + IOSurfaceUnlock(surface, 0, &surfaceSeed); + auto surfaceObj = IOSurfaceCreateXPCObject(surface); + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_FRAME_READY); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_value(dictionary, "frame", surfaceObj); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + xpc_release(reply); + xpc_release(surfaceObj); + CFRelease(surface); + + return true; +} + +void AkVCam::IpcBridge::setMirroring(const std::string &deviceId, + bool horizontalMirrored, + bool verticalMirrored) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_bool(dictionary, "hmirror", horizontalMirrored); + xpc_dictionary_set_bool(dictionary, "vmirror", verticalMirrored); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + xpc_release(reply); +} + +void AkVCam::IpcBridge::setScaling(const std::string &deviceId, + Scaling scaling) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_int64(dictionary, "scaling", scaling); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + xpc_release(reply); +} + +void AkVCam::IpcBridge::setAspectRatio(const std::string &deviceId, + AspectRatio aspectRatio) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_int64(dictionary, "aspect", aspectRatio); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + xpc_release(reply); +} + +void AkVCam::IpcBridge::setSwapRgb(const std::string &deviceId, bool swap) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_bool(dictionary, "swap", swap); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + xpc_release(reply); +} + +bool AkVCam::IpcBridge::addListener(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return false; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_string(dictionary, "listener", this->d->m_portName.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return false; + } + + bool status = xpc_dictionary_get_bool(reply, "status"); + xpc_release(reply); + + return status; +} + +bool AkVCam::IpcBridge::removeListener(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->d->m_serverMessagePort) + return true; + + auto dictionary = xpc_dictionary_create(nullptr, nullptr, 0); + xpc_dictionary_set_int64(dictionary, "message", AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE); + xpc_dictionary_set_string(dictionary, "device", deviceId.c_str()); + xpc_dictionary_set_string(dictionary, "listener", this->d->m_portName.c_str()); + auto reply = xpc_connection_send_message_with_reply_sync(this->d->m_serverMessagePort, + dictionary); + xpc_release(dictionary); + auto replyType = xpc_get_type(reply); + + if (replyType != XPC_TYPE_DICTIONARY) { + xpc_release(reply); + + return true; + } + + bool status = xpc_dictionary_get_bool(reply, "status"); + xpc_release(reply); + + return status; +} + +AkVCam::IpcBridgePrivate::IpcBridgePrivate(IpcBridge *self): + self(self), + m_messagePort(nullptr), + m_serverMessagePort(nullptr), + m_asClient(false), + m_uninstall(true) +{ + this->m_messageHandlers = { + {AKVCAM_ASSISTANT_MSG_ISALIVE , AKVCAM_BIND_FUNC(IpcBridgePrivate::isAlive) }, + {AKVCAM_ASSISTANT_MSG_FRAME_READY , AKVCAM_BIND_FUNC(IpcBridgePrivate::frameReady) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_CREATE , AKVCAM_BIND_FUNC(IpcBridgePrivate::deviceCreate) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY , AKVCAM_BIND_FUNC(IpcBridgePrivate::deviceDestroy) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD , AKVCAM_BIND_FUNC(IpcBridgePrivate::listenerAdd) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE, AKVCAM_BIND_FUNC(IpcBridgePrivate::listenerRemove) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING, AKVCAM_BIND_FUNC(IpcBridgePrivate::setBroadcasting)}, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING , AKVCAM_BIND_FUNC(IpcBridgePrivate::setMirror) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING , AKVCAM_BIND_FUNC(IpcBridgePrivate::setScaling) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO , AKVCAM_BIND_FUNC(IpcBridgePrivate::setAspectRatio) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB , AKVCAM_BIND_FUNC(IpcBridgePrivate::setSwapRgb) }, + }; +} + +AkVCam::IpcBridgePrivate::~IpcBridgePrivate() +{ + +} + +std::vector *AkVCam::IpcBridgePrivate::driverPaths() +{ + static std::vector paths; + + return &paths; +} + +void AkVCam::IpcBridgePrivate::add(IpcBridge *bridge) +{ + this->m_bridges.push_back(bridge); +} + +void AkVCam::IpcBridgePrivate::remove(IpcBridge *bridge) +{ + for (size_t i = 0; i < this->m_bridges.size(); i++) + if (this->m_bridges[i] == bridge) { + this->m_bridges.erase(this->m_bridges.begin() + long(i)); + + break; + } +} + +std::vector &AkVCam::IpcBridgePrivate::bridges() +{ + return this->m_bridges; +} + +void AkVCam::IpcBridgePrivate::isAlive(xpc_connection_t client, + xpc_object_t event) +{ + AkIpcBridgePrivateLogMethod(); + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "alive", true); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::IpcBridgePrivate::deviceCreate(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + std::string device = xpc_dictionary_get_string(event, "device"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, DeviceAdded, device) +} + +void AkVCam::IpcBridgePrivate::deviceDestroy(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string device = xpc_dictionary_get_string(event, "device"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, DeviceRemoved, device) +} + +void AkVCam::IpcBridgePrivate::frameReady(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = + xpc_dictionary_get_string(event, "device"); + auto frame = xpc_dictionary_get_value(event, "frame"); + auto surface = IOSurfaceLookupFromXPCObject(frame); + + if (surface) { + uint32_t surfaceSeed = 0; + IOSurfaceLock(surface, kIOSurfaceLockReadOnly, &surfaceSeed); + FourCC fourcc = IOSurfaceGetPixelFormat(surface); + int width = int(IOSurfaceGetWidth(surface)); + int height = int(IOSurfaceGetHeight(surface)); + size_t size = IOSurfaceGetAllocSize(surface); + auto data = reinterpret_cast(IOSurfaceGetBaseAddress(surface)); + VideoFormat videoFormat(fourcc, width, height); + VideoFrame videoFrame(videoFormat); + memcpy(videoFrame.data().data(), data, size); + IOSurfaceUnlock(surface, kIOSurfaceLockReadOnly, &surfaceSeed); + CFRelease(surface); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, FrameReady, deviceId, videoFrame) + } + + auto reply = xpc_dictionary_create_reply(event); + xpc_dictionary_set_bool(reply, "status", surface? true: false); + xpc_connection_send_message(client, reply); + xpc_release(reply); +} + +void AkVCam::IpcBridgePrivate::setBroadcasting(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = + xpc_dictionary_get_string(event, "device"); + std::string broadcaster = + xpc_dictionary_get_string(event, "broadcaster"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, BroadcastingChanged, deviceId, broadcaster) +} + +void AkVCam::IpcBridgePrivate::setMirror(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = + xpc_dictionary_get_string(event, "device"); + bool horizontalMirror = + xpc_dictionary_get_bool(event, "hmirror"); + bool verticalMirror = + xpc_dictionary_get_bool(event, "vmirror"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, + MirrorChanged, + deviceId, + horizontalMirror, + verticalMirror) +} + +void AkVCam::IpcBridgePrivate::setScaling(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = + xpc_dictionary_get_string(event, "device"); + auto scaling = + Scaling(xpc_dictionary_get_int64(event, "scaling")); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, ScalingChanged, deviceId, scaling) +} + +void AkVCam::IpcBridgePrivate::setAspectRatio(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = + xpc_dictionary_get_string(event, "device"); + auto aspectRatio = + AspectRatio(xpc_dictionary_get_int64(event, "aspect")); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, AspectRatioChanged, deviceId, aspectRatio) +} + +void AkVCam::IpcBridgePrivate::setSwapRgb(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = + xpc_dictionary_get_string(event, "device"); + auto swap = xpc_dictionary_get_bool(event, "swap"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, SwapRgbChanged, deviceId, swap) +} + +void AkVCam::IpcBridgePrivate::listenerAdd(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::string listener = xpc_dictionary_get_string(event, "listener"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, ListenerAdded, deviceId, listener) +} + +void AkVCam::IpcBridgePrivate::listenerRemove(xpc_connection_t client, + xpc_object_t event) +{ + UNUSED(client) + AkIpcBridgePrivateLogMethod(); + + std::string deviceId = xpc_dictionary_get_string(event, "device"); + std::string listener = xpc_dictionary_get_string(event, "listener"); + + for (auto bridge: this->m_bridges) + AKVCAM_EMIT(bridge, ListenerRemoved, deviceId, listener) +} + +void AkVCam::IpcBridgePrivate::messageReceived(xpc_connection_t client, + xpc_object_t event) +{ + auto type = xpc_get_type(event); + + if (type == XPC_TYPE_ERROR) { + auto description = xpc_copy_description(event); + AkLoggerLog("ERROR: ", description); + free(description); + } else if (type == XPC_TYPE_DICTIONARY) { + auto message = xpc_dictionary_get_int64(event, "message"); + + if (this->m_messageHandlers.count(message)) + this->m_messageHandlers[message](client, event); + } +} + +void AkVCam::IpcBridgePrivate::connectionInterrupted() +{ + for (auto bridge: this->m_bridges) { + AKVCAM_EMIT(bridge, ServerStateChanged, IpcBridge::ServerStateGone) + bridge->unregisterPeer(); + } + + // Restart service + for (auto bridge: this->m_bridges) + if (bridge->registerPeer(bridge->d->m_asClient)) { + AKVCAM_EMIT(bridge, + ServerStateChanged, + IpcBridge::ServerStateAvailable) + } +} + +std::string AkVCam::IpcBridgePrivate::homePath() const +{ + auto homePath = NSHomeDirectory(); + + if (!homePath) + return {}; + + return std::string(homePath.UTF8String); +} + +bool AkVCam::IpcBridgePrivate::fileExists(const std::wstring &path) const +{ + return this->fileExists(std::string(path.begin(), path.end())); +} + +bool AkVCam::IpcBridgePrivate::fileExists(const std::string &path) const +{ + struct stat stats; + memset(&stats, 0, sizeof(struct stat)); + + return stat(path.c_str(), &stats) == 0; +} + +std::wstring AkVCam::IpcBridgePrivate::fileName(const std::wstring &path) const +{ + return path.substr(path.rfind(L'/') + 1); +} + +bool AkVCam::IpcBridgePrivate::mkpath(const std::string &path) const +{ + if (path.empty()) + return false; + + if (this->fileExists(path)) + return true; + + // Create parent folders + for (auto pos = path.find('/'); + pos != std::string::npos; + pos = path.find('/', pos + 1)) { + auto path_ = path.substr(0, pos); + + if (path_.empty() || this->fileExists(path_)) + continue; + + if (mkdir(path_.c_str(), + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) + return false; + } + + return !mkdir(path.c_str(), + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); +} + +bool AkVCam::IpcBridgePrivate::rm(const std::string &path) const +{ + if (path.empty()) + return false; + + struct stat stats; + memset(&stats, 0, sizeof(struct stat)); + + if (stat(path.c_str(), &stats)) + return false; + + bool ok = true; + + if (S_ISDIR(stats.st_mode)) { + auto dir = opendir(path.c_str()); + + while (auto entry = readdir(dir)) + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) + this->rm(entry->d_name); + + closedir(dir); + + ok &= !rmdir(path.c_str()); + } else { + ok &= !::remove(path.c_str()); + } + + return ok; +} + +bool AkVCam::IpcBridgePrivate::createDaemonPlist(const std::string &fileName) const +{ + AkIpcBridgePrivateLogMethod(); + std::fstream plistFile; + plistFile.open(fileName, std::ios_base::out); + + if (!plistFile.is_open()) + return false; + + plistFile << "" << std::endl + << "" + << std::endl + << "" << std::endl + << " " << std::endl + << " Label" << std::endl + << " " << AKVCAM_ASSISTANT_NAME + << "" << std::endl + << " ProgramArguments" << std::endl + << " " << std::endl + << " " << CMIO_PLUGINS_DAL_PATH + << "/" + << CMIO_PLUGIN_NAME + << ".plugin/Contents/Resources/" + << CMIO_PLUGIN_ASSISTANT_NAME + << "" << std::endl + << " --timeout" << std::endl + << " 300.0" << std::endl + << " " << std::endl + << " MachServices" << std::endl + << " " << std::endl + << " " << AKVCAM_ASSISTANT_NAME + << "" << std::endl + << " " << std::endl + << " " << std::endl; + +#ifdef QT_DEBUG + std::string daemonLog = "/tmp/" AKVCAM_ASSISTANT_NAME ".log"; + + plistFile << " StandardOutPath" << std::endl + << " " << daemonLog << "" << std::endl + << " StandardErrorPath" << std::endl + << " " << daemonLog << "" << std::endl; +#endif + + plistFile << " " << std::endl + << "" << std::endl; + + return true; +} + +bool AkVCam::IpcBridgePrivate::loadDaemon() +{ + AkIpcBridgePrivateLogMethod(); + auto launchctl = popen("launchctl list " AKVCAM_ASSISTANT_NAME, "r"); + + if (launchctl && !pclose(launchctl)) + return true; + + auto daemonsPath = replace(CMIO_DAEMONS_PATH, "~", this->homePath()); + auto dstDaemonsPath = daemonsPath + "/" AKVCAM_ASSISTANT_NAME ".plist"; + + if (!this->fileExists(dstDaemonsPath)) { + this->m_error = L"Daemon plist does not exists"; + + return false; + } + + launchctl = popen(("launchctl load -w '" + dstDaemonsPath + "'").c_str(), + "r"); + + bool result = launchctl && !pclose(launchctl); + + if (!result) + this->m_error = L"Can't launch daemon"; + + return result; +} + +void AkVCam::IpcBridgePrivate::unloadDaemon() const +{ + AkIpcBridgePrivateLogMethod(); + std::string daemonPlist = AKVCAM_ASSISTANT_NAME ".plist"; + auto daemonsPath = replace(CMIO_DAEMONS_PATH, "~", this->homePath()); + auto dstDaemonsPath = daemonsPath + "/" + daemonPlist; + + if (!this->fileExists(dstDaemonsPath)) + return; + + auto launchctl = + popen(("launchctl unload -w '" + dstDaemonsPath + "'").c_str(), + "r"); + pclose(launchctl); +} + +bool AkVCam::IpcBridgePrivate::checkDaemon() +{ + AkIpcBridgePrivateLogMethod(); + auto driverPath = this->locateDriverPath(); + + if (driverPath.empty()) { + this->m_error = L"Driver not found"; + + return false; + } + + auto plugin = this->fileName(driverPath); + std::wstring dstPath = CMIO_PLUGINS_DAL_PATH_L; + std::wstring pluginInstallPath = dstPath + L'/' + plugin; + + if (!this->fileExists(pluginInstallPath)) { + const std::string cmdFileName = "/tmp/akvcam_exec.sh"; + + std::wfstream cmds; + cmds.open(cmdFileName, std::ios_base::out); + + if (!cmds.is_open()) { + this->m_error = L"Can't create script"; + + return false; + } + + cmds << L"mkdir -p " + << pluginInstallPath + << std::endl + << L"cp -rvf '" + << driverPath << L"'/* " + << pluginInstallPath << L"/" + << std::endl + << L"chmod +x " + << pluginInstallPath << L"/Contents/Resources/" CMIO_PLUGIN_ASSISTANT_NAME_L + << std::endl; + cmds.close(); + chmod(cmdFileName.c_str(), S_IRWXU | S_IRGRP | S_IROTH); + + if (this->sudo({"sh", cmdFileName})) { + this->rm(cmdFileName); + + return false; + } + + this->rm(cmdFileName); + } + + auto daemonsPath = replace(CMIO_DAEMONS_PATH, "~", this->homePath()); + auto dstDaemonsPath = daemonsPath + "/" + AKVCAM_ASSISTANT_NAME + ".plist"; + + if (!this->fileExists(dstDaemonsPath)) { + if (!this->mkpath(daemonsPath)) { + this->m_error = L"Can't create daemon path"; + + return false; + } + + if (!this->createDaemonPlist(dstDaemonsPath)) { + this->m_error = L"Can't create daemon plist"; + + return false; + } + } + + return this->loadDaemon(); +} + +void AkVCam::IpcBridgePrivate::uninstallPlugin() +{ + AkIpcBridgePrivateLogMethod(); + + // Stop the daemon + this->unloadDaemon(); + + // Remove the agent plist + auto daemonsPath = + replace(CMIO_DAEMONS_PATH, "~", this->homePath()); + this->rm(daemonsPath + "/" AKVCAM_ASSISTANT_NAME ".plist"); + + // Remove the plugin + auto driverPath = this->locateDriverPath(); + + if (driverPath.empty()) + return; + + auto plugin = this->fileName(driverPath); + std::wstring dstPath = CMIO_PLUGINS_DAL_PATH_L; + this->sudo({"rm", "-rvf", + std::string(dstPath.begin(), dstPath.end()) + + '/' + + std::string(plugin.begin(), plugin.end())}); +} + +std::wstring AkVCam::IpcBridgePrivate::locateDriverPath() const +{ + AkIpcBridgePrivateLogMethod(); + std::wstring driverPath; + + for (auto it = this->driverPaths()->rbegin(); + it != this->driverPaths()->rend(); + it++) { + auto path = *it; + path = replace(path, L"\\", L"/"); + + if (path.back() != L'/') + path += L'/'; + + path += CMIO_PLUGIN_NAME_L L".plugin"; + + if (!this->fileExists(path + L"/Contents/MacOS/" CMIO_PLUGIN_NAME_L)) + continue; + + if (!this->fileExists(path + L"/Contents/Resources/" CMIO_PLUGIN_ASSISTANT_NAME_L)) + continue; + + driverPath = path; + + break; + } + + return driverPath; +} + +int AkVCam::IpcBridgePrivate::sudo(const std::vector ¶meters) +{ + AkIpcBridgePrivateLogMethod(); + std::stringstream ss; + ss << "osascript -e \"do shell script \\\""; + + for (auto param: parameters) { + if (param.find(' ') == std::string::npos) + ss << param; + else + ss << "'" << param << "'"; + + ss << ' '; + } + + ss << "\\\" with administrator privileges\" 2>&1"; + auto sudo = popen(ss.str().c_str(), "r"); + + if (!sudo) + return -1; + + std::string output; + char buffer[1024]; + + while (fgets(buffer, 1024, sudo)) + output += std::string(buffer); + + auto result = pclose(sudo); + + if (result) + AkLoggerLog(output); + + return result; +} diff --git a/cmio/VirtualCamera/Info.plist b/cmio/VirtualCamera/Info.plist new file mode 100644 index 0000000..9a6959e --- /dev/null +++ b/cmio/VirtualCamera/Info.plist @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + AkVirtualCamera + CFBundleIconFile + + CFBundleIdentifier + org.webcamoid.cmio.DAL.VirtualCamera + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + AkVirtualCamera + CFBundlePackageType + BNDL + CFBundleShortVersionString + 8.7.1 + CFBundleSignature + ???? + CFBundleVersion + 8.7.1 + CFBundleSupportedPlatforms + + MacOSX + + CFPlugInFactories + + 41764B79-7320-5643-616D-363462697473 + akPluginMain + + CFPlugInTypes + + 30010C1C-93BF-11D8-8B5B-000A95AF9C6A + + 41764B79-7320-5643-616D-363462697473 + + + CMIOHardwareAssistantServiceNames + + org.webcamoid.cmio.AkVCam.Assistant + + + diff --git a/cmio/VirtualCamera/VirtualCamera.pro b/cmio/VirtualCamera/VirtualCamera.pro new file mode 100644 index 0000000..2ce35e5 --- /dev/null +++ b/cmio/VirtualCamera/VirtualCamera.pro @@ -0,0 +1,93 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../cmio.pri) +include(../../VCamUtils/VCamUtils.pri) + +CONFIG -= qt link_prl +CONFIG += \ + unversioned_libname \ + unversioned_soname + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +INCLUDEPATH += \ + .. \ + ../.. + +LIBS = \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -L$${OUT_PWD}/../VCamIPC/$${BIN_DIR} -lVCamIPC \ + -framework CoreFoundation \ + -framework CoreMedia \ + -framework CoreMediaIO \ + -framework CoreVideo \ + -framework Foundation \ + -framework IOKit \ + -framework IOSurface + +TARGET = $${CMIO_PLUGIN_NAME} +TEMPLATE = lib + +HEADERS += \ + src/plugin.h \ + src/plugininterface.h \ + src/utils.h \ + src/device.h \ + src/object.h \ + src/stream.h \ + src/objectinterface.h \ + src/objectproperties.h \ + src/clock.h \ + src/queue.h + +SOURCES += \ + src/plugin.cpp \ + src/plugininterface.cpp \ + src/utils.cpp \ + src/device.cpp \ + src/object.cpp \ + src/stream.cpp \ + src/objectinterface.cpp \ + src/objectproperties.cpp \ + src/clock.cpp + +OTHER_FILES = \ + Info.plist + +INSTALLS += vcam +vcam.files = $${OUT_PWD}/$${TARGET}.plugin +vcam.path = $${DATAROOTDIR} +vcam.CONFIG += no_check_exist + +QMAKE_POST_LINK = \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/$${TARGET}.plugin/Contents/MacOS)) $${CMD_SEP} \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/$${TARGET}.plugin/Contents/Resources)) $${CMD_SEP} \ + $(COPY) $$shell_path($${PWD}/Info.plist) $$shell_path($${OUT_PWD}/$${TARGET}.plugin/Contents) $${CMD_SEP} \ + $(COPY) $$shell_path($${OUT_PWD}/$${BIN_DIR}/lib$${TARGET}.dylib) $$shell_path($${OUT_PWD}/$${TARGET}.plugin/Contents/MacOS/$${TARGET}) $${CMD_SEP} \ + $(COPY) $$shell_path($${PWD}/../../share/TestFrame/TestFrame.bmp) $$shell_path($${OUT_PWD}/$${TARGET}.plugin/Contents/Resources) diff --git a/cmio/VirtualCamera/src/clock.cpp b/cmio/VirtualCamera/src/clock.cpp new file mode 100644 index 0000000..3baa8fe --- /dev/null +++ b/cmio/VirtualCamera/src/clock.cpp @@ -0,0 +1,73 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "clock.h" + +AkVCam::Clock::Clock(const std::string& name, + const CMTime getTimeCallMinimumInterval, + UInt32 numberOfEventsForRateSmoothing, + UInt32 numberOfAveragesForRateSmoothing, + void *parent): + m_parent(parent), + m_clock(nullptr) +{ + auto nameRef = + CFStringCreateWithCString(kCFAllocatorDefault, + name.c_str(), + kCFStringEncodingUTF8); + + auto status = + CMIOStreamClockCreate(kCFAllocatorDefault, + nameRef, + this->m_parent, + getTimeCallMinimumInterval, + numberOfEventsForRateSmoothing, + numberOfAveragesForRateSmoothing, + &this->m_clock); + + if (status != noErr) + this->m_clock = nullptr; + + CFRelease(nameRef); +} + +AkVCam::Clock::~Clock() +{ + if (this->m_clock) { + CMIOStreamClockInvalidate(this->m_clock); + CFRelease(this->m_clock); + } +} + +CFTypeRef AkVCam::Clock::ref() const +{ + return this->m_clock; +} + +OSStatus AkVCam::Clock::postTimingEvent(CMTime eventTime, + UInt64 hostTime, + Boolean resynchronize) +{ + return CMIOStreamClockPostTimingEvent(eventTime, + hostTime, + resynchronize, + this->m_clock); +} diff --git a/cmio/VirtualCamera/src/clock.h b/cmio/VirtualCamera/src/clock.h new file mode 100644 index 0000000..cf99c9b --- /dev/null +++ b/cmio/VirtualCamera/src/clock.h @@ -0,0 +1,53 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef CLOCK_H +#define CLOCK_H + +#include +#include +#include + +namespace AkVCam +{ + class Clock; + typedef std::shared_ptr ClockPtr; + + class Clock + { + public: + Clock(const std::string& name, + const CMTime getTimeCallMinimumInterval, + UInt32 numberOfEventsForRateSmoothing, + UInt32 numberOfAveragesForRateSmoothing, + void *parent=nullptr); + ~Clock(); + + CFTypeRef ref() const; + OSStatus postTimingEvent(CMTime eventTime, + UInt64 hostTime, + Boolean resynchronize); + + private: + void *m_parent; + CFTypeRef m_clock; + }; +} + +#endif // CLOCK_H diff --git a/cmio/VirtualCamera/src/device.cpp b/cmio/VirtualCamera/src/device.cpp new file mode 100644 index 0000000..5481d41 --- /dev/null +++ b/cmio/VirtualCamera/src/device.cpp @@ -0,0 +1,339 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "device.h" +#include "utils.h" +#include "VCamUtils/src/logger/logger.h" + +AkVCam::Device::Device(CMIOHardwarePlugInRef pluginInterface, + bool registerObject): + AkVCam::Object(pluginInterface) +{ + this->m_className = "Device"; + this->m_classID = kCMIODeviceClassID; + + if (registerObject) { + this->createObject(); + this->registerObject(); + } +} + +AkVCam::Device::~Device() +{ + this->registerStreams(false); + this->registerObject(false); +} + +OSStatus AkVCam::Device::createObject() +{ + AkObjectLogMethod(); + + if (!this->m_pluginInterface + || !*this->m_pluginInterface) + return kCMIOHardwareUnspecifiedError; + + CMIOObjectID deviceID = 0; + + auto status = + CMIOObjectCreate(this->m_pluginInterface, + kCMIOObjectSystemObject, + this->m_classID, + &deviceID); + + if (status == kCMIOHardwareNoError) { + this->m_isCreated = true; + this->m_objectID = deviceID; + AkLoggerLog("Created device: ", this->m_objectID); + } + + return status; +} + +OSStatus AkVCam::Device::registerObject(bool regist) +{ + AkObjectLogMethod(); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!this->m_isCreated + || !this->m_pluginInterface + || !*this->m_pluginInterface) + return status; + + if (regist) { + status = CMIOObjectsPublishedAndDied(this->m_pluginInterface, + kCMIOObjectSystemObject, + 1, + &this->m_objectID, + 0, + nullptr); + } else { + status = CMIOObjectsPublishedAndDied(this->m_pluginInterface, + kCMIOObjectSystemObject, + 0, + nullptr, + 1, + &this->m_objectID); + } + + return status; +} + +AkVCam::StreamPtr AkVCam::Device::addStream() +{ + AkObjectLogMethod(); + auto stream = StreamPtr(new Stream(false, this)); + + if (stream->createObject() == kCMIOHardwareNoError) { + this->m_streams[stream->objectID()] = stream; + this->updateStreamsProperty(); + + return stream; + } + + return StreamPtr(); +} + +std::list AkVCam::Device::addStreams(int n) +{ + AkObjectLogMethod(); + std::list streams; + + for (int i = 0; i < n; i++) { + auto stream = StreamPtr(new Stream(false, this)); + + if (stream->createObject() != kCMIOHardwareNoError) + return std::list(); + + streams.push_back(stream); + } + + for (auto &stream: streams) { + this->m_streams[stream->objectID()] = stream; + this->updateStreamsProperty(); + } + + return streams; +} + +OSStatus AkVCam::Device::registerStreams(bool regist) +{ + AkObjectLogMethod(); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!this->m_isCreated + || !this->m_pluginInterface + || !*this->m_pluginInterface + || this->m_streams.empty()) + return status; + + std::vector streams; + + for (auto &stream: this->m_streams) + streams.push_back(stream.first); + + if (regist) { + status = CMIOObjectsPublishedAndDied(this->m_pluginInterface, + kCMIOObjectSystemObject, + UInt32(streams.size()), + streams.data(), + 0, + nullptr); + } else { + status = CMIOObjectsPublishedAndDied(this->m_pluginInterface, + kCMIOObjectSystemObject, + 0, + nullptr, + UInt32(streams.size()), + streams.data()); + } + + return status; +} + +std::string AkVCam::Device::deviceId() const +{ + return this->m_deviceId; +} + +void AkVCam::Device::setDeviceId(const std::string &deviceId) +{ + this->m_deviceId = deviceId; +} + +void AkVCam::Device::stopStreams() +{ + for (auto &stream: this->m_streams) + stream.second->stop(); +} + +void AkVCam::Device::serverStateChanged(IpcBridge::ServerState state) +{ + for (auto &stream: this->m_streams) + stream.second->serverStateChanged(state); +} + +void AkVCam::Device::frameReady(const AkVCam::VideoFrame &frame) +{ + for (auto &stream: this->m_streams) + stream.second->frameReady(frame); +} + +void AkVCam::Device::setBroadcasting(const std::string &broadcaster) +{ + for (auto &stream: this->m_streams) + stream.second->setBroadcasting(broadcaster); +} + +void AkVCam::Device::setMirror(bool horizontalMirror, bool verticalMirror) +{ + for (auto &stream: this->m_streams) + stream.second->setMirror(horizontalMirror, verticalMirror); +} + +void AkVCam::Device::setScaling(Scaling scaling) +{ + for (auto &stream: this->m_streams) + stream.second->setScaling(scaling); +} + +void AkVCam::Device::setAspectRatio(AspectRatio aspectRatio) +{ + for (auto &stream: this->m_streams) + stream.second->setAspectRatio(aspectRatio); +} + +void AkVCam::Device::setSwapRgb(bool swap) +{ + for (auto &stream: this->m_streams) + stream.second->setSwapRgb(swap); +} + +OSStatus AkVCam::Device::suspend() +{ + AkObjectLogMethod(); + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Device::resume() +{ + AkObjectLogMethod(); + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Device::startStream(CMIOStreamID stream) +{ + AkObjectLogMethod(); + + UInt32 isRunning = 0; + this->m_properties.getProperty(kCMIODevicePropertyDeviceIsRunning, + &isRunning); + + if (isRunning) + return kCMIOHardwareUnspecifiedError; + + if (!this->m_streams.count(stream)) + return kCMIOHardwareNotRunningError; + + if (!this->m_streams[stream]->start()) + return kCMIOHardwareNotRunningError; + + bool deviceRunning = true; + + for (auto &stream: this->m_streams) + deviceRunning &= stream.second->running(); + + if (deviceRunning) { + this->m_properties.setProperty(kCMIODevicePropertyDeviceIsRunning, + UInt32(1)); + auto address = this->address(kCMIODevicePropertyDeviceIsRunning); + this->propertyChanged(1, &address); + } + + AKVCAM_EMIT(this, AddListener, this->m_deviceId) + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::Device::stopStream(CMIOStreamID stream) +{ + AkObjectLogMethod(); + + UInt32 isRunning = 0; + this->m_properties.getProperty(kCMIODevicePropertyDeviceIsRunning, + &isRunning); + + if (!isRunning) + return kCMIOHardwareNotRunningError; + + if (!this->m_streams.count(stream)) + return kCMIOHardwareNotRunningError; + + this->m_streams[stream]->stop(); + bool deviceRunning = false; + + for (auto &stream: this->m_streams) + deviceRunning |= stream.second->running(); + + if (!deviceRunning) { + this->m_properties.setProperty(kCMIODevicePropertyDeviceIsRunning, + UInt32(0)); + auto address = this->address(kCMIODevicePropertyDeviceIsRunning); + this->propertyChanged(1, &address); + } + + AKVCAM_EMIT(this, RemoveListener, this->m_deviceId) + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::Device::processAVCCommand(CMIODeviceAVCCommand *ioAVCCommand) +{ + AkObjectLogMethod(); + UNUSED(ioAVCCommand) + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Device::processRS422Command(CMIODeviceRS422Command *ioRS422Command) +{ + AkObjectLogMethod(); + UNUSED(ioRS422Command) + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +void AkVCam::Device::updateStreamsProperty() +{ + std::vector streams; + + for (auto &stream: this->m_streams) + streams.push_back(stream.second); + + this->m_properties.setProperty(kCMIODevicePropertyStreams, streams); +} diff --git a/cmio/VirtualCamera/src/device.h b/cmio/VirtualCamera/src/device.h new file mode 100644 index 0000000..4a73045 --- /dev/null +++ b/cmio/VirtualCamera/src/device.h @@ -0,0 +1,77 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include +#include + +#include "stream.h" + +namespace AkVCam +{ + class Device; + typedef std::shared_ptr DevicePtr; + + class Device: public Object + { + AKVCAM_SIGNAL(AddListener, const std::string &deviceId) + AKVCAM_SIGNAL(RemoveListener, const std::string &deviceId) + + public: + Device(CMIOHardwarePlugInRef pluginInterface, + bool createObject=false); + ~Device(); + + OSStatus createObject(); + OSStatus registerObject(bool regist=true); + StreamPtr addStream(); + std::list addStreams(int n); + OSStatus registerStreams(bool regist=true); + std::string deviceId() const; + void setDeviceId(const std::string &deviceId); + void stopStreams(); + + void serverStateChanged(IpcBridge::ServerState state); + void frameReady(const VideoFrame &frame); + void setBroadcasting(const std::string &broadcaster); + void setMirror(bool horizontalMirror, bool verticalMirror); + void setScaling(Scaling scaling); + void setAspectRatio(AspectRatio aspectRatio); + void setSwapRgb(bool swap); + + // Device Interface + OSStatus suspend(); + OSStatus resume(); + OSStatus startStream(CMIOStreamID stream); + OSStatus stopStream(CMIOStreamID stream); + OSStatus processAVCCommand(CMIODeviceAVCCommand *ioAVCCommand); + OSStatus processRS422Command(CMIODeviceRS422Command *ioRS422Command); + + private: + std::string m_deviceId; + std::map m_streams; + + void updateStreamsProperty(); + }; +} + +#endif // DEVICE_H diff --git a/cmio/VirtualCamera/src/object.cpp b/cmio/VirtualCamera/src/object.cpp new file mode 100644 index 0000000..7b319a1 --- /dev/null +++ b/cmio/VirtualCamera/src/object.cpp @@ -0,0 +1,142 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "object.h" +#include "utils.h" + +AkVCam::Object::Object(CMIOHardwarePlugInRef pluginInterface, + Object *parent): + ObjectInterface(), + m_pluginInterface(pluginInterface), + m_parent(parent), + m_isCreated(false) +{ + this->m_className = "Object"; + this->m_properties.setProperty(kCMIOObjectPropertyOwnedObjects, + std::vector()); + this->m_properties.setProperty(kCMIOObjectPropertyListenerAdded, + this->address()); + this->m_properties.setProperty(kCMIOObjectPropertyListenerRemoved, + this->address()); + + if (this->m_parent) { + this->m_parent->m_childs.push_back(this); + this->m_parent->childsUpdate(); + } +} + +AkVCam::Object::Object(Object *parent): + ObjectInterface(), + m_pluginInterface(parent? parent->m_pluginInterface: nullptr), + m_parent(parent), + m_isCreated(false) +{ + this->m_className = "Object"; + this->m_properties.setProperty(kCMIOObjectPropertyOwnedObjects, + std::vector()); + this->m_properties.setProperty(kCMIOObjectPropertyListenerAdded, + this->address()); + this->m_properties.setProperty(kCMIOObjectPropertyListenerRemoved, + this->address()); + + if (this->m_parent) { + this->m_parent->m_childs.push_back(this); + this->m_parent->childsUpdate(); + } +} + +AkVCam::Object::~Object() +{ + if (this->m_parent) { + this->m_parent->m_childs.remove(this); + this->m_parent->childsUpdate(); + } +} + +CMIOObjectID AkVCam::Object::objectID() const +{ + return this->m_objectID; +} + +UInt32 AkVCam::Object::classID() const +{ + return this->m_classID; +} + +OSStatus AkVCam::Object::createObject() +{ + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Object::registerObject(bool regist) +{ + UNUSED(regist) + + return kCMIOHardwareNoError; +} + +AkVCam::Object *AkVCam::Object::findObject(CMIOObjectID objectID) +{ + if (this->m_objectID == objectID) + return this; + + for (auto child: this->m_childs) + if (auto object = child->findObject(objectID)) + return object; + + return nullptr; +} + +OSStatus AkVCam::Object::propertyChanged(UInt32 numberAddresses, + const CMIOObjectPropertyAddress *addresses) +{ + return CMIOObjectPropertiesChanged(this->m_pluginInterface, + this->m_objectID, + numberAddresses, + addresses); +} + +OSStatus AkVCam::Object::setPropertyData(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + const void *data) +{ + auto status = + ObjectInterface::setPropertyData(address, + qualifierDataSize, + qualifierData, + dataSize, + data); + + if (status == kCMIOHardwareUnspecifiedError) + status = this->propertyChanged(1, address); + + return status; +} + +void AkVCam::Object::childsUpdate() +{ + std::vector objects; + + for (auto &object: this->m_childs) + objects.push_back(object); + + this->m_properties.setProperty(kCMIOObjectPropertyOwnedObjects, objects); +} diff --git a/cmio/VirtualCamera/src/object.h b/cmio/VirtualCamera/src/object.h new file mode 100644 index 0000000..945cf7f --- /dev/null +++ b/cmio/VirtualCamera/src/object.h @@ -0,0 +1,65 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef OBJECT_H +#define OBJECT_H + +#include + +#include "objectinterface.h" + +namespace AkVCam +{ + class Object; + typedef std::shared_ptr ObjectPtr; + + class Object: public ObjectInterface + { + public: + Object(CMIOHardwarePlugInRef m_pluginInterface, + Object *m_parent=nullptr); + Object(Object *m_parent=nullptr); + virtual ~Object(); + + CMIOObjectID objectID() const; + UInt32 classID() const; + virtual OSStatus createObject(); + virtual OSStatus registerObject(bool regist); + Object *findObject(CMIOObjectID objectID); + OSStatus propertyChanged(UInt32 numberAddresses, + const CMIOObjectPropertyAddress *addresses); + + OSStatus setPropertyData(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + const void *data); + + private: + void childsUpdate(); + + protected: + CMIOHardwarePlugInRef m_pluginInterface; + Object *m_parent; + std::list m_childs; + bool m_isCreated; + }; +} + +#endif // OBJECT_H diff --git a/cmio/VirtualCamera/src/objectinterface.cpp b/cmio/VirtualCamera/src/objectinterface.cpp new file mode 100644 index 0000000..4d9fd41 --- /dev/null +++ b/cmio/VirtualCamera/src/objectinterface.cpp @@ -0,0 +1,174 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "objectinterface.h" +#include "utils.h" +#include "VCamUtils/src/logger/logger.h" + +AkVCam::ObjectInterface::ObjectInterface(): + m_objectID(0), + m_classID(0) +{ + +} + +AkVCam::ObjectInterface::~ObjectInterface() +{ + +} + +AkVCam::ObjectProperties AkVCam::ObjectInterface::properties() const +{ + return this->m_properties; +} + +AkVCam::ObjectProperties &AkVCam::ObjectInterface::properties() +{ + return this->m_properties; +} + +void AkVCam::ObjectInterface::setProperties(const ObjectProperties &properties) +{ + this->m_properties = properties; +} + +void AkVCam::ObjectInterface::updateProperties(const ObjectProperties &properties) +{ + this->m_properties.update(properties); +} + +CMIOObjectPropertyAddress AkVCam::ObjectInterface::address(CMIOObjectPropertySelector selector, + CMIOObjectPropertyScope scope, + CMIOObjectPropertyElement element) +{ + return CMIOObjectPropertyAddress {selector, scope, element}; +} + +void AkVCam::ObjectInterface::show() +{ + AkObjectLogMethod(); + + AkLoggerLog("STUB"); +} + +Boolean AkVCam::ObjectInterface::hasProperty(const CMIOObjectPropertyAddress *address) +{ + AkObjectLogMethod(); + + if (!this->m_properties.getProperty(address->mSelector)) { + AkLoggerLog("Unknown property ", enumToString(address->mSelector)); + + return false; + } + + AkLoggerLog("Found property ", enumToString(address->mSelector)); + + return true; +} + +OSStatus AkVCam::ObjectInterface::isPropertySettable(const CMIOObjectPropertyAddress *address, + Boolean *isSettable) +{ + AkObjectLogMethod(); + + if (!this->m_properties.getProperty(address->mSelector)) { + AkLoggerLog("Unknown property ", enumToString(address->mSelector)); + + return kCMIOHardwareUnknownPropertyError; + } + + bool settable = this->m_properties.isSettable(address->mSelector); + + if (isSettable) + *isSettable = settable; + + AkLoggerLog("Is property ", + enumToString(address->mSelector), + " settable? ", + (settable? "YES": "NO")); + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::ObjectInterface::getPropertyDataSize(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 *dataSize) +{ + AkObjectLogMethod(); + AkLoggerLog("Getting property size ", enumToString(address->mSelector)); + + if (!this->m_properties.getProperty(address->mSelector, + qualifierDataSize, + qualifierData, + 0, + dataSize)) { + AkLoggerLog("Unknown property ", enumToString(address->mSelector)); + + return kCMIOHardwareUnknownPropertyError; + } + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::ObjectInterface::getPropertyData(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + UInt32 *dataUsed, + void *data) +{ + AkObjectLogMethod(); + AkLoggerLog("Getting property ", enumToString(address->mSelector)); + + if (!this->m_properties.getProperty(address->mSelector, + qualifierDataSize, + qualifierData, + dataSize, + dataUsed, + data)) { + AkLoggerLog("Unknown property ", enumToString(address->mSelector)); + + return kCMIOHardwareUnknownPropertyError; + } + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::ObjectInterface::setPropertyData(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + const void *data) +{ + AkObjectLogMethod(); + AkLoggerLog("Setting property ", enumToString(address->mSelector)); + UNUSED(qualifierDataSize) + UNUSED(qualifierData) + + if (!this->m_properties.setProperty(address->mSelector, + dataSize, + data)) { + AkLoggerLog("Unknown property ", enumToString(address->mSelector)); + + return kCMIOHardwareUnknownPropertyError; + } + + return kCMIOHardwareNoError; +} diff --git a/cmio/VirtualCamera/src/objectinterface.h b/cmio/VirtualCamera/src/objectinterface.h new file mode 100644 index 0000000..4304b6d --- /dev/null +++ b/cmio/VirtualCamera/src/objectinterface.h @@ -0,0 +1,81 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef OBJECTINTERFACE_H +#define OBJECTINTERFACE_H + +#include +#include + +#include "objectproperties.h" +#include "VCamUtils/src/utils.h" + +#define AkObjectLogMethod() \ + AkLoggerLog(this->m_className, \ + "(", \ + this->m_objectID, \ + ")::", \ + __FUNCTION__, \ + "()") + +namespace AkVCam +{ + class ObjectInterface + { + public: + ObjectInterface(); + virtual ~ObjectInterface(); + + ObjectProperties properties() const; + ObjectProperties &properties(); + void setProperties(const ObjectProperties &properties); + void updateProperties(const ObjectProperties &properties); + static CMIOObjectPropertyAddress address(CMIOObjectPropertySelector selector=0, + CMIOObjectPropertyScope scope=kCMIOObjectPropertyScopeGlobal, + CMIOObjectPropertyElement element=kCMIOObjectPropertyElementMaster); + + virtual void show(); + virtual Boolean hasProperty(const CMIOObjectPropertyAddress *address); + virtual OSStatus isPropertySettable(const CMIOObjectPropertyAddress *address, + Boolean *isSettable); + virtual OSStatus getPropertyDataSize(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 *dataSize); + virtual OSStatus getPropertyData(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + UInt32 *dataUsed, + void *data); + virtual OSStatus setPropertyData(const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + const void *data); + + protected: + CMIOObjectID m_objectID; + std::string m_className; + UInt32 m_classID; + ObjectProperties m_properties; + }; +} + +#endif // OBJECTINTERFACE_H diff --git a/cmio/VirtualCamera/src/objectproperties.cpp b/cmio/VirtualCamera/src/objectproperties.cpp new file mode 100644 index 0000000..86cac9b --- /dev/null +++ b/cmio/VirtualCamera/src/objectproperties.cpp @@ -0,0 +1,743 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "object.h" +#include "utils.h" +#include "VCamUtils/src/image/videoformat.h" + +namespace AkVCam +{ + enum PropertyType + { + PropertyTypeUInt32, + PropertyTypeFloat64, + PropertyTypePidT, + PropertyTypeString, + PropertyTypeWString, + PropertyTypeObjectVector, + PropertyTypeObjectPtrVector, + PropertyTypeVideoFormat, + PropertyTypeVideoFormatVector, + PropertyTypeFloat64Vector, + PropertyTypeAudioValueRangeVector, + PropertyTypeClock, + PropertyTypeAddress + }; + + struct PropertyValue + { + PropertyType type; + bool isSettable; + + union + { + UInt32 uint32; + Float64 float64; + pid_t pidT; + } num; + + std::string str; + std::wstring wstr; + std::vector objects; + std::vector objectsPtr; + std::vector videoFormats; + std::vector float64Vector; + std::vector audioValueRangeVector; + VideoFormat videoFormat; + ClockPtr clock; + CMIOObjectPropertyAddress address; + }; + + class ObjectPropertiesPrivate + { + public: + std::map m_properties; + }; +} + +AkVCam::ObjectProperties::ObjectProperties() +{ + this->d = new ObjectPropertiesPrivate(); +} + +AkVCam::ObjectProperties::ObjectProperties(const ObjectProperties &other) +{ + this->d = new ObjectPropertiesPrivate(); + this->d->m_properties = other.d->m_properties; +} + +AkVCam::ObjectProperties &AkVCam::ObjectProperties::operator =(const ObjectProperties &other) +{ + if (this != &other) + this->d->m_properties = other.d->m_properties; + + return *this; +} + +AkVCam::ObjectProperties::~ObjectProperties() +{ + delete this->d; +} + +std::vector AkVCam::ObjectProperties::properties() const +{ + std::vector properties; + + for (auto &property: this->d->m_properties) + properties.push_back(property.first); + + return properties; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::string &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeString; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].str = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::wstring &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeWString; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].wstr = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + UInt32 value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeUInt32; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].num.uint32 = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + Float64 value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeFloat64; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].num.float64 = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + pid_t value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypePidT; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].num.pidT = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeObjectVector; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].objects = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeObjectPtrVector; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].objectsPtr = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const VideoFormat &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeVideoFormat; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].videoFormat = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeVideoFormatVector; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].videoFormats = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeFloat64Vector; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].float64Vector = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + std::vector fvalue; + + for (auto &v: value) + fvalue.push_back(v.value()); + + this->d->m_properties[property].type = PropertyTypeFloat64Vector; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].float64Vector = fvalue; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeAudioValueRangeVector; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].audioValueRangeVector = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const std::vector &value, + bool isSettable) +{ + std::vector valueRanges; + + for (auto &range: value) + valueRanges.push_back({range.first.value(), range.second.value()}); + + return this->setProperty(property, valueRanges, isSettable); +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const ClockPtr &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeClock; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].clock = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + const CMIOObjectPropertyAddress &value, + bool isSettable) +{ + this->d->m_properties[property].type = PropertyTypeAddress; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].address = value; + + return true; +} + +bool AkVCam::ObjectProperties::setProperty(UInt32 property, + UInt32 dataSize, + const void *data) +{ + if (!this->d->m_properties.count(property)) + return false; + + bool isSettable = this->d->m_properties[property].isSettable; + + if (!isSettable) + return false; + + auto propertyType = this->d->m_properties[property].type; + bool ok = true; + + switch (propertyType) { + case PropertyTypeAddress: + if (dataSize == sizeof(CMIOObjectPropertyAddress)) { + this->d->m_properties[property].type = PropertyTypeAddress; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].address = + *static_cast(data); + } else { + ok = false; + } + + break; + + case PropertyTypeVideoFormat: + if (dataSize == sizeof(CMFormatDescriptionRef)) { + this->d->m_properties[property].type = PropertyTypeVideoFormat; + this->d->m_properties[property].isSettable = isSettable; + auto videoDescription = + *static_cast(data); + auto mediaType = CMFormatDescriptionGetMediaSubType(videoDescription); + auto dimensions = CMVideoFormatDescriptionGetDimensions(videoDescription); + this->d->m_properties[property].videoFormat = + VideoFormat(formatFromCM(mediaType), + dimensions.width, + dimensions.height); + } else { + ok = false; + } + + break; + + case PropertyTypeUInt32: + if (dataSize == sizeof(UInt32)) { + this->d->m_properties[property].type = PropertyTypeUInt32; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].num.uint32 = + *static_cast(data); + } else { + ok = false; + } + + break; + + case PropertyTypeFloat64: + if (dataSize == sizeof(Float64)) { + this->d->m_properties[property].type = PropertyTypeFloat64; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].num.float64 = + *static_cast(data); + } else { + ok = false; + } + + break; + + case PropertyTypePidT: + if (dataSize == sizeof(pid_t)) { + this->d->m_properties[property].type = PropertyTypePidT; + this->d->m_properties[property].isSettable = isSettable; + this->d->m_properties[property].num.pidT = + *static_cast(data); + } else { + ok = false; + } + + break; + + default: + return false; + } + + return ok; +} + +bool AkVCam::ObjectProperties::getProperty(UInt32 property, UInt32 *value) +{ + if (!value || !this->d->m_properties.count(property)) + return false; + + auto propertyType = this->d->m_properties[property].type; + + if (propertyType != PropertyTypeUInt32) + return false; + + *value = this->d->m_properties[property].num.uint32; + + return true; +} + +bool AkVCam::ObjectProperties::getProperty(UInt32 property, Float64 *value) +{ + if (!value || !this->d->m_properties.count(property)) + return false; + + auto propertyType = this->d->m_properties[property].type; + + if (propertyType != PropertyTypeFloat64) + return false; + + *value = this->d->m_properties[property].num.float64; + + return true; +} + +bool AkVCam::ObjectProperties::getProperty(UInt32 property, std::string *value) +{ + if (!value || !this->d->m_properties.count(property)) + return false; + + auto propertyType = this->d->m_properties[property].type; + + if (propertyType != PropertyTypeString) + return false; + + *value = this->d->m_properties[property].str; + + return true; +} + +bool AkVCam::ObjectProperties::getProperty(UInt32 property, + AkVCam::VideoFormat *value) +{ + if (!value || !this->d->m_properties.count(property)) + return false; + + auto propertyType = this->d->m_properties[property].type; + + if (propertyType != PropertyTypeVideoFormat) + return false; + + *value = this->d->m_properties[property].videoFormat; + + return true; +} + +bool AkVCam::ObjectProperties::getProperty(UInt32 property, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + UInt32 *dataUsed, + void *data) +{ + if (!this->d->m_properties.count(property)) + return false; + + bool ok = true; + auto propertyType = this->d->m_properties[property].type; + + switch (propertyType) { + case PropertyTypeString: + if (dataUsed) { + *dataUsed = sizeof(CFStringRef); + + if (data) + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto value = this->d->m_properties[property].str; + *static_cast(data) = + CFStringCreateWithCString(kCFAllocatorDefault, + value.c_str(), + kCFStringEncodingUTF8); + } + + break; + + case PropertyTypeWString: + if (dataUsed) { + *dataUsed = sizeof(CFStringRef); + + if (data) + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto value = this->d->m_properties[property].wstr; + *static_cast(data) = + CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast(value.c_str()), + CFIndex(value.size() * sizeof(wchar_t)), + kCFStringEncodingUTF32LE, + false); + } + + break; + + case PropertyTypeObjectVector: { + auto &objects = this->d->m_properties[property].objects; + auto objectList = static_cast(data); + size_t i = 0; + + for (auto &object: objects) { + if (qualify(property, + qualifierDataSize, + qualifierData, + object)) { + if (data) + objectList[i] = object->objectID(); + + i++; + } + } + + if (dataUsed) + *dataUsed = UInt32(i * sizeof(CMIOObjectID)); + + break; + } + + case PropertyTypeObjectPtrVector: { + auto &objects = this->d->m_properties[property].objectsPtr; + auto objectList = static_cast(data); + size_t i = 0; + + for (auto &object: objects) { + if (qualify(property, + qualifierDataSize, + qualifierData, + &object)) { + if (data) + objectList[i] = object->objectID(); + + i++; + } + } + + if (dataUsed) + *dataUsed = UInt32(i * sizeof(CMIOObjectID)); + + break; + } + + case PropertyTypeVideoFormat: { + if (dataUsed) { + *dataUsed = sizeof(CMFormatDescriptionRef); + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto videoFormat = this->d->m_properties[property].videoFormat; + auto status = + CMVideoFormatDescriptionCreate(kCFAllocatorDefault, + formatToCM(PixelFormat(videoFormat.fourcc())), + videoFormat.width(), + videoFormat.height(), + nullptr, + static_cast(data)); + + if (status != noErr) + ok = false; + } + + break; + } + + case PropertyTypeVideoFormatVector: { + if (dataUsed) { + *dataUsed = sizeof(CFArrayRef); + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto videoFormats = this->d->m_properties[property].videoFormats; + std::vector formats; + + for (auto &format: videoFormats) { + CMFormatDescriptionRef formatRef = nullptr; + auto status = + CMVideoFormatDescriptionCreate(kCFAllocatorDefault, + formatToCM(PixelFormat(format.fourcc())), + format.width(), + format.height(), + nullptr, + &formatRef); + + if (status == noErr) + formats.push_back(formatRef); + } + + CFArrayRef array = nullptr; + + if (!formats.empty()) + array = CFArrayCreate(kCFAllocatorDefault, + reinterpret_cast(formats.data()), + UInt32(formats.size()), + nullptr); + + *static_cast(data) = array; + } + + break; + } + + case PropertyTypeFloat64Vector: { + auto &values = this->d->m_properties[property].float64Vector; + auto valueList = static_cast(data); + size_t i = 0; + + for (auto &value: values) { + if (qualify(property, + qualifierDataSize, + qualifierData, + &value)) { + if (data) + valueList[i] = value; + + i++; + } + } + + if (dataUsed) + *dataUsed = UInt32(i * sizeof(Float64)); + + break; + } + + case PropertyTypeAudioValueRangeVector: { + auto &values = this->d->m_properties[property].audioValueRangeVector; + auto valueList = static_cast(data); + size_t i = 0; + + for (auto &value: values) { + if (qualify(property, + qualifierDataSize, + qualifierData, + &value)) { + if (data) + valueList[i] = value; + + i++; + } + } + + if (dataUsed) + *dataUsed = UInt32(i * sizeof(AudioValueRange)); + + break; + } + + case PropertyTypeClock: + if (dataUsed) { + *dataUsed = sizeof(CFTypeRef); + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto value = this->d->m_properties[property].clock; + *static_cast(data) = value->ref(); + CFRetain(value->ref()); + } + + break; + + case PropertyTypeUInt32: + if (dataUsed) { + *dataUsed = sizeof(UInt32); + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto value = this->d->m_properties[property].num.uint32; + *static_cast(data) = value; + } + + break; + + case PropertyTypeFloat64: + if (dataUsed) { + *dataUsed = sizeof(Float64); + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto value = this->d->m_properties[property].num.float64; + *static_cast(data) = value; + } + + break; + + case PropertyTypePidT: + if (dataUsed) { + *dataUsed = sizeof(pid_t); + ok = dataSize == *dataUsed; + } + + if (ok && data) { + auto value = this->d->m_properties[property].num.pidT; + *static_cast(data) = value; + } + + break; + + default: + return false; + } + + return ok; +} + +void AkVCam::ObjectProperties::removeProperty(UInt32 property) +{ + this->d->m_properties.erase(property); +} + +void AkVCam::ObjectProperties::update(const ObjectProperties &other) +{ + for (auto &property: other.d->m_properties) + this->d->m_properties[property.first] = property.second; +} + +bool AkVCam::ObjectProperties::isSettable(UInt32 property) +{ + if (this->d->m_properties.count(property)) + return this->d->m_properties[property].isSettable; + + return true; +} + +bool AkVCam::ObjectProperties::qualify(UInt32 property, + UInt32 qualifierDataSize, + const void *qualifierData, + const void *data) +{ + if (qualifierDataSize && qualifierData && data) + switch (property) { + case kCMIOObjectPropertyOwnedObjects: { + auto object = static_cast(data); + auto qualifier = static_cast(qualifierData); + + for (UInt32 i = 0; i < qualifierDataSize; i++) + if (qualifier[i] == object->classID()) + return true; + + return false; + } + + case kCMIOStreamPropertyFrameRates: + case kCMIOStreamPropertyFrameRateRanges: + // Not implemented. + break; + + default: + break; + } + + return true; +} diff --git a/cmio/VirtualCamera/src/objectproperties.h b/cmio/VirtualCamera/src/objectproperties.h new file mode 100644 index 0000000..97e88a7 --- /dev/null +++ b/cmio/VirtualCamera/src/objectproperties.h @@ -0,0 +1,129 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef OBJECTPROPERTIES_H +#define OBJECTPROPERTIES_H + +#include +#include +#include +#include + +#include "clock.h" +#include "VCamUtils/src/fraction.h" + +namespace AkVCam +{ + class ObjectPropertiesPrivate; + class Object; + class VideoFormat; + typedef std::shared_ptr ObjectPtr; + + class ObjectProperties + { + public: + ObjectProperties(); + ObjectProperties(const ObjectProperties &other); + ObjectProperties &operator =(const ObjectProperties &other); + virtual ~ObjectProperties(); + + std::vector properties() const; + + // Set properties + bool setProperty(UInt32 property, + const std::string &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::wstring &value, + bool isSettable=true); + bool setProperty(UInt32 property, + UInt32 value, + bool isSettable=true); + bool setProperty(UInt32 property, + Float64 value, + bool isSettable=true); + bool setProperty(UInt32 property, + pid_t value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const VideoFormat &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const std::vector &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const ClockPtr &value, + bool isSettable=true); + bool setProperty(UInt32 property, + const CMIOObjectPropertyAddress &value, + bool isSettable=true); + bool setProperty(UInt32 property, + UInt32 dataSize, + const void *data); + + // Get properties + bool getProperty(UInt32 property, + UInt32 *value); + bool getProperty(UInt32 property, + Float64 *value); + bool getProperty(UInt32 property, + std::string *value); + bool getProperty(UInt32 property, + VideoFormat *value); + bool getProperty(UInt32 property, + UInt32 qualifierDataSize=0, + const void *qualifierData=nullptr, + UInt32 dataSize=0, + UInt32 *dataUsed=nullptr, + void *data=nullptr); + + void removeProperty(UInt32 property); + void update(const ObjectProperties &other); + bool isSettable(UInt32 property); + + private: + ObjectPropertiesPrivate *d; + + protected: + virtual bool qualify(UInt32 property, + UInt32 qualifierDataSize, + const void *qualifierData, + const void *data); + }; +} + +#endif // OBJECTPROPERTIES_H diff --git a/cmio/VirtualCamera/src/plugin.cpp b/cmio/VirtualCamera/src/plugin.cpp new file mode 100644 index 0000000..601557d --- /dev/null +++ b/cmio/VirtualCamera/src/plugin.cpp @@ -0,0 +1,42 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "plugin.h" +#include "utils.h" +#include "VCamUtils/src/ipcbridge.h" +#include "VCamUtils/src/logger/logger.h" + +extern "C" void *akPluginMain(CFAllocatorRef allocator, + CFUUIDRef requestedTypeUUID) +{ + UNUSED(allocator) + +#if defined(QT_DEBUG) && 0 + // Turn on lights + freopen("/dev/tty", "a", stdout); + freopen("/dev/tty", "a", stderr); +#endif + + AkLoggerStart("/tmp/" CMIO_PLUGIN_NAME, "log"); + + if (!CFEqual(requestedTypeUUID, kCMIOHardwarePlugInTypeID)) + return nullptr; + + return AkVCam::PluginInterface::create(); +} diff --git a/cmio/VirtualCamera/src/plugin.h b/cmio/VirtualCamera/src/plugin.h new file mode 100644 index 0000000..6977bc1 --- /dev/null +++ b/cmio/VirtualCamera/src/plugin.h @@ -0,0 +1,25 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PLUGIN_H +#define PLUGIN_H + +#include "plugininterface.h" + +#endif // PLUGIN_H diff --git a/cmio/VirtualCamera/src/plugininterface.cpp b/cmio/VirtualCamera/src/plugininterface.cpp new file mode 100644 index 0000000..0eb799d --- /dev/null +++ b/cmio/VirtualCamera/src/plugininterface.cpp @@ -0,0 +1,972 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "plugininterface.h" +#include "utils.h" +#include "Assistant/src/assistantglobals.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/ipcbridge.h" +#include "VCamUtils/src/logger/logger.h" + +#define AkPluginPrivateIntefaceLog() \ + AkLoggerLog("PluginInterfacePrivate::", __FUNCTION__, "()") + +#define AkPluginPrivateIntefaceLogID(x) \ + AkLoggerLog("PluginInterfacePrivate::", __FUNCTION__, "(id = ", x, ")") + +namespace AkVCam +{ + struct PluginInterfacePrivate + { + public: + CMIOHardwarePlugInInterface *pluginInterface; + PluginInterface *self; + ULONG m_ref; + ULONG m_reserved; + IpcBridge m_ipcBridge; + + void updateDevices(); + static HRESULT QueryInterface(void *self, + REFIID uuid, + LPVOID *interface); + static ULONG AddRef(void *self); + static ULONG Release(void *self); + static OSStatus Initialize(CMIOHardwarePlugInRef self); + static OSStatus InitializeWithObjectID(CMIOHardwarePlugInRef self, + CMIOObjectID objectID); + static OSStatus Teardown(CMIOHardwarePlugInRef self); + static void ObjectShow(CMIOHardwarePlugInRef self, + CMIOObjectID objectID); + static Boolean ObjectHasProperty(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address); + static OSStatus ObjectIsPropertySettable(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + Boolean *isSettable); + static OSStatus ObjectGetPropertyDataSize(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 *dataSize); + static OSStatus ObjectGetPropertyData(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + UInt32 *dataUsed, + void *data); + static OSStatus ObjectSetPropertyData(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + const void *data); + static OSStatus DeviceSuspend(CMIOHardwarePlugInRef self, + CMIODeviceID device); + static OSStatus DeviceResume(CMIOHardwarePlugInRef self, + CMIODeviceID device); + static OSStatus DeviceStartStream(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIOStreamID stream); + static OSStatus DeviceStopStream(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIOStreamID stream); + static OSStatus DeviceProcessAVCCommand(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIODeviceAVCCommand *ioAVCCommand); + static OSStatus DeviceProcessRS422Command(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIODeviceRS422Command *ioRS422Command); + static OSStatus StreamCopyBufferQueue(CMIOHardwarePlugInRef self, + CMIOStreamID stream, + CMIODeviceStreamQueueAlteredProc queueAlteredProc, + void *queueAlteredRefCon, + CMSimpleQueueRef *queue); + static OSStatus StreamDeckPlay(CMIOHardwarePlugInRef self, + CMIOStreamID stream); + static OSStatus StreamDeckStop(CMIOHardwarePlugInRef self, + CMIOStreamID stream); + static OSStatus StreamDeckJog(CMIOHardwarePlugInRef self, + CMIOStreamID stream, + SInt32 speed); + static OSStatus StreamDeckCueTo(CMIOHardwarePlugInRef self, + CMIOStreamID stream, + Float64 frameNumber, + Boolean playOnCue); + }; +} + +AkVCam::PluginInterface::PluginInterface(): + ObjectInterface(), + m_objectID(0) +{ + this->m_className = "PluginInterface"; + this->d = new PluginInterfacePrivate; + this->d->self = this; + this->d->pluginInterface = new CMIOHardwarePlugInInterface { + // Padding for COM + NULL, + + // IUnknown Routines + PluginInterfacePrivate::QueryInterface, + PluginInterfacePrivate::AddRef, + PluginInterfacePrivate::Release, + + // DAL Plug-In Routines + PluginInterfacePrivate::Initialize, + PluginInterfacePrivate::InitializeWithObjectID, + PluginInterfacePrivate::Teardown, + PluginInterfacePrivate::ObjectShow, + PluginInterfacePrivate::ObjectHasProperty, + PluginInterfacePrivate::ObjectIsPropertySettable, + PluginInterfacePrivate::ObjectGetPropertyDataSize, + PluginInterfacePrivate::ObjectGetPropertyData, + PluginInterfacePrivate::ObjectSetPropertyData, + PluginInterfacePrivate::DeviceSuspend, + PluginInterfacePrivate::DeviceResume, + PluginInterfacePrivate::DeviceStartStream, + PluginInterfacePrivate::DeviceStopStream, + PluginInterfacePrivate::DeviceProcessAVCCommand, + PluginInterfacePrivate::DeviceProcessRS422Command, + PluginInterfacePrivate::StreamCopyBufferQueue, + PluginInterfacePrivate::StreamDeckPlay, + PluginInterfacePrivate::StreamDeckStop, + PluginInterfacePrivate::StreamDeckJog, + PluginInterfacePrivate::StreamDeckCueTo + }; + this->d->m_ref = 0; + this->d->m_reserved = 0; + + auto homePath = std::string("/Users/") + getenv("USER"); + + std::stringstream ss; + ss << CMIO_DAEMONS_PATH << "/" << AKVCAM_ASSISTANT_NAME << ".plist"; + auto daemon = ss.str(); + + if (daemon[0] == '~') + daemon.replace(0, 1, homePath); + + struct stat fileInfo; + + if (stat(daemon.c_str(), &fileInfo) == 0) + this->d->m_ipcBridge.connectService(true); + + this->d->m_ipcBridge.connectServerStateChanged(this, &PluginInterface::serverStateChanged); + this->d->m_ipcBridge.connectDeviceAdded(this, &PluginInterface::deviceAdded); + this->d->m_ipcBridge.connectDeviceRemoved(this, &PluginInterface::deviceRemoved); + this->d->m_ipcBridge.connectFrameReady(this, &PluginInterface::frameReady); + this->d->m_ipcBridge.connectBroadcastingChanged(this, &PluginInterface::setBroadcasting); + this->d->m_ipcBridge.connectMirrorChanged(this, &PluginInterface::setMirror); + this->d->m_ipcBridge.connectScalingChanged(this, &PluginInterface::setScaling); + this->d->m_ipcBridge.connectAspectRatioChanged(this, &PluginInterface::setAspectRatio); + this->d->m_ipcBridge.connectSwapRgbChanged(this, &PluginInterface::setSwapRgb); +} + +AkVCam::PluginInterface::~PluginInterface() +{ + this->d->m_ipcBridge.disconnectService(); + delete this->d->pluginInterface; + delete this->d; +} + +CMIOObjectID AkVCam::PluginInterface::objectID() const +{ + return this->m_objectID; +} + +CMIOHardwarePlugInRef AkVCam::PluginInterface::create() +{ + AkLoggerLog("Creating plugin interface."); + + auto pluginInterface = new PluginInterface(); + pluginInterface->d->AddRef(pluginInterface->d); + + return reinterpret_cast(pluginInterface->d); +} + +AkVCam::Object *AkVCam::PluginInterface::findObject(CMIOObjectID objectID) +{ + for (auto device: this->m_devices) + if (auto object = device->findObject(objectID)) + return object; + + return nullptr; +} + +HRESULT AkVCam::PluginInterface::QueryInterface(REFIID uuid, LPVOID *interface) +{ + if (!interface) + return E_POINTER; + + AkLoggerLog("AkVCam::PluginInterface::QueryInterface"); + + if (uuidEqual(uuid, kCMIOHardwarePlugInInterfaceID) + || uuidEqual(uuid, IUnknownUUID)) { + AkLoggerLog("Found plugin interface."); + this->d->AddRef(this->d); + *interface = this->d; + + return S_OK; + } + + return E_NOINTERFACE; +} + +OSStatus AkVCam::PluginInterface::Initialize() +{ + AkLoggerLog("AkVCam::PluginInterface::Initialize"); + + return this->InitializeWithObjectID(kCMIOObjectUnknown); +} + +OSStatus AkVCam::PluginInterface::InitializeWithObjectID(CMIOObjectID objectID) +{ + AkLoggerLog("AkVCam::PluginInterface::InitializeWithObjectID: ", objectID); + + this->m_objectID = objectID; + +#if defined(QT_DEBUG) && 0 + std::vector formats { + {PixelFormatRGB32, 640, 480, {30.0}} + }; + + this->createDevice("org.webcamoid.cmio.AkVCam.Driver.Debug", + "Virtual Debug Camera (driver side)", + formats); +#endif + + for (auto deviceId: this->d->m_ipcBridge.listDevices()) + this->deviceAdded(this, deviceId); + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::PluginInterface::Teardown() +{ + AkLoggerLog("AkVCam::PluginInterface::Teardown"); + + return kCMIOHardwareNoError; +} + +void AkVCam::PluginInterface::serverStateChanged(void *userData, + IpcBridge::ServerState state) +{ + AkLoggerLog("AkVCam::PluginInterface::frameReady"); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + device->serverStateChanged(state); + + if (state == IpcBridge::ServerStateAvailable) + self->d->updateDevices(); +} + +void AkVCam::PluginInterface::deviceAdded(void *userData, + const std::string &deviceId) +{ + AkLoggerLog("AkVCam::PluginInterface::deviceAdded"); + AkLoggerLog("Device Added: ", deviceId); + + auto self = reinterpret_cast(userData); + auto description = self->d->m_ipcBridge.description(deviceId); + auto formats = self->d->m_ipcBridge.formats(deviceId); + + self->createDevice(deviceId, description, formats); +} + +void AkVCam::PluginInterface::deviceRemoved(void *userData, + const std::string &deviceId) +{ + AkLoggerLog("AkVCam::PluginInterface::deviceRemoved"); + AkLoggerLog("Device Removed: ", deviceId); + + auto self = reinterpret_cast(userData); + self->destroyDevice(deviceId); +} + +void AkVCam::PluginInterface::frameReady(void *userData, + const std::string &deviceId, + const VideoFrame &frame) +{ + AkLoggerLog("AkVCam::PluginInterface::frameReady"); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + if (device->deviceId() == deviceId) + device->frameReady(frame); +} + +void AkVCam::PluginInterface::setBroadcasting(void *userData, + const std::string &deviceId, + const std::string &broadcaster) +{ + AkLoggerLog("AkVCam::PluginInterface::setBroadcasting"); + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Broadcaster: ", broadcaster); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + if (device->deviceId() == deviceId) + device->setBroadcasting(broadcaster); +} + +void AkVCam::PluginInterface::setMirror(void *userData, + const std::string &deviceId, + bool horizontalMirror, + bool verticalMirror) +{ + AkLoggerLog("AkVCam::PluginInterface::setMirror"); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + if (device->deviceId() == deviceId) + device->setMirror(horizontalMirror, verticalMirror); +} + +void AkVCam::PluginInterface::setScaling(void *userData, + const std::string &deviceId, + Scaling scaling) +{ + AkLoggerLog("AkVCam::PluginInterface::setScaling"); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + if (device->deviceId() == deviceId) + device->setScaling(scaling); +} + +void AkVCam::PluginInterface::setAspectRatio(void *userData, + const std::string &deviceId, + AspectRatio aspectRatio) +{ + AkLoggerLog("AkVCam::PluginInterface::setAspectRatio"); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + if (device->deviceId() == deviceId) + device->setAspectRatio(aspectRatio); +} + +void AkVCam::PluginInterface::setSwapRgb(void *userData, + const std::string &deviceId, + bool swap) +{ + AkLoggerLog("AkVCam::PluginInterface::setSwapRgb"); + auto self = reinterpret_cast(userData); + + for (auto device: self->m_devices) + if (device->deviceId() == deviceId) + device->setSwapRgb(swap); +} + +void AkVCam::PluginInterface::addListener(void *userData, + const std::string &deviceId) +{ + AkLoggerLog("AkVCam::PluginInterface::addListener"); + auto self = reinterpret_cast(userData); + self->d->m_ipcBridge.addListener(deviceId); +} + +void AkVCam::PluginInterface::removeListener(void *userData, + const std::string &deviceId) +{ + AkLoggerLog("AkVCam::PluginInterface::removeListener"); + auto self = reinterpret_cast(userData); + self->d->m_ipcBridge.removeListener(deviceId); +} + +bool AkVCam::PluginInterface::createDevice(const std::string &deviceId, + const std::wstring &description, + const std::vector &formats) +{ + AkLoggerLog("AkVCam::PluginInterface::createDevice"); + + StreamPtr stream; + + // Create one device. + auto pluginRef = reinterpret_cast(this->d); + auto device = std::make_shared(pluginRef); + device->setDeviceId(deviceId); + device->connectAddListener(this, &PluginInterface::addListener); + device->connectRemoveListener(this, &PluginInterface::removeListener); + this->m_devices.push_back(device); + + // Define device properties. + device->properties().setProperty(kCMIOObjectPropertyName, + description.c_str()); + device->properties().setProperty(kCMIOObjectPropertyManufacturer, + CMIO_PLUGIN_VENDOR); + device->properties().setProperty(kCMIODevicePropertyModelUID, + CMIO_PLUGIN_PRODUCT); + device->properties().setProperty(kCMIODevicePropertyLinkedCoreAudioDeviceUID, + ""); + device->properties().setProperty(kCMIODevicePropertyLinkedAndSyncedCoreAudioDeviceUID, + ""); + device->properties().setProperty(kCMIODevicePropertySuspendedByUser, + UInt32(0)); + device->properties().setProperty(kCMIODevicePropertyHogMode, + pid_t(-1), + false); + device->properties().setProperty(kCMIODevicePropertyDeviceMaster, + pid_t(-1)); + device->properties().setProperty(kCMIODevicePropertyExcludeNonDALAccess, + UInt32(0)); + device->properties().setProperty(kCMIODevicePropertyDeviceIsAlive, + UInt32(1)); + device->properties().setProperty(kCMIODevicePropertyDeviceUID, + deviceId.c_str()); + device->properties().setProperty(kCMIODevicePropertyTransportType, + UInt32(kIOAudioDeviceTransportTypePCI)); + device->properties().setProperty(kCMIODevicePropertyDeviceIsRunningSomewhere, + UInt32(0)); + + if (device->createObject() != kCMIOHardwareNoError) + goto createDevice_failed; + + stream = device->addStream(); + + // Register one stream for this device. + if (!stream) + goto createDevice_failed; + + stream->setFormats(formats); + stream->properties().setProperty(kCMIOStreamPropertyDirection, UInt32(0)); + + if (device->registerStreams() != kCMIOHardwareNoError) { + device->registerStreams(false); + + goto createDevice_failed; + } + + // Register the device. + if (device->registerObject() != kCMIOHardwareNoError) { + device->registerObject(false); + device->registerStreams(false); + + goto createDevice_failed; + } + + device->setBroadcasting(this->d->m_ipcBridge.broadcaster(deviceId)); + device->setMirror(this->d->m_ipcBridge.isHorizontalMirrored(deviceId), + this->d->m_ipcBridge.isVerticalMirrored(deviceId)); + device->setScaling(this->d->m_ipcBridge.scalingMode(deviceId)); + device->setAspectRatio(this->d->m_ipcBridge.aspectRatioMode(deviceId)); + device->setSwapRgb(this->d->m_ipcBridge.swapRgb(deviceId)); + + return true; + +createDevice_failed: + this->m_devices.erase(std::prev(this->m_devices.end())); + + return false; +} + +void AkVCam::PluginInterface::destroyDevice(const std::string &deviceId) +{ + AkLoggerLog("AkVCam::PluginInterface::destroyDevice"); + + for (auto it = this->m_devices.begin(); it != this->m_devices.end(); it++) { + auto device = *it; + + std::string curDeviceId; + device->properties().getProperty(kCMIODevicePropertyDeviceUID, + &curDeviceId); + + if (curDeviceId == deviceId) { + device->stopStreams(); + device->registerObject(false); + device->registerStreams(false); + this->m_devices.erase(it); + + break; + } + } +} + +void AkVCam::PluginInterfacePrivate::updateDevices() +{ + for (auto &device: this->self->m_devices) { + device->setBroadcasting(this->m_ipcBridge.broadcaster(device->deviceId())); + device->setMirror(this->m_ipcBridge.isHorizontalMirrored(device->deviceId()), + this->m_ipcBridge.isVerticalMirrored(device->deviceId())); + device->setScaling(this->m_ipcBridge.scalingMode(device->deviceId())); + device->setAspectRatio(this->m_ipcBridge.aspectRatioMode(device->deviceId())); + device->setSwapRgb(this->m_ipcBridge.swapRgb(device->deviceId())); + } +} + +HRESULT AkVCam::PluginInterfacePrivate::QueryInterface(void *self, + REFIID uuid, + LPVOID *interface) +{ + AkPluginPrivateIntefaceLog(); + + if (!self) + return E_FAIL; + + auto _self = reinterpret_cast(self); + + return _self->self->QueryInterface(uuid, interface); +} + +ULONG AkVCam::PluginInterfacePrivate::AddRef(void *self) +{ + AkPluginPrivateIntefaceLog(); + + if (!self) + return 0; + + auto _self = reinterpret_cast(self); + _self->m_ref++; + + return _self->m_ref; +} + +ULONG AkVCam::PluginInterfacePrivate::Release(void *self) +{ + AkPluginPrivateIntefaceLog(); + + if (!self) + return 0; + + auto _self = reinterpret_cast(self); + + if (_self->m_ref > 0) { + _self->m_ref--; + + if (_self->m_ref < 1) { + delete _self->self; + + return 0UL; + } + } + + return _self->m_ref; +} + +OSStatus AkVCam::PluginInterfacePrivate::Initialize(CMIOHardwarePlugInRef self) +{ + AkPluginPrivateIntefaceLog(); + + if (!self) + return kCMIOHardwareUnspecifiedError; + + auto _self = reinterpret_cast(self); + + return _self->self->Initialize(); +} + +OSStatus AkVCam::PluginInterfacePrivate::InitializeWithObjectID(CMIOHardwarePlugInRef self, + CMIOObjectID objectID) +{ + AkPluginPrivateIntefaceLog(); + + if (!self) + return kCMIOHardwareUnspecifiedError; + + auto _self = reinterpret_cast(self); + + return _self->self->InitializeWithObjectID(objectID); +} + +OSStatus AkVCam::PluginInterfacePrivate::Teardown(CMIOHardwarePlugInRef self) +{ + AkPluginPrivateIntefaceLog(); + + if (!self) + return kCMIOHardwareUnspecifiedError; + + auto _self = reinterpret_cast(self); + + return _self->self->Teardown(); +} + +void AkVCam::PluginInterfacePrivate::ObjectShow(CMIOHardwarePlugInRef self, + CMIOObjectID objectID) +{ + AkPluginPrivateIntefaceLogID(objectID); + + if (!self) + return; + + auto _self = reinterpret_cast(self); + + if (_self->self->objectID() == objectID) + _self->self->show(); + else if (auto object = _self->self->findObject(objectID)) + object->show(); +} + +Boolean AkVCam::PluginInterfacePrivate::ObjectHasProperty(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address) +{ + AkPluginPrivateIntefaceLogID(objectID); + Boolean result = false; + + if (!self) + return result; + + auto _self = reinterpret_cast(self); + + if (_self->self->objectID() == objectID) + result = _self->self->hasProperty(address); + else if (auto object = _self->self->findObject(objectID)) + result = object->hasProperty(address); + + return result; +} + +OSStatus AkVCam::PluginInterfacePrivate::ObjectIsPropertySettable(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + Boolean *isSettable) +{ + AkPluginPrivateIntefaceLogID(objectID); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + + if (_self->self->objectID() == objectID) + status = _self->self->isPropertySettable(address, + isSettable); + else if (auto object = _self->self->findObject(objectID)) + status = object->isPropertySettable(address, + isSettable); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::ObjectGetPropertyDataSize(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 *dataSize) +{ + AkPluginPrivateIntefaceLogID(objectID); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + + if (_self->self->objectID() == objectID) + status = _self->self->getPropertyDataSize(address, + qualifierDataSize, + qualifierData, + dataSize); + else if (auto object = _self->self->findObject(objectID)) + status = object->getPropertyDataSize(address, + qualifierDataSize, + qualifierData, + dataSize); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::ObjectGetPropertyData(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + UInt32 *dataUsed, + void *data) +{ + AkPluginPrivateIntefaceLogID(objectID); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + + if (_self->self->objectID() == objectID) + status = _self->self->getPropertyData(address, + qualifierDataSize, + qualifierData, + dataSize, + dataUsed, + data); + else if (auto object = _self->self->findObject(objectID)) + status = object->getPropertyData(address, + qualifierDataSize, + qualifierData, + dataSize, + dataUsed, + data); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::ObjectSetPropertyData(CMIOHardwarePlugInRef self, + CMIOObjectID objectID, + const CMIOObjectPropertyAddress *address, + UInt32 qualifierDataSize, + const void *qualifierData, + UInt32 dataSize, + const void *data) +{ + AkPluginPrivateIntefaceLogID(objectID); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + + if (_self->self->objectID() == objectID) + status = _self->self->setPropertyData(address, + qualifierDataSize, + qualifierData, + dataSize, + data); + else if (auto object = _self->self->findObject(objectID)) + status = object->setPropertyData(address, + qualifierDataSize, + qualifierData, + dataSize, + data); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::DeviceSuspend(CMIOHardwarePlugInRef self, + CMIODeviceID device) +{ + AkPluginPrivateIntefaceLogID(device); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(device)); + + if (object) + status = object->suspend(); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::DeviceResume(CMIOHardwarePlugInRef self, + CMIODeviceID device) +{ + AkPluginPrivateIntefaceLogID(device); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(device)); + + if (object) + status = object->resume(); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::DeviceStartStream(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIOStreamID stream) +{ + AkPluginPrivateIntefaceLogID(device); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(device)); + + if (object) + status = object->startStream(stream); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::DeviceStopStream(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIOStreamID stream) +{ + AkPluginPrivateIntefaceLogID(device); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(device)); + + if (object) + status = object->stopStream(stream); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::DeviceProcessAVCCommand(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIODeviceAVCCommand *ioAVCCommand) +{ + AkPluginPrivateIntefaceLogID(device); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(device)); + + if (object) + status = object->processAVCCommand(ioAVCCommand); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::DeviceProcessRS422Command(CMIOHardwarePlugInRef self, + CMIODeviceID device, + CMIODeviceRS422Command *ioRS422Command) +{ + AkPluginPrivateIntefaceLogID(device); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(device)); + + if (object) + status = object->processRS422Command(ioRS422Command); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::StreamCopyBufferQueue(CMIOHardwarePlugInRef self, + CMIOStreamID stream, + CMIODeviceStreamQueueAlteredProc queueAlteredProc, + void *queueAlteredRefCon, + CMSimpleQueueRef *queue) +{ + AkPluginPrivateIntefaceLogID(stream); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(stream)); + + if (object) + status = object->copyBufferQueue(queueAlteredProc, + queueAlteredRefCon, + queue); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::StreamDeckPlay(CMIOHardwarePlugInRef self, + CMIOStreamID stream) +{ + AkPluginPrivateIntefaceLogID(stream); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(stream)); + + if (object) + status = object->deckPlay(); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::StreamDeckStop(CMIOHardwarePlugInRef self, + CMIOStreamID stream) +{ + AkPluginPrivateIntefaceLogID(stream); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(stream)); + + if (object) + status = object->deckStop(); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::StreamDeckJog(CMIOHardwarePlugInRef self, + CMIOStreamID stream, + SInt32 speed) +{ + AkPluginPrivateIntefaceLogID(stream); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(stream)); + + if (object) + status = object->deckJog(speed); + + return status; +} + +OSStatus AkVCam::PluginInterfacePrivate::StreamDeckCueTo(CMIOHardwarePlugInRef self, + CMIOStreamID stream, + Float64 frameNumber, + Boolean playOnCue) +{ + AkPluginPrivateIntefaceLogID(stream); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!self) + return status; + + auto _self = reinterpret_cast(self); + auto object = reinterpret_cast(_self->self->findObject(stream)); + + if (object) + status = object->deckCueTo(frameNumber, playOnCue); + + return status; +} diff --git a/cmio/VirtualCamera/src/plugininterface.h b/cmio/VirtualCamera/src/plugininterface.h new file mode 100644 index 0000000..5233492 --- /dev/null +++ b/cmio/VirtualCamera/src/plugininterface.h @@ -0,0 +1,89 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PLUGININTERFACE_H +#define PLUGININTERFACE_H + +#include "VCamUtils/src/ipcbridge.h" +#include "device.h" + +namespace AkVCam +{ + struct PluginInterfacePrivate; + + class PluginInterface: public ObjectInterface + { + public: + PluginInterface(); + PluginInterface(const PluginInterface &other) = delete; + ~PluginInterface(); + + CMIOObjectID objectID() const; + static CMIOHardwarePlugInRef create(); + Object *findObject(CMIOObjectID objectID); + + HRESULT QueryInterface(REFIID uuid, LPVOID *interface); + OSStatus Initialize(); + OSStatus InitializeWithObjectID(CMIOObjectID objectID); + OSStatus Teardown(); + + private: + PluginInterfacePrivate *d; + CMIOObjectID m_objectID; + std::vector m_devices; + + static void serverStateChanged(void *userData, + IpcBridge::ServerState state); + static void deviceAdded(void *userData, + const std::string &deviceId); + static void deviceRemoved(void *userData, + const std::string &deviceId); + static void frameReady(void *userData, + const std::string &deviceId, + const VideoFrame &frame); + static void setBroadcasting(void *userData, + const std::string &deviceId, + const std::string &broadcaster); + static void setMirror(void *userData, + const std::string &deviceId, + bool horizontalMirror, + bool verticalMirror); + static void setScaling(void *userData, + const std::string &deviceId, + Scaling scaling); + static void setAspectRatio(void *userData, + const std::string &deviceId, + AspectRatio aspectRatio); + static void setSwapRgb(void *userData, + const std::string &deviceId, + bool swap); + static void addListener(void *userData, + const std::string &deviceId); + static void removeListener(void *userData, + const std::string &deviceId); + bool createDevice(const std::string &deviceId, + const std::wstring &description, + const std::vector &formats); + void destroyDevice(const std::string &deviceId); + + friend struct PluginInterfacePrivate; + }; +} + +#endif // PLUGININTERFACE_H diff --git a/cmio/VirtualCamera/src/queue.h b/cmio/VirtualCamera/src/queue.h new file mode 100644 index 0000000..75b2c8f --- /dev/null +++ b/cmio/VirtualCamera/src/queue.h @@ -0,0 +1,101 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef QUEUE_H +#define QUEUE_H + +#include +#include + +namespace AkVCam +{ + template + class Queue; + template + using QueuePtr = std::shared_ptr>; + + template + class Queue + { + public: + Queue(int32_t capacity): + m_queue(nullptr) + { + CMSimpleQueueCreate(kCFAllocatorDefault, + capacity, + &this->m_queue); + } + + ~Queue() + { + CFRelease(this->m_queue); + } + + void enqueue(const T &t) + { + CMSimpleQueueEnqueue(this->m_queue, + t); + } + + T dequeue() + { + return CMSimpleQueueDequeue(this->m_queue); + } + + void clear() + { + CMSimpleQueueReset(this->m_queue); + } + + int32_t capacity() + { + return CMSimpleQueueGetCapacity(this->m_queue); + } + + int32_t count() + { + return CMSimpleQueueGetCount(this->m_queue); + } + + int32_t size() + { + return this->count(); + } + + Float32 fullness() + { + return CMSimpleQueueGetFullness(this->m_queue); + } + + CFTypeID typeID() + { + return CMSimpleQueueGetTypeID(); + } + + CMSimpleQueueRef ref() + { + return m_queue; + } + + private: + CMSimpleQueueRef m_queue; + }; +} + +#endif // QUEUE_H diff --git a/cmio/VirtualCamera/src/stream.cpp b/cmio/VirtualCamera/src/stream.cpp new file mode 100644 index 0000000..5e4a86e --- /dev/null +++ b/cmio/VirtualCamera/src/stream.cpp @@ -0,0 +1,618 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include + +#include "stream.h" +#include "clock.h" +#include "utils.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/logger/logger.h" + +namespace AkVCam +{ + class StreamPrivate + { + public: + Stream *self; + ClockPtr m_clock; + UInt64 m_sequence; + CMTime m_pts; + SampleBufferQueuePtr m_queue; + CMIODeviceStreamQueueAlteredProc m_queueAltered {nullptr}; + VideoFrame m_currentFrame; + VideoFrame m_testFrame; + VideoFrame m_testFrameAdapted; + void *m_queueAlteredRefCon {nullptr}; + CFRunLoopTimerRef m_timer {nullptr}; + std::string m_broadcaster; + std::mutex m_mutex; + Scaling m_scaling {ScalingFast}; + AspectRatio m_aspectRatio {AspectRatioIgnore}; + bool m_running {false}; + bool m_horizontalMirror {false}; + bool m_verticalMirror {false}; + bool m_swapRgb {false}; + + explicit StreamPrivate(Stream *self); + bool startTimer(); + void stopTimer(); + static void streamLoop(CFRunLoopTimerRef timer, void *info); + void sendFrame(const VideoFrame &frame); + void updateTestFrame(); + VideoFrame applyAdjusts(const VideoFrame &frame); + VideoFrame randomFrame(); + }; +} + +AkVCam::Stream::Stream(bool registerObject, + Object *parent): + Object(parent) +{ + this->d = new StreamPrivate(this); + this->m_className = "Stream"; + this->m_classID = kCMIOStreamClassID; + this->d->m_testFrame.load(CMIO_PLUGINS_DAL_PATH + "/" + CMIO_PLUGIN_NAME + ".plugin/Contents/Resources/TestFrame.bmp"); + + this->d->m_clock = + std::make_shared("CMIO::VirtualCamera::Stream", + CMTimeMake(1, 10), + 100, + 10); + this->d->m_queue = std::make_shared(30); + + if (registerObject) { + this->createObject(); + this->registerObject(); + } + + this->m_properties.setProperty(kCMIOStreamPropertyClock, this->d->m_clock); +} + +AkVCam::Stream::~Stream() +{ + this->registerObject(false); + delete this->d; +} + +OSStatus AkVCam::Stream::createObject() +{ + AkObjectLogMethod(); + + if (!this->m_pluginInterface + || !*this->m_pluginInterface + || !this->m_parent) + return kCMIOHardwareUnspecifiedError; + + CMIOObjectID streamID = 0; + + auto status = + CMIOObjectCreate(this->m_pluginInterface, + this->m_parent->objectID(), + this->m_classID, + &streamID); + + if (status == kCMIOHardwareNoError) { + this->m_isCreated = true; + this->m_objectID = streamID; + AkLoggerLog("Created stream: ", this->m_objectID); + } + + return status; +} + +OSStatus AkVCam::Stream::registerObject(bool regist) +{ + AkObjectLogMethod(); + OSStatus status = kCMIOHardwareUnspecifiedError; + + if (!this->m_isCreated + || !this->m_pluginInterface + || !*this->m_pluginInterface + || !this->m_parent) + return status; + + if (regist) { + status = CMIOObjectsPublishedAndDied(this->m_pluginInterface, + this->m_parent->objectID(), + 1, + &this->m_objectID, + 0, + nullptr); + } else { + status = CMIOObjectsPublishedAndDied(this->m_pluginInterface, + this->m_parent->objectID(), + 0, + nullptr, + 1, + &this->m_objectID); + } + + return status; +} + +void AkVCam::Stream::setFormats(const std::vector &formats) +{ + AkObjectLogMethod(); + + if (formats.empty()) + return; + + std::vector formatsAdjusted; + + for (auto format: formats) { + int width; + int height; + AkVCam::VideoFormat::roundNearest(format.width(), + format.height(), + &width, + &height); + format.width() = width; + format.height() = height; + formatsAdjusted.push_back(format); + } + +#ifdef QT_DEBUG + for (auto &format: formatsAdjusted) + AkLoggerLog("Format: ", + enumToString(format.fourcc()), + " ", + format.width(), + "x", + format.height()); +#endif + + this->m_properties.setProperty(kCMIOStreamPropertyFormatDescriptions, + formatsAdjusted); + this->setFormat(formatsAdjusted[0]); +} + +void AkVCam::Stream::setFormat(const VideoFormat &format) +{ + AkObjectLogMethod(); + this->m_properties.setProperty(kCMIOStreamPropertyFormatDescription, + format); + this->m_properties.setProperty(kCMIOStreamPropertyFrameRates, + format.frameRates()); + this->m_properties.setProperty(kCMIOStreamPropertyFrameRateRanges, + format.frameRateRanges()); + this->m_properties.setProperty(kCMIOStreamPropertyMinimumFrameRate, + format.minimumFrameRate().value()); + + if (!format.frameRates().empty()) + this->setFrameRate(format.frameRates().front()); +} + +void AkVCam::Stream::setFrameRate(const Fraction &frameRate) +{ + this->m_properties.setProperty(kCMIOStreamPropertyFrameRate, + frameRate.value()); +} + +bool AkVCam::Stream::start() +{ + AkObjectLogMethod(); + + if (this->d->m_running) + return false; + + this->d->updateTestFrame(); + this->d->m_currentFrame = this->d->m_testFrameAdapted; + this->d->m_sequence = 0; + memset(&this->d->m_pts, 0, sizeof(CMTime)); + this->d->m_running = this->d->startTimer(); + AkLoggerLog("Running: ", this->d->m_running); + + return this->d->m_running; +} + +void AkVCam::Stream::stop() +{ + AkObjectLogMethod(); + + if (!this->d->m_running) + return; + + this->d->m_running = false; + this->d->stopTimer(); + this->d->m_currentFrame.clear(); + this->d->m_testFrameAdapted.clear(); +} + +bool AkVCam::Stream::running() +{ + return this->d->m_running; +} + +void AkVCam::Stream::serverStateChanged(IpcBridge::ServerState state) +{ + AkObjectLogMethod(); + + if (state == IpcBridge::ServerStateGone) { + this->d->m_broadcaster.clear(); + this->d->m_horizontalMirror = false; + this->d->m_verticalMirror = false; + this->d->m_scaling = ScalingFast; + this->d->m_aspectRatio = AspectRatioIgnore; + this->d->m_swapRgb = false; + this->d->updateTestFrame(); + + this->d->m_mutex.lock(); + this->d->m_currentFrame = this->d->m_testFrameAdapted; + this->d->m_mutex.unlock(); + } +} + +void AkVCam::Stream::frameReady(const AkVCam::VideoFrame &frame) +{ + AkObjectLogMethod(); + AkLoggerLog("Running: ", this->d->m_running); + AkLoggerLog("Broadcaster: ", this->d->m_broadcaster); + + if (!this->d->m_running) + return; + + this->d->m_mutex.lock(); + + if (!this->d->m_broadcaster.empty()) + this->d->m_currentFrame = this->d->applyAdjusts(frame); + + this->d->m_mutex.unlock(); +} + +void AkVCam::Stream::setBroadcasting(const std::string &broadcaster) +{ + AkObjectLogMethod(); + + if (this->d->m_broadcaster == broadcaster) + return; + + this->d->m_mutex.lock(); + this->d->m_broadcaster = broadcaster; + + if (broadcaster.empty()) + this->d->m_currentFrame = this->d->m_testFrameAdapted; + + this->d->m_mutex.unlock(); +} + +void AkVCam::Stream::setMirror(bool horizontalMirror, bool verticalMirror) +{ + AkObjectLogMethod(); + + if (this->d->m_horizontalMirror == horizontalMirror + && this->d->m_verticalMirror == verticalMirror) + return; + + this->d->m_horizontalMirror = horizontalMirror; + this->d->m_verticalMirror = verticalMirror; + this->d->updateTestFrame(); +} + +void AkVCam::Stream::setScaling(Scaling scaling) +{ + AkObjectLogMethod(); + + if (this->d->m_scaling == scaling) + return; + + this->d->m_scaling = scaling; + this->d->updateTestFrame(); +} + +void AkVCam::Stream::setAspectRatio(AspectRatio aspectRatio) +{ + AkObjectLogMethod(); + + if (this->d->m_aspectRatio == aspectRatio) + return; + + this->d->m_aspectRatio = aspectRatio; + this->d->updateTestFrame(); +} + +void AkVCam::Stream::setSwapRgb(bool swap) +{ + AkObjectLogMethod(); + + if (this->d->m_swapRgb == swap) + return; + + this->d->m_swapRgb = swap; + this->d->updateTestFrame(); +} + +OSStatus AkVCam::Stream::copyBufferQueue(CMIODeviceStreamQueueAlteredProc queueAlteredProc, + void *queueAlteredRefCon, + CMSimpleQueueRef *queue) +{ + AkObjectLogMethod(); + + this->d->m_queueAltered = queueAlteredProc; + this->d->m_queueAlteredRefCon = queueAlteredRefCon; + *queue = queueAlteredProc? this->d->m_queue->ref(): nullptr; + + if (*queue) + CFRetain(*queue); + + return kCMIOHardwareNoError; +} + +OSStatus AkVCam::Stream::deckPlay() +{ + AkObjectLogMethod(); + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Stream::deckStop() +{ + AkObjectLogMethod(); + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Stream::deckJog(SInt32 speed) +{ + AkObjectLogMethod(); + UNUSED(speed) + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +OSStatus AkVCam::Stream::deckCueTo(Float64 frameNumber, Boolean playOnCue) +{ + AkObjectLogMethod(); + UNUSED(frameNumber) + UNUSED(playOnCue) + + AkLoggerLog("STUB"); + + return kCMIOHardwareUnspecifiedError; +} + +AkVCam::StreamPrivate::StreamPrivate(AkVCam::Stream *self): + self(self) +{ +} + +bool AkVCam::StreamPrivate::startTimer() +{ + AkLoggerLog("AkVCam::StreamPrivate::startTimer()"); + + if (this->m_timer) + return false; + + Float64 fps = 0; + this->self->m_properties.getProperty(kCMIOStreamPropertyFrameRate, &fps); + + CFTimeInterval interval = 1.0 / fps; + CFRunLoopTimerContext context {0, this, nullptr, nullptr, nullptr}; + this->m_timer = + CFRunLoopTimerCreate(kCFAllocatorDefault, + 0.0, + interval, + 0, + 0, + StreamPrivate::streamLoop, + &context); + + if (!this->m_timer) + return false; + + CFRunLoopAddTimer(CFRunLoopGetMain(), + this->m_timer, + kCFRunLoopCommonModes); + + return true; +} + +void AkVCam::StreamPrivate::stopTimer() +{ + AkLoggerLog("AkVCam::StreamPrivate::stopTimer()"); + + if (!this->m_timer) + return; + + CFRunLoopTimerInvalidate(this->m_timer); + CFRunLoopRemoveTimer(CFRunLoopGetMain(), + this->m_timer, + kCFRunLoopCommonModes); + CFRelease(this->m_timer); + this->m_timer = nullptr; +} + +void AkVCam::StreamPrivate::streamLoop(CFRunLoopTimerRef timer, void *info) +{ + AkLoggerLog("AkVCam::StreamPrivate::streamLoop()"); + UNUSED(timer) + + auto self = reinterpret_cast(info); + AkLoggerLog("Running: ", self->m_running); + + if (!self->m_running) + return; + + self->m_mutex.lock(); + + if (self->m_currentFrame.format().size() < 1) + self->sendFrame(self->randomFrame()); + else + self->sendFrame(self->m_currentFrame); + + self->m_mutex.unlock(); +} + +void AkVCam::StreamPrivate::sendFrame(const VideoFrame &frame) +{ + AkLoggerLog("AkVCam::StreamPrivate::sendFrame()"); + + if (this->m_queue->fullness() >= 1.0f) + return; + + FourCC fourcc = frame.format().fourcc(); + int width = frame.format().width(); + int height = frame.format().height(); + + AkLoggerLog("Sending Frame: ", + enumToString(fourcc), + " ", + width, + "x", + height); + + bool resync = false; + auto hostTime = CFAbsoluteTimeGetCurrent(); + auto pts = CMTimeMake(int64_t(hostTime), 1e9); + auto ptsDiff = CMTimeGetSeconds(CMTimeSubtract(this->m_pts, pts)); + + if (CMTimeCompare(pts, this->m_pts) == 0) + return; + + Float64 fps = 0; + this->self->m_properties.getProperty(kCMIOStreamPropertyFrameRate, &fps); + + if (CMTIME_IS_INVALID(this->m_pts) + || ptsDiff < 0 + || ptsDiff > 2. / fps) { + this->m_pts = pts; + resync = true; + } + + CMIOStreamClockPostTimingEvent(this->m_pts, + UInt64(hostTime), + resync, + this->m_clock->ref()); + + CVImageBufferRef imageBuffer = nullptr; + CVPixelBufferCreate(kCFAllocatorDefault, + size_t(width), + size_t(height), + formatToCM(PixelFormat(fourcc)), + nullptr, + &imageBuffer); + + if (!imageBuffer) + return; + + CVPixelBufferLockBaseAddress(imageBuffer, 0); + auto data = CVPixelBufferGetBaseAddress(imageBuffer); + memcpy(data, frame.data().data(), frame.data().size()); + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + + CMVideoFormatDescriptionRef format = nullptr; + CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, + imageBuffer, + &format); + + auto duration = CMTimeMake(1e3, int32_t(1e3 * fps)); + CMSampleTimingInfo timingInfo { + duration, + this->m_pts, + this->m_pts + }; + + CMSampleBufferRef buffer = nullptr; + CMIOSampleBufferCreateForImageBuffer(kCFAllocatorDefault, + imageBuffer, + format, + &timingInfo, + this->m_sequence, + resync? + kCMIOSampleBufferDiscontinuityFlag_UnknownDiscontinuity: + kCMIOSampleBufferNoDiscontinuities, + &buffer); + CFRelease(format); + CFRelease(imageBuffer); + + this->m_queue->enqueue(buffer); + this->m_pts = CMTimeAdd(this->m_pts, duration); + this->m_sequence++; + + if (this->m_queueAltered) + this->m_queueAltered(this->self->m_objectID, + buffer, + this->m_queueAlteredRefCon); +} + +void AkVCam::StreamPrivate::updateTestFrame() +{ + this->m_testFrameAdapted = this->applyAdjusts(this->m_testFrame); +} + +AkVCam::VideoFrame AkVCam::StreamPrivate::applyAdjusts(const VideoFrame &frame) +{ + VideoFormat format; + this->self->m_properties.getProperty(kCMIOStreamPropertyFormatDescription, + &format); + + FourCC fourcc = format.fourcc(); + int width = format.width(); + int height = format.height(); + + if (width * height > frame.format().width() * frame.format().height()) { + return frame.mirror(this->m_horizontalMirror, + this->m_verticalMirror) + .swapRgb(this->m_swapRgb) + .scaled(width, height, + this->m_scaling, + this->m_aspectRatio) + .convert(fourcc); + } + + return frame.scaled(width, height, + this->m_scaling, + this->m_aspectRatio) + .mirror(this->m_horizontalMirror, + this->m_verticalMirror) + .swapRgb(this->m_swapRgb) + .convert(fourcc); +} + +AkVCam::VideoFrame AkVCam::StreamPrivate::randomFrame() +{ + VideoFormat format; + this->self->m_properties.getProperty(kCMIOStreamPropertyFormatDescription, + &format); + VideoData data(format.size()); + static std::uniform_int_distribution distribution(std::numeric_limits::min(), + std::numeric_limits::max()); + static std::default_random_engine engine; + std::generate(data.begin(), data.end(), [] () { + return distribution(engine); + }); + + VideoFrame frame; + frame.format() = format; + frame.data() = data; + + return frame; +} diff --git a/cmio/VirtualCamera/src/stream.h b/cmio/VirtualCamera/src/stream.h new file mode 100644 index 0000000..390ddf1 --- /dev/null +++ b/cmio/VirtualCamera/src/stream.h @@ -0,0 +1,78 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef STREAM_H +#define STREAM_H + +#include "VCamUtils/src/fraction.h" +#include "VCamUtils/src/image/videoframetypes.h" +#include "VCamUtils/src/ipcbridge.h" +#include "object.h" +#include "queue.h" + +namespace AkVCam +{ + class StreamPrivate; + class Stream; + class VideoFrame; + typedef std::shared_ptr StreamPtr; + typedef Queue SampleBufferQueue; + typedef QueuePtr SampleBufferQueuePtr; + + class Stream: public Object + { + public: + Stream(bool registerObject=false, Object *m_parent=nullptr); + Stream(const Stream &other) = delete; + ~Stream(); + + OSStatus createObject(); + OSStatus registerObject(bool regist=true); + void setFormats(const std::vector &formats); + void setFormat(const VideoFormat &format); + void setFrameRate(const Fraction &frameRate); + bool start(); + void stop(); + bool running(); + + void serverStateChanged(IpcBridge::ServerState state); + void frameReady(const VideoFrame &frame); + void setBroadcasting(const std::string &broadcaster); + void setMirror(bool horizontalMirror, bool verticalMirror); + void setScaling(Scaling scaling); + void setAspectRatio(AspectRatio aspectRatio); + void setSwapRgb(bool swap); + + // Stream Interface + OSStatus copyBufferQueue(CMIODeviceStreamQueueAlteredProc queueAlteredProc, + void *queueAlteredRefCon, + CMSimpleQueueRef *queue); + OSStatus deckPlay(); + OSStatus deckStop(); + OSStatus deckJog(SInt32 speed); + OSStatus deckCueTo(Float64 frameNumber, Boolean playOnCue); + + private: + StreamPrivate *d; + + friend class StreamPrivate; + }; +} + +#endif // STREAM_H diff --git a/cmio/VirtualCamera/src/utils.cpp b/cmio/VirtualCamera/src/utils.cpp new file mode 100644 index 0000000..4ca472f --- /dev/null +++ b/cmio/VirtualCamera/src/utils.cpp @@ -0,0 +1,85 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include + +#include "utils.h" + +inline const std::map *formatsTable() +{ + static const std::map formatsTable { + {AkVCam::PixelFormatRGB32, kCMPixelFormat_32ARGB }, + {AkVCam::PixelFormatRGB24, kCMPixelFormat_24RGB }, + {AkVCam::PixelFormatRGB16, kCMPixelFormat_16LE565 }, + {AkVCam::PixelFormatRGB15, kCMPixelFormat_16LE555 }, + {AkVCam::PixelFormatUYVY , kCMPixelFormat_422YpCbCr8 }, + {AkVCam::PixelFormatYUY2 , kCMPixelFormat_422YpCbCr8_yuvs} + }; + + return &formatsTable; +} + +bool AkVCam::uuidEqual(const REFIID &uuid1, const CFUUIDRef uuid2) +{ + auto iid2 = CFUUIDGetUUIDBytes(uuid2); + auto puuid1 = reinterpret_cast(&uuid1); + auto puuid2 = reinterpret_cast(&iid2); + + for (int i = 0; i < 16; i++) + if (puuid1[i] != puuid2[i]) + return false; + + return true; +} + +std::string AkVCam::enumToString(UInt32 value) +{ + auto valueChr = reinterpret_cast(&value); + std::stringstream ss; + + for (int i = 3; i >= 0; i--) + if (valueChr[i] < 0) + ss << std::hex << valueChr[i]; + else if (valueChr[i] < 32) + ss << int(valueChr[i]); + else + ss << valueChr[i]; + + return "'" + ss.str() + "'"; +} + +FourCharCode AkVCam::formatToCM(PixelFormat format) +{ + for (auto &fmt: *formatsTable()) + if (fmt.first == format) + return fmt.second; + + return FourCharCode(0); +} + +AkVCam::PixelFormat AkVCam::formatFromCM(FourCharCode format) +{ + for (auto &fmt: *formatsTable()) + if (fmt.second == format) + return fmt.first; + + return PixelFormat(0); +} diff --git a/cmio/VirtualCamera/src/utils.h b/cmio/VirtualCamera/src/utils.h new file mode 100644 index 0000000..a082b4b --- /dev/null +++ b/cmio/VirtualCamera/src/utils.h @@ -0,0 +1,37 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + +#include "VCamUtils/src/image/videoformattypes.h" + +namespace AkVCam +{ + bool uuidEqual(const REFIID &uuid1, const CFUUIDRef uuid2); + std::string enumToString(UInt32 value); + FourCharCode formatToCM(PixelFormat format); + PixelFormat formatFromCM(FourCharCode format); +} + +#endif // UTILS_H diff --git a/cmio/cmio.pri b/cmio/cmio.pri new file mode 100644 index 0000000..64ed962 --- /dev/null +++ b/cmio/cmio.pri @@ -0,0 +1,44 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +isEmpty(CMIO_PLUGINS_DAL_PATH): + CMIO_PLUGINS_DAL_PATH = /Library/CoreMediaIO/Plug-Ins/DAL +isEmpty(CMIO_DAEMONS_PATH): + CMIO_DAEMONS_PATH = ~/Library/LaunchAgents +isEmpty(CMIO_PLUGIN_NAME): + CMIO_PLUGIN_NAME = AkVirtualCamera +isEmpty(CMIO_PLUGIN_ASSISTANT_NAME): + CMIO_PLUGIN_ASSISTANT_NAME = AkVCamAssistant +isEmpty(CMIO_PLUGIN_DEVICE_PREFIX): + CMIO_PLUGIN_DEVICE_PREFIX = /akvcam/video +isEmpty(CMIO_PLUGIN_VENDOR): + CMIO_PLUGIN_VENDOR = "Webcamoid Project" +isEmpty(CMIO_PLUGIN_PRODUCT): + CMIO_PLUGIN_PRODUCT = $$CMIO_PLUGIN_NAME + +DEFINES += \ + CMIO_PLUGINS_DAL_PATH=\"\\\"$$CMIO_PLUGINS_DAL_PATH\\\"\" \ + CMIO_PLUGINS_DAL_PATH_L=\"L\\\"$$CMIO_PLUGINS_DAL_PATH\\\"\" \ + CMIO_DAEMONS_PATH=\"\\\"$$CMIO_DAEMONS_PATH\\\"\" \ + CMIO_PLUGIN_NAME=\"\\\"$$CMIO_PLUGIN_NAME\\\"\" \ + CMIO_PLUGIN_NAME_L=\"L\\\"$$CMIO_PLUGIN_NAME\\\"\" \ + CMIO_PLUGIN_ASSISTANT_NAME=\"\\\"$$CMIO_PLUGIN_ASSISTANT_NAME\\\"\" \ + CMIO_PLUGIN_ASSISTANT_NAME_L=\"L\\\"$$CMIO_PLUGIN_ASSISTANT_NAME\\\"\" \ + CMIO_PLUGIN_DEVICE_PREFIX=\"\\\"$$CMIO_PLUGIN_DEVICE_PREFIX\\\"\" \ + CMIO_PLUGIN_VENDOR=\"\\\"$$CMIO_PLUGIN_VENDOR\\\"\" \ + CMIO_PLUGIN_PRODUCT=\"\\\"$$CMIO_PLUGIN_PRODUCT\\\"\" diff --git a/cmio/cmio.pro b/cmio/cmio.pro new file mode 100644 index 0000000..d0c4993 --- /dev/null +++ b/cmio/cmio.pro @@ -0,0 +1,25 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = \ + VCamIPC \ + Assistant \ + VirtualCamera diff --git a/cmio/pspec.json b/cmio/pspec.json new file mode 100644 index 0000000..03145bc --- /dev/null +++ b/cmio/pspec.json @@ -0,0 +1,4 @@ +{ + "pluginType": "Ak.SubModule", + "type": "output" +} diff --git a/commons.pri b/commons.pri new file mode 100644 index 0000000..83bc620 --- /dev/null +++ b/commons.pri @@ -0,0 +1,189 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +COMMONS_APPNAME = "AkVirtualCamera" +COMMONS_TARGET = $$lower($${COMMONS_APPNAME}) +COMMONS_TARGET = $$replace(COMMONS_TARGET, lib, "") + +VER_MAJ = 9 +VER_MIN = 0 +VER_PAT = 0 +VERSION = $${VER_MAJ}.$${VER_MIN}.$${VER_PAT} + +isEmpty(BUNDLENAME): BUNDLENAME = $${COMMONS_APPNAME} + +win32 { + host_name = $$lower($$QMAKE_HOST.os) + + !isEmpty(ProgramW6432) { + DEFAULT_PREFIX = $(ProgramW6432)/$${BUNDLENAME} + } else: !isEmpty(ProgramFiles) { + DEFAULT_PREFIX = $(ProgramFiles)/$${BUNDLENAME} + } else: contains(host_name, linux) { + DEFAULT_PREFIX = /$${BUNDLENAME} + } else { + DEFAULT_PREFIX = C:/$${BUNDLENAME} + } +} else: macx: isEmpty(NOAPPBUNDLE) { + DEFAULT_PREFIX = /Applications +} else { + DEFAULT_PREFIX = $$[QT_INSTALL_PREFIX] +} + +isEmpty(PREFIX): PREFIX = $${DEFAULT_PREFIX} +isEmpty(EXECPREFIX) { + macx: isEmpty(NOAPPBUNDLE) { + EXECPREFIX = $${PREFIX}/$${BUNDLENAME}.app/Contents + } else { + EXECPREFIX = $${PREFIX} + } +} +isEmpty(BINDIR) { + macx: isEmpty(NOAPPBUNDLE) { + BINDIR = $${EXECPREFIX}/MacOS + } else { + BINDIR = $${EXECPREFIX}/bin + } +} +isEmpty(SBINDIR): SBINDIR = $${EXECPREFIX}/sbin +isEmpty(LIBEXECDIR): LIBEXECDIR = $${EXECPREFIX}/libexec +isEmpty(DATAROOTDIR) { + macx: isEmpty(NOAPPBUNDLE) { + DATAROOTDIR = $${EXECPREFIX}/Resources + } else { + DATAROOTDIR = $${PREFIX}/share + } +} +isEmpty(DATDIR): DATDIR = $${DATAROOTDIR}/$${COMMONS_TARGET} +isEmpty(SYSCONFDIR): SYSCONFDIR = $${PREFIX}/etc +isEmpty(SHAREDSTATEDIR): SHAREDSTATEDIR = $${PREFIX}/com +isEmpty(LOCALSTATEDIR): LOCALSTATEDIR = $${PREFIX}/var +isEmpty(INCLUDEDIR): INCLUDEDIR = $${PREFIX}/include +isEmpty(DOCDIR): DOCDIR = $${DATAROOTDIR}/doc/$${COMMONS_TARGET} +isEmpty(INFODIR): INFODIR = $${DATAROOTDIR}/info +isEmpty(HTMLDIR): HTMLDIR = $${DOCDIR}/html +isEmpty(DVIDIR): DVIDIR = $${DOCDIR}/dvi +isEmpty(PDFDIR): PDFDIR = $${DOCDIR}/pdf +isEmpty(PSDIR): PSDIR = $${DOCDIR}/ps +isEmpty(LIBDIR) { + macx: isEmpty(NOAPPBUNDLE) { + LIBDIR = $${EXECPREFIX}/Frameworks + } else { + INSTALL_LIBS = $$[QT_INSTALL_LIBS] + LIBDIR = $$replace(INSTALL_LIBS, $$[QT_INSTALL_PREFIX], $${EXECPREFIX}) + } +} +isEmpty(LOCALEDIR): LOCALEDIR = $${DATAROOTDIR}/locale +isEmpty(MANDIR): MANDIR = $${DATAROOTDIR}/man +isEmpty(LICENSEDIR): LICENSEDIR = $${DATAROOTDIR}/licenses/$${COMMONS_TARGET} +isEmpty(LOCALDIR): LOCALDIR = $${PREFIX}/local +isEmpty(LOCALLIBDIR): LOCALLIBDIR = $${LOCALDIR}/lib +isEmpty(INSTALLQMLDIR) { + macx: isEmpty(NOAPPBUNDLE) { + INSTALLQMLDIR = $${DATAROOTDIR}/qml + } else { + INSTALL_QML = $$[QT_INSTALL_QML] + INSTALLQMLDIR = $$replace(INSTALL_QML, $$[QT_INSTALL_LIBS], $${LIBDIR}) + } +} +isEmpty(INSTALLPLUGINSDIR) { + macx: isEmpty(NOAPPBUNDLE) { + INSTALLPLUGINSDIR = $${EXECPREFIX}/Plugins/$${COMMONS_TARGET} + } else { + INSTALLPLUGINSDIR = $${LIBDIR}/$${COMMONS_TARGET} + } +} +isEmpty(JARDIR): JARDIR = $${EXECPREFIX}/libs + +macx: !isEmpty(NOAPPBUNDLE): DEFINES += NOAPPBUNDLE + +DEFINES += \ + COMMONS_APPNAME=\"\\\"$$COMMONS_APPNAME\\\"\" \ + COMMONS_TARGET=\"\\\"$$COMMONS_TARGET\\\"\" \ + COMMONS_VER_MAJ=\"\\\"$$VER_MAJ\\\"\" \ + COMMONS_VERSION=\"\\\"$$VERSION\\\"\" \ + PREFIX=\"\\\"$$PREFIX\\\"\" \ + EXECPREFIX=\"\\\"$$EXECPREFIX\\\"\" \ + BINDIR=\"\\\"$$BINDIR\\\"\" \ + SBINDIR=\"\\\"$$SBINDIR\\\"\" \ + LIBEXECDIR=\"\\\"$$LIBEXECDIR\\\"\" \ + DATAROOTDIR=\"\\\"$$DATAROOTDIR\\\"\" \ + DATDIR=\"\\\"$$DATDIR\\\"\" \ + SYSCONFDIR=\"\\\"$$SYSCONFDIR\\\"\" \ + SHAREDSTATEDIR=\"\\\"$$SHAREDSTATEDIR\\\"\" \ + LOCALSTATEDIR=\"\\\"$$LOCALSTATEDIR\\\"\" \ + INCLUDEDIR=\"\\\"$$INCLUDEDIR\\\"\" \ + DOCDIR=\"\\\"$$DOCDIR\\\"\" \ + INFODIR=\"\\\"$$INFODIR\\\"\" \ + HTMLDIR=\"\\\"$$HTMLDIR\\\"\" \ + DVIDIR=\"\\\"$$DVIDIR\\\"\" \ + PDFDIR=\"\\\"$$PDFDIR\\\"\" \ + PSDIR=\"\\\"$$PSDIR\\\"\" \ + LIBDIR=\"\\\"$$LIBDIR\\\"\" \ + LOCALEDIR=\"\\\"$$LOCALEDIR\\\"\" \ + MANDIR=\"\\\"$$MANDIR\\\"\" \ + LICENSEDIR=\"\\\"$$LICENSEDIR\\\"\" \ + LOCALDIR=\"\\\"$$LOCALDIR\\\"\" \ + LOCALLIBDIR=\"\\\"$$LOCALLIBDIR\\\"\" \ + INSTALLQMLDIR=\"\\\"$$INSTALLQMLDIR\\\"\" \ + INSTALLPLUGINSDIR=\"\\\"$$INSTALLPLUGINSDIR\\\"\" + +msvc { + TARGET_ARCH = $${QMAKE_TARGET.arch} + TARGET_ARCH = $$basename(TARGET_ARCH) + TARGET_ARCH = $$replace(TARGET_ARCH, x64, x86_64) +} else { + TARGET_ARCH = $$system($${QMAKE_CXX} -dumpmachine) + TARGET_ARCH = $$split(TARGET_ARCH, -) + TARGET_ARCH = $$first(TARGET_ARCH) +} + +COMPILER = $$basename(QMAKE_CXX) +COMPILER = $$replace(COMPILER, \+\+, pp) +COMPILER = $$join(COMPILER, _) + +CONFIG(debug, debug|release) { + COMMONS_BUILD_PATH = debug/$${COMPILER}/$${TARGET_ARCH} + DEFINES += QT_DEBUG +} else { + COMMONS_BUILD_PATH = release/$$COMPILER/$${TARGET_ARCH} +} + +android: COMMONS_BUILD_PATH = $${COMMONS_BUILD_PATH}/$${ANDROID_PLATFORM} + +BIN_DIR = $${COMMONS_BUILD_PATH}/bin +MOC_DIR = $${COMMONS_BUILD_PATH}/moc +OBJECTS_DIR = $${COMMONS_BUILD_PATH}/obj +RCC_DIR = $${COMMONS_BUILD_PATH}/rcc +UI_DIR = $${COMMONS_BUILD_PATH}/ui + +win32 { + CONFIG += skip_target_version_ext + !isEmpty(STATIC_BUILD):!isEqual(STATIC_BUILD, 0) { + win32-g++: QMAKE_LFLAGS = -static-libgcc -static-libstdc++ + } +} + +# Enable c++11 support in all platforms +!CONFIG(c++11): CONFIG += c++11 + +!win32: !macx { + error("This driver only works in Mac an Windows. For Linux check 'akvcam' instead.") +} + +CMD_SEP = $$escape_expand(\n\t) diff --git a/dshow/Assistant/Assistant.pro b/dshow/Assistant/Assistant.pro new file mode 100644 index 0000000..6d9a82f --- /dev/null +++ b/dshow/Assistant/Assistant.pro @@ -0,0 +1,70 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../dshow.pri) +include(../../VCamUtils/VCamUtils.pri) + +TEMPLATE = app +CONFIG += console link_prl +CONFIG -= app_bundle +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = $${DSHOW_PLUGIN_ASSISTANT_NAME} + +SOURCES += \ + src/main.cpp \ + src/service.cpp + +HEADERS += \ + src/service.h + +INCLUDEPATH += \ + .. \ + ../.. + +LIBS += \ + -L$${OUT_PWD}/../PlatformUtils/$${BIN_DIR} -lPlatformUtils \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -ladvapi32 \ + -lgdi32 \ + -lole32 \ + -lshell32 \ + -lstrmiids \ + -luuid + +win32-g++: LIBS += -lssp + +isEmpty(STATIC_BUILD) | isEqual(STATIC_BUILD, 0) { + win32-g++: QMAKE_LFLAGS = -static -static-libgcc -static-libstdc++ +} + +QMAKE_POST_LINK = \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/../VirtualCamera/$${DSHOW_PLUGIN_NAME}.plugin/$$normalizedArch(TARGET_ARCH))) $${CMD_SEP} \ + $(COPY) $$shell_path($${OUT_PWD}/$${BIN_DIR}/$${DSHOW_PLUGIN_ASSISTANT_NAME}.exe) $$shell_path($${OUT_PWD}/../VirtualCamera/$${DSHOW_PLUGIN_NAME}.plugin/$$normalizedArch(TARGET_ARCH)) diff --git a/dshow/Assistant/src/main.cpp b/dshow/Assistant/src/main.cpp new file mode 100644 index 0000000..c2de344 --- /dev/null +++ b/dshow/Assistant/src/main.cpp @@ -0,0 +1,67 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include + +#include "service.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/logger/logger.h" + +int main(int argc, char **argv) +{ + auto temp = AkVCam::tempPath(); + AkLoggerStart(std::string(temp.begin(), temp.end()) + + "\\" DSHOW_PLUGIN_ASSISTANT_NAME, "log"); + AkVCam::Service service; + + if (argc > 1) { + if (!strcmp(argv[1], "-i") || !strcmp(argv[1], "--install")) { + return service.install()? EXIT_SUCCESS: EXIT_FAILURE; + } else if (!strcmp(argv[1], "-u") || !strcmp(argv[1], "--uninstall")) { + service.uninstall(); + + return EXIT_SUCCESS; + } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--debug")) { + service.debug(); + + return EXIT_SUCCESS; + } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + service.showHelp(argc, argv); + + return EXIT_SUCCESS; + } + } + + AkLoggerLog("Setting service dispatcher"); + + WCHAR serviceName[] = TEXT(DSHOW_PLUGIN_ASSISTANT_NAME); + SERVICE_TABLE_ENTRY serviceTable[] = { + {serviceName, serviceMain}, + {nullptr , nullptr } + }; + + if (!StartServiceCtrlDispatcher(serviceTable)) { + AkLoggerLog("Service dispatcher failed"); + + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/dshow/Assistant/src/service.cpp b/dshow/Assistant/src/service.cpp new file mode 100644 index 0000000..51958c2 --- /dev/null +++ b/dshow/Assistant/src/service.cpp @@ -0,0 +1,925 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "service.h" +#include "PlatformUtils/src/messageserver.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/timer.h" +#include "VCamUtils/src/logger/logger.h" + +#define AkServiceLogMethod() \ + AkLoggerLog("Service::", __FUNCTION__, "()") + +#define AkServicePrivateLogMethod() \ + AkLoggerLog("ServicePrivate::", __FUNCTION__, "()") + +namespace AkVCam +{ + struct AssistantDevice + { + std::string broadcaster; + std::vector listeners; + bool horizontalMirror; + bool verticalMirror; + Scaling scaling; + AspectRatio aspectRatio; + bool swapRgb; + + AssistantDevice(): + horizontalMirror(false), + verticalMirror(false), + scaling(ScalingFast), + aspectRatio(AspectRatioIgnore), + swapRgb(false) + { + } + }; + + typedef std::map AssistantPeers; + typedef std::map DeviceConfigs; + + class ServicePrivate + { + public: + SERVICE_STATUS m_status; + SERVICE_STATUS_HANDLE m_statusHandler; + MessageServer m_messageServer; + AssistantPeers m_servers; + AssistantPeers m_clients; + DeviceConfigs m_deviceConfigs; + Timer m_timer; + std::mutex m_peerMutex; + + ServicePrivate(); + static void stateChanged(void *userData, + MessageServer::State state); + static void checkPeers(void *userData); + void sendStatus(DWORD currentState, DWORD exitCode, DWORD wait); + inline static uint64_t id(); + void removePortByName(const std::string &portName); + void releaseDevicesFromPeer(const std::string &portName); + void requestPort(Message *message); + void addPort(Message *message); + void removePort(Message *message); + void setBroadCasting(Message *message); + void setMirroring(Message *message); + void setScaling(Message *message); + void setAspectRatio(Message *message); + void setSwapRgb(Message *message); + void frameReady(Message *message); + void listeners(Message *message); + void listener(Message *message); + void broadcasting(Message *message); + void mirroring(Message *message); + void scaling(Message *message); + void aspectRatio(Message *message); + void swapRgb(Message *message); + void listenerAdd(Message *message); + void listenerRemove(Message *message); + }; + + GLOBAL_STATIC(ServicePrivate, servicePrivate) +} + +DWORD WINAPI controlHandler(DWORD control, + DWORD eventType, + LPVOID eventData, + LPVOID context); +BOOL WINAPI controlDebugHandler(DWORD control); + +AkVCam::Service::Service() +{ +} + +AkVCam::Service::~Service() +{ +} + +BOOL AkVCam::Service::install() +{ + AkServiceLogMethod(); + WCHAR fileName[MAX_PATH]; + + if (!GetModuleFileName(nullptr, fileName, MAX_PATH)) { + AkLoggerLog("Can't read module file name"); + + return false; + } + + auto scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); + + if (!scManager) { + AkLoggerLog("Can't open SCManager"); + + return false; + } + + auto service = + CreateService(scManager, + TEXT(DSHOW_PLUGIN_ASSISTANT_NAME), + TEXT(DSHOW_PLUGIN_ASSISTANT_DESCRIPTION), + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + fileName, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + + if (!service) { + AkLoggerLog("Can't create service"); + CloseServiceHandle(scManager); + + return false; + } + + // Add detailed description to the service. + SERVICE_DESCRIPTION serviceDescription; + WCHAR description[] = TEXT(DSHOW_PLUGIN_DESCRIPTION_EXT); + serviceDescription.lpDescription = description; + auto result = + ChangeServiceConfig2(service, + SERVICE_CONFIG_DESCRIPTION, + &serviceDescription); + + // Configure the service so it will restart if fail. + WCHAR rebootMsg[] = L"Service failed restarting..."; + + std::vector actions { + {SC_ACTION_RESTART, 5000} + }; + + SERVICE_FAILURE_ACTIONS failureActions { + INFINITE, + rebootMsg, + nullptr, + DWORD(actions.size()), + actions.data() + }; + + result = + ChangeServiceConfig2(service, + SERVICE_CONFIG_FAILURE_ACTIONS, + &failureActions); + + // Run the service + StartService(service, 0, nullptr); + CloseServiceHandle(service); + CloseServiceHandle(scManager); + + return result; +} + +void AkVCam::Service::uninstall() +{ + AkServiceLogMethod(); + auto scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); + + if (!scManager) { + AkLoggerLog("Can't open SCManager"); + + return; + } + + auto sevice = OpenService(scManager, + TEXT(DSHOW_PLUGIN_ASSISTANT_NAME), + SERVICE_ALL_ACCESS); + + if (sevice) { + if (ControlService(sevice, + SERVICE_CONTROL_STOP, + &servicePrivate()->m_status)) { + AkLoggerLog("Stopping service"); + + do { + Sleep(1000); + QueryServiceStatus(sevice, &servicePrivate()->m_status); + } while(servicePrivate()->m_status.dwCurrentState != SERVICE_STOPPED); + } + + if (!DeleteService(sevice)) { + AkLoggerLog("Delete service failed"); + } + + CloseServiceHandle(sevice); + } else { + AkLoggerLog("Can't open service"); + } + + CloseServiceHandle(scManager); +} + +void AkVCam::Service::debug() +{ + AkServiceLogMethod(); + SetConsoleCtrlHandler(controlDebugHandler, TRUE); + servicePrivate()->m_messageServer.start(true); +} + +void AkVCam::Service::showHelp(int argc, char **argv) +{ + AkServiceLogMethod(); + UNUSED(argc) + + auto programName = strrchr(argv[0], '\\'); + + if (!programName) + programName = strrchr(argv[0], '/'); + + if (!programName) + programName = argv[0]; + else + programName++; + + std::cout << "Usage: " << programName << " [options]" << std::endl; + std::cout << std::endl; + std::cout << "Webcamoid virtual camera server." << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << std::endl; + std::cout << "\t-i, --install\tInstall the service." << std::endl; + std::cout << "\t-u, --uninstall\tUnistall the service." << std::endl; + std::cout << "\t-d, --debug\tDebug the service." << std::endl; + std::cout << "\t-h, --help\tShow this help." << std::endl; +} + +AkVCam::ServicePrivate::ServicePrivate() +{ + AkServicePrivateLogMethod(); + + this->m_status = { + SERVICE_WIN32_OWN_PROCESS, + SERVICE_STOPPED, + 0, + NO_ERROR, + NO_ERROR, + 0, + 0 + }; + this->m_statusHandler = nullptr; + this->m_messageServer.setPipeName(L"\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME_L); + this->m_messageServer.setHandlers({ + {AKVCAM_ASSISTANT_MSG_FRAME_READY , AKVCAM_BIND_FUNC(ServicePrivate::frameReady) }, + {AKVCAM_ASSISTANT_MSG_REQUEST_PORT , AKVCAM_BIND_FUNC(ServicePrivate::requestPort) }, + {AKVCAM_ASSISTANT_MSG_ADD_PORT , AKVCAM_BIND_FUNC(ServicePrivate::addPort) }, + {AKVCAM_ASSISTANT_MSG_REMOVE_PORT , AKVCAM_BIND_FUNC(ServicePrivate::removePort) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD , AKVCAM_BIND_FUNC(ServicePrivate::listenerAdd) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE, AKVCAM_BIND_FUNC(ServicePrivate::listenerRemove) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENERS , AKVCAM_BIND_FUNC(ServicePrivate::listeners) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER , AKVCAM_BIND_FUNC(ServicePrivate::listener) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING , AKVCAM_BIND_FUNC(ServicePrivate::broadcasting) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING, AKVCAM_BIND_FUNC(ServicePrivate::setBroadCasting)}, + {AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING , AKVCAM_BIND_FUNC(ServicePrivate::mirroring) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING , AKVCAM_BIND_FUNC(ServicePrivate::setMirroring) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SCALING , AKVCAM_BIND_FUNC(ServicePrivate::scaling) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING , AKVCAM_BIND_FUNC(ServicePrivate::setScaling) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_ASPECTRATIO , AKVCAM_BIND_FUNC(ServicePrivate::aspectRatio) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO , AKVCAM_BIND_FUNC(ServicePrivate::setAspectRatio) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB , AKVCAM_BIND_FUNC(ServicePrivate::swapRgb) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB , AKVCAM_BIND_FUNC(ServicePrivate::setSwapRgb) }, + }); + this->m_timer.setInterval(60000); + this->m_timer.connectTimeout(this, &ServicePrivate::checkPeers); +} + +void AkVCam::ServicePrivate::stateChanged(void *userData, + MessageServer::State state) +{ + UNUSED(userData) + + switch (state) { + case MessageServer::StateAboutToStart: + AkVCam::servicePrivate()->sendStatus(SERVICE_START_PENDING, NO_ERROR, 3000); + break; + + case MessageServer::StateStarted: + AkVCam::servicePrivate()->sendStatus(SERVICE_RUNNING, NO_ERROR, 0); + break; + + case MessageServer::StateAboutToStop: + AkVCam::servicePrivate()->sendStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + break; + + case MessageServer::StateStopped: + AkVCam::servicePrivate()->sendStatus(SERVICE_STOPPED, NO_ERROR, 0); + break; + } +} + +void AkVCam::ServicePrivate::checkPeers(void *userData) +{ + auto self = reinterpret_cast(userData); + std::vector removePorts; + + self->m_peerMutex.lock(); + std::vector allPeers { + &self->m_clients, + &self->m_servers + }; + + for (auto peers: allPeers) + for (auto &peer: *peers) { + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_ISALIVE; + message.dataSize = sizeof(MsgIsAlive); + MessageServer::sendMessage(peer.second, &message); + auto requestData = messageData(&message); + + if (!requestData->alive) + removePorts.push_back(peer.first); + } + + self->m_peerMutex.unlock(); + + for (auto &port: removePorts) { + AkLoggerLog(port, " died, removing..."); + self->removePortByName(port); + } +} + +void AkVCam::ServicePrivate::sendStatus(DWORD currentState, + DWORD exitCode, + DWORD wait) +{ + AkServicePrivateLogMethod(); + + this->m_status.dwControlsAccepted = + currentState == SERVICE_START_PENDING? 0: SERVICE_ACCEPT_STOP; + this->m_status.dwCurrentState = currentState; + this->m_status.dwWin32ExitCode = exitCode; + this->m_status.dwWaitHint = wait; + + if (currentState == SERVICE_RUNNING || currentState == SERVICE_STOPPED) + this->m_status.dwCheckPoint = 0; + else + this->m_status.dwCheckPoint++; + + SetServiceStatus(this->m_statusHandler, &this->m_status); +} + +uint64_t AkVCam::ServicePrivate::id() +{ + static uint64_t id = 0; + + return id++; +} + +void AkVCam::ServicePrivate::removePortByName(const std::string &portName) +{ + AkServicePrivateLogMethod(); + AkLoggerLog("Port: ", portName); + + this->m_peerMutex.lock(); + + std::vector allPeers { + &this->m_clients, + &this->m_servers + }; + + bool breakLoop = false; + + for (auto peers: allPeers) { + for (auto &peer: *peers) + if (peer.first == portName) { + peers->erase(portName); + breakLoop = true; + + break; + } + + if (breakLoop) + break; + } + + bool peersEmpty = this->m_servers.empty() && this->m_clients.empty(); + this->m_peerMutex.unlock(); + + if (peersEmpty) + this->m_timer.stop(); + + this->releaseDevicesFromPeer(portName); +} + +void AkVCam::ServicePrivate::releaseDevicesFromPeer(const std::string &portName) +{ + for (auto &config: this->m_deviceConfigs) + if (config.second.broadcaster == portName) { + config.second.broadcaster.clear(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING; + message.dataSize = sizeof(MsgBroadcasting); + auto data = messageData(&message); + memcpy(data->device, + config.first.c_str(), + (std::min)(config.first.size(), MAX_STRING)); + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) + MessageServer::sendMessage(client.second, &message); + + this->m_peerMutex.unlock(); + } else { + auto it = std::find(config.second.listeners.begin(), + config.second.listeners.end(), + portName); + + if (it != config.second.listeners.end()) + config.second.listeners.erase(it); + } +} + +void AkVCam::ServicePrivate::requestPort(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + + auto data = messageData(message); + std::string portName = data->client? + AKVCAM_ASSISTANT_CLIENT_NAME: + AKVCAM_ASSISTANT_SERVER_NAME; + portName += std::to_string(this->id()); + + AkLoggerLog("Returning Port: ", portName); + memcpy(data->port, + portName.c_str(), + (std::min)(portName.size(), MAX_STRING)); +} + +void AkVCam::ServicePrivate::addPort(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + + auto data = messageData(message); + std::string portName(data->port); + std::string pipeName(data->pipeName); + bool ok = true; + + this->m_peerMutex.lock(); + AssistantPeers *peers; + + if (portName.find(AKVCAM_ASSISTANT_CLIENT_NAME) != std::string::npos) + peers = &this->m_clients; + else + peers = &this->m_servers; + + for (auto &peer: *peers) + if (peer.first == portName) { + ok = false; + + break; + } + + if (ok) { + AkLoggerLog("Adding Peer: ", portName); + (*peers)[portName] = pipeName; + } + + size_t nPeers = this->m_servers.size() + this->m_clients.size(); + + this->m_peerMutex.unlock(); + + if (ok && nPeers == 1) + this->m_timer.start(); + + data->status = ok; +} + +void AkVCam::ServicePrivate::removePort(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + + auto data = messageData(message); + this->removePortByName(data->port); +} + +void AkVCam::ServicePrivate::setBroadCasting(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + std::string broadcaster(data->broadcaster); + data->status = false; + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + if (this->m_deviceConfigs[deviceId].broadcaster == broadcaster) + return; + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Broadcaster: ", broadcaster); + this->m_deviceConfigs[deviceId].broadcaster = broadcaster; + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); +} + +void AkVCam::ServicePrivate::setMirroring(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + data->status = false; + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + if (this->m_deviceConfigs[deviceId].horizontalMirror == data->hmirror + && this->m_deviceConfigs[deviceId].verticalMirror == data->vmirror) + return; + + this->m_deviceConfigs[deviceId].horizontalMirror = data->hmirror; + this->m_deviceConfigs[deviceId].verticalMirror = data->vmirror; + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); +} + +void AkVCam::ServicePrivate::setScaling(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + data->status = false; + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + if (this->m_deviceConfigs[deviceId].scaling == data->scaling) + return; + + this->m_deviceConfigs[deviceId].scaling = data->scaling; + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); +} + +void AkVCam::ServicePrivate::setAspectRatio(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + data->status = false; + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + if (this->m_deviceConfigs[deviceId].aspectRatio == data->aspect) + return; + + this->m_deviceConfigs[deviceId].aspectRatio = data->aspect; + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); +} + +void AkVCam::ServicePrivate::setSwapRgb(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + data->status = false; + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + if (this->m_deviceConfigs[deviceId].swapRgb == data->swap) + return; + + this->m_deviceConfigs[deviceId].swapRgb = data->swap; + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); +} + +void AkVCam::ServicePrivate::frameReady(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) + MessageServer::sendMessage(client.second, message); + + this->m_peerMutex.unlock(); +} + +void AkVCam::ServicePrivate::listeners(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + data->nlistener = this->m_deviceConfigs[deviceId].listeners.size(); + + if (data->nlistener > 0) { + memcpy(data->listener, + this->m_deviceConfigs[deviceId].listeners[0].c_str(), + std::min(this->m_deviceConfigs[deviceId].listeners[0].size(), + MAX_STRING)); + } + + data->status = true; +} + +void AkVCam::ServicePrivate::listener(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + auto nlistener = this->m_deviceConfigs[deviceId].listeners.size(); + + if (data->nlistener >= nlistener) { + data->status = false; + + return; + } + + memcpy(data->listener, + this->m_deviceConfigs[deviceId].listeners[data->nlistener].c_str(), + std::min(this->m_deviceConfigs[deviceId].listeners[data->nlistener].size(), + MAX_STRING)); + + data->status = true; +} + +void AkVCam::ServicePrivate::broadcasting(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + memcpy(data->broadcaster, + this->m_deviceConfigs[deviceId].broadcaster.c_str(), + std::min(this->m_deviceConfigs[deviceId].broadcaster.size(), + MAX_STRING)); + data->status = true; +} + +void AkVCam::ServicePrivate::mirroring(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + data->hmirror = this->m_deviceConfigs[deviceId].horizontalMirror; + data->vmirror = this->m_deviceConfigs[deviceId].verticalMirror; + data->status = true; +} + +void AkVCam::ServicePrivate::scaling(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + data->scaling = this->m_deviceConfigs[deviceId].scaling; + data->status = true; +} + +void AkVCam::ServicePrivate::aspectRatio(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + data->aspect = this->m_deviceConfigs[deviceId].aspectRatio; + data->status = true; +} + +void AkVCam::ServicePrivate::swapRgb(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + data->swap = this->m_deviceConfigs[deviceId].swapRgb; + data->status = true; +} + +void AkVCam::ServicePrivate::listenerAdd(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + auto &listeners = this->m_deviceConfigs[deviceId].listeners; + std::string listener(data->listener); + auto it = std::find(listeners.begin(), listeners.end(), listener); + + if (it == listeners.end()) { + listeners.push_back(listener); + data->nlistener = listeners.size(); + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); + } else { + data->nlistener = listeners.size(); + data->status = false; + } +} + +void AkVCam::ServicePrivate::listenerRemove(AkVCam::Message *message) +{ + AkServicePrivateLogMethod(); + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_deviceConfigs.count(deviceId) < 1) + this->m_deviceConfigs[deviceId] = {}; + + auto &listeners = this->m_deviceConfigs[deviceId].listeners; + std::string listener(data->listener); + auto it = std::find(listeners.begin(), listeners.end(), listener); + + if (it != listeners.end()) { + listeners.erase(it); + data->nlistener = listeners.size(); + data->status = true; + + this->m_peerMutex.lock(); + + for (auto &client: this->m_clients) { + Message msg(message); + MessageServer::sendMessage(client.second, &msg); + } + + this->m_peerMutex.unlock(); + } else { + data->nlistener = listeners.size(); + data->status = false; + } +} + +DWORD WINAPI controlHandler(DWORD control, + DWORD eventType, + LPVOID eventData, + LPVOID context) +{ + UNUSED(eventType) + UNUSED(eventData) + UNUSED(context) + AkLoggerLog("controlHandler()"); + + DWORD result = ERROR_CALL_NOT_IMPLEMENTED; + + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + AkVCam::servicePrivate()->sendStatus(SERVICE_STOP_PENDING, + NO_ERROR, + 0); + AkVCam::servicePrivate()->m_messageServer.stop(); + result = NO_ERROR; + + break; + + case SERVICE_CONTROL_INTERROGATE: + result = NO_ERROR; + + break; + + default: + break; + } + + auto state = AkVCam::servicePrivate()->m_status.dwCurrentState; + AkVCam::servicePrivate()->sendStatus(state, NO_ERROR, 0); + + return result; +} + +BOOL WINAPI controlDebugHandler(DWORD control) +{ + AkLoggerLog("controlDebugHandler()"); + + if (control == CTRL_BREAK_EVENT || control == CTRL_C_EVENT) { + AkVCam::servicePrivate()->m_messageServer.stop(); + + return TRUE; + } + + return FALSE; +} + +void WINAPI serviceMain(DWORD dwArgc, LPTSTR *lpszArgv) +{ + UNUSED(dwArgc) + UNUSED(lpszArgv) + AkLoggerLog("serviceMain()"); + AkLoggerLog("Setting service control handler"); + + AkVCam::servicePrivate()->m_statusHandler = + RegisterServiceCtrlHandlerEx(TEXT(DSHOW_PLUGIN_ASSISTANT_NAME), + controlHandler, + nullptr); + + if (!AkVCam::servicePrivate()->m_statusHandler) + return; + + AkVCam::servicePrivate()->sendStatus(SERVICE_START_PENDING, NO_ERROR, 3000); + + AkLoggerLog("Setting up service"); + AkVCam::servicePrivate()->m_messageServer + .connectStateChanged(AkVCam::servicePrivate(), + &AkVCam::ServicePrivate::stateChanged); + AkVCam::servicePrivate()->m_messageServer.start(true); +} diff --git a/dshow/Assistant/src/service.h b/dshow/Assistant/src/service.h new file mode 100644 index 0000000..5a1245d --- /dev/null +++ b/dshow/Assistant/src/service.h @@ -0,0 +1,42 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef SERVICE_H +#define SERVICE_H + +#include + +namespace AkVCam +{ + class Service + { + public: + Service(); + ~Service(); + + BOOL install(); + void uninstall(); + void debug(); + void showHelp(int argc, char **argv); + }; +} + +void WINAPI serviceMain(DWORD dwArgc, LPTSTR *lpszArgv); + +#endif // SERVICE_H diff --git a/dshow/PlatformUtils/PlatformUtils.pro b/dshow/PlatformUtils/PlatformUtils.pro new file mode 100644 index 0000000..c93e097 --- /dev/null +++ b/dshow/PlatformUtils/PlatformUtils.pro @@ -0,0 +1,63 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../dshow.pri) + +CONFIG += \ + staticlib \ + create_prl \ + no_install_prl +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = PlatformUtils + +TEMPLATE = lib + +LIBS = \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -ladvapi32 \ + -lkernel32 \ + -lgdi32 \ + -lshell32 + +SOURCES = \ + src/messageserver.cpp \ + src/mutex.cpp \ + src/utils.cpp \ + src/sharedmemory.cpp + +HEADERS = \ + src/messagecommons.h \ + src/messageserver.h \ + src/mutex.h \ + src/utils.h \ + src/sharedmemory.h + +INCLUDEPATH += ../.. diff --git a/dshow/PlatformUtils/src/messagecommons.h b/dshow/PlatformUtils/src/messagecommons.h new file mode 100644 index 0000000..93b9b67 --- /dev/null +++ b/dshow/PlatformUtils/src/messagecommons.h @@ -0,0 +1,211 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MESSAGECOMMONS_H +#define MESSAGECOMMONS_H + +#include +#include +#include + +#include "VCamUtils/src/image/videoframetypes.h" + +#define AKVCAM_ASSISTANT_CLIENT_NAME "AkVCam_Client" +#define AKVCAM_ASSISTANT_SERVER_NAME "AkVCam_Server" + +// General messages +#define AKVCAM_ASSISTANT_MSG_ISALIVE 0x000 +#define AKVCAM_ASSISTANT_MSG_FRAME_READY 0x001 + +// Assistant messages +#define AKVCAM_ASSISTANT_MSG_REQUEST_PORT 0x100 +#define AKVCAM_ASSISTANT_MSG_ADD_PORT 0x101 +#define AKVCAM_ASSISTANT_MSG_REMOVE_PORT 0x102 + +// Device control and information +#define AKVCAM_ASSISTANT_MSG_DEVICES 0x200 +#define AKVCAM_ASSISTANT_MSG_DEVICE_CREATE 0x201 +#define AKVCAM_ASSISTANT_MSG_DEVICE_DESTROY 0x202 +#define AKVCAM_ASSISTANT_MSG_DEVICE_DESCRIPTION 0x203 +#define AKVCAM_ASSISTANT_MSG_DEVICE_FORMATS 0x204 + +// Device listeners controls +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENERS 0x300 +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER 0x301 +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD 0x302 +#define AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE 0x303 + +// Device dynamic properties +#define AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING 0x400 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING 0x401 +#define AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING 0x402 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING 0x403 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SCALING 0x404 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING 0x405 +#define AKVCAM_ASSISTANT_MSG_DEVICE_ASPECTRATIO 0x406 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO 0x407 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB 0x408 +#define AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB 0x409 + +#define MSG_BUFFER_SIZE 4096 +#define MAX_STRING 1024 + +#define AKVCAM_BIND_FUNC(member) \ + std::bind(&member, this, std::placeholders::_1) + +namespace AkVCam +{ + struct Frame + { + uint32_t format; + int32_t width; + int32_t height; + uint32_t size; + uint8_t data[4]; + }; + + struct Message + { + uint32_t messageId; + uint32_t dataSize; + uint8_t data[MSG_BUFFER_SIZE]; + + Message(): + messageId(0), + dataSize(0) + { + memset(this->data, 0, MSG_BUFFER_SIZE); + } + + Message(const Message &other): + messageId(other.messageId), + dataSize(other.dataSize) + { + memcpy(this->data, other.data, MSG_BUFFER_SIZE); + } + + Message(const Message *other): + messageId(other->messageId), + dataSize(other->dataSize) + { + memcpy(this->data, other->data, MSG_BUFFER_SIZE); + } + + Message &operator =(const Message &other) + { + if (this != &other) { + this->messageId = other.messageId; + this->dataSize = other.dataSize; + memcpy(this->data, other.data, MSG_BUFFER_SIZE); + } + + return *this; + } + + inline void clear() + { + this->messageId = 0; + this->dataSize = 0; + memset(this->data, 0, MSG_BUFFER_SIZE); + } + }; + + template + inline T *messageData(Message *message) + { + return reinterpret_cast(message->data); + } + + using MessageHandler = std::function; + + struct MsgRequestPort + { + bool client; + char port[MAX_STRING]; + }; + + struct MsgAddPort + { + char port[MAX_STRING]; + char pipeName[MAX_STRING]; + bool status; + }; + + struct MsgRemovePort + { + char port[MAX_STRING]; + }; + + struct MsgBroadcasting + { + char device[MAX_STRING]; + char broadcaster[MAX_STRING]; + bool status; + }; + + struct MsgMirroring + { + char device[MAX_STRING]; + bool hmirror; + bool vmirror; + bool status; + }; + + struct MsgScaling + { + char device[MAX_STRING]; + Scaling scaling; + bool status; + }; + + struct MsgAspectRatio + { + char device[MAX_STRING]; + AspectRatio aspect; + bool status; + }; + + struct MsgSwapRgb + { + char device[MAX_STRING]; + bool swap; + bool status; + }; + + struct MsgListeners + { + char device[MAX_STRING]; + char listener[MAX_STRING]; + size_t nlistener; + bool status; + }; + + struct MsgIsAlive + { + bool alive; + }; + + struct MsgFrameReady + { + char device[MAX_STRING]; + char port[MAX_STRING]; + }; +} + +#endif // MESSAGECOMMONS_H diff --git a/dshow/PlatformUtils/src/messageserver.cpp b/dshow/PlatformUtils/src/messageserver.cpp new file mode 100644 index 0000000..42cf102 --- /dev/null +++ b/dshow/PlatformUtils/src/messageserver.cpp @@ -0,0 +1,455 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include + +#include "messageserver.h" +#include "utils.h" +#include "VCamUtils/src/logger/logger.h" + +#define AK_CUR_INTERFACE "MessageServer" + +namespace AkVCam +{ + class MessageServerPrivate + { + public: + MessageServer *self; + std::wstring m_pipeName; + std::map m_handlers; + MessageServer::ServerMode m_mode {MessageServer::ServerModeReceive}; + MessageServer::PipeState m_pipeState {MessageServer::PipeStateGone}; + HANDLE m_pipe {INVALID_HANDLE_VALUE}; + OVERLAPPED m_overlapped; + std::thread m_thread; + std::mutex m_mutex; + std::condition_variable_any m_exitCheckLoop; + int m_checkInterval {5000}; + bool m_running {false}; + + explicit MessageServerPrivate(MessageServer *self); + bool startReceive(bool wait=false); + void stopReceive(bool wait=false); + bool startSend(); + void stopSend(); + void messagesLoop(); + void checkLoop(); + HRESULT waitResult(DWORD *bytesTransferred); + bool readMessage(Message *message); + bool writeMessage(const Message &message); + }; +} + +AkVCam::MessageServer::MessageServer() +{ + this->d = new MessageServerPrivate(this); +} + +AkVCam::MessageServer::~MessageServer() +{ + this->stop(true); + delete this->d; +} + +std::wstring AkVCam::MessageServer::pipeName() const +{ + return this->d->m_pipeName; +} + +std::wstring &AkVCam::MessageServer::pipeName() +{ + return this->d->m_pipeName; +} + +void AkVCam::MessageServer::setPipeName(const std::wstring &pipeName) +{ + this->d->m_pipeName = pipeName; +} + +AkVCam::MessageServer::ServerMode AkVCam::MessageServer::mode() const +{ + return this->d->m_mode; +} + +AkVCam::MessageServer::ServerMode &AkVCam::MessageServer::mode() +{ + return this->d->m_mode; +} + +void AkVCam::MessageServer::setMode(ServerMode mode) +{ + this->d->m_mode = mode; +} + +int AkVCam::MessageServer::checkInterval() const +{ + return this->d->m_checkInterval; +} + +int &AkVCam::MessageServer::checkInterval() +{ + return this->d->m_checkInterval; +} + +void AkVCam::MessageServer::setCheckInterval(int checkInterval) +{ + this->d->m_checkInterval = checkInterval; +} + +void AkVCam::MessageServer::setHandlers(const std::map &handlers) +{ + this->d->m_handlers = handlers; +} + +bool AkVCam::MessageServer::start(bool wait) +{ + AkLogMethod(); + + switch (this->d->m_mode) { + case ServerModeReceive: + AkLoggerLog("Starting mode receive"); + + return this->d->startReceive(wait); + + case ServerModeSend: + AkLoggerLog("Starting mode send"); + + return this->d->startSend(); + } + + return false; +} + +void AkVCam::MessageServer::stop(bool wait) +{ + AkLogMethod(); + + if (this->d->m_mode == ServerModeReceive) + this->d->stopReceive(wait); + else + this->d->stopSend(); +} + +BOOL AkVCam::MessageServer::sendMessage(Message *message, + uint32_t timeout) +{ + return this->sendMessage(this->d->m_pipeName, message, timeout); +} + +BOOL AkVCam::MessageServer::sendMessage(const Message &messageIn, + Message *messageOut, + uint32_t timeout) +{ + return this->sendMessage(this->d->m_pipeName, + messageIn, + messageOut, + timeout); +} + +BOOL AkVCam::MessageServer::sendMessage(const std::string &pipeName, + Message *message, + uint32_t timeout) +{ + return sendMessage(std::wstring(pipeName.begin(), pipeName.end()), + message, + timeout); +} + +BOOL AkVCam::MessageServer::sendMessage(const std::wstring &pipeName, + Message *message, + uint32_t timeout) +{ + return sendMessage(pipeName, + *message, + message, + timeout); +} + +BOOL AkVCam::MessageServer::sendMessage(const std::wstring &pipeName, + const Message &messageIn, + Message *messageOut, + uint32_t timeout) +{ + DWORD bytesTransferred = 0; + + return CallNamedPipe(pipeName.c_str(), + const_cast(&messageIn), + DWORD(sizeof(Message)), + messageOut, + DWORD(sizeof(Message)), + &bytesTransferred, + timeout); +} + +AkVCam::MessageServerPrivate::MessageServerPrivate(MessageServer *self): + self(self) +{ + memset(&this->m_overlapped, 0, sizeof(OVERLAPPED)); +} + +bool AkVCam::MessageServerPrivate::startReceive(bool wait) +{ + AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateAboutToStart) + bool ok = false; + + // Define who can read and write from pipe. + + /* Define the SDDL for the DACL. + * + * https://msdn.microsoft.com/en-us/library/windows/desktop/aa379570(v=vs.85).aspx + */ + WCHAR descriptor[] = + L"D:" // Discretionary ACL + L"(D;OICI;GA;;;BG)" // Deny access to Built-in Guests + L"(D;OICI;GA;;;AN)" // Deny access to Anonymous Logon + L"(A;OICI;GRGWGX;;;AU)" // Allow read/write/execute to Authenticated Users + L"(A;OICI;GA;;;BA)"; // Allow full control to Administrators + + SECURITY_ATTRIBUTES securityAttributes; + PSECURITY_DESCRIPTOR securityDescriptor = + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + + if (!securityDescriptor) + goto startReceive_failed; + + if (!InitializeSecurityDescriptor(securityDescriptor, + SECURITY_DESCRIPTOR_REVISION)) + goto startReceive_failed; + + if (!ConvertStringSecurityDescriptorToSecurityDescriptor(descriptor, + SDDL_REVISION_1, + &securityDescriptor, + nullptr)) + goto startReceive_failed; + + securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + securityAttributes.lpSecurityDescriptor = securityDescriptor; + securityAttributes.bInheritHandle = TRUE; + + // Create a read/write message type pipe. + this->m_pipe = CreateNamedPipe(this->m_pipeName.c_str(), + PIPE_ACCESS_DUPLEX + | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_MESSAGE + | PIPE_READMODE_BYTE + | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + sizeof(Message), + sizeof(Message), + NMPWAIT_USE_DEFAULT_WAIT, + &securityAttributes); + + if (this->m_pipe == INVALID_HANDLE_VALUE) + goto startReceive_failed; + + memset(&this->m_overlapped, 0, sizeof(OVERLAPPED)); + this->m_overlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateStarted) + this->m_running = true; + + if (wait) + this->messagesLoop(); + else + this->m_thread = + std::thread(&MessageServerPrivate::messagesLoop, this); + + ok = true; + +startReceive_failed: + + if (!ok) { + AkLoggerLog("Error starting server: ", + errorToString(GetLastError()), + " (", GetLastError(), ")"); + AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateStopped) + } + + if (securityDescriptor) + LocalFree(securityDescriptor); + + return ok; +} + +void AkVCam::MessageServerPrivate::stopReceive(bool wait) +{ + if (!this->m_running) + return; + + this->m_running = false; + SetEvent(this->m_overlapped.hEvent); + + if (wait) + this->m_thread.join(); +} + +bool AkVCam::MessageServerPrivate::startSend() +{ + this->m_running = true; + this->m_thread = std::thread(&MessageServerPrivate::checkLoop, this); + + return true; +} + +void AkVCam::MessageServerPrivate::stopSend() +{ + if (!this->m_running) + return; + + this->m_running = false; + this->m_mutex.lock(); + this->m_exitCheckLoop.notify_all(); + this->m_mutex.unlock(); + this->m_thread.join(); + this->m_pipeState = MessageServer::PipeStateGone; +} + +void AkVCam::MessageServerPrivate::messagesLoop() +{ + DWORD bytesTransferred = 0; + + while (this->m_running) { + HRESULT result = S_OK; + + // Wait for a connection. + if (!ConnectNamedPipe(this->m_pipe, &this->m_overlapped)) + result = this->waitResult(&bytesTransferred); + + if (result == S_OK) { + Message message; + + if (this->readMessage(&message)) { + if (this->m_handlers.count(message.messageId)) + this->m_handlers[message.messageId](&message); + + this->writeMessage(message); + } + } + + DisconnectNamedPipe(this->m_pipe); + } + + AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateStopped) + + if (this->m_overlapped.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(this->m_overlapped.hEvent); + memset(&this->m_overlapped, 0, sizeof(OVERLAPPED)); + } + + if (this->m_pipe != INVALID_HANDLE_VALUE) { + CloseHandle(this->m_pipe); + this->m_pipe = INVALID_HANDLE_VALUE; + } + + AKVCAM_EMIT(this->self, StateChanged, MessageServer::StateStopped) +} + +void AkVCam::MessageServerPrivate::checkLoop() +{ + while (this->m_running) { + auto result = WaitNamedPipe(this->m_pipeName.c_str(), NMPWAIT_NOWAIT); + + if (result + && this->m_pipeState != AkVCam::MessageServer::PipeStateAvailable) { + AkLoggerLog("Pipe Available: ", + std::string(this->m_pipeName.begin(), + this->m_pipeName.end())); + this->m_pipeState = AkVCam::MessageServer::PipeStateAvailable; + AKVCAM_EMIT(this->self, PipeStateChanged, this->m_pipeState); + } else if (!result + && this->m_pipeState != AkVCam::MessageServer::PipeStateGone + && GetLastError() != ERROR_SEM_TIMEOUT) { + AkLoggerLog("Pipe Gone: ", + std::string(this->m_pipeName.begin(), + this->m_pipeName.end())); + this->m_pipeState = AkVCam::MessageServer::PipeStateGone; + AKVCAM_EMIT(this->self, PipeStateChanged, this->m_pipeState); + } + + if (!this->m_running) + break; + + this->m_mutex.lock(); + this->m_exitCheckLoop.wait_for(this->m_mutex, + std::chrono::milliseconds(this->m_checkInterval)); + this->m_mutex.unlock(); + } +} + +HRESULT AkVCam::MessageServerPrivate::waitResult(DWORD *bytesTransferred) +{ + auto lastError = GetLastError(); + + if (lastError == ERROR_IO_PENDING) { + if (WaitForSingleObject(this->m_overlapped.hEvent, + INFINITE) == WAIT_OBJECT_0) { + if (!GetOverlappedResult(this->m_pipe, + &this->m_overlapped, + bytesTransferred, + FALSE)) + return S_FALSE; + } else { + CancelIo(this->m_pipe); + + return S_FALSE; + } + } else { + AkLoggerLog("Wait result failed: ", + errorToString(lastError), + " (", lastError, ")"); + + return E_FAIL; + } + + return S_OK; +} + +bool AkVCam::MessageServerPrivate::readMessage(Message *message) +{ + DWORD bytesTransferred = 0; + HRESULT result = S_OK; + + if (!ReadFile(this->m_pipe, + message, + DWORD(sizeof(Message)), + &bytesTransferred, + &this->m_overlapped)) + result = this->waitResult(&bytesTransferred); + + return result == S_OK; +} + +bool AkVCam::MessageServerPrivate::writeMessage(const Message &message) +{ + DWORD bytesTransferred = 0; + HRESULT result = S_OK; + + if (!WriteFile(this->m_pipe, + &message, + DWORD(sizeof(Message)), + &bytesTransferred, + &this->m_overlapped)) + result = this->waitResult(&bytesTransferred); + + return result == S_OK; +} diff --git a/dshow/PlatformUtils/src/messageserver.h b/dshow/PlatformUtils/src/messageserver.h new file mode 100644 index 0000000..c070086 --- /dev/null +++ b/dshow/PlatformUtils/src/messageserver.h @@ -0,0 +1,104 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MESSAGESERVER_H +#define MESSAGESERVER_H + +#include +#include +#include + +#include "messagecommons.h" +#include "VCamUtils/src/utils.h" + +#define MSERVER_TIMEOUT_DEFAULT 0 +#define MSERVER_TIMEOUT_MIN 1 +#define MSERVER_TIMEOUT_MAX (std::numeric_limits::max)() + +namespace AkVCam +{ + class MessageServerPrivate; + + class MessageServer + { + public: + enum ServerMode + { + ServerModeReceive, + ServerModeSend + }; + + enum State + { + StateAboutToStart, + StateStarted, + StateAboutToStop, + StateStopped + }; + + enum PipeState + { + PipeStateAvailable, + PipeStateGone + }; + + AKVCAM_SIGNAL(StateChanged, State state) + AKVCAM_SIGNAL(PipeStateChanged, PipeState state) + + public: + MessageServer(); + MessageServer(const MessageServer &other) = delete; + ~MessageServer(); + + std::wstring pipeName() const; + std::wstring &pipeName(); + void setPipeName(const std::wstring &pipeName); + ServerMode mode() const; + ServerMode &mode(); + void setMode(ServerMode mode); + int checkInterval() const; + int &checkInterval(); + void setCheckInterval(int checkInterval); + void setHandlers(const std::map &handlers); + bool start(bool wait=false); + void stop(bool wait=false); + BOOL sendMessage(Message *message, + uint32_t timeout=MSERVER_TIMEOUT_MAX); + BOOL sendMessage(const Message &messageIn, + Message *messageOut, + uint32_t timeout=MSERVER_TIMEOUT_MAX); + static BOOL sendMessage(const std::string &pipeName, + Message *message, + uint32_t timeout=MSERVER_TIMEOUT_MAX); + static BOOL sendMessage(const std::wstring &pipeName, + Message *message, + uint32_t timeout=MSERVER_TIMEOUT_MAX); + static BOOL sendMessage(const std::wstring &pipeName, + const Message &messageIn, + Message *messageOut, + uint32_t timeout=MSERVER_TIMEOUT_MAX); + + private: + MessageServerPrivate *d; + friend class MessageServerPrivate; + }; +} + +#endif // MESSAGESERVER_H diff --git a/dshow/PlatformUtils/src/mutex.cpp b/dshow/PlatformUtils/src/mutex.cpp new file mode 100644 index 0000000..4352a58 --- /dev/null +++ b/dshow/PlatformUtils/src/mutex.cpp @@ -0,0 +1,110 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "mutex.h" + +namespace AkVCam +{ + class MutexPrivate + { + public: + HANDLE m_mutex; + std::wstring m_name; + }; +} + +AkVCam::Mutex::Mutex(const std::wstring &name) +{ + this->d = new MutexPrivate(); + this->d->m_mutex = CreateMutex(nullptr, + FALSE, + name.empty()? + nullptr: name.c_str()); + this->d->m_name = name; +} + +AkVCam::Mutex::Mutex(const Mutex &other) +{ + this->d = new MutexPrivate(); + this->d->m_mutex = CreateMutex(nullptr, + FALSE, + other.d->m_name.empty()? + nullptr: other.d->m_name.c_str()); + this->d->m_name = other.d->m_name; +} + +AkVCam::Mutex::~Mutex() +{ + if (this->d->m_mutex) + CloseHandle(this->d->m_mutex); + + delete this->d; +} + +AkVCam::Mutex &AkVCam::Mutex::operator =(const Mutex &other) +{ + if (this != &other) { + this->unlock(); + + if (this->d->m_mutex) + CloseHandle(this->d->m_mutex); + + this->d->m_mutex = CreateMutex(nullptr, + FALSE, + other.d->m_name.empty()? + nullptr: other.d->m_name.c_str()); + this->d->m_name = other.d->m_name; + } + + return *this; +} + +std::wstring AkVCam::Mutex::name() const +{ + return this->d->m_name; +} + +void AkVCam::Mutex::lock() +{ + if (!this->d->m_mutex) + return; + + WaitForSingleObject(this->d->m_mutex, INFINITE); +} + +bool AkVCam::Mutex::tryLock(int timeout) +{ + if (!this->d->m_mutex) + return false; + + DWORD waitResult = WaitForSingleObject(this->d->m_mutex, + !timeout? INFINITE: DWORD(timeout)); + + return waitResult != WAIT_FAILED && waitResult != WAIT_TIMEOUT; +} + +void AkVCam::Mutex::unlock() +{ + if (!this->d->m_mutex) + return; + + ReleaseMutex(this->d->m_mutex); +} diff --git a/dshow/PlatformUtils/src/mutex.h b/dshow/PlatformUtils/src/mutex.h new file mode 100644 index 0000000..9172148 --- /dev/null +++ b/dshow/PlatformUtils/src/mutex.h @@ -0,0 +1,47 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MUTEX_H +#define MUTEX_H + +#include + +namespace AkVCam +{ + class MutexPrivate; + + class Mutex + { + public: + Mutex(const std::wstring &name={}); + Mutex(const Mutex &other); + ~Mutex(); + Mutex &operator =(const Mutex &other); + + std::wstring name() const; + void lock(); + bool tryLock(int timeout=0); + void unlock(); + + private: + MutexPrivate *d; + }; +} + +#endif // MUTEX_H diff --git a/dshow/PlatformUtils/src/sharedmemory.cpp b/dshow/PlatformUtils/src/sharedmemory.cpp new file mode 100644 index 0000000..4cb5464 --- /dev/null +++ b/dshow/PlatformUtils/src/sharedmemory.cpp @@ -0,0 +1,204 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "sharedmemory.h" +#include "mutex.h" +#include "utils.h" +#include "VCamUtils/src/logger/logger.h" + +namespace AkVCam +{ + class SharedMemoryPrivate + { + public: + HANDLE m_sharedHandle; + std::wstring m_name; + void *m_buffer; + size_t m_pageSize; + SharedMemory::OpenMode m_mode; + bool m_isOpen; + }; +} + +AkVCam::SharedMemory::SharedMemory() +{ + this->d = new SharedMemoryPrivate; + this->d->m_sharedHandle = nullptr; + this->d->m_buffer = nullptr; + this->d->m_pageSize = 0; + this->d->m_mode = OpenModeRead; + this->d->m_isOpen = false; +} + +AkVCam::SharedMemory::SharedMemory(const SharedMemory &other) +{ + this->d = new SharedMemoryPrivate; + this->d->m_sharedHandle = nullptr; + this->d->m_name = other.d->m_name; + this->d->m_buffer = nullptr; + this->d->m_pageSize = 0; + this->d->m_mode = OpenModeRead; + this->d->m_isOpen = false; + + if (other.d->m_isOpen) + this->open(other.d->m_pageSize, other.d->m_mode); +} + +AkVCam::SharedMemory::~SharedMemory() +{ + this->close(); + delete this->d; +} + +AkVCam::SharedMemory &AkVCam::SharedMemory::operator =(const SharedMemory &other) +{ + if (this != &other) { + this->close(); + this->d->m_name = other.d->m_name; + this->d->m_buffer = nullptr; + this->d->m_pageSize = 0; + this->d->m_mode = OpenModeRead; + this->d->m_isOpen = false; + + if (other.d->m_isOpen) + this->open(other.d->m_pageSize, other.d->m_mode); + } + + return *this; +} + +std::wstring AkVCam::SharedMemory::name() const +{ + return this->d->m_name; +} + +std::wstring &AkVCam::SharedMemory::name() +{ + return this->d->m_name; +} + +void AkVCam::SharedMemory::setName(const std::wstring &name) +{ + this->d->m_name = name; +} + +bool AkVCam::SharedMemory::open(size_t pageSize, OpenMode mode) +{ + if (this->d->m_isOpen) + return false; + + if (this->d->m_name.empty()) + return false; + + if (mode == OpenModeRead) { + this->d->m_sharedHandle = + OpenFileMapping(FILE_MAP_ALL_ACCESS, + FALSE, + this->d->m_name.c_str()); + } else { + if (pageSize < 1) + return false; + + this->d->m_sharedHandle = + CreateFileMapping(INVALID_HANDLE_VALUE, + nullptr, + PAGE_READWRITE, + 0, + DWORD(pageSize), + this->d->m_name.c_str()); + } + + if (!this->d->m_sharedHandle) { + AkLoggerLog("Error opening shared memory (", + std::string(this->d->m_name.begin(), + this->d->m_name.end()), + "): ", + errorToString(GetLastError()), + " (", GetLastError(), ")"); + + return false; + } + + this->d->m_buffer = MapViewOfFile(this->d->m_sharedHandle, + FILE_MAP_ALL_ACCESS, + 0, + 0, + pageSize); + + if (!this->d->m_buffer) { + CloseHandle(this->d->m_sharedHandle); + this->d->m_sharedHandle = nullptr; + + return false; + } + + this->d->m_pageSize = pageSize; + this->d->m_mode = mode; + this->d->m_isOpen = true; + + return true; +} + +bool AkVCam::SharedMemory::isOpen() const +{ + return this->d->m_isOpen; +} + +size_t AkVCam::SharedMemory::pageSize() const +{ + return this->d->m_pageSize; +} + +AkVCam::SharedMemory::OpenMode AkVCam::SharedMemory::mode() const +{ + return this->d->m_mode; +} + +void *AkVCam::SharedMemory::lock(AkVCam::Mutex *mutex, int timeout) +{ + if (mutex && !mutex->tryLock(timeout)) + return nullptr; + + return this->d->m_buffer; +} + +void AkVCam::SharedMemory::unlock(AkVCam::Mutex *mutex) +{ + if (mutex) + mutex->unlock(); +} + +void AkVCam::SharedMemory::close() +{ + if (this->d->m_buffer) { + UnmapViewOfFile(this->d->m_buffer); + this->d->m_buffer = nullptr; + } + + if (this->d->m_sharedHandle) { + CloseHandle(this->d->m_sharedHandle); + this->d->m_sharedHandle = nullptr; + } + + this->d->m_pageSize = 0; + this->d->m_mode = OpenModeRead; + this->d->m_isOpen = false; +} diff --git a/dshow/PlatformUtils/src/sharedmemory.h b/dshow/PlatformUtils/src/sharedmemory.h new file mode 100644 index 0000000..1ff3b55 --- /dev/null +++ b/dshow/PlatformUtils/src/sharedmemory.h @@ -0,0 +1,60 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef SHAREDMEMORY_H +#define SHAREDMEMORY_H + +#include + +namespace AkVCam +{ + class SharedMemoryPrivate; + class Mutex; + + class SharedMemory + { + public: + enum OpenMode + { + OpenModeRead, + OpenModeWrite + }; + + SharedMemory(); + SharedMemory(const SharedMemory &other); + ~SharedMemory(); + SharedMemory &operator =(const SharedMemory &other); + + std::wstring name() const; + std::wstring &name(); + void setName(const std::wstring &name); + bool open(size_t pageSize=0, OpenMode mode=OpenModeRead); + bool isOpen() const; + size_t pageSize() const; + OpenMode mode() const; + void *lock(Mutex *mutex=nullptr, int timeout=0); + void unlock(Mutex *mutex=nullptr); + void close(); + + private: + SharedMemoryPrivate *d; + }; +} + +#endif // SHAREDMEMORY_H diff --git a/dshow/PlatformUtils/src/utils.cpp b/dshow/PlatformUtils/src/utils.cpp new file mode 100644 index 0000000..8f9da88 --- /dev/null +++ b/dshow/PlatformUtils/src/utils.cpp @@ -0,0 +1,1140 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "VCamUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" + +#define TIME_BASE 1.0e7 + +namespace AkVCam +{ + class VideoFormatSpecsPrivate + { + public: + FourCC pixelFormat; + DWORD compression; + GUID guid; + const DWORD *masks; + + inline static const std::vector &formats() + { + static const DWORD bits555[] = {0x007c00, 0x0003e0, 0x00001f}; + static const DWORD bits565[] = {0x00f800, 0x0007e0, 0x00001f}; + + static const std::vector formats { + {PixelFormatRGB32, BI_RGB , MEDIASUBTYPE_RGB32 , nullptr}, + {PixelFormatRGB24, BI_RGB , MEDIASUBTYPE_RGB24 , nullptr}, + {PixelFormatRGB16, BI_BITFIELDS , MEDIASUBTYPE_RGB565, bits565}, + {PixelFormatRGB15, BI_BITFIELDS , MEDIASUBTYPE_RGB555, bits555}, + {PixelFormatUYVY , MAKEFOURCC('U', 'Y', 'V', 'Y'), MEDIASUBTYPE_UYVY , nullptr}, + {PixelFormatYUY2 , MAKEFOURCC('Y', 'U', 'Y', '2'), MEDIASUBTYPE_YUY2 , nullptr}, + {PixelFormatNV12 , MAKEFOURCC('N', 'V', '1', '2'), MEDIASUBTYPE_NV12 , nullptr} + }; + + return formats; + } + + static inline const VideoFormatSpecsPrivate *byGuid(const GUID &guid) + { + for (auto &format: formats()) + if (IsEqualGUID(format.guid, guid)) + return &format; + + return nullptr; + } + + static inline const VideoFormatSpecsPrivate *byPixelFormat(FourCC pixelFormat) + { + for (auto &format: formats()) + if (format.pixelFormat == pixelFormat) + return &format; + + return nullptr; + } + }; +} + +bool operator <(const CLSID &a, const CLSID &b) +{ + return AkVCam::stringFromIid(a) < AkVCam::stringFromIid(b); +} + +BOOL AkVCam::isWow64() +{ + BOOL isWow64 = FALSE; + + if (!IsWow64Process(GetCurrentProcess(), &isWow64)) + return false; + + return isWow64; +} + +std::wstring AkVCam::tempPath() +{ + WCHAR tempPath[MAX_PATH]; + memset(tempPath, 0, MAX_PATH * sizeof(WCHAR)); + GetTempPath(MAX_PATH, tempPath); + + return std::wstring(tempPath); +} + +std::wstring AkVCam::programFilesPath() +{ + WCHAR programFiles[MAX_PATH]; + DWORD programFilesSize = MAX_PATH * sizeof(WCHAR); + memset(programFiles, 0, programFilesSize); + bool ok = false; + + if (isWow64() + && regGetValue(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion", + L"ProgramFilesDir", + RRF_RT_REG_SZ, + nullptr, + &programFiles, + &programFilesSize) == ERROR_SUCCESS) + ok = true; + + if (!ok) + SHGetSpecialFolderPath(nullptr, + programFiles, + CSIDL_PROGRAM_FILES, + FALSE); + + return std::wstring(programFiles); +} + +std::wstring AkVCam::moduleFileNameW(HINSTANCE hinstDLL) +{ + WCHAR fileName[MAX_PATH]; + memset(fileName, 0, MAX_PATH * sizeof(WCHAR)); + GetModuleFileName(hinstDLL, fileName, MAX_PATH); + + return std::wstring(fileName); +} + +std::string AkVCam::moduleFileName(HINSTANCE hinstDLL) +{ + auto fileName = moduleFileNameW(hinstDLL); + + return std::string(fileName.begin(), fileName.end()); +} + +std::wstring AkVCam::errorToStringW(DWORD errorCode) +{ + WCHAR *errorStr = nullptr; + auto size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + reinterpret_cast(&errorStr), + 0, + nullptr); + std::wstring error(errorStr, size); + LocalFree(errorStr); + + return error; +} + +std::string AkVCam::errorToString(DWORD errorCode) +{ + auto errorStr = errorToStringW(errorCode); + + return std::string(errorStr.begin(), errorStr.end()); +} + +// Converts a human redable string to a CLSID using MD5 hash. +CLSID AkVCam::createClsidFromStr(const std::string &str) +{ + return createClsidFromStr(std::wstring(str.begin(), str.end())); +} + +CLSID AkVCam::createClsidFromStr(const std::wstring &str) +{ + HCRYPTPROV provider = 0; + HCRYPTHASH hash = 0; + CLSID clsid; + DWORD clsidLen = sizeof(CLSID); + memset(&clsid, 0, sizeof(CLSID)); + + if (!CryptAcquireContext(&provider, + nullptr, + nullptr, + PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) + goto clsidFromStr_failed; + + if (!CryptCreateHash(provider, CALG_MD5, 0, 0, &hash)) + goto clsidFromStr_failed; + + if (!CryptHashData(hash, + reinterpret_cast(str.c_str()), + DWORD(str.size() * sizeof(wchar_t)), + 0)) + goto clsidFromStr_failed; + + CryptGetHashParam(hash, + HP_HASHVAL, + reinterpret_cast(&clsid), + &clsidLen, + 0); + +clsidFromStr_failed: + if (hash) + CryptDestroyHash(hash); + + if (provider) + CryptReleaseContext(provider, 0); + + return clsid; +} + +std::wstring AkVCam::createClsidWStrFromStr(const std::string &str) +{ + return createClsidWStrFromStr(std::wstring(str.begin(), str.end())); +} + +std::wstring AkVCam::createClsidWStrFromStr(const std::wstring &str) +{ + auto clsid = createClsidFromStr(str); + OLECHAR *clsidWStr = nullptr; + + if (StringFromCLSID(clsid, &clsidWStr) != S_OK) + return std::wstring(); + + std::wstring wstr(clsidWStr); + CoTaskMemFree(clsidWStr); + + return wstr; +} + +std::string AkVCam::stringFromIid(const IID &iid) +{ + auto wstr = wstringFromIid(iid); + + return std::string(wstr.begin(), wstr.end()); +} + +std::wstring AkVCam::wstringFromIid(const IID &iid) +{ + WCHAR *strIID = nullptr; + StringFromIID(iid, &strIID); + std::wstring wstr(strIID); + CoTaskMemFree(strIID); + + return wstr; +} + +std::string AkVCam::stringFromResult(HRESULT result) +{ + auto msg = std::wstring(_com_error(result).ErrorMessage()); + + return std::string(msg.begin(), msg.end()); +} + +std::string AkVCam::stringFromClsid(const CLSID &clsid) +{ + static const std::map clsidToString { + {IID_IAgileObject , "IAgileObject" }, + {IID_IAMAnalogVideoDecoder, "IAMAnalogVideoDecoder"}, + {IID_IAMAudioInputMixer , "IAMAudioInputMixer" }, + {IID_IAMAudioRendererStats, "IAMAudioRendererStats"}, + {IID_IAMBufferNegotiation , "IAMBufferNegotiation" }, + {IID_IAMCameraControl , "IAMCameraControl" }, + {IID_IAMClockAdjust , "IAMClockAdjust" }, + {IID_IAMCrossbar , "IAMCrossbar" }, + {IID_IAMDeviceRemoval , "IAMDeviceRemoval" }, + {IID_IAMExtDevice , "IAMExtDevice" }, + {IID_IAMFilterMiscFlags , "IAMFilterMiscFlags" }, + {IID_IAMOpenProgress , "IAMOpenProgress" }, + {IID_IAMPushSource , "IAMPushSource" }, + {IID_IAMStreamConfig , "IAMStreamConfig" }, + {IID_IAMTVTuner , "IAMTVTuner" }, + {IID_IAMVfwCaptureDialogs , "IAMVfwCaptureDialogs" }, + {IID_IAMVfwCompressDialogs, "IAMVfwCompressDialogs"}, + {IID_IAMVideoCompression , "IAMVideoCompression" }, + {IID_IAMVideoControl , "IAMVideoControl" }, + {IID_IAMVideoProcAmp , "IAMVideoProcAmp" }, + {IID_IBaseFilter , "IBaseFilter" }, + {IID_IBasicAudio , "IBasicAudio" }, + {IID_IBasicVideo , "IBasicVideo" }, + {IID_IClassFactory , "IClassFactory" }, + {IID_IEnumMediaTypes , "IEnumMediaTypes" }, + {IID_IEnumPins , "IEnumPins" }, + {IID_IFileSinkFilter , "IFileSinkFilter" }, + {IID_IFileSinkFilter2 , "IFileSinkFilter2" }, + {IID_IFileSourceFilter , "IFileSourceFilter" }, + {IID_IKsPropertySet , "IKsPropertySet" }, + {IID_IMarshal , "IMarshal" }, + {IID_IMediaControl , "IMediaControl" }, + {IID_IMediaFilter , "IMediaFilter" }, + {IID_IMediaPosition , "IMediaPosition" }, + {IID_IMediaSample , "IMediaSample" }, + {IID_IMediaSample2 , "IMediaSample2" }, + {IID_IMediaSeeking , "IMediaSeeking" }, + {IID_IMediaEventSink , "IMediaEventSink" }, + {IID_IMemAllocator , "IMemAllocator" }, + {IID_INoMarshal , "INoMarshal" }, + {IID_IPersist , "IPersist" }, + {IID_IPersistPropertyBag , "IPersistPropertyBag" }, + {IID_IPin , "IPin" }, + {IID_IProvideClassInfo , "IProvideClassInfo" }, + {IID_IQualityControl , "IQualityControl" }, + {IID_IReferenceClock , "IReferenceClock" }, + {IID_IRpcOptions , "IRpcOptions" }, + {IID_ISpecifyPropertyPages, "ISpecifyPropertyPages"}, + {IID_IVideoWindow , "IVideoWindow" }, + {IID_IUnknown , "IUnknown" }, + }; + + for (auto &id: clsidToString) + if (IsEqualCLSID(id.first, clsid)) + return id.second; + + return stringFromIid(clsid); +} + +wchar_t *AkVCam::wcharStrFromWStr(const std::wstring &wstr) +{ + if (wstr.size() < 1) + return nullptr; + + auto wcstrSize = wstr.size() * sizeof(wchar_t); + auto wcstr = reinterpret_cast(CoTaskMemAlloc(wcstrSize + 1)); + wcstr[wstr.size()] = 0; + memcpy(wcstr, wstr.data(), wcstrSize); + + return wcstr; +} + +AkVCam::FourCC AkVCam::formatFromGuid(const GUID &guid) +{ + auto formatSpec = VideoFormatSpecsPrivate::byGuid(guid); + + if (!formatSpec) + return 0; + + return formatSpec->pixelFormat; +} + +const GUID &AkVCam::guidFromFormat(FourCC format) +{ + auto formatSpec = VideoFormatSpecsPrivate::byPixelFormat(format); + + if (!formatSpec) + return GUID_NULL; + + return formatSpec->guid; +} + +DWORD AkVCam::compressionFromFormat(FourCC format) +{ + auto formatSpec = VideoFormatSpecsPrivate::byPixelFormat(format); + + if (!formatSpec) + return 0; + + return formatSpec->compression; +} + +bool AkVCam::isSubTypeSupported(const GUID &subType) +{ + for (auto &format: VideoFormatSpecsPrivate::formats()) + if (IsEqualGUID(format.guid, subType)) + return true; + + return false; +} + +AM_MEDIA_TYPE *AkVCam::mediaTypeFromFormat(const AkVCam::VideoFormat &format) +{ + auto subtype = guidFromFormat(format.fourcc()); + + if (IsEqualGUID(subtype, GUID_NULL)) + return nullptr; + + auto frameSize = format.size(); + + if (!frameSize) + return nullptr; + + auto videoInfo = + reinterpret_cast(CoTaskMemAlloc(sizeof(VIDEOINFO))); + memset(videoInfo, 0, sizeof(VIDEOINFO)); + auto fps = format.minimumFrameRate(); + + // Initialize info header. + videoInfo->rcSource = {0, 0, 0, 0}; + videoInfo->rcTarget = videoInfo->rcSource; + videoInfo->dwBitRate = DWORD(8 + * frameSize + * fps.num() + / fps.den()); + videoInfo->AvgTimePerFrame = REFERENCE_TIME(TIME_BASE + * fps.den() + / fps.num()); + + // Initialize bitmap header. + videoInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + videoInfo->bmiHeader.biWidth = format.width(); + videoInfo->bmiHeader.biHeight = format.height(); + videoInfo->bmiHeader.biPlanes = 1; + videoInfo->bmiHeader.biBitCount = WORD(format.bpp()); + videoInfo->bmiHeader.biCompression = compressionFromFormat(format.fourcc()); + videoInfo->bmiHeader.biSizeImage = DWORD(format.size()); + + switch (videoInfo->bmiHeader.biCompression) { + case BI_RGB: + if (videoInfo->bmiHeader.biBitCount == 8) { + videoInfo->bmiHeader.biClrUsed = iPALETTE_COLORS; + + if (HDC hdc = GetDC(nullptr)) { + PALETTEENTRY palette[iPALETTE_COLORS]; + + if (GetSystemPaletteEntries(hdc, + 0, + iPALETTE_COLORS, + palette)) + for (int i = 0; i < iPALETTE_COLORS; i++) { + videoInfo->TrueColorInfo.bmiColors[i].rgbRed = palette[i].peRed; + videoInfo->TrueColorInfo.bmiColors[i].rgbBlue = palette[i].peBlue; + videoInfo->TrueColorInfo.bmiColors[i].rgbGreen = palette[i].peGreen; + videoInfo->TrueColorInfo.bmiColors[i].rgbReserved = 0; + } + + ReleaseDC(nullptr, hdc); + } + } + + break; + + case BI_BITFIELDS: { + auto masks = VideoFormatSpecsPrivate::byPixelFormat(format.fourcc())->masks; + + if (masks) + memcpy(videoInfo->TrueColorInfo.dwBitMasks, masks, 3); + } + + break; + + default: + break; + } + + auto mediaType = + reinterpret_cast(CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE))); + memset(mediaType, 0, sizeof(AM_MEDIA_TYPE)); + + // Initialize media type. + mediaType->majortype = MEDIATYPE_Video; + mediaType->subtype = subtype; + mediaType->bFixedSizeSamples = TRUE; + mediaType->bTemporalCompression = FALSE; + mediaType->lSampleSize = ULONG(frameSize); + mediaType->formattype = FORMAT_VideoInfo; + mediaType->cbFormat = sizeof(VIDEOINFO); + mediaType->pbFormat = reinterpret_cast(videoInfo); + + return mediaType; +} + +AkVCam::VideoFormat AkVCam::formatFromMediaType(const AM_MEDIA_TYPE *mediaType) +{ + if (!mediaType) + return VideoFormat(); + + if (!IsEqualGUID(mediaType->majortype, MEDIATYPE_Video)) + return VideoFormat(); + + if (!isSubTypeSupported(mediaType->subtype)) + return VideoFormat(); + + if (!mediaType->pbFormat) + return VideoFormat(); + + if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) { + auto format = reinterpret_cast(mediaType->pbFormat); + auto fps = Fraction {uint32_t(TIME_BASE), + uint32_t(format->AvgTimePerFrame)}; + + return VideoFormat(formatFromGuid(mediaType->subtype), + format->bmiHeader.biWidth, + std::abs(format->bmiHeader.biHeight), + {fps}); + } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) { + auto format = reinterpret_cast(mediaType->pbFormat); + auto fps = Fraction {uint32_t(TIME_BASE), + uint32_t(format->AvgTimePerFrame)}; + + return VideoFormat(formatFromGuid(mediaType->subtype), + format->bmiHeader.biWidth, + std::abs(format->bmiHeader.biHeight), + {fps}); + } + + return VideoFormat(); +} + +bool AkVCam::isEqualMediaType(const AM_MEDIA_TYPE *mediaType1, + const AM_MEDIA_TYPE *mediaType2, + bool exact) +{ + if (mediaType1 == mediaType2) + return true; + + if (!mediaType1 || !mediaType2) + return false; + + if (!IsEqualGUID(mediaType1->majortype, mediaType2->majortype) + || !IsEqualGUID(mediaType1->subtype, mediaType2->subtype) + || !IsEqualGUID(mediaType1->formattype, mediaType2->formattype)) + return false; + + if (mediaType1->pbFormat == mediaType2->pbFormat) + return true; + + if (exact) + return memcmp(mediaType1->pbFormat, + mediaType2->pbFormat, + mediaType1->cbFormat) == 0; + + if (IsEqualGUID(mediaType1->formattype, FORMAT_VideoInfo)) { + auto format1 = reinterpret_cast(mediaType1->pbFormat); + auto format2 = reinterpret_cast(mediaType2->pbFormat); + + if (format1->bmiHeader.biWidth == format2->bmiHeader.biWidth + && format1->bmiHeader.biHeight == format2->bmiHeader.biHeight) + return true; + } else if (IsEqualGUID(mediaType1->formattype, FORMAT_VideoInfo2)) { + auto format1 = reinterpret_cast(mediaType1->pbFormat); + auto format2 = reinterpret_cast(mediaType2->pbFormat); + + if (format1->bmiHeader.biWidth == format2->bmiHeader.biWidth + && format1->bmiHeader.biHeight == format2->bmiHeader.biHeight) + return true; + } + + return false; +} + +bool AkVCam::copyMediaType(AM_MEDIA_TYPE *dstMediaType, + const AM_MEDIA_TYPE *srcMediaType) +{ + if (!dstMediaType) + return false; + + if (!srcMediaType) { + memset(dstMediaType, 0, sizeof(AM_MEDIA_TYPE)); + + return false; + } + + memcpy(dstMediaType, srcMediaType, sizeof(AM_MEDIA_TYPE)); + + if (dstMediaType->cbFormat && dstMediaType->pbFormat) { + dstMediaType->pbFormat = + reinterpret_cast(CoTaskMemAlloc(dstMediaType->cbFormat)); + memcpy(dstMediaType->pbFormat, + srcMediaType->pbFormat, + dstMediaType->cbFormat); + } + + return true; +} + +AM_MEDIA_TYPE *AkVCam::createMediaType(const AM_MEDIA_TYPE *mediaType) +{ + if (!mediaType) + return nullptr; + + auto newMediaType = + reinterpret_cast(CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE))); + memcpy(newMediaType, mediaType, sizeof(AM_MEDIA_TYPE)); + + if (newMediaType->cbFormat && newMediaType->pbFormat) { + newMediaType->pbFormat = + reinterpret_cast(CoTaskMemAlloc(newMediaType->cbFormat)); + memcpy(newMediaType->pbFormat, + mediaType->pbFormat, + newMediaType->cbFormat); + } + + return newMediaType; +} + +void AkVCam::deleteMediaType(AM_MEDIA_TYPE **mediaType) +{ + if (!mediaType || !*mediaType) + return; + + auto format = (*mediaType)->pbFormat; + + if (format && (*mediaType)->cbFormat) + CoTaskMemFree(format); + + CoTaskMemFree(*mediaType); + *mediaType = nullptr; +} + +bool AkVCam::containsMediaType(const AM_MEDIA_TYPE *mediaType, + IEnumMediaTypes *mediaTypes) +{ + AM_MEDIA_TYPE *mt = nullptr; + mediaTypes->Reset(); + auto isEqual = false; + + while (mediaTypes->Next(1, &mt, nullptr) == S_OK) { + isEqual = isEqualMediaType(mt, mediaType); + deleteMediaType(&mt); + + if (isEqual) + break; + } + + return isEqual; +} + +std::string AkVCam::stringFromMajorType(const GUID &majorType) +{ + static const std::map mtToStr { + {GUID_NULL , "GUID_NULL" }, + {MEDIATYPE_AnalogAudio , "MEDIATYPE_AnalogAudio" }, + {MEDIATYPE_AnalogVideo , "MEDIATYPE_AnalogVideo" }, + {MEDIATYPE_Audio , "MEDIATYPE_Audio" }, + {MEDIATYPE_AUXLine21Data, "MEDIATYPE_AUXLine21Data"}, + {MEDIATYPE_File , "MEDIATYPE_File" }, + {MEDIATYPE_Interleaved , "MEDIATYPE_Interleaved" }, + {MEDIATYPE_LMRT , "MEDIATYPE_LMRT" }, + {MEDIATYPE_Midi , "MEDIATYPE_Midi" }, + {MEDIATYPE_MPEG2_PES , "MEDIATYPE_MPEG2_PES" }, + {MEDIATYPE_ScriptCommand, "MEDIATYPE_ScriptCommand"}, + {MEDIATYPE_Stream , "MEDIATYPE_Stream" }, + {MEDIATYPE_Text , "MEDIATYPE_Text" }, + {MEDIATYPE_Timecode , "MEDIATYPE_Timecode" }, + {MEDIATYPE_URL_STREAM , "MEDIATYPE_URL_STREAM" }, + {MEDIATYPE_VBI , "MEDIATYPE_VBI" }, + {MEDIATYPE_Video , "MEDIATYPE_Video" } + }; + + for (auto &mediaType: mtToStr) + if (IsEqualGUID(mediaType.first, majorType)) + return mediaType.second; + + return stringFromIid(majorType); +} + +std::string AkVCam::stringFromSubType(const GUID &subType) +{ + static const std::map mstToStr { + {GUID_NULL , "GUID_NULL" }, + {MEDIASUBTYPE_RGB1 , "MEDIASUBTYPE_RGB1" }, + {MEDIASUBTYPE_RGB4 , "MEDIASUBTYPE_RGB4" }, + {MEDIASUBTYPE_RGB8 , "MEDIASUBTYPE_RGB8" }, + {MEDIASUBTYPE_RGB555 , "MEDIASUBTYPE_RGB555" }, + {MEDIASUBTYPE_RGB565 , "MEDIASUBTYPE_RGB565" }, + {MEDIASUBTYPE_RGB24 , "MEDIASUBTYPE_RGB24" }, + {MEDIASUBTYPE_RGB32 , "MEDIASUBTYPE_RGB32" }, + {MEDIASUBTYPE_ARGB1555 , "MEDIASUBTYPE_ARGB1555" }, + {MEDIASUBTYPE_ARGB32 , "MEDIASUBTYPE_ARGB32" }, + {MEDIASUBTYPE_ARGB4444 , "MEDIASUBTYPE_ARGB4444" }, + {MEDIASUBTYPE_A2R10G10B10, "MEDIASUBTYPE_A2R10G10B10"}, + {MEDIASUBTYPE_A2B10G10R10, "MEDIASUBTYPE_A2B10G10R10"}, + {MEDIASUBTYPE_AYUV , "MEDIASUBTYPE_AYUV" }, + {MEDIASUBTYPE_YUY2 , "MEDIASUBTYPE_YUY2" }, + {MEDIASUBTYPE_UYVY , "MEDIASUBTYPE_UYVY" }, + {MEDIASUBTYPE_IMC1 , "MEDIASUBTYPE_IMC1" }, + {MEDIASUBTYPE_IMC3 , "MEDIASUBTYPE_IMC3" }, + {MEDIASUBTYPE_IMC2 , "MEDIASUBTYPE_IMC2" }, + {MEDIASUBTYPE_IMC4 , "MEDIASUBTYPE_IMC4" }, + {MEDIASUBTYPE_YV12 , "MEDIASUBTYPE_YV12" }, + {MEDIASUBTYPE_NV12 , "MEDIASUBTYPE_NV12" }, + {MEDIASUBTYPE_IF09 , "MEDIASUBTYPE_IF09" }, + {MEDIASUBTYPE_IYUV , "MEDIASUBTYPE_IYUV" }, + {MEDIASUBTYPE_Y211 , "MEDIASUBTYPE_Y211" }, + {MEDIASUBTYPE_Y411 , "MEDIASUBTYPE_Y411" }, + {MEDIASUBTYPE_Y41P , "MEDIASUBTYPE_Y41P" }, + {MEDIASUBTYPE_YVU9 , "MEDIASUBTYPE_YVU9" }, + {MEDIASUBTYPE_YVYU , "MEDIASUBTYPE_YVYU" } + }; + + for (auto &mediaType: mstToStr) + if (IsEqualGUID(mediaType.first, subType)) + return mediaType.second; + + return stringFromIid(subType); +} + +std::string AkVCam::stringFromFormatType(const GUID &formatType) +{ + static const std::map ftToStr { + {GUID_NULL , "GUID_NULL" }, + {FORMAT_DvInfo , "FORMAT_DvInfo" }, + {FORMAT_MPEG2Video , "FORMAT_MPEG2Video" }, + {FORMAT_MPEGStreams , "FORMAT_MPEGStreams" }, + {FORMAT_MPEGVideo , "FORMAT_MPEGVideo" }, + {FORMAT_None , "FORMAT_None" }, + {FORMAT_VideoInfo , "FORMAT_VideoInfo" }, + {FORMAT_VideoInfo2 , "FORMAT_VideoInfo2" }, + {FORMAT_WaveFormatEx, "FORMAT_WaveFormatEx"} + }; + + for (auto &mediaType: ftToStr) + if (IsEqualGUID(mediaType.first, formatType)) + return mediaType.second; + + return stringFromIid(formatType); +} + +std::string AkVCam::stringFromMediaType(const AM_MEDIA_TYPE *mediaType) +{ + if (!mediaType) + return std::string("MediaType(NULL)"); + + std::stringstream ss; + ss << "MediaType(" + << stringFromMajorType(mediaType->majortype) + << ", " + << stringFromSubType(mediaType->subtype) + << ", " + << stringFromFormatType(mediaType->formattype); + + if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) { + auto format = reinterpret_cast(mediaType->pbFormat); + ss << ", " + << format->bmiHeader.biWidth + << ", " + << format->bmiHeader.biHeight; + } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) { + auto format = reinterpret_cast(mediaType->pbFormat); + ss << ", " + << format->bmiHeader.biWidth + << ", " + << format->bmiHeader.biHeight; + } + + ss << ")"; + + return ss.str(); +} + +std::string AkVCam::stringFromMediaSample(IMediaSample *mediaSample) +{ + if (!mediaSample) + return std::string("MediaSample(NULL)"); + + BYTE *buffer = nullptr; + mediaSample->GetPointer(&buffer); + auto bufferSize = mediaSample->GetSize(); + AM_MEDIA_TYPE *mediaType = nullptr; + mediaSample->GetMediaType(&mediaType); + REFERENCE_TIME timeStart = 0; + REFERENCE_TIME timeEnd = 0; + mediaSample->GetTime(&timeStart, &timeEnd); + REFERENCE_TIME mediaTimeStart = 0; + REFERENCE_TIME mediaTimeEnd = 0; + mediaSample->GetMediaTime(&mediaTimeStart, &mediaTimeEnd); + auto discontinuity = mediaSample->IsDiscontinuity() == S_OK; + auto preroll = mediaSample->IsPreroll() == S_OK; + auto syncPoint = mediaSample->IsSyncPoint() == S_OK; + auto dataLength = mediaSample->GetActualDataLength(); + + std::stringstream ss; + ss << "MediaSample(" << std::endl + << " Buffer: " << size_t(buffer) << std::endl + << " Buffer Size: " << bufferSize << std::endl + << " Media Type: " << stringFromMediaType(mediaType) << std::endl + << " Time: (" << timeStart << ", " << timeEnd << ")" << std::endl + << " Media Time: (" << mediaTimeStart << ", " << mediaTimeEnd << ")" << std::endl + << " Discontinuity: " << discontinuity << std::endl + << " Preroll: " << preroll << std::endl + << " Sync Point: " << syncPoint << std::endl + << " Data Length: " << dataLength << std::endl + << ")"; + + deleteMediaType(&mediaType); + + return ss.str(); +} + +LONG AkVCam::regGetValue(HKEY hkey, + LPCWSTR lpSubKey, + LPCWSTR lpValue, + DWORD dwFlags, + LPDWORD pdwType, + PVOID pvData, + LPDWORD pcbData) +{ + HKEY key = nullptr; + auto result = RegOpenKeyEx(hkey, + lpSubKey, + 0, + KEY_READ | KEY_WOW64_64KEY, + &key); + + if (result != ERROR_SUCCESS) + return result; + + result = RegGetValue(key, + nullptr, + lpValue, + dwFlags, + pdwType, + pvData, + pcbData); + RegCloseKey(key); + + return result; +} + +std::vector AkVCam::listRegisteredCameras(HINSTANCE hinstDLL) +{ + WCHAR *strIID = nullptr; + StringFromIID(CLSID_VideoInputDeviceCategory, &strIID); + + std::wstringstream ss; + ss << L"CLSID\\" + << strIID + << L"\\Instance"; + CoTaskMemFree(strIID); + + HKEY key = nullptr; + auto result = RegOpenKeyEx(HKEY_CLASSES_ROOT, + ss.str().c_str(), + 0, + MAXIMUM_ALLOWED, + &key); + + if (result != ERROR_SUCCESS) + return {}; + + DWORD subkeys = 0; + + result = RegQueryInfoKey(key, + nullptr, + nullptr, + nullptr, + &subkeys, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr); + + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + + return {}; + } + + std::vector cameras; + FILETIME lastWrite; + + for (DWORD i = 0; i < subkeys; i++) { + TCHAR subKey[MAX_PATH]; + memset(subKey, 0, MAX_PATH * sizeof(TCHAR)); + DWORD subKeyLen = MAX_PATH; + result = RegEnumKeyEx(key, + i, + subKey, + &subKeyLen, + nullptr, + nullptr, + nullptr, + &lastWrite); + + if (result != ERROR_SUCCESS) + continue; + + std::wstringstream ss; + ss << L"CLSID\\" << subKey << L"\\InprocServer32"; + WCHAR path[MAX_PATH]; + memset(path, 0, MAX_PATH * sizeof(WCHAR)); + DWORD pathSize = MAX_PATH; + + if (RegGetValue(HKEY_CLASSES_ROOT, + ss.str().c_str(), + nullptr, + RRF_RT_REG_SZ, + nullptr, + path, + &pathSize) == ERROR_SUCCESS) { + WCHAR modulePath[MAX_PATH]; + memset(modulePath, 0, MAX_PATH * sizeof(WCHAR)); + GetModuleFileName(hinstDLL, modulePath, MAX_PATH); + + if (!lstrcmpi(path, modulePath)) { + CLSID clsid; + memset(&clsid, 0, sizeof(CLSID)); + CLSIDFromString(subKey, &clsid); + cameras.push_back(clsid); + } + } + } + + RegCloseKey(key); + + return cameras; +} + +DWORD AkVCam::camerasCount() +{ + DWORD nCameras = 0; + DWORD nCamerasSize = sizeof(DWORD); + + regGetValue(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras", + L"size", + RRF_RT_REG_DWORD, + nullptr, + &nCameras, + &nCamerasSize); + + return nCameras; +} + +std::wstring AkVCam::createDevicePath() +{ + // List device paths in use. + std::vector cameraPaths; + + for (DWORD i = 0; i < camerasCount(); i++) + cameraPaths.push_back(cameraPath(i)); + + const int maxId = 64; + + for (int i = 0; i < maxId; i++) { + /* There are no rules for device paths in Windows. Just append an + * incremental index to a common prefix. + */ + auto path = DSHOW_PLUGIN_DEVICE_PREFIX_L + std::to_wstring(i); + + // Check if the path is being used, if not return it. + if (std::find(cameraPaths.begin(), + cameraPaths.end(), + path) == cameraPaths.end()) + return path; + } + + return {}; +} + +int AkVCam::cameraFromId(const std::wstring &path) +{ + auto clsid = createClsidFromStr(path); + + return cameraFromId(clsid); +} + +int AkVCam::cameraFromId(const CLSID &clsid) +{ + for (DWORD i = 0; i < camerasCount(); i++) { + auto cameraClsid = createClsidFromStr(cameraPath(i)); + + if (IsEqualCLSID(cameraClsid, clsid) && !cameraFormats(i).empty()) + return int(i); + } + + return -1; +} + +bool AkVCam::cameraExists(const std::string &path) +{ + return cameraExists(std::wstring(path.begin(), path.end())); +} + +bool AkVCam::cameraExists(const std::wstring &path) +{ + for (DWORD i = 0; i < camerasCount(); i++) + if (cameraPath(i) == path) + return true; + + return false; +} + +std::wstring AkVCam::cameraDescription(DWORD cameraIndex) +{ + std::wstringstream ss; + ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + << cameraIndex + 1; + + WCHAR description[1024]; + DWORD descriptionSize = 1024 * sizeof(WCHAR); + memset(description, 0, descriptionSize); + + if (regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"description", + RRF_RT_REG_SZ, + nullptr, + &description, + &descriptionSize) != ERROR_SUCCESS) + return std::wstring(); + + return std::wstring(description); +} + +std::wstring AkVCam::cameraPath(DWORD cameraIndex) +{ + std::wstringstream ss; + ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + << cameraIndex + 1; + + WCHAR path[1024]; + DWORD pathSize = 1024 * sizeof(WCHAR); + memset(path, 0, pathSize); + + if (regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"path", + RRF_RT_REG_SZ, + nullptr, + &path, + &pathSize) != ERROR_SUCCESS) + return std::wstring(); + + return std::wstring(path); +} + +std::wstring AkVCam::cameraPath(const CLSID &clsid) +{ + auto camera = cameraFromId(clsid); + + if (camera < 0) + return {}; + + return cameraPath(DWORD(camera)); +} + +DWORD AkVCam::formatsCount(DWORD cameraIndex) +{ + std::wstringstream ss; + ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + << cameraIndex + 1 + << L"\\Formats"; + + DWORD nFormats; + DWORD nFormatsSize = sizeof(DWORD); + memset(&nFormats, 0, nFormatsSize); + + regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"size", + RRF_RT_REG_DWORD, + nullptr, + &nFormats, + &nFormatsSize); + + return nFormats; +} + +AkVCam::VideoFormat AkVCam::cameraFormat(DWORD cameraIndex, DWORD formatIndex) +{ + std::wstringstream ss; + ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + << cameraIndex + 1 + << L"\\Formats\\" + << formatIndex + 1; + + WCHAR formatStr[1024]; + DWORD variableSize = 1024 * sizeof(WCHAR); + memset(formatStr, 0, variableSize); + + if (regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"format", + RRF_RT_REG_SZ, + nullptr, + &formatStr, + &variableSize) != ERROR_SUCCESS) + return {}; + + DWORD width = 0; + variableSize = sizeof(DWORD); + + if (regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"width", + RRF_RT_REG_DWORD, + nullptr, + &width, + &variableSize) != ERROR_SUCCESS) + return {}; + + DWORD height = 0; + variableSize = sizeof(DWORD); + + if (regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"height", + RRF_RT_REG_DWORD, + nullptr, + &height, + &variableSize) != ERROR_SUCCESS) + return {}; + + WCHAR fpsStr[1024]; + variableSize = 1024 * sizeof(WCHAR); + memset(fpsStr, 0, variableSize); + + if (regGetValue(HKEY_LOCAL_MACHINE, + ss.str().c_str(), + L"fps", + RRF_RT_REG_SZ, + nullptr, + &fpsStr, + &variableSize) != ERROR_SUCCESS) + return {}; + + std::wstring format(formatStr); + auto fourcc = VideoFormat::fourccFromString(std::string(format.begin(), + format.end())); + + return VideoFormat(fourcc, + int(width), + int(height), + {Fraction(fpsStr)}); +} + +std::vector AkVCam::cameraFormats(DWORD cameraIndex) +{ + std::vector formats; + + for (DWORD i = 0; i < formatsCount(cameraIndex); i++) { + auto videoFormat = cameraFormat(cameraIndex, i); + + if (videoFormat) + formats.push_back(videoFormat); + } + + return formats; +} diff --git a/dshow/PlatformUtils/src/utils.h b/dshow/PlatformUtils/src/utils.h new file mode 100644 index 0000000..b470b73 --- /dev/null +++ b/dshow/PlatformUtils/src/utils.h @@ -0,0 +1,97 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PLATFORM_UTILS_H +#define PLATFORM_UTILS_H + +#include +#include + +#include "VCamUtils/src/image/videoformattypes.h" +#include "VCamUtils/src/logger/logger.h" + +#define AkLogInterface(interface, instance) \ + AkLoggerLog("Returning ", #interface, "(", instance, ")") + +#define AkLogMethod() \ + AkLoggerLog(AK_CUR_INTERFACE, "(", this, ")::", __FUNCTION__, "()") + +namespace AkVCam +{ + class VideoFormat; + + BOOL isWow64(); + std::wstring tempPath(); + std::wstring programFilesPath(); + std::wstring moduleFileNameW(HINSTANCE hinstDLL); + std::string moduleFileName(HINSTANCE hinstDLL); + std::wstring errorToStringW(DWORD errorCode); + std::string errorToString(DWORD errorCode); + CLSID createClsidFromStr(const std::string &str); + CLSID createClsidFromStr(const std::wstring &str); + std::wstring createClsidWStrFromStr(const std::string &str); + std::wstring createClsidWStrFromStr(const std::wstring &str); + std::string stringFromIid(const IID &iid); + std::wstring wstringFromIid(const IID &iid); + std::string stringFromResult(HRESULT result); + std::string stringFromClsid(const CLSID &clsid); + wchar_t *wcharStrFromWStr(const std::wstring &wstr); + FourCC formatFromGuid(const GUID &guid); + const GUID &guidFromFormat(FourCC format); + DWORD compressionFromFormat(FourCC format); + bool isSubTypeSupported(const GUID &subType); + AM_MEDIA_TYPE *mediaTypeFromFormat(const VideoFormat &format); + VideoFormat formatFromMediaType(const AM_MEDIA_TYPE *mediaType); + bool isEqualMediaType(const AM_MEDIA_TYPE *mediaType1, + const AM_MEDIA_TYPE *mediaType2, + bool exact=false); + bool copyMediaType(AM_MEDIA_TYPE *dstMediaType, + const AM_MEDIA_TYPE *srcMediaType); + AM_MEDIA_TYPE *createMediaType(const AM_MEDIA_TYPE *mediaType); + void deleteMediaType(AM_MEDIA_TYPE **mediaType); + bool containsMediaType(const AM_MEDIA_TYPE *mediaType, + IEnumMediaTypes *mediaTypes); + std::string stringFromMajorType(const GUID &majorType); + std::string stringFromSubType(const GUID &subType); + std::string stringFromFormatType(const GUID &formatType); + std::string stringFromMediaType(const AM_MEDIA_TYPE *mediaType); + std::string stringFromMediaSample(IMediaSample *mediaSample); + LONG regGetValue(HKEY hkey, + LPCWSTR lpSubKey, + LPCWSTR lpValue, + DWORD dwFlags, + LPDWORD pdwType, + PVOID pvData, + LPDWORD pcbData); + std::vector listRegisteredCameras(HINSTANCE hinstDLL); + DWORD camerasCount(); + std::wstring createDevicePath(); + int cameraFromId(const std::wstring &path); + int cameraFromId(const CLSID &clsid); + bool cameraExists(const std::string &path); + bool cameraExists(const std::wstring &path); + std::wstring cameraDescription(DWORD cameraIndex); + std::wstring cameraPath(DWORD cameraIndex); + std::wstring cameraPath(const CLSID &clsid); + DWORD formatsCount(DWORD cameraIndex); + VideoFormat cameraFormat(DWORD cameraIndex, DWORD formatIndex); + std::vector cameraFormats(DWORD cameraIndex); +} + +#endif // PLATFORM_UTILS_H diff --git a/dshow/VCamIPC/VCamIPC.pro b/dshow/VCamIPC/VCamIPC.pro new file mode 100644 index 0000000..b8694e3 --- /dev/null +++ b/dshow/VCamIPC/VCamIPC.pro @@ -0,0 +1,65 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../dshow.pri) + +CONFIG += \ + staticlib \ + create_prl \ + no_install_prl +CONFIG -= qt + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +TARGET = VCamIPC + +TEMPLATE = lib + +LIBS = \ + -L$${OUT_PWD}/../PlatformUtils/$${BIN_DIR} -lPlatformUtils \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -ladvapi32 \ + -lkernel32 \ + -lpsapi \ + -lrstrmgr + +win32-g++: LIBS += -lssp + +SOURCES = \ + src/ipcbridge.cpp + +HEADERS = \ + ../../ipcbridge.h + +INCLUDEPATH += \ + .. \ + ../.. + +DEFINES += \ + DSHOW_PLUGIN_ARCH=\"\\\"$$normalizedArch(TARGET_ARCH)\\\"\" \ + DSHOW_PLUGIN_ARCH_L=\"L\\\"$$normalizedArch(TARGET_ARCH)\\\"\" diff --git a/dshow/VCamIPC/src/ipcbridge.cpp b/dshow/VCamIPC/src/ipcbridge.cpp new file mode 100644 index 0000000..c13b483 --- /dev/null +++ b/dshow/VCamIPC/src/ipcbridge.cpp @@ -0,0 +1,2120 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PlatformUtils/src/messageserver.h" +#include "PlatformUtils/src/mutex.h" +#include "PlatformUtils/src/sharedmemory.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/ipcbridge.h" +#include "VCamUtils/src/logger/logger.h" + +#define AkIpcBridgeLogMethod() \ + AkLoggerLog("IpcBridge::", __FUNCTION__, "()") + +#define AkIpcBridgePrivateLogMethod() \ + AkLoggerLog("IpcBridgePrivate::", __FUNCTION__, "()") + +namespace AkVCam +{ + typedef std::shared_ptr MonikerPtr; + typedef std::shared_ptr BaseFilterPtr; + typedef std::shared_ptr PropertyBagPtr; + typedef std::shared_ptr PinPtr; + typedef std::shared_ptr MediaTypePtr; + + struct DeviceSharedProperties + { + SharedMemory sharedMemory; + Mutex mutex; + }; + + class IpcBridgePrivate + { + public: + IpcBridge *self; + std::map m_devices; + std::map m_messageHandlers; + std::vector m_broadcasting; + std::map m_options; + MessageServer m_messageServer; + MessageServer m_mainServer; + SharedMemory m_sharedMemory; + Mutex m_globalMutex; + std::string m_portName; + std::wstring m_error; + bool m_asClient; + + explicit IpcBridgePrivate(IpcBridge *self); + ~IpcBridgePrivate(); + + static inline std::vector *driverPaths(); + std::vector listCameras() const; + BaseFilterPtr filter(IMoniker *moniker) const; + PropertyBagPtr propertyBag(IMoniker *moniker) const; + bool isVirtualCamera(const MonikerPtr &moniker) const; + bool isVirtualCamera(IBaseFilter *baseFilter) const; + std::string cameraPath(const MonikerPtr &moniker) const; + std::string cameraPath(IPropertyBag *propertyBag) const; + std::wstring cameraDescription(const MonikerPtr &moniker) const; + std::wstring cameraDescription(IPropertyBag *propertyBag) const; + std::vector enumPins(IBaseFilter *baseFilter) const; + std::vector enumVideoFormats(IPin *pin) const; + std::vector findFiles(const std::wstring &path) const; + std::vector findFiles(const std::string &path, + const std::string &fileName) const; + std::vector findFiles(const std::wstring &path, + const std::wstring &fileName) const; + std::wstring regAddLine(const std::wstring &key, + const std::wstring &value, + const std::wstring &data, + BOOL wow=false) const; + std::wstring regAddLine(const std::wstring &key, + const std::wstring &value, + int data, + BOOL wow=false) const; + std::wstring regDeleteLine(const std::wstring &key, + BOOL wow=false) const; + std::wstring regDeleteLine(const std::wstring &key, + const std::wstring &value, + BOOL wow=false) const; + std::wstring regMoveLine(const std::wstring &fromKey, + const std::wstring &toKey, + BOOL wow=false) const; + std::wstring dirname(const std::wstring &path) const; + void updateDeviceSharedProperties(); + void updateDeviceSharedProperties(const std::string &deviceId, + const std::string &owner); + std::wstring locateDriverPath() const; + static void pipeStateChanged(void *userData, + MessageServer::PipeState state); + + // Message handling methods + void isAlive(Message *message); + void frameReady(Message *message); + void setBroadcasting(Message *message); + void setMirror(Message *message); + void setScaling(Message *message); + void setAspectRatio(Message *message); + void setSwapRgb(Message *message); + void listenerAdd(Message *message); + void listenerRemove(Message *message); + + // Execute commands with elevated privileges. + int sudo(const std::vector ¶meters, + const std::wstring &directory={}, + bool show=false); + }; + + static const int maxFrameWidth = 1920; + static const int maxFrameHeight = 1080; + static const size_t maxFrameSize = maxFrameWidth * maxFrameHeight; + static const size_t maxBufferSize = sizeof(Frame) + 3 * maxFrameSize; +} + +AkVCam::IpcBridge::IpcBridge() +{ + AkIpcBridgeLogMethod(); + this->d = new IpcBridgePrivate(this); +} + +AkVCam::IpcBridge::~IpcBridge() +{ + delete this->d; +} + +std::wstring AkVCam::IpcBridge::errorMessage() const +{ + return this->d->m_error; +} + +void AkVCam::IpcBridge::setOption(const std::string &key, const std::string &value) +{ + AkIpcBridgeLogMethod(); + + if (value.empty()) + this->d->m_options.erase(key); + else + this->d->m_options[key] = value; +} + +std::vector AkVCam::IpcBridge::driverPaths() const +{ + AkIpcBridgeLogMethod(); + + return *this->d->driverPaths(); +} + +void AkVCam::IpcBridge::setDriverPaths(const std::vector &driverPaths) +{ + AkIpcBridgeLogMethod(); + *this->d->driverPaths() = driverPaths; +} + +std::vector AkVCam::IpcBridge::availableDrivers() const +{ + return {"AkVirtualCamera"}; +} + +std::string AkVCam::IpcBridge::driver() const +{ + return {"AkVirtualCamera"}; +} + +bool AkVCam::IpcBridge::setDriver(const std::string &driver) +{ + return driver == "AkVirtualCamera"; +} + +std::vector AkVCam::IpcBridge::availableRootMethods() const +{ + return {"runas"}; +} + +std::string AkVCam::IpcBridge::rootMethod() const +{ + return {"runas"}; +} + +bool AkVCam::IpcBridge::setRootMethod(const std::string &rootMethod) +{ + return rootMethod == "runas"; +} + +void AkVCam::IpcBridge::connectService(bool asClient) +{ + AkIpcBridgeLogMethod(); + this->d->m_asClient = asClient; + this->d->m_mainServer.start(); +} + +void AkVCam::IpcBridge::disconnectService() +{ + AkIpcBridgeLogMethod(); + this->d->m_mainServer.stop(true); + this->d->m_asClient = false; +} + +bool AkVCam::IpcBridge::registerPeer(bool asClient) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_REQUEST_PORT; + message.dataSize = sizeof(MsgRequestPort); + auto requestData = messageData(&message); + requestData->client = asClient; + + if (!MessageServer::sendMessage(L"\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME_L, + &message)) + return false; + + std::string portName(requestData->port); + auto pipeName = "\\\\.\\pipe\\" + portName; + this->d->m_messageServer.setPipeName(std::wstring(pipeName.begin(), + pipeName.end())); + this->d->m_messageServer.setHandlers(this->d->m_messageHandlers); + AkLoggerLog("Recommended port name: ", portName); + + if (!this->d->m_messageServer.start()) { + AkLoggerLog("Can't start message server"); + + return false; + } + + message.clear(); + message.messageId = AKVCAM_ASSISTANT_MSG_ADD_PORT; + message.dataSize = sizeof(MsgAddPort); + auto addData = messageData(&message); + memcpy(addData->port, + portName.c_str(), + (std::min)(portName.size(), MAX_STRING)); + memcpy(addData->pipeName, + pipeName.c_str(), + (std::min)(pipeName.size(), MAX_STRING)); + + AkLoggerLog("Registering port name: ", portName); + + if (!MessageServer::sendMessage(L"\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME_L, + &message)) { + this->d->m_messageServer.stop(true); + + return false; + } + + if (!addData->status) { + this->d->m_messageServer.stop(true); + + return false; + } + + this->d->m_sharedMemory.setName(L"Local\\" + + std::wstring(portName.begin(), + portName.end()) + + L".data"); + this->d->m_globalMutex = Mutex(std::wstring(portName.begin(), + portName.end()) + + L".mutex"); + this->d->m_portName = portName; + AkLoggerLog("Peer registered as ", portName); + + return true; +} + +void AkVCam::IpcBridge::unregisterPeer() +{ + AkIpcBridgeLogMethod(); + + if (this->d->m_portName.empty()) + return; + + this->d->m_sharedMemory.setName({}); + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_REMOVE_PORT; + message.dataSize = sizeof(MsgRemovePort); + auto data = messageData(&message); + memcpy(data->port, + this->d->m_portName.c_str(), + (std::min)(this->d->m_portName.size(), MAX_STRING)); + MessageServer::sendMessage(L"\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME_L, + &message); + this->d->m_messageServer.stop(true); + this->d->m_portName.clear(); +} + +std::vector AkVCam::IpcBridge::listDevices() const +{ + AkIpcBridgeLogMethod(); + std::vector devices; + + for (auto camera: this->d->listCameras()) + if (this->d->isVirtualCamera(camera)) + devices.push_back(this->d->cameraPath(camera)); + +#ifdef QT_DEBUG + AkLoggerLog("Devices:"); + + for (auto &device: devices) + AkLoggerLog(" ", device); +#endif + + return devices; +} + +std::wstring AkVCam::IpcBridge::description(const std::string &deviceId) const +{ + AkIpcBridgeLogMethod(); + + for (auto camera: this->d->listCameras()) { + auto propertyBag = this->d->propertyBag(camera.get()); + + if (this->d->isVirtualCamera(camera) + && this->d->cameraPath(propertyBag.get()) == deviceId) + return this->d->cameraDescription(propertyBag.get()); + } + + return {}; +} + +std::vector AkVCam::IpcBridge::supportedOutputPixelFormats() const +{ + return { + PixelFormatRGB32, + PixelFormatRGB24, + PixelFormatRGB16, + PixelFormatRGB15, + PixelFormatUYVY, + PixelFormatYUY2, + PixelFormatNV12 + }; +} + +AkVCam::PixelFormat AkVCam::IpcBridge::defaultOutputPixelFormat() const +{ + return PixelFormatYUY2; +} + +std::vector AkVCam::IpcBridge::formats(const std::string &deviceId) const +{ + AkIpcBridgeLogMethod(); + + std::vector formats; + + for (auto camera: this->d->listCameras()) { + auto baseFilter = this->d->filter(camera.get()); + + if (this->d->isVirtualCamera(baseFilter.get()) + && this->d->cameraPath(camera) == deviceId) { + auto pins = this->d->enumPins(baseFilter.get()); + + if (!pins.empty()) + formats = this->d->enumVideoFormats(pins[0].get()); + + break; + } + } + + return formats; +} + +std::string AkVCam::IpcBridge::broadcaster(const std::string &deviceId) const +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING; + message.dataSize = sizeof(MsgBroadcasting); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return {}; + + if (!data->status) + return {}; + + std::string broadcaster(data->broadcaster); + + AkLoggerLog("Device: ", deviceId); + AkLoggerLog("Broadcaster: ", broadcaster); + + return broadcaster; +} + +bool AkVCam::IpcBridge::isHorizontalMirrored(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING; + message.dataSize = sizeof(MsgMirroring); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return false; + + if (!data->status) + return false; + + return data->hmirror; +} + +bool AkVCam::IpcBridge::isVerticalMirrored(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_MIRRORING; + message.dataSize = sizeof(MsgMirroring); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return false; + + if (!data->status) + return false; + + return data->vmirror; +} + +AkVCam::Scaling AkVCam::IpcBridge::scalingMode(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SCALING; + message.dataSize = sizeof(MsgScaling); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return ScalingFast; + + if (!data->status) + return ScalingFast; + + return data->scaling; +} + +AkVCam::AspectRatio AkVCam::IpcBridge::aspectRatioMode(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_ASPECTRATIO; + message.dataSize = sizeof(MsgAspectRatio); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return AspectRatioIgnore; + + if (!data->status) + return AspectRatioIgnore; + + return data->aspect; +} + +bool AkVCam::IpcBridge::swapRgb(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SWAPRGB; + message.dataSize = sizeof(MsgSwapRgb); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return false; + + if (!data->status) + return false; + + return data->swap; +} + +std::vector AkVCam::IpcBridge::listeners(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_LISTENERS; + message.dataSize = sizeof(MsgListeners); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return {}; + + if (!data->status) + return {}; + + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER; + std::vector listeners; + + for (size_t i = 0; i < data->nlistener; i++) { + data->nlistener = i; + + if (!this->d->m_mainServer.sendMessage(&message)) + continue; + + if (!data->status) + continue; + + listeners.push_back(std::string(data->listener)); + } + + return listeners; +} + +std::vector AkVCam::IpcBridge::clientsPids() const +{ + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) + return {}; + + auto driverInstallPath = + programFilesPath() + L"\\" DSHOW_PLUGIN_NAME_L L".plugin"; + + std::vector pluginsPaths; + + for (auto path: this->d->findFiles(driverPath, + DSHOW_PLUGIN_NAME_L L".dll")) { + auto pluginPath = replace(path, driverPath, driverInstallPath); + pluginsPaths.push_back(pluginPath); + } + + std::vector pids; + DWORD sessionHnd = 0; + WCHAR sessionKey[CCH_RM_SESSION_KEY + 1]; + memset(sessionKey, 0, (CCH_RM_SESSION_KEY + 1) * sizeof(WCHAR)); + auto currentPid = GetCurrentProcessId(); + + if (SUCCEEDED(RmStartSession(&sessionHnd, 0, sessionKey))) { + std::vector resources; + + for (auto &plugin: pluginsPaths) + resources.push_back(plugin.c_str()); + + if (SUCCEEDED(RmRegisterResources(sessionHnd, + UINT(resources.size()), + resources.data(), + 0, + nullptr, + 0, + nullptr))) { + UINT nProcInfoNeeded = 0; + UINT nProcInfo = 0; + DWORD rebootReasons = 0; + + if (SUCCEEDED(RmGetList(sessionHnd, + &nProcInfoNeeded, + &nProcInfo, + nullptr, + &rebootReasons))) { + nProcInfo = nProcInfoNeeded; + nProcInfoNeeded = 0; + rebootReasons = 0; + std::vector affectedApps(nProcInfo); + + if (SUCCEEDED(RmGetList(sessionHnd, + &nProcInfoNeeded, + &nProcInfo, + affectedApps.data(), + &rebootReasons))) { + for (UINT i = 0; i < nProcInfo; i++) { + auto pid = affectedApps[i].Process.dwProcessId; + auto it = std::find(pids.begin(), pids.end(), pid); + + if (pid > 0 && it == pids.end() && pid != currentPid) + pids.push_back(pid); + } + } + } + } + + RmEndSession(sessionHnd); + } + + return pids; +} + +std::string AkVCam::IpcBridge::clientExe(uint64_t pid) const +{ + std::string exe; + auto processHnd = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, + FALSE, + DWORD(pid)); + if (processHnd) { + CHAR exeName[MAX_PATH]; + memset(exeName, 0, MAX_PATH * sizeof(CHAR)); + auto size = + GetModuleFileNameExA(processHnd, nullptr, exeName, MAX_PATH); + + if (size > 0) + exe = std::string(exeName, size); + + CloseHandle(processHnd); + } + + return exe; +} + +bool AkVCam::IpcBridge::needsRestart(Operation operation) const +{ + return operation == OperationDestroyAll + || (operation == OperationDestroy + && this->listDevices().size() == 1); +} + +bool AkVCam::IpcBridge::canApply(AkVCam::IpcBridge::Operation operation) const +{ + return this->clientsPids().empty() && !this->needsRestart(operation); +} + +std::string AkVCam::IpcBridge::deviceCreate(const std::wstring &description, + const std::vector &formats) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationCreate)) { + this->d->m_error = L"The driver is in use"; + + return {}; + } + + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) { + this->d->m_error = L"Driver not found"; + + return {}; + } + + // Create a device path for the new device and add it's entry. + auto devicePath = createDevicePath(); + + if (devicePath.empty()) { + this->d->m_error = L"Can't create a device"; + + return {}; + } + + std::wstringstream ss; + ss << L"@echo off" << std::endl; + ss << L"chcp " << GetACP() << std::endl; + + auto driverInstallPath = + programFilesPath() + L"\\" DSHOW_PLUGIN_NAME_L L".plugin"; + + // Copy all plugins + std::vector installPaths; + + for (auto path: this->d->findFiles(driverPath, + DSHOW_PLUGIN_NAME_L L".dll")) { + auto installPath = replace(path, driverPath, driverInstallPath); + + if (!isEqualFile(path, installPath)) + ss << L"mkdir \"" + << this->d->dirname(installPath) + << L"\"" + << std::endl + << L"copy /y \"" + << path + << L"\" \"" + << installPath + << L"\"" + << std::endl; + + installPaths.push_back(installPath); + } + + // Copy all services + std::vector assistantInstallPaths; + + for (auto path: this->d->findFiles(driverPath, + DSHOW_PLUGIN_ASSISTANT_NAME_L L".exe")) { + auto installPath = replace(path, driverPath, driverInstallPath); + + if (!isEqualFile(path, installPath)) + ss << L"mkdir \"" + << this->d->dirname(installPath) + << L"\"" + << std::endl + << L"copy /y \"" + << path + << L"\" \"" + << installPath + << L"\"" + << std::endl; + + assistantInstallPaths.push_back(installPath); + } + + // Copy shared files + for (auto path: this->d->findFiles(driverPath + L"/share")) { + auto installPath = replace(path, driverPath, driverInstallPath); + + if (!isEqualFile(path, installPath)) + ss << L"mkdir \"" + << this->d->dirname(installPath) + << L"\"" + << std::endl + << L"copy /y \"" + << path + << L"\" \"" + << installPath + << L"\"" + << std::endl; + } + + BOOL wow = isWow64(); + + // List cameras and create a line with the number of cameras. + auto nCameras = camerasCount(); + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras", + L"size", + int(nCameras + 1), + wow) + << std::endl; + + // Set camera path. + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1), + L"path", + devicePath, + wow) + << std::endl; + + // Set description. + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1), + L"description", + description, + wow) + << std::endl; + + // Set number of formats. + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1) + + L"\\Formats", + L"size", + int(formats.size()), + wow) + << std::endl; + + // Setup formats. + for (size_t i = 0; i < formats.size(); i++) { + auto videoFormat = formats[i]; + auto format = VideoFormat::wstringFromFourcc(videoFormat.fourcc()); + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"format", + format, + wow) + << std::endl; + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"width", + videoFormat.width(), + wow) + << std::endl; + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"height", + videoFormat.height(), + wow) + << std::endl; + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(nCameras + 1) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"fps", + videoFormat.minimumFrameRate().toWString(), + wow) + << std::endl; + } + + for (auto path: installPaths) + ss << L"regsvr32 /s \"" << path << L"\"" << std::endl; + + std::vector preferredArch; + + if (wow) + preferredArch.push_back(L"x64"); + + preferredArch.push_back(DSHOW_PLUGIN_ARCH_L); + + if (wcscmp(DSHOW_PLUGIN_ARCH_L, L"x64") == 0) + preferredArch.push_back(L"x32"); + + for (auto &arch: preferredArch) { + auto assistantPath = driverInstallPath + + L"\\" + + arch + + L"\\" DSHOW_PLUGIN_ASSISTANT_NAME_L L".exe"; + + if (std::find(assistantInstallPaths.begin(), + assistantInstallPaths.end(), + assistantPath) != assistantInstallPaths.end()) { + ss << "\"" << assistantPath << "\" --install" << std::endl; + + break; + } + } + + // Create the script. + auto temp = tempPath(); + auto scriptPath = std::string(temp.begin(), temp.end()) + + "\\device_create_" + + timeStamp() + + ".bat"; + std::wfstream script; + script.imbue(std::locale("")); + script.open(scriptPath, std::ios_base::out | std::ios_base::trunc); + + if (script.is_open()) { + script << ss.str(); + script.close(); + + // Execute the script with elevated privileges. + if (this->d->sudo({"cmd", "/c", scriptPath})) + devicePath.clear(); + + std::wstring wScriptPath(scriptPath.begin(), scriptPath.end()); + DeleteFile(wScriptPath.c_str()); + } else { + devicePath.clear(); + } + + return std::string(devicePath.begin(), devicePath.end()); +} + +bool AkVCam::IpcBridge::deviceEdit(const std::string &deviceId, + const std::wstring &description, + const std::vector &formats) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationEdit)) { + this->d->m_error = L"The driver is in use"; + + return {}; + } + + auto camera = cameraFromId(std::wstring(deviceId.begin(), deviceId.end())); + + if (camera < 0) + return false; + + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) { + this->d->m_error = L"Driver not found"; + + return false; + } + + std::wstringstream ss; + ss << L"@echo off" << std::endl; + ss << L"chcp " << GetACP() << std::endl; + + auto driverInstallPath = + programFilesPath() + L"\\" DSHOW_PLUGIN_NAME_L L".plugin"; + std::vector installPaths; + + for (auto path: this->d->findFiles(std::wstring(driverPath.begin(), + driverPath.end()), + DSHOW_PLUGIN_NAME_L L".dll")) { + auto installPath = replace(path, driverPath, driverInstallPath); + installPaths.push_back(installPath); + } + + BOOL wow = isWow64(); + + // Set camera path. + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera), + L"path", + std::wstring(deviceId.begin(), deviceId.end()), + wow) + << std::endl; + + // Set description. + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera), + L"description", + description, + wow) + << std::endl; + + // Set number of formats. + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera) + + L"\\Formats", + L"size", + int(formats.size()), + wow) + << std::endl; + + // Setup formats. + for (size_t i = 0; i < formats.size(); i++) { + auto videoFormat = formats[i]; + auto format = VideoFormat::wstringFromFourcc(videoFormat.fourcc()); + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"format", + format, + wow) + << std::endl; + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"width", + videoFormat.width(), + wow) + << std::endl; + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"height", + videoFormat.height(), + wow) + << std::endl; + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera) + + L"\\Formats\\" + + std::to_wstring(i + 1), + L"fps", + videoFormat.minimumFrameRate().toWString(), + wow) + << std::endl; + } + + for (auto path: installPaths) + ss << L"regsvr32 /s \"" << path << L"\"" << std::endl; + + // Create the script. + auto temp = tempPath(); + auto scriptPath = std::string(temp.begin(), temp.end()) + + "\\device_create_" + + timeStamp() + + ".bat"; + std::wfstream script; + script.imbue(std::locale("")); + script.open(scriptPath, std::ios_base::out | std::ios_base::trunc); + bool ok = false; + + if (script.is_open()) { + script << ss.str(); + script.close(); + ok = this->d->sudo({"cmd", "/c", scriptPath}) == 0; + std::wstring wScriptPath(scriptPath.begin(), scriptPath.end()); + DeleteFile(wScriptPath.c_str()); + } + + return ok; +} + +bool AkVCam::IpcBridge::changeDescription(const std::string &deviceId, + const std::wstring &description) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationEdit)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + auto camera = cameraFromId(std::wstring(deviceId.begin(), deviceId.end())); + + if (camera < 0) + return false; + + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) { + this->d->m_error = L"Driver not found"; + + return false; + } + + std::wstringstream ss; + ss << L"@echo off" << std::endl; + ss << L"chcp " << GetACP() << std::endl; + + auto driverInstallPath = + programFilesPath() + L"\\" DSHOW_PLUGIN_NAME_L L".plugin"; + std::vector installPaths; + + for (auto path: this->d->findFiles(std::wstring(driverPath.begin(), + driverPath.end()), + DSHOW_PLUGIN_NAME_L L".dll")) { + auto installPath = replace(path, driverPath, driverInstallPath); + installPaths.push_back(installPath); + } + + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera + 1), + L"description", + description, + isWow64()) + << std::endl; + + for (auto path: installPaths) + ss << L"regsvr32 /s \"" << path << L"\"" << std::endl; + + auto temp = tempPath(); + auto scriptPath = std::string(temp.begin(), temp.end()) + + "\\device_change_description_" + + timeStamp() + + ".bat"; + std::wfstream script; + script.imbue(std::locale("")); + script.open(scriptPath, std::ios_base::out | std::ios_base::trunc); + bool ok = false; + + if (script.is_open()) { + script << ss.str(); + script.close(); + ok = this->d->sudo({"cmd", "/c", scriptPath}) == 0; + std::wstring wScriptPath(scriptPath.begin(), scriptPath.end()); + DeleteFile(wScriptPath.c_str()); + } + + return ok; +} + +bool AkVCam::IpcBridge::deviceDestroy(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationDestroy)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + auto camera = cameraFromId(std::wstring(deviceId.begin(), deviceId.end())); + + if (camera < 0) + return false; + + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) { + this->d->m_error = L"Driver not found"; + + return false; + } + + std::wstringstream ss; + ss << L"@echo off" << std::endl; + ss << L"chcp " << GetACP() << std::endl; + + auto driverInstallPath = + programFilesPath() + L"\\" DSHOW_PLUGIN_NAME_L L".plugin"; + std::vector installPaths; + + for (auto path: this->d->findFiles(std::wstring(driverPath.begin(), + driverPath.end()), + DSHOW_PLUGIN_NAME_L L".dll")) { + auto installPath = replace(path, driverPath, driverInstallPath); + + installPaths.push_back(installPath); + } + + std::vector assistantInstallPaths; + + for (auto path: this->d->findFiles(std::wstring(driverPath.begin(), + driverPath.end()), + DSHOW_PLUGIN_ASSISTANT_NAME_L L".exe")) { + auto installPath = replace(path, driverPath, driverInstallPath); + + assistantInstallPaths.push_back(installPath); + } + + BOOL wow = isWow64(); + + // List cameras and create a line with the number of cameras. + auto nCameras = camerasCount(); + + if (nCameras > 1) { + ss << this->d->regAddLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras", + L"size", + int(nCameras - 1), + wow) + << std::endl; + + ss << this->d->regDeleteLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(camera + 1), + wow) + << std::endl; + + for (DWORD i = DWORD(camera + 1); i < nCameras; i++) { + ss << this->d->regMoveLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(i + 1), + L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\" + + std::to_wstring(i), + wow) + << std::endl; + } + + for (auto path: installPaths) + ss << L"regsvr32 /s \"" << path << L"\"" << std::endl; + } else { + for (auto path: installPaths) + ss << L"regsvr32 /s /u \"" << path << L"\"" << std::endl; + + for (auto path: assistantInstallPaths) + ss << L"\"" << path << L"\" --uninstall" << std::endl; + + ss << this->d->regDeleteLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras", + wow) + << std::endl; + + if (lstrcmpi(driverPath.c_str(), driverInstallPath.c_str())) + ss << L"rmdir /s /q \"" << driverInstallPath << L"\"" << std::endl; + } + + auto temp = tempPath(); + auto scriptPath = std::string(temp.begin(), temp.end()) + + "\\device_destroy_" + + timeStamp() + + ".bat"; + std::wfstream script; + script.imbue(std::locale("")); + script.open(scriptPath, std::ios_base::out | std::ios_base::trunc); + + if (script.is_open()) { + script << ss.str(); + script.close(); + this->d->sudo({"cmd", "/c", scriptPath}); + std::wstring wScriptPath(scriptPath.begin(), scriptPath.end()); + DeleteFile(wScriptPath.c_str()); + } + + return true; +} + +bool AkVCam::IpcBridge::destroyAllDevices() +{ + AkIpcBridgeLogMethod(); + + if (!this->canApply(OperationDestroyAll)) { + this->d->m_error = L"The driver is in use"; + + return false; + } + + auto driverPath = this->d->locateDriverPath(); + + if (driverPath.empty()) { + this->d->m_error = L"Driver not found"; + + return false; + } + + std::wstringstream ss; + ss << L"@echo off" << std::endl; + ss << L"chcp " << GetACP() << std::endl; + + auto driverInstallPath = + programFilesPath() + L"\\" DSHOW_PLUGIN_NAME_L L".plugin"; + + for (auto path: this->d->findFiles(std::wstring(driverPath.begin(), + driverPath.end()), + DSHOW_PLUGIN_NAME_L L".dll")) { + auto installPath = replace(path, driverPath, driverInstallPath); + ss << L"regsvr32 /s /u \"" << installPath << L"\"" << std::endl; + } + + for (auto path: this->d->findFiles(std::wstring(driverPath.begin(), + driverPath.end()), + DSHOW_PLUGIN_ASSISTANT_NAME_L L".exe")) { + auto installPath = replace(path, driverPath, driverInstallPath); + ss << L"\"" << installPath << L"\" --uninstall" << std::endl; + } + + ss << this->d->regDeleteLine(L"HKLM\\SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras", + isWow64()) + << std::endl; + + if (lstrcmpi(driverPath.c_str(), driverInstallPath.c_str())) + ss << "rmdir /s /q \"" << driverInstallPath << L"\"" << std::endl; + + auto temp = tempPath(); + auto scriptPath = std::string(temp.begin(), temp.end()) + + "\\device_remove_all_" + + timeStamp() + + ".bat"; + std::wfstream script; + script.imbue(std::locale("")); + script.open(scriptPath, std::ios_base::out | std::ios_base::trunc); + bool ok = false; + + if (script.is_open()) { + script << ss.str(); + script.close(); + ok = this->d->sudo({"cmd", "/c", scriptPath}) == 0; + std::wstring wScriptPath(scriptPath.begin(), scriptPath.end()); + DeleteFile(wScriptPath.c_str()); + } + + return ok; +} + +bool AkVCam::IpcBridge::deviceStart(const std::string &deviceId, + const VideoFormat &format) +{ + UNUSED(format) + AkIpcBridgeLogMethod(); + auto it = std::find(this->d->m_broadcasting.begin(), + this->d->m_broadcasting.end(), + deviceId); + + if (it != this->d->m_broadcasting.end()) + return false; + + std::wstring portName(this->d->m_portName.begin(), + this->d->m_portName.end()); + this->d->m_sharedMemory.setName(L"Local\\" + portName + L".data"); + this->d->m_globalMutex = Mutex(portName + L".mutex"); + + if (!this->d->m_sharedMemory.open(maxBufferSize, + SharedMemory::OpenModeWrite)) + return false; + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING; + message.dataSize = sizeof(MsgBroadcasting); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + memcpy(data->broadcaster, + this->d->m_portName.c_str(), + (std::min)(this->d->m_portName.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) { + this->d->m_sharedMemory.close(); + + return false; + } + + if (!data->status) + return false; + + this->d->m_broadcasting.push_back(deviceId); + + return true; +} + +void AkVCam::IpcBridge::deviceStop(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + auto it = std::find(this->d->m_broadcasting.begin(), + this->d->m_broadcasting.end(), + deviceId); + + if (it == this->d->m_broadcasting.end()) + return; + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING; + message.dataSize = sizeof(MsgBroadcasting); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + + this->d->m_mainServer.sendMessage(&message); + this->d->m_sharedMemory.close(); + this->d->m_broadcasting.erase(it); +} + +bool AkVCam::IpcBridge::write(const std::string &deviceId, + const VideoFrame &frame) +{ + AkIpcBridgeLogMethod(); + + if (frame.format().size() < 1) + return false; + + auto buffer = + reinterpret_cast(this->d->m_sharedMemory.lock(&this->d->m_globalMutex)); + + if (!buffer) + return false; + + if (size_t(frame.format().width() * frame.format().height()) > maxFrameSize) { + auto scaledFrame = frame.scaled(maxFrameSize); + buffer->format = scaledFrame.format().fourcc(); + buffer->width = scaledFrame.format().width(); + buffer->height = scaledFrame.format().height(); + buffer->size = uint32_t(frame.data().size()); + memcpy(buffer->data, + scaledFrame.data().data(), + scaledFrame.data().size()); + } else { + buffer->format = frame.format().fourcc(); + buffer->width = frame.format().width(); + buffer->height = frame.format().height(); + buffer->size = uint32_t(frame.data().size()); + memcpy(buffer->data, + frame.data().data(), + frame.data().size()); + } + + this->d->m_sharedMemory.unlock(&this->d->m_globalMutex); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_FRAME_READY; + message.dataSize = sizeof(MsgFrameReady); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + memcpy(data->port, + this->d->m_portName.c_str(), + (std::min)(this->d->m_portName.size(), MAX_STRING)); + + return this->d->m_mainServer.sendMessage(&message) == TRUE; +} + +void AkVCam::IpcBridge::setMirroring(const std::string &deviceId, + bool horizontalMirrored, + bool verticalMirrored) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING; + message.dataSize = sizeof(MsgMirroring); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + data->hmirror = horizontalMirrored; + data->vmirror = verticalMirrored; + this->d->m_mainServer.sendMessage(&message); +} + +void AkVCam::IpcBridge::setScaling(const std::string &deviceId, + Scaling scaling) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING; + message.dataSize = sizeof(MsgScaling); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + data->scaling = scaling; + this->d->m_mainServer.sendMessage(&message); +} + +void AkVCam::IpcBridge::setAspectRatio(const std::string &deviceId, + AspectRatio aspectRatio) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO; + message.dataSize = sizeof(MsgAspectRatio); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + data->aspect = aspectRatio; + this->d->m_mainServer.sendMessage(&message); +} + +void AkVCam::IpcBridge::setSwapRgb(const std::string &deviceId, bool swap) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB; + message.dataSize = sizeof(MsgSwapRgb); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + data->swap = swap; + this->d->m_mainServer.sendMessage(&message); +} + +bool AkVCam::IpcBridge::addListener(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD; + message.dataSize = sizeof(MsgListeners); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + memcpy(data->listener, + this->d->m_portName.c_str(), + (std::min)(this->d->m_portName.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return false; + + return data->status; +} + +bool AkVCam::IpcBridge::removeListener(const std::string &deviceId) +{ + AkIpcBridgeLogMethod(); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE; + message.dataSize = sizeof(MsgListeners); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + memcpy(data->listener, + this->d->m_portName.c_str(), + (std::min)(this->d->m_portName.size(), MAX_STRING)); + + if (!this->d->m_mainServer.sendMessage(&message)) + return false; + + return data->status; +} + +AkVCam::IpcBridgePrivate::IpcBridgePrivate(IpcBridge *self): + self(self), + m_asClient(false) +{ + this->m_mainServer.setPipeName(L"\\\\.\\pipe\\" DSHOW_PLUGIN_ASSISTANT_NAME_L); + this->m_mainServer.setMode(MessageServer::ServerModeSend); + this->m_mainServer.connectPipeStateChanged(this, + &IpcBridgePrivate::pipeStateChanged); + this->updateDeviceSharedProperties(); + + this->m_messageHandlers = std::map { + {AKVCAM_ASSISTANT_MSG_ISALIVE , AKVCAM_BIND_FUNC(IpcBridgePrivate::isAlive) }, + {AKVCAM_ASSISTANT_MSG_FRAME_READY , AKVCAM_BIND_FUNC(IpcBridgePrivate::frameReady) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETBROADCASTING, AKVCAM_BIND_FUNC(IpcBridgePrivate::setBroadcasting)}, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETMIRRORING , AKVCAM_BIND_FUNC(IpcBridgePrivate::setMirror) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSCALING , AKVCAM_BIND_FUNC(IpcBridgePrivate::setScaling) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETASPECTRATIO , AKVCAM_BIND_FUNC(IpcBridgePrivate::setAspectRatio) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_SETSWAPRGB , AKVCAM_BIND_FUNC(IpcBridgePrivate::setSwapRgb) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_ADD , AKVCAM_BIND_FUNC(IpcBridgePrivate::listenerAdd) }, + {AKVCAM_ASSISTANT_MSG_DEVICE_LISTENER_REMOVE, AKVCAM_BIND_FUNC(IpcBridgePrivate::listenerRemove) }, + }; +} + +AkVCam::IpcBridgePrivate::~IpcBridgePrivate() +{ + this->m_mainServer.stop(true); +} + +std::vector *AkVCam::IpcBridgePrivate::driverPaths() +{ + static std::vector paths; + + return &paths; +} + +std::vector AkVCam::IpcBridgePrivate::listCameras() const +{ + std::vector cameras; + + // Create the System Device Enumerator. + ICreateDevEnum *deviceEnumerator = nullptr; + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, + nullptr, + CLSCTX_INPROC_SERVER, + IID_ICreateDevEnum, + reinterpret_cast(&deviceEnumerator)); + + if (FAILED(hr)) + return cameras; + + // Create an enumerator for the category. + IEnumMoniker *enumMoniker = nullptr; + + if (deviceEnumerator->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + &enumMoniker, + 0) == S_OK) { + enumMoniker->Reset(); + IMoniker *moniker = nullptr; + + while (enumMoniker->Next(1, &moniker, nullptr) == S_OK) + cameras.push_back(MonikerPtr(moniker, [](IMoniker *moniker) { + moniker->Release(); + })); + + enumMoniker->Release(); + } + + deviceEnumerator->Release(); + + return cameras; +} + +AkVCam::BaseFilterPtr AkVCam::IpcBridgePrivate::filter(IMoniker *moniker) const +{ + if (!moniker) + return {}; + + IBaseFilter *baseFilter = nullptr; + + if (FAILED(moniker->BindToObject(nullptr, + nullptr, + IID_IBaseFilter, + reinterpret_cast(&baseFilter)))) { + return {}; + } + + return BaseFilterPtr(baseFilter, [] (IBaseFilter *baseFilter) { + baseFilter->Release(); + }); +} + +AkVCam::PropertyBagPtr AkVCam::IpcBridgePrivate::propertyBag(IMoniker *moniker) const +{ + if (!moniker) + return {}; + + IPropertyBag *propertyBag = nullptr; + + if (FAILED(moniker->BindToStorage(nullptr, + nullptr, + IID_IPropertyBag, + reinterpret_cast(&propertyBag)))) { + return {}; + } + + return PropertyBagPtr(propertyBag, [] (IPropertyBag *propertyBag) { + propertyBag->Release(); + }); +} + +bool AkVCam::IpcBridgePrivate::isVirtualCamera(const MonikerPtr &moniker) const +{ + auto baseFilter = this->filter(moniker.get()); + + if (!baseFilter) + return false; + + return this->isVirtualCamera(baseFilter.get()); +} + +bool AkVCam::IpcBridgePrivate::isVirtualCamera(IBaseFilter *baseFilter) const +{ + if (!baseFilter) + return false; + + CLSID clsid; + memset(&clsid, 0, sizeof(CLSID)); + baseFilter->GetClassID(&clsid); + + return cameraFromId(clsid) >= 0; +} + +std::string AkVCam::IpcBridgePrivate::cameraPath(const MonikerPtr &moniker) const +{ + auto propertyBag = this->propertyBag(moniker.get()); + + return this->cameraPath(propertyBag.get()); +} + +std::string AkVCam::IpcBridgePrivate::cameraPath(IPropertyBag *propertyBag) const +{ + VARIANT var; + VariantInit(&var); + + if (FAILED(propertyBag->Read(L"DevicePath", &var, nullptr))) + return std::string(); + + std::wstring wstr(var.bstrVal); + std::string devicePath(wstr.begin(), wstr.end()); + VariantClear(&var); + + return devicePath; +} + +std::wstring AkVCam::IpcBridgePrivate::cameraDescription(const MonikerPtr &moniker) const +{ + auto propertyBag = this->propertyBag(moniker.get()); + + return this->cameraDescription(propertyBag.get()); +} + +std::wstring AkVCam::IpcBridgePrivate::cameraDescription(IPropertyBag *propertyBag) const +{ + VARIANT var; + VariantInit(&var); + + if (FAILED(propertyBag->Read(L"Description", &var, nullptr))) + if (FAILED(propertyBag->Read(L"FriendlyName", &var, nullptr))) + return {}; + + std::wstring wstr(var.bstrVal); + VariantClear(&var); + + return wstr; +} + +std::vector AkVCam::IpcBridgePrivate::enumPins(IBaseFilter *baseFilter) const +{ + std::vector pins; + IEnumPins *enumPins = nullptr; + + if (SUCCEEDED(baseFilter->EnumPins(&enumPins))) { + enumPins->Reset(); + IPin *pin = nullptr; + + while (enumPins->Next(1, &pin, nullptr) == S_OK) { + PIN_DIRECTION direction = PINDIR_INPUT; + + if (SUCCEEDED(pin->QueryDirection(&direction)) + && direction == PINDIR_OUTPUT) { + pins.push_back(PinPtr(pin, [] (IPin *pin) { + pin->Release(); + })); + + continue; + } + + pin->Release(); + } + + enumPins->Release(); + } + + return pins; +} + +std::vector AkVCam::IpcBridgePrivate::enumVideoFormats(IPin *pin) const +{ + std::vector mediaTypes; + IEnumMediaTypes *pEnum = nullptr; + + if (FAILED(pin->EnumMediaTypes(&pEnum))) + return mediaTypes; + + pEnum->Reset(); + AM_MEDIA_TYPE *mediaType = nullptr; + + while (pEnum->Next(1, &mediaType, nullptr) == S_OK) { + auto format = formatFromMediaType(mediaType); + deleteMediaType(&mediaType); + + if (format.size() > 0) + mediaTypes.push_back(format); + } + + pEnum->Release(); + + return mediaTypes; +} + +std::vector AkVCam::IpcBridgePrivate::findFiles(const std::wstring &path) const +{ + std::wstring path_ = path; + + auto attributes = GetFileAttributes(path.c_str()); + + if (attributes & FILE_ATTRIBUTE_DIRECTORY) + path_ += L"\\*"; + + WIN32_FIND_DATA data; + memset(&data, 0, sizeof(WIN32_FIND_DATA)); + auto find = FindFirstFile(path_.c_str(), &data); + + if (find == INVALID_HANDLE_VALUE) + return {}; + + std::vector paths; + + do { + std::wstring fileName(data.cFileName); + + if (fileName == L"." || fileName == L"..") + continue; + + std::wstring filePath = path + L"\\" + fileName; + + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + for (auto path: this->findFiles(filePath)) + paths.push_back(path); + else + paths.push_back(filePath); + } while (FindNextFile(find, &data)); + + FindClose(find); + + return paths; +} + +std::vector AkVCam::IpcBridgePrivate::findFiles(const std::string &path, + const std::string &fileName) const +{ + auto wfiles = + this->findFiles(std::wstring(path.begin(), path.end()), + std::wstring(fileName.begin(), fileName.end())); + + std::vector files; + + for (auto &file: wfiles) + files.push_back(std::string(file.begin(), file.end())); + + return files; +} + +std::vector AkVCam::IpcBridgePrivate::findFiles(const std::wstring &path, + const std::wstring &fileName) const +{ + std::vector plugins; + + for (auto file: this->findFiles(path)) { + auto pos = file.rfind(L"\\"); + auto fName = file.substr(pos + 1); + + if (!lstrcmpi(fName.c_str(), fileName.c_str())) + plugins.push_back(file); + } + + return plugins; +} + +std::wstring AkVCam::IpcBridgePrivate::regAddLine(const std::wstring &key, + const std::wstring &value, + const std::wstring &data, + BOOL wow) const +{ + std::wstringstream ss; + ss << L"reg add \"" + << key + << L"\" /v " + << value + << L" /d \"" + << data + << L"\" /f"; + + if (wow) + ss << L" /reg:64"; + + return ss.str(); +} + +std::wstring AkVCam::IpcBridgePrivate::regAddLine(const std::wstring &key, + const std::wstring &value, + int data, + BOOL wow) const +{ + std::wstringstream ss; + ss << L"reg add \"" + << key + << L"\" /v " + << value + << L" /t REG_DWORD" + << L" /d " + << data + << L" /f"; + + if (wow) + ss << L" /reg:64"; + + return ss.str(); +} + +std::wstring AkVCam::IpcBridgePrivate::regDeleteLine(const std::wstring &key, + BOOL wow) const +{ + std::wstringstream ss; + ss << L"reg delete \"" + key + L"\" /f"; + + if (wow) + ss << L" /reg:64"; + + return ss.str(); +} + +std::wstring AkVCam::IpcBridgePrivate::regDeleteLine(const std::wstring &key, + const std::wstring &value, + BOOL wow) const +{ + std::wstringstream ss; + ss << L"reg delete \"" + key + L"\" /v \"" + value + L"\" /f"; + + if (wow) + ss << L" /reg:64"; + + return ss.str(); +} + +std::wstring AkVCam::IpcBridgePrivate::regMoveLine(const std::wstring &fromKey, + const std::wstring &toKey, + BOOL wow) const +{ + std::wstringstream ss; + ss << L"reg copy \"" << fromKey << L"\" \"" << toKey << L"\" /s /f"; + + if (wow) + ss << L" /reg:64"; + + ss << std::endl + << regDeleteLine(fromKey, wow); + + return ss.str(); +} + +std::wstring AkVCam::IpcBridgePrivate::dirname(const std::wstring &path) const +{ + return path.substr(0, path.rfind(L"\\")); +} + +void AkVCam::IpcBridgePrivate::updateDeviceSharedProperties() +{ + for (DWORD i = 0; i < camerasCount(); i++) { + auto cameraPath = AkVCam::cameraPath(i); + std::string deviceId(cameraPath.begin(), cameraPath.end()); + + Message message; + message.messageId = AKVCAM_ASSISTANT_MSG_DEVICE_BROADCASTING; + message.dataSize = sizeof(MsgBroadcasting); + auto data = messageData(&message); + memcpy(data->device, + deviceId.c_str(), + (std::min)(deviceId.size(), MAX_STRING)); + this->m_mainServer.sendMessage(&message); + this->updateDeviceSharedProperties(deviceId, + std::string(data->broadcaster)); + } +} + +void AkVCam::IpcBridgePrivate::updateDeviceSharedProperties(const std::string &deviceId, + const std::string &owner) +{ + if (owner.empty()) { + this->m_devices[deviceId] = {SharedMemory(), Mutex()}; + } else { + Mutex mutex(std::wstring(owner.begin(), owner.end()) + L".mutex"); + SharedMemory sharedMemory; + sharedMemory.setName(L"Local\\" + + std::wstring(owner.begin(), owner.end()) + + L".data"); + + if (sharedMemory.open()) + this->m_devices[deviceId] = {sharedMemory, mutex}; + } +} + +std::wstring AkVCam::IpcBridgePrivate::locateDriverPath() const +{ + std::wstring driverPath; + + for (auto it = this->driverPaths()->rbegin(); + it != this->driverPaths()->rend(); + it++) { + auto path = *it; + path = replace(path, L"/", L"\\"); + + if (path.back() != L'\\') + path += L'\\'; + + path += DSHOW_PLUGIN_NAME_L L".plugin"; + + if (this->findFiles(path, DSHOW_PLUGIN_NAME_L L".dll").empty()) + continue; + + if (this->findFiles(path, DSHOW_PLUGIN_ASSISTANT_NAME_L L".exe").empty()) + continue; + + driverPath = path; + + break; + } + + return driverPath; +} + +void AkVCam::IpcBridgePrivate::pipeStateChanged(void *userData, + MessageServer::PipeState state) +{ + AkIpcBridgePrivateLogMethod(); + auto self = reinterpret_cast(userData); + + switch (state) { + case MessageServer::PipeStateAvailable: + AkLoggerLog("Server Available"); + + if (self->self->registerPeer(self->m_asClient)) { + AKVCAM_EMIT(self->self, + ServerStateChanged, + IpcBridge::ServerStateAvailable) + } + + break; + + case MessageServer::PipeStateGone: + AkLoggerLog("Server Gone"); + AKVCAM_EMIT(self->self, + ServerStateChanged, + IpcBridge::ServerStateGone) + self->self->unregisterPeer(); + + break; + } +} + +void AkVCam::IpcBridgePrivate::isAlive(Message *message) +{ + auto data = messageData(message); + data->alive = true; +} + +void AkVCam::IpcBridgePrivate::frameReady(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + + if (this->m_devices.count(deviceId) < 1) { + this->updateDeviceSharedProperties(deviceId, std::string(data->port)); + + return; + } + + auto frame = + reinterpret_cast(this->m_devices[deviceId] + .sharedMemory + .lock(&this->m_devices[deviceId].mutex)); + + if (!frame) + return; + + VideoFormat videoFormat(frame->format, frame->width, frame->height); + VideoFrame videoFrame(videoFormat); + memcpy(videoFrame.data().data(), frame->data, frame->size); + this->m_devices[deviceId].sharedMemory.unlock(&this->m_devices[deviceId].mutex); + AKVCAM_EMIT(this->self, FrameReady, deviceId, videoFrame) +} + +void AkVCam::IpcBridgePrivate::setBroadcasting(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + std::string broadcaster(data->broadcaster); + this->updateDeviceSharedProperties(deviceId, broadcaster); + AKVCAM_EMIT(this->self, BroadcastingChanged, deviceId, broadcaster) +} + +void AkVCam::IpcBridgePrivate::setMirror(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + AKVCAM_EMIT(this->self, + MirrorChanged, + deviceId, + data->hmirror, + data->vmirror) +} + +void AkVCam::IpcBridgePrivate::setScaling(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + AKVCAM_EMIT(this->self, ScalingChanged, deviceId, data->scaling) +} + +void AkVCam::IpcBridgePrivate::setAspectRatio(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + AKVCAM_EMIT(this->self, AspectRatioChanged, deviceId, data->aspect) +} + +void AkVCam::IpcBridgePrivate::setSwapRgb(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + AKVCAM_EMIT(this->self, SwapRgbChanged, deviceId, data->swap) +} + +void AkVCam::IpcBridgePrivate::listenerAdd(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + AKVCAM_EMIT(this->self, + ListenerAdded, + deviceId, + std::string(data->listener)) +} + +void AkVCam::IpcBridgePrivate::listenerRemove(Message *message) +{ + auto data = messageData(message); + std::string deviceId(data->device); + AKVCAM_EMIT(this->self, + ListenerRemoved, + deviceId, + std::string(data->listener)) +} + +int AkVCam::IpcBridgePrivate::sudo(const std::vector ¶meters, + const std::wstring &directory, + bool show) +{ + if (parameters.size() < 1) + return E_FAIL; + + auto command = parameters[0]; + std::wstring wcommand(command.begin(), command.end()); + std::wstring wparameters; + + for (size_t i = 1; i < parameters.size(); i++) { + auto param = parameters[i]; + + if (i > 1) + wparameters += L" "; + + wparameters += std::wstring(param.begin(), param.end()); + } + + SHELLEXECUTEINFO execInfo; + memset(&execInfo, 0, sizeof(SHELLEXECUTEINFO)); + execInfo.cbSize = sizeof(SHELLEXECUTEINFO); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + execInfo.hwnd = nullptr; + execInfo.lpVerb = L"runas"; + execInfo.lpFile = wcommand.data(); + execInfo.lpParameters = wparameters.data(); + execInfo.lpDirectory = directory.data(); + execInfo.nShow = show? SW_SHOWNORMAL: SW_HIDE; + execInfo.hInstApp = nullptr; + ShellExecuteEx(&execInfo); + + if (!execInfo.hProcess) { + this->m_error = L"Failed executing script"; + + return E_FAIL; + } + + WaitForSingleObject(execInfo.hProcess, INFINITE); + + DWORD exitCode; + GetExitCodeProcess(execInfo.hProcess, &exitCode); + CloseHandle(execInfo.hProcess); + + if (FAILED(exitCode)) + this->m_error = L"Script failed with code " + std::to_wstring(exitCode); + + return int(exitCode); +} diff --git a/dshow/VirtualCamera/VirtualCamera.def b/dshow/VirtualCamera/VirtualCamera.def new file mode 100644 index 0000000..98f97fe --- /dev/null +++ b/dshow/VirtualCamera/VirtualCamera.def @@ -0,0 +1,26 @@ +; akvirtualcamera, virtual camera for Mac and Windows. +; Copyright (C) 2020 Gonzalo Exequiel Pedone +; +; akvirtualcamera 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 3 of the License, or +; (at your option) any later version. +; +; akvirtualcamera 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 akvirtualcamera. If not, see . +; +; Web-Site: http://webcamoid.github.io/ + +LIBRARY VirtualCameraSource.dll + +EXPORTS + DllMain PRIVATE + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/dshow/VirtualCamera/VirtualCamera.pro b/dshow/VirtualCamera/VirtualCamera.pro new file mode 100644 index 0000000..1a3ced4 --- /dev/null +++ b/dshow/VirtualCamera/VirtualCamera.pro @@ -0,0 +1,130 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +exists(commons.pri) { + include(commons.pri) +} else { + exists(../../commons.pri) { + include(../../commons.pri) + } else { + error("commons.pri file not found.") + } +} + +include(../dshow.pri) +include(../../VCamUtils/VCamUtils.pri) + +CONFIG -= qt +CONFIG += link_prl + +INCLUDEPATH += \ + .. \ + ../.. + +LIBS += \ + -L$${OUT_PWD}/../VCamIPC/$${BIN_DIR} -lVCamIPC \ + -L$${OUT_PWD}/../PlatformUtils/$${BIN_DIR} -lPlatformUtils \ + -L$${OUT_PWD}/../../VCamUtils/$${BIN_DIR} -lVCamUtils \ + -ladvapi32 \ + -lgdi32 \ + -lkernel32 \ + -lole32 \ + -loleaut32 \ + -lpsapi \ + -lshell32 \ + -lstrmiids \ + -luser32 \ + -luuid \ + -lwinmm + +TARGET = $${DSHOW_PLUGIN_NAME} +TEMPLATE = lib + +HEADERS += \ + src/basefilter.h \ + src/classfactory.h \ + src/cunknown.h \ + src/enummediatypes.h \ + src/enumpins.h \ + src/filtermiscflags.h \ + src/latency.h \ + src/mediafilter.h \ + src/mediasample.h \ + src/mediasample2.h \ + src/memallocator.h \ + src/persist.h \ + src/persistpropertybag.h \ + src/pin.h \ + src/plugin.h \ + src/plugininterface.h \ + src/propertyset.h \ + src/pushsource.h \ + src/qualitycontrol.h \ + src/referenceclock.h \ + src/specifypropertypages.h \ + src/streamconfig.h \ + src/videocontrol.h \ + src/videoprocamp.h + +SOURCES += \ + src/basefilter.cpp \ + src/classfactory.cpp \ + src/cunknown.cpp \ + src/enummediatypes.cpp \ + src/enumpins.cpp \ + src/filtermiscflags.cpp \ + src/latency.cpp \ + src/mediafilter.cpp \ + src/mediasample.cpp \ + src/mediasample2.cpp \ + src/memallocator.cpp \ + src/persist.cpp \ + src/persistpropertybag.cpp \ + src/pin.cpp \ + src/plugin.cpp \ + src/plugininterface.cpp \ + src/propertyset.cpp \ + src/pushsource.cpp \ + src/qualitycontrol.cpp \ + src/referenceclock.cpp \ + src/specifypropertypages.cpp \ + src/streamconfig.cpp \ + src/videocontrol.cpp \ + src/videoprocamp.cpp + +DESTDIR = $${OUT_PWD}/$${BIN_DIR} + +OTHER_FILES = \ + VirtualCamera.def + +DEF_FILE = VirtualCamera.def + +isEmpty(STATIC_BUILD) | isEqual(STATIC_BUILD, 0) { + win32-g++: QMAKE_LFLAGS = -static -static-libgcc -static-libstdc++ +} + +INSTALLS += vcam +vcam.files = $${OUT_PWD}/$${TARGET}.plugin +vcam.path = $${DATAROOTDIR} +vcam.CONFIG += no_check_exist + +QMAKE_POST_LINK = \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/$${TARGET}.plugin/$$normalizedArch(TARGET_ARCH))) $${CMD_SEP} \ + $$sprintf($$QMAKE_MKDIR_CMD, $$shell_path($${OUT_PWD}/$${TARGET}.plugin/share)) $${CMD_SEP} \ + $(COPY) $$shell_path($${OUT_PWD}/$${BIN_DIR}/$${TARGET}.dll) $$shell_path($${OUT_PWD}/$${TARGET}.plugin/$$normalizedArch(TARGET_ARCH)) $${CMD_SEP} \ + $(COPY) $$shell_path($${PWD}/../../share/TestFrame/TestFrame.bmp) $$shell_path($${OUT_PWD}/$${TARGET}.plugin/share) diff --git a/dshow/VirtualCamera/src/basefilter.cpp b/dshow/VirtualCamera/src/basefilter.cpp new file mode 100644 index 0000000..384b6ec --- /dev/null +++ b/dshow/VirtualCamera/src/basefilter.cpp @@ -0,0 +1,524 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include + +#include "basefilter.h" +#include "enumpins.h" +#include "filtermiscflags.h" +#include "pin.h" +#include "referenceclock.h" +#include "specifypropertypages.h" +#include "videocontrol.h" +#include "videoprocamp.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/ipcbridge.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "BaseFilter" + +#define AkBaseFilterPrivateLog() \ + AkLoggerLog("BaseFilterPrivate::", __FUNCTION__, "()") + +#define AkVCamPinCall(pins, func, ...) \ + pins->Reset(); \ + Pin *pin = nullptr; \ + \ + while (pins->Next(1, reinterpret_cast(&pin), nullptr) == S_OK) { \ + pin->func(__VA_ARGS__); \ + pin->Release(); \ + } + +#define AkVCamDevicePinCall(deviceId, where, func, ...) \ + if (auto pins = where->pinsForDevice(deviceId)) { \ + AkVCamPinCall(pins, func, __VA_ARGS__) \ + pins->Release(); \ + } + +namespace AkVCam +{ + class BaseFilterPrivate + { + public: + BaseFilter *self; + EnumPins *m_pins; + VideoProcAmp *m_videoProcAmp; + ReferenceClock *m_referenceClock; + std::wstring m_vendor; + std::wstring m_filterName; + IFilterGraph *m_filterGraph; + IpcBridge m_ipcBridge; + + BaseFilterPrivate(BaseFilter *self, + const std::wstring &filterName, + const std::wstring &vendor); + BaseFilterPrivate(const BaseFilterPrivate &other) = delete; + ~BaseFilterPrivate(); + IEnumPins *pinsForDevice(const std::string &deviceId); + void updatePins(); + static void serverStateChanged(void *userData, + IpcBridge::ServerState state); + static void frameReady(void *userData, + const std::string &deviceId, + const VideoFrame &frame); + static void setBroadcasting(void *userData, + const std::string &deviceId, + const std::string &broadcasting); + static void setMirror(void *userData, + const std::string &deviceId, + bool horizontalMirror, + bool verticalMirror); + static void setScaling(void *userData, + const std::string &deviceId, + Scaling scaling); + static void setAspectRatio(void *userData, + const std::string &deviceId, + AspectRatio aspectRatio); + static void setSwapRgb(void *userData, + const std::string &deviceId, + bool swap); + }; +} + +AkVCam::BaseFilter::BaseFilter(const GUID &clsid, + const std::wstring &filterName, + const std::wstring &vendor): + MediaFilter(clsid, this) +{ + this->setParent(this, &IID_IBaseFilter); + this->d = new BaseFilterPrivate(this, filterName, vendor); +} + +AkVCam::BaseFilter::~BaseFilter() +{ + delete this->d; +} + +void AkVCam::BaseFilter::addPin(const std::vector &formats, + const std::wstring &pinName, + bool changed) +{ + AkLogMethod(); + this->d->m_pins->addPin(new Pin(this, formats, pinName), changed); + + if (this->d->m_pins->count() == 1) + this->d->m_ipcBridge.connectService(true); +} + +void AkVCam::BaseFilter::removePin(IPin *pin, bool changed) +{ + AkLogMethod(); + this->d->m_ipcBridge.disconnectService(); + this->d->m_pins->removePin(pin, changed); +} + +AkVCam::BaseFilter *AkVCam::BaseFilter::create(const GUID &clsid) +{ + AkLoggerLog("BaseFilter::create()"); + auto camera = cameraFromId(clsid); + AkLoggerLog("CLSID: ", stringFromClsid(clsid)); + AkLoggerLog("ID: ", camera); + + if (camera < 0) + return nullptr; + + auto description = cameraDescription(DWORD(camera)); + AkLoggerLog("Description: ", std::string(description.begin(), + description.end())); + auto baseFilter = new BaseFilter(clsid, + description, + DSHOW_PLUGIN_VENDOR_L); + auto formats = cameraFormats(DWORD(camera)); + baseFilter->addPin(formats, L"Video", false); + + return baseFilter; +} + +IFilterGraph *AkVCam::BaseFilter::filterGraph() const +{ + return this->d->m_filterGraph; +} + +IReferenceClock *AkVCam::BaseFilter::referenceClock() const +{ + return this->d->m_referenceClock; +} + +HRESULT AkVCam::BaseFilter::QueryInterface(const IID &riid, void **ppvObject) +{ + AkLogMethod(); + AkLoggerLog("IID: ", AkVCam::stringFromClsid(riid)); + + if (!ppvObject) + return E_POINTER; + + *ppvObject = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) + || IsEqualIID(riid, IID_IBaseFilter) + || IsEqualIID(riid, IID_IMediaFilter)) { + AkLogInterface(IBaseFilter, this); + this->AddRef(); + *ppvObject = this; + + return S_OK; + } else if (IsEqualIID(riid, IID_IAMFilterMiscFlags)) { + auto filterMiscFlags = new FilterMiscFlags; + AkLogInterface(IAMFilterMiscFlags, filterMiscFlags); + filterMiscFlags->AddRef(); + *ppvObject = filterMiscFlags; + + return S_OK; + } else if (IsEqualIID(riid, IID_IAMVideoControl)) { + IEnumPins *pins = nullptr; + this->d->m_pins->Clone(&pins); + auto videoControl = new VideoControl(pins); + pins->Release(); + AkLogInterface(IAMVideoControl, videoControl); + videoControl->AddRef(); + *ppvObject = videoControl; + + return S_OK; + } else if (IsEqualIID(riid, IID_IAMVideoProcAmp)) { + auto videoProcAmp = this->d->m_videoProcAmp; + AkLogInterface(IAMVideoProcAmp, videoProcAmp); + videoProcAmp->AddRef(); + *ppvObject = videoProcAmp; + + return S_OK; + } else if (IsEqualIID(riid, IID_IReferenceClock)) { + auto referenceClock = this->d->m_referenceClock; + AkLogInterface(IReferenceClock, referenceClock); + referenceClock->AddRef(); + *ppvObject = referenceClock; + + return S_OK; + } else if (IsEqualIID(riid, IID_ISpecifyPropertyPages)) { + this->d->m_pins->Reset(); + IPin *pin = nullptr; + this->d->m_pins->Next(1, &pin, nullptr); + auto specifyPropertyPages = new SpecifyPropertyPages(pin); + pin->Release(); + AkLogInterface(ISpecifyPropertyPages, specifyPropertyPages); + specifyPropertyPages->AddRef(); + *ppvObject = specifyPropertyPages; + + return S_OK; + } else { + this->d->m_pins->Reset(); + IPin *pin = nullptr; + this->d->m_pins->Next(1, &pin, nullptr); + auto result = pin->QueryInterface(riid, ppvObject); + pin->Release(); + + if (SUCCEEDED(result)) + return result; + } + + return MediaFilter::QueryInterface(riid, ppvObject); +} + +HRESULT AkVCam::BaseFilter::EnumPins(IEnumPins **ppEnum) +{ + AkLogMethod(); + + if (!this->d->m_pins) + return E_FAIL; + + auto result = this->d->m_pins->Clone(ppEnum); + + if (SUCCEEDED(result)) + (*ppEnum)->Reset(); + + return result; +} + +HRESULT AkVCam::BaseFilter::FindPin(LPCWSTR Id, IPin **ppPin) +{ + AkLogMethod(); + + if (!ppPin) + return E_POINTER; + + *ppPin = nullptr; + + if (!Id) + return VFW_E_NOT_FOUND; + + IPin *pin = nullptr; + HRESULT result = VFW_E_NOT_FOUND; + this->d->m_pins->Reset(); + + while (this->d->m_pins->Next(1, &pin, nullptr) == S_OK) { + WCHAR *pinId = nullptr; + auto ok = pin->QueryId(&pinId); + + if (ok == S_OK && wcscmp(pinId, Id) == 0) { + *ppPin = pin; + (*ppPin)->AddRef(); + result = S_OK; + } + + CoTaskMemFree(pinId); + pin->Release(); + pin = nullptr; + + if (result == S_OK) + break; + } + + return result; +} + +HRESULT AkVCam::BaseFilter::QueryFilterInfo(FILTER_INFO *pInfo) +{ + AkLogMethod(); + + if (!pInfo) + return E_POINTER; + + memset(pInfo->achName, 0, MAX_FILTER_NAME * sizeof(WCHAR)); + + if (this->d->m_filterName.size() > 0) { + memcpy(pInfo->achName, + this->d->m_filterName.c_str(), + std::max(this->d->m_filterName.size() * sizeof(WCHAR), + MAX_FILTER_NAME)); + } + + pInfo->pGraph = this->d->m_filterGraph; + + if (pInfo->pGraph) + pInfo->pGraph->AddRef(); + + return S_OK; +} + +HRESULT AkVCam::BaseFilter::JoinFilterGraph(IFilterGraph *pGraph, LPCWSTR pName) +{ + AkLogMethod(); + + this->d->m_filterGraph = pGraph; + this->d->m_filterName = std::wstring(pName? pName: L""); + + AkLoggerLog("Filter graph: ", this->d->m_filterGraph); + AkLoggerLog("Name: ", std::string(this->d->m_filterName.begin(), + this->d->m_filterName.end())); + + return S_OK; +} + +HRESULT AkVCam::BaseFilter::QueryVendorInfo(LPWSTR *pVendorInfo) +{ + AkLogMethod(); + + if (this->d->m_vendor.size() < 1) + return E_NOTIMPL; + + if (!pVendorInfo) + return E_POINTER; + + *pVendorInfo = wcharStrFromWStr(this->d->m_vendor); + + return S_OK; +} + +void AkVCam::BaseFilter::stateChanged(FILTER_STATE state) +{ + CLSID clsid; + this->GetClassID(&clsid); + auto path = cameraPath(clsid); + std::string deviceId(path.begin(), path.end()); + + if (state == State_Running) + this->d->m_ipcBridge.addListener(deviceId); + else + this->d->m_ipcBridge.removeListener(deviceId); +} + +AkVCam::BaseFilterPrivate::BaseFilterPrivate(AkVCam::BaseFilter *self, + const std::wstring &filterName, + const std::wstring &vendor): + self(self), + m_pins(new AkVCam::EnumPins), + m_videoProcAmp(new VideoProcAmp), + m_referenceClock(new ReferenceClock), + m_vendor(vendor), + m_filterName(filterName), + m_filterGraph(nullptr) +{ + this->m_pins->AddRef(); + this->m_videoProcAmp->AddRef(); + this->m_referenceClock->AddRef(); + + this->m_ipcBridge.connectServerStateChanged(this, + &BaseFilterPrivate::serverStateChanged); + this->m_ipcBridge.connectFrameReady(this, + &BaseFilterPrivate::frameReady); + this->m_ipcBridge.connectBroadcastingChanged(this, + &BaseFilterPrivate::setBroadcasting); + this->m_ipcBridge.connectMirrorChanged(this, + &BaseFilterPrivate::setMirror); + this->m_ipcBridge.connectScalingChanged(this, + &BaseFilterPrivate::setScaling); + this->m_ipcBridge.connectAspectRatioChanged(this, + &BaseFilterPrivate::setAspectRatio); + this->m_ipcBridge.connectSwapRgbChanged(this, + &BaseFilterPrivate::setSwapRgb); +} + +AkVCam::BaseFilterPrivate::~BaseFilterPrivate() +{ + this->m_ipcBridge.disconnectService(); + this->m_pins->setBaseFilter(nullptr); + this->m_pins->Release(); + this->m_videoProcAmp->Release(); + this->m_referenceClock->Release(); +} + +IEnumPins *AkVCam::BaseFilterPrivate::pinsForDevice(const std::string &deviceId) +{ + AkLogMethod(); + + CLSID clsid; + self->GetClassID(&clsid); + auto path = cameraPath(clsid); + + if (path.empty() || std::string(path.begin(), path.end()) != deviceId) + return nullptr; + + IEnumPins *pins = nullptr; + self->EnumPins(&pins); + + return pins; +} + +void AkVCam::BaseFilterPrivate::updatePins() +{ + CLSID clsid; + this->self->GetClassID(&clsid); + auto path = cameraPath(clsid); + std::string deviceId(path.begin(), path.end()); + + auto broadcaster = this->m_ipcBridge.broadcaster(deviceId); + AkVCamDevicePinCall(deviceId, + this, + setBroadcasting, + broadcaster); + auto hmirror = this->m_ipcBridge.isHorizontalMirrored(deviceId); + auto vmirror = this->m_ipcBridge.isVerticalMirrored(deviceId); + AkVCamDevicePinCall(deviceId, + this, + setMirror, + hmirror, + vmirror); + auto scaling = this->m_ipcBridge.scalingMode(deviceId); + AkVCamDevicePinCall(deviceId, + this, + setScaling, + scaling); + auto aspect = this->m_ipcBridge.aspectRatioMode(deviceId); + AkVCamDevicePinCall(deviceId, + this, + setAspectRatio, + aspect); + auto swap = this->m_ipcBridge.swapRgb(deviceId); + AkVCamDevicePinCall(deviceId, + this, + setSwapRgb, + swap); +} + +void AkVCam::BaseFilterPrivate::serverStateChanged(void *userData, + IpcBridge::ServerState state) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + IEnumPins *pins = nullptr; + self->self->EnumPins(&pins); + + if (pins) { + AkVCamPinCall(pins, serverStateChanged, state) + pins->Release(); + } + + if (state == IpcBridge::ServerStateAvailable) + self->updatePins(); +} + +void AkVCam::BaseFilterPrivate::frameReady(void *userData, + const std::string &deviceId, + const VideoFrame &frame) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + AkVCamDevicePinCall(deviceId, self, frameReady, frame); +} + +void AkVCam::BaseFilterPrivate::setBroadcasting(void *userData, + const std::string &deviceId, + const std::string &broadcaster) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + AkVCamDevicePinCall(deviceId, self, setBroadcasting, broadcaster); +} + +void AkVCam::BaseFilterPrivate::setMirror(void *userData, + const std::string &deviceId, + bool horizontalMirror, + bool verticalMirror) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + AkVCamDevicePinCall(deviceId, + self, + setMirror, + horizontalMirror, + verticalMirror); +} + +void AkVCam::BaseFilterPrivate::setScaling(void *userData, + const std::string &deviceId, + Scaling scaling) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + AkVCamDevicePinCall(deviceId, self, setScaling, scaling); +} + +void AkVCam::BaseFilterPrivate::setAspectRatio(void *userData, + const std::string &deviceId, + AspectRatio aspectRatio) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + AkVCamDevicePinCall(deviceId, self, setAspectRatio, aspectRatio); +} + +void AkVCam::BaseFilterPrivate::setSwapRgb(void *userData, + const std::string &deviceId, + bool swap) +{ + AkBaseFilterPrivateLog(); + auto self = reinterpret_cast(userData); + AkVCamDevicePinCall(deviceId, self, setSwapRgb, swap); +} diff --git a/dshow/VirtualCamera/src/basefilter.h b/dshow/VirtualCamera/src/basefilter.h new file mode 100644 index 0000000..5b66f55 --- /dev/null +++ b/dshow/VirtualCamera/src/basefilter.h @@ -0,0 +1,73 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef BASEFILTER_H +#define BASEFILTER_H + +#include +#include + +#include "mediafilter.h" + +namespace AkVCam +{ + class BaseFilterPrivate; + class VideoFormat; + + class BaseFilter: + public IBaseFilter, + public MediaFilter + { + public: + BaseFilter(const GUID &clsid, + const std::wstring &filterName={}, + const std::wstring &vendor={}); + virtual ~BaseFilter(); + + void addPin(const std::vector &formats={}, + const std::wstring &pinName={}, + bool changed=true); + void removePin(IPin *pin, bool changed=true); + static BaseFilter *create(const GUID &clsid); + IFilterGraph *filterGraph() const; + IReferenceClock *referenceClock() const; + + DECLARE_IMEDIAFILTER_NQ + + // IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + void **ppvObject); + + // IBaseFilter + HRESULT STDMETHODCALLTYPE EnumPins(IEnumPins **ppEnum); + HRESULT STDMETHODCALLTYPE FindPin(LPCWSTR Id, IPin **ppPin); + HRESULT STDMETHODCALLTYPE QueryFilterInfo(FILTER_INFO *pInfo); + HRESULT STDMETHODCALLTYPE JoinFilterGraph(IFilterGraph *pGraph, + LPCWSTR pName); + HRESULT STDMETHODCALLTYPE QueryVendorInfo(LPWSTR *pVendorInfo); + + private: + BaseFilterPrivate *d; + + protected: + void stateChanged(FILTER_STATE state); + }; +} + +#endif // BASEFILTER_H diff --git a/dshow/VirtualCamera/src/classfactory.cpp b/dshow/VirtualCamera/src/classfactory.cpp new file mode 100644 index 0000000..0f75a62 --- /dev/null +++ b/dshow/VirtualCamera/src/classfactory.cpp @@ -0,0 +1,121 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "classfactory.h" +#include "basefilter.h" +#include "persistpropertybag.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "ClassFactory" + +namespace AkVCam +{ + class ClassFactoryPrivate + { + public: + CLSID m_clsid; + static int m_locked; + }; + + int ClassFactoryPrivate::m_locked = 0; +} + +AkVCam::ClassFactory::ClassFactory(const CLSID &clsid): + CUnknown(this, IID_IClassFactory) +{ + this->d = new ClassFactoryPrivate; + this->d->m_clsid = clsid; +} + +AkVCam::ClassFactory::~ClassFactory() +{ + delete this->d; +} + +bool AkVCam::ClassFactory::locked() +{ + return ClassFactoryPrivate::m_locked > 0; +} + +HRESULT AkVCam::ClassFactory::QueryInterface(const IID &riid, void **ppvObject) +{ + AkLogMethod(); + AkLoggerLog("IID: ", AkVCam::stringFromClsid(riid)); + + if (!ppvObject) + return E_POINTER; + + *ppvObject = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) + || IsEqualIID(riid, IID_IClassFactory)) { + AkLogInterface(IClassFactory, this); + this->AddRef(); + *ppvObject = this; + + return S_OK; + } else if (IsEqualIID(riid, IID_IPersistPropertyBag)) { + auto persistPropertyBag = new PersistPropertyBag(this->d->m_clsid); + AkLogInterface(IPersistPropertyBag, persistPropertyBag); + persistPropertyBag->AddRef(); + *ppvObject = persistPropertyBag; + + return S_OK; + } else if (IsEqualIID(riid, IID_IBaseFilter)) { + auto baseFilter = BaseFilter::create(this->d->m_clsid); + AkLogInterface(IBaseFilter, baseFilter); + baseFilter->AddRef(); + *ppvObject = baseFilter; + + return S_OK; + } + + return CUnknown::QueryInterface(riid, ppvObject); +} + +HRESULT AkVCam::ClassFactory::CreateInstance(IUnknown *pUnkOuter, + const IID &riid, + void **ppvObject) +{ + AkLogMethod(); + AkLoggerLog("Outer: ", ULONG_PTR(pUnkOuter)); + AkLoggerLog("IID: ", stringFromClsid(riid)); + + if (!ppvObject) + return E_INVALIDARG; + + *ppvObject = nullptr; + + if (pUnkOuter && !IsEqualIID(riid, IID_IUnknown)) + return E_NOINTERFACE; + + this->AddRef(); + *ppvObject = this; + + return S_OK; +} + +HRESULT AkVCam::ClassFactory::LockServer(BOOL fLock) +{ + AkLogMethod(); + this->d->m_locked += fLock? 1: -1; + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/classfactory.h b/dshow/VirtualCamera/src/classfactory.h new file mode 100644 index 0000000..e0b7d8c --- /dev/null +++ b/dshow/VirtualCamera/src/classfactory.h @@ -0,0 +1,56 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef CLASSFACTORY_H +#define CLASSFACTORY_H + +#include "cunknown.h" + +namespace AkVCam +{ + class ClassFactoryPrivate; + + class ClassFactory: + public IClassFactory, + public CUnknown + { + public: + ClassFactory(const CLSID &clsid); + virtual ~ClassFactory(); + + static bool locked(); + + DECLARE_IUNKNOWN_NQ + + // IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + void **ppvObject); + + // IClassFactory + HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, + REFIID riid, + void **ppvObject); + HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock); + + private: + ClassFactoryPrivate *d; + }; +} + +#endif // CLASSFACTORY_H diff --git a/dshow/VirtualCamera/src/cunknown.cpp b/dshow/VirtualCamera/src/cunknown.cpp new file mode 100644 index 0000000..4c65e89 --- /dev/null +++ b/dshow/VirtualCamera/src/cunknown.cpp @@ -0,0 +1,118 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "cunknown.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AkCUnknownLogMethod() \ + AkLoggerLog((this->d->m_parent? \ + stringFromClsid(this->d->m_parentCLSID): \ + std::string("CUnknown")), \ + "(", (this->d->m_parent? this->d->m_parent: this), \ + ")::", __FUNCTION__, "()") + +#define AkCUnknownLogThis() \ + AkLoggerLog("Returning ", \ + (this->d->m_parent? \ + stringFromClsid(this->d->m_parentCLSID): \ + std::string("CUnknown")), \ + "(", (this->d->m_parent? this->d->m_parent: this), ")") + +namespace AkVCam +{ + class CUnknownPrivate + { + public: + std::atomic m_ref; + CUnknown *m_parent; + CLSID m_parentCLSID; + }; +} + +AkVCam::CUnknown::CUnknown(CUnknown *parent, REFIID parentCLSID) +{ + this->d = new CUnknownPrivate; + this->d->m_ref = 0; + this->d->m_parent = parent; + this->d->m_parentCLSID = parentCLSID; +} + +AkVCam::CUnknown::~CUnknown() +{ + +} + +void AkVCam::CUnknown::setParent(AkVCam::CUnknown *parent, + const IID *parentCLSID) +{ + this->d->m_parent = parent; + this->d->m_parentCLSID = parentCLSID? *parentCLSID: GUID_NULL; +} + +ULONG AkVCam::CUnknown::ref() const +{ + return this->d->m_ref; +} + +HRESULT AkVCam::CUnknown::QueryInterface(const IID &riid, void **ppvObject) +{ + AkCUnknownLogMethod(); + AkLoggerLog("IID: ", AkVCam::stringFromClsid(riid)); + + if (!ppvObject) + return E_POINTER; + + *ppvObject = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) + || IsEqualIID(riid, this->d->m_parentCLSID)) { + AkCUnknownLogThis(); + this->d->m_parent->AddRef(); + *ppvObject = this->d->m_parent; + + return S_OK; + } else { + AkLoggerLog("Unknown interface"); + } + + return E_NOINTERFACE; +} + +ULONG AkVCam::CUnknown::AddRef() +{ + AkCUnknownLogMethod(); + this->d->m_ref++; + AkLoggerLog("REF: ", this->d->m_ref); + + return this->d->m_ref; +} + +ULONG AkVCam::CUnknown::Release() +{ + AkCUnknownLogMethod(); + AkLoggerLog("REF: ", this->d->m_ref); + + if (this->d->m_ref) + this->d->m_ref--; + + return this->d->m_ref; +} diff --git a/dshow/VirtualCamera/src/cunknown.h b/dshow/VirtualCamera/src/cunknown.h new file mode 100644 index 0000000..7a2d444 --- /dev/null +++ b/dshow/VirtualCamera/src/cunknown.h @@ -0,0 +1,108 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef CUNKNOWN_H +#define CUNKNOWN_H + +#include + +namespace AkVCam +{ + class CUnknownPrivate; + + class CUnknown: public IUnknown + { + public: + CUnknown(CUnknown *parent, REFIID parentCLSID); + virtual ~CUnknown(); + + void setParent(CUnknown *parent, const IID *parentCLSID=nullptr); + ULONG ref() const; + + // IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + void **ppvObject); + ULONG STDMETHODCALLTYPE AddRef(); + ULONG STDMETHODCALLTYPE Release(); + + private: + CUnknownPrivate *d; + }; +} + +#define DECLARE_IUNKNOWN_Q(interfaceIid) \ + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, \ + void **ppvObject) \ + { \ + if (!ppvObject) \ + return E_POINTER; \ + \ + *ppvObject = nullptr; \ + \ + if (IsEqualIID(riid, interfaceIid)) { \ + this->AddRef(); \ + *ppvObject = this; \ + \ + return S_OK; \ + } \ + \ + return CUnknown::QueryInterface(riid, ppvObject); \ + } + +#define DECLARE_IUNKNOWN_R \ + ULONG STDMETHODCALLTYPE Release() \ + { \ + auto result = CUnknown::Release(); \ + \ + if (!result) \ + delete this; \ + \ + return result; \ + } + +#define DECLARE_IUNKNOWN_NQR \ + ULONG ref() const \ + { \ + return CUnknown::ref(); \ + } \ + \ + void setParent(CUnknown *parent, const IID *parentCLSID=nullptr) \ + { \ + return CUnknown::setParent(parent, parentCLSID); \ + } \ + \ + ULONG STDMETHODCALLTYPE AddRef() \ + { \ + return CUnknown::AddRef(); \ + } + +#define DECLARE_IUNKNOWN_NQ \ + DECLARE_IUNKNOWN_NQR \ + DECLARE_IUNKNOWN_R + +#define DECLARE_IUNKNOWN_NR(interfaceIid) \ + DECLARE_IUNKNOWN_NQR \ + DECLARE_IUNKNOWN_Q(interfaceIid) + +#define DECLARE_IUNKNOWN(interfaceIid) \ + DECLARE_IUNKNOWN_NQR \ + DECLARE_IUNKNOWN_Q(interfaceIid) \ + DECLARE_IUNKNOWN_R + +#endif // CUNKNOWN_H diff --git a/dshow/VirtualCamera/src/enummediatypes.cpp b/dshow/VirtualCamera/src/enummediatypes.cpp new file mode 100644 index 0000000..a8c1159 --- /dev/null +++ b/dshow/VirtualCamera/src/enummediatypes.cpp @@ -0,0 +1,181 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "enummediatypes.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "EnumMediaTypes" + +namespace AkVCam +{ + class EnumMediaTypesPrivate + { + public: + std::vector m_formats; + size_t m_position; + bool m_changed; + }; +} + +AkVCam::EnumMediaTypes::EnumMediaTypes(const std::vector &formats): + CUnknown(this, IID_IEnumMediaTypes) +{ + this->d = new EnumMediaTypesPrivate; + this->d->m_formats = formats; + this->d->m_position = 0; + this->d->m_changed = false; +} + +AkVCam::EnumMediaTypes::EnumMediaTypes(const AkVCam::EnumMediaTypes &other): + CUnknown(this, IID_IEnumMediaTypes) +{ + this->d = new EnumMediaTypesPrivate; + this->d->m_formats = other.d->m_formats; + this->d->m_position = other.d->m_position; + this->d->m_changed = other.d->m_changed; +} + +AkVCam::EnumMediaTypes &AkVCam::EnumMediaTypes::operator =(const EnumMediaTypes &other) +{ + if (this != &other) { + this->d->m_formats = other.d->m_formats; + this->d->m_position = other.d->m_position; + this->d->m_changed = other.d->m_changed; + } + + return *this; +} + +AkVCam::EnumMediaTypes::~EnumMediaTypes() +{ + delete this->d; +} + +std::vector &AkVCam::EnumMediaTypes::formats() +{ + return this->d->m_formats; +} + +std::vector AkVCam::EnumMediaTypes::formats() const +{ + return this->d->m_formats; +} + +void AkVCam::EnumMediaTypes::setFormats(const std::vector &formats, + bool changed) +{ + if (this->d->m_formats == formats) + return; + + this->d->m_formats = formats; + this->d->m_changed = changed; +} + +HRESULT AkVCam::EnumMediaTypes::Next(ULONG cMediaTypes, + AM_MEDIA_TYPE **ppMediaTypes, + ULONG *pcFetched) +{ + AkLogMethod(); + + if (pcFetched) + *pcFetched = 0; + + if (!cMediaTypes) + return E_INVALIDARG; + + if (!ppMediaTypes) + return E_POINTER; + + memset(ppMediaTypes, 0, cMediaTypes * sizeof(AM_MEDIA_TYPE *)); + + if (this->d->m_changed) { + this->d->m_changed = false; + + return VFW_E_ENUM_OUT_OF_SYNC; + } + + ULONG fetched = 0; + + for (; + fetched < cMediaTypes + && this->d->m_position < this->d->m_formats.size(); + fetched++, this->d->m_position++) { + *ppMediaTypes = mediaTypeFromFormat(this->d->m_formats[this->d->m_position]); + + if (*ppMediaTypes) + ppMediaTypes++; + } + + if (pcFetched) + *pcFetched = fetched; + + return fetched == cMediaTypes? S_OK: S_FALSE; +} + +HRESULT AkVCam::EnumMediaTypes::Skip(ULONG cMediaTypes) +{ + AkLogMethod(); + AkLoggerLog("Skip ", cMediaTypes, " media types"); + + if (this->d->m_changed) { + this->d->m_changed = false; + + return VFW_E_ENUM_OUT_OF_SYNC; + } + + auto position = this->d->m_position + cMediaTypes; + + if (position > this->d->m_formats.size()) + return S_FALSE; + + this->d->m_position = position; + + return S_OK; +} + +HRESULT AkVCam::EnumMediaTypes::Reset() +{ + AkLogMethod(); + this->d->m_position = 0; + + return S_OK; +} + +HRESULT AkVCam::EnumMediaTypes::Clone(IEnumMediaTypes **ppEnum) +{ + AkLogMethod(); + + if (!ppEnum) + return E_POINTER; + + if (this->d->m_changed) { + this->d->m_changed = false; + + return VFW_E_ENUM_OUT_OF_SYNC; + } + + *ppEnum = new EnumMediaTypes(*this); + (*ppEnum)->AddRef(); + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/enummediatypes.h b/dshow/VirtualCamera/src/enummediatypes.h new file mode 100644 index 0000000..359d499 --- /dev/null +++ b/dshow/VirtualCamera/src/enummediatypes.h @@ -0,0 +1,63 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef ENUMMEDIATYPES_H +#define ENUMMEDIATYPES_H + +#include +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class EnumMediaTypesPrivate; + class VideoFormat; + + class EnumMediaTypes: + public IEnumMediaTypes, + public CUnknown + { + public: + EnumMediaTypes(const std::vector &formats={}); + EnumMediaTypes(const EnumMediaTypes &other); + EnumMediaTypes &operator =(const EnumMediaTypes &other); + virtual ~EnumMediaTypes(); + + std::vector &formats(); + std::vector formats() const; + void setFormats(const std::vector &formats, + bool changed=true); + + DECLARE_IUNKNOWN(IID_IEnumMediaTypes) + + // IEnumMediaTypes + HRESULT STDMETHODCALLTYPE Next(ULONG cMediaTypes, + AM_MEDIA_TYPE **ppMediaTypes, + ULONG *pcFetched); + HRESULT STDMETHODCALLTYPE Skip(ULONG cMediaTypes); + HRESULT STDMETHODCALLTYPE Reset(); + HRESULT STDMETHODCALLTYPE Clone(IEnumMediaTypes **ppEnum); + + private: + EnumMediaTypesPrivate *d; + }; +} + +#endif // ENUMMEDIATYPES_H diff --git a/dshow/VirtualCamera/src/enumpins.cpp b/dshow/VirtualCamera/src/enumpins.cpp new file mode 100644 index 0000000..84dea31 --- /dev/null +++ b/dshow/VirtualCamera/src/enumpins.cpp @@ -0,0 +1,186 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include + +#include "enumpins.h" +#include "pin.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "EnumPins" + +namespace AkVCam +{ + class EnumPinsPrivate + { + public: + std::vector m_pins; + size_t m_position {0}; + bool m_changed {false}; + }; +} + +AkVCam::EnumPins::EnumPins(): + CUnknown(this, IID_IEnumPins) +{ + this->d = new EnumPinsPrivate; +} + +AkVCam::EnumPins::EnumPins(const EnumPins &other): + CUnknown(this, IID_IEnumPins) +{ + this->d = new EnumPinsPrivate; + this->d->m_position = other.d->m_position; + this->d->m_changed = other.d->m_changed; + + for (auto &pin: other.d->m_pins) { + this->d->m_pins.push_back(pin); + this->d->m_pins.back()->AddRef(); + } +} + +AkVCam::EnumPins::~EnumPins() +{ + for (auto &pin: this->d->m_pins) + pin->Release(); + + delete this->d; +} + +size_t AkVCam::EnumPins::count() const +{ + return this->d->m_pins.size(); +} + +void AkVCam::EnumPins::addPin(IPin *pin, bool changed) +{ + this->d->m_pins.push_back(pin); + this->d->m_pins.back()->AddRef(); + this->d->m_changed = changed; +} + +void AkVCam::EnumPins::removePin(IPin *pin, bool changed) +{ + for (auto it = this->d->m_pins.begin(); it != this->d->m_pins.end(); it++) + if (*it == pin) { + this->d->m_pins.erase(it); + pin->Release(); + this->d->m_changed = changed; + + break; + } +} + +void AkVCam::EnumPins::setBaseFilter(BaseFilter *baseFilter) +{ + for (auto &pin: this->d->m_pins) { + auto akPin = static_cast(pin); + akPin->setBaseFilter(baseFilter); + } +} + +HRESULT AkVCam::EnumPins::Next(ULONG cPins, IPin **ppPins, ULONG *pcFetched) +{ + AkLogMethod(); + + if (pcFetched) + *pcFetched = 0; + + if (!cPins) + return E_INVALIDARG; + + if (!ppPins) + return E_POINTER; + + memset(ppPins, 0, cPins * sizeof(IPin *)); + + if (this->d->m_changed) { + this->d->m_changed = false; + + return VFW_E_ENUM_OUT_OF_SYNC; + } + + ULONG fetched = 0; + + for (; + fetched < cPins + && this->d->m_position < this->d->m_pins.size(); + fetched++, this->d->m_position++) { + *ppPins = this->d->m_pins[this->d->m_position]; + (*ppPins)->AddRef(); + + if (*ppPins) + ppPins++; + } + + if (pcFetched) + *pcFetched = fetched; + + return fetched == cPins? S_OK: S_FALSE; +} + +HRESULT AkVCam::EnumPins::Skip(ULONG cPins) +{ + AkLogMethod(); + AkLoggerLog("Skip ", cPins, " pins"); + + if (this->d->m_changed) { + this->d->m_changed = false; + + return VFW_E_ENUM_OUT_OF_SYNC; + } + + auto position = this->d->m_position + cPins; + + if (position > this->d->m_pins.size()) + return S_FALSE; + + this->d->m_position = position; + + return S_OK; +} + +HRESULT AkVCam::EnumPins::Reset() +{ + AkLogMethod(); + this->d->m_position = 0; + + return S_OK; +} + +HRESULT AkVCam::EnumPins::Clone(IEnumPins **ppEnum) +{ + AkLogMethod(); + + if (!ppEnum) + return E_POINTER; + + if (this->d->m_changed) { + this->d->m_changed = false; + + return VFW_E_ENUM_OUT_OF_SYNC; + } + + *ppEnum = new EnumPins(*this); + (*ppEnum)->AddRef(); + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/enumpins.h b/dshow/VirtualCamera/src/enumpins.h new file mode 100644 index 0000000..8be7225 --- /dev/null +++ b/dshow/VirtualCamera/src/enumpins.h @@ -0,0 +1,61 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef ENUMPINS_H +#define ENUMPINS_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class EnumPinsPrivate; + class BaseFilter; + + class EnumPins: + public IEnumPins, + public CUnknown + { + public: + EnumPins(); + EnumPins(const EnumPins &other); + virtual ~EnumPins(); + + size_t count() const; + void addPin(IPin *pin, bool changed=true); + void removePin(IPin *pin, bool changed=true); + void setBaseFilter(AkVCam::BaseFilter *baseFilter); + + DECLARE_IUNKNOWN(IID_IEnumPins) + + // IEnumPins + HRESULT STDMETHODCALLTYPE Next(ULONG cPins, + IPin **ppPins, + ULONG *pcFetched); + HRESULT STDMETHODCALLTYPE Skip(ULONG cPins); + HRESULT STDMETHODCALLTYPE Reset(); + HRESULT STDMETHODCALLTYPE Clone(IEnumPins **ppEnum); + + private: + EnumPinsPrivate *d; + }; +} + +#endif // ENUMPINS_H diff --git a/dshow/VirtualCamera/src/filtermiscflags.cpp b/dshow/VirtualCamera/src/filtermiscflags.cpp new file mode 100644 index 0000000..a7be7f5 --- /dev/null +++ b/dshow/VirtualCamera/src/filtermiscflags.cpp @@ -0,0 +1,42 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "filtermiscflags.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "FilterMiscFlags" + +AkVCam::FilterMiscFlags::FilterMiscFlags(): + CUnknown(this, IID_IAMFilterMiscFlags) +{ + +} + +AkVCam::FilterMiscFlags::~FilterMiscFlags() +{ + +} + +ULONG AkVCam::FilterMiscFlags::GetMiscFlags() +{ + AkLogMethod(); + + return AM_FILTER_MISC_FLAGS_IS_SOURCE; +} diff --git a/dshow/VirtualCamera/src/filtermiscflags.h b/dshow/VirtualCamera/src/filtermiscflags.h new file mode 100644 index 0000000..b32d3ea --- /dev/null +++ b/dshow/VirtualCamera/src/filtermiscflags.h @@ -0,0 +1,44 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef FILTERMISCFLAGS_H +#define FILTERMISCFLAGS_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class FilterMiscFlags: + public IAMFilterMiscFlags, + public CUnknown + { + public: + FilterMiscFlags(); + virtual ~FilterMiscFlags(); + + DECLARE_IUNKNOWN(IID_IAMFilterMiscFlags) + + // IAMFilterMiscFlags + ULONG STDMETHODCALLTYPE GetMiscFlags(); + }; +} + +#endif // FILTERMISCFLAGS_H diff --git a/dshow/VirtualCamera/src/latency.cpp b/dshow/VirtualCamera/src/latency.cpp new file mode 100644 index 0000000..19bc964 --- /dev/null +++ b/dshow/VirtualCamera/src/latency.cpp @@ -0,0 +1,78 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "latency.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "Latency" + +namespace AkVCam +{ + class LatencyPrivate + { + public: + IAMStreamConfig *m_streamConfig; + }; +} + +AkVCam::Latency::Latency(IAMStreamConfig *streamConfig): + CUnknown(this, IID_IAMLatency) +{ + this->d = new LatencyPrivate; + this->d->m_streamConfig = streamConfig; +} + +AkVCam::Latency::~Latency() +{ + delete this->d; +} + +HRESULT AkVCam::Latency::GetLatency(REFERENCE_TIME *prtLatency) +{ + AkLogMethod(); + + if (!prtLatency) + return E_POINTER; + + *prtLatency = 0; + + if (this->d->m_streamConfig) { + AM_MEDIA_TYPE *mediaType = nullptr; + + if (SUCCEEDED(this->d->m_streamConfig->GetFormat(&mediaType))) { + if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) { + auto format = reinterpret_cast(mediaType->pbFormat); + *prtLatency = format->AvgTimePerFrame; + } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) { + auto format = reinterpret_cast(mediaType->pbFormat); + *prtLatency = format->AvgTimePerFrame; + } + + deleteMediaType(&mediaType); + } + } + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/latency.h b/dshow/VirtualCamera/src/latency.h new file mode 100644 index 0000000..1220213 --- /dev/null +++ b/dshow/VirtualCamera/src/latency.h @@ -0,0 +1,61 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef LATENCY_H +#define LATENCY_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class LatencyPrivate; + + class Latency: + public IAMLatency, + public CUnknown + { + public: + Latency(IAMStreamConfig *streamConfig=nullptr); + virtual ~Latency(); + + DECLARE_IUNKNOWN(IID_IAMLatency) + + // IAMLatency + HRESULT WINAPI GetLatency(REFERENCE_TIME *prtLatency); + + private: + LatencyPrivate *d; + }; +} + +#define DECLARE_IAMLATENCY_NQ \ + DECLARE_IUNKNOWN_NQ \ + \ + HRESULT WINAPI GetLatency(REFERENCE_TIME *prtLatency) \ + { \ + return Latency::GetLatency(prtLatency); \ + } + +#define DECLARE_IAMLATENCY(interfaceIid) \ + DECLARE_IUNKNOWN_Q(interfaceIid) \ + DECLARE_IAMLATENCY_NQ + +#endif // LATENCY_H diff --git a/dshow/VirtualCamera/src/mediafilter.cpp b/dshow/VirtualCamera/src/mediafilter.cpp new file mode 100644 index 0000000..e83d513 --- /dev/null +++ b/dshow/VirtualCamera/src/mediafilter.cpp @@ -0,0 +1,191 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include + +#include "mediafilter.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "MediaFilter" + +namespace AkVCam +{ + typedef std::pair StateChangedCallback; + + class MediaFilterPrivate + { + public: + IBaseFilter *m_baseFilter; + IReferenceClock *m_clock; + std::vector m_stateChanged; + FILTER_STATE m_state; + REFERENCE_TIME m_start; + }; +} + +AkVCam::MediaFilter::MediaFilter(const IID &classCLSID, + IBaseFilter *baseFilter): + PersistPropertyBag(classCLSID) +{ + this->setParent(this, &IID_IMediaFilter); + this->d = new MediaFilterPrivate; + this->d->m_baseFilter = baseFilter; + this->d->m_clock = nullptr; + this->d->m_state = State_Stopped; + this->d->m_start = 0; +} + +AkVCam::MediaFilter::~MediaFilter() +{ + if (this->d->m_clock) + this->d->m_clock->Release(); + + delete this->d; +} + +void AkVCam::MediaFilter::connectStateChanged(void *userData, + StateChangedCallbackT callback) +{ + AkLogMethod(); + this->d->m_stateChanged.push_back({userData, callback}); +} + +void AkVCam::MediaFilter::disconnectStateChanged(void *userData, + StateChangedCallbackT callback) +{ + AkLogMethod(); + + for (auto it = this->d->m_stateChanged.begin(); + it != this->d->m_stateChanged.end(); + it++) { + if (it->first == userData + && it->second == callback) { + this->d->m_stateChanged.erase(it); + + break; + } + } +} + +HRESULT AkVCam::MediaFilter::Stop() +{ + AkLogMethod(); + this->d->m_state = State_Stopped; + HRESULT result = S_OK; + + for (auto &callback: this->d->m_stateChanged) { + auto r = callback.second(callback.first, this->d->m_state); + + if (r != S_OK) + result = r; + } + + this->stateChanged(this->d->m_state); + + return result; +} + +HRESULT AkVCam::MediaFilter::Pause() +{ + AkLogMethod(); + this->d->m_state = State_Paused; + HRESULT result = S_OK; + + for (auto &callback: this->d->m_stateChanged) { + auto r = callback.second(callback.first, this->d->m_state); + + if (r != S_OK) + result = r; + } + + this->stateChanged(this->d->m_state); + + return result; +} + +HRESULT AkVCam::MediaFilter::Run(REFERENCE_TIME tStart) +{ + AkLogMethod(); + this->d->m_start = tStart; + this->d->m_state = State_Running; + HRESULT result = S_OK; + + for (auto &callback: this->d->m_stateChanged) { + auto r = callback.second(callback.first, this->d->m_state); + + if (r != S_OK) + result = r; + } + + this->stateChanged(this->d->m_state); + + return result; +} + +HRESULT AkVCam::MediaFilter::GetState(DWORD dwMilliSecsTimeout, + FILTER_STATE *State) +{ + UNUSED(dwMilliSecsTimeout) + AkLogMethod(); + + if (!State) + return E_POINTER; + + *State = this->d->m_state; + AkLoggerLog("State: ", *State); + + return S_OK; +} + +HRESULT AkVCam::MediaFilter::SetSyncSource(IReferenceClock *pClock) +{ + AkLogMethod(); + + if (this->d->m_clock) + this->d->m_clock->Release(); + + this->d->m_clock = pClock; + + if (this->d->m_clock) + this->d->m_clock->AddRef(); + + return S_OK; +} + +HRESULT AkVCam::MediaFilter::GetSyncSource(IReferenceClock **pClock) +{ + AkLogMethod(); + + if (!pClock) + return E_POINTER; + + *pClock = this->d->m_clock; + + if (*pClock) + (*pClock)->AddRef(); + + return S_OK; +} + +void AkVCam::MediaFilter::stateChanged(FILTER_STATE state) +{ + UNUSED(state) +} diff --git a/dshow/VirtualCamera/src/mediafilter.h b/dshow/VirtualCamera/src/mediafilter.h new file mode 100644 index 0000000..f8f7286 --- /dev/null +++ b/dshow/VirtualCamera/src/mediafilter.h @@ -0,0 +1,115 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MEDIAFILTER_H +#define MEDIAFILTER_H + +#include + +#include "persistpropertybag.h" + +namespace AkVCam +{ + class MediaFilterPrivate; + typedef HRESULT (*StateChangedCallbackT)(void *userData, + FILTER_STATE state); + + class MediaFilter: + public IMediaFilter, + public PersistPropertyBag + { + public: + MediaFilter(REFIID classCLSID, IBaseFilter *baseFilter); + virtual ~MediaFilter(); + + void connectStateChanged(void *userData, + StateChangedCallbackT callback); + void disconnectStateChanged(void *userData, + StateChangedCallbackT callback); + + DECLARE_IPERSISTPROPERTYBAG(IID_IMediaFilter) + + // IMediaFilter + HRESULT STDMETHODCALLTYPE Stop(); + HRESULT STDMETHODCALLTYPE Pause(); + HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart); + HRESULT STDMETHODCALLTYPE GetState(DWORD dwMilliSecsTimeout, + FILTER_STATE *State); + HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock *pClock); + HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock **pClock); + + private: + MediaFilterPrivate *d; + + protected: + virtual void stateChanged(FILTER_STATE state); + }; +} + +#define DECLARE_IMEDIAFILTER_NQ \ + DECLARE_IPERSISTPROPERTYBAG_NQ \ + \ + void connectStateChanged(void *userData, \ + StateChangedCallbackT callback) \ + { \ + MediaFilter::connectStateChanged(userData, callback); \ + } \ + \ + void disconnectStateChanged(void *userData, \ + StateChangedCallbackT callback) \ + { \ + MediaFilter::disconnectStateChanged(userData, callback); \ + } \ + \ + HRESULT STDMETHODCALLTYPE Stop() \ + { \ + return MediaFilter::Stop(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE Pause() \ + { \ + return MediaFilter::Pause(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE Run(REFERENCE_TIME tStart) \ + { \ + return MediaFilter::Run(tStart); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetState(DWORD dwMilliSecsTimeout, \ + FILTER_STATE *State) \ + { \ + return MediaFilter::GetState(dwMilliSecsTimeout, State); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetSyncSource(IReferenceClock *pClock) \ + { \ + return MediaFilter::SetSyncSource(pClock); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetSyncSource(IReferenceClock **pClock) \ + { \ + return MediaFilter::GetSyncSource(pClock); \ + } + +#define DECLARE_IMEDIAFILTER(interfaceIid) \ + DECLARE_IPERSISTPROPERTYBAG_Q(interfaceIid) \ + DECLARE_IMEDIAFILTER_NQ + +#endif // MEDIAFILTER_H diff --git a/dshow/VirtualCamera/src/mediasample.cpp b/dshow/VirtualCamera/src/mediasample.cpp new file mode 100644 index 0000000..5ab1225 --- /dev/null +++ b/dshow/VirtualCamera/src/mediasample.cpp @@ -0,0 +1,275 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "mediasample.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "MediaSample" + +namespace AkVCam +{ + class MediaSamplePrivate + { + public: + IMemAllocator *m_memAllocator; + BYTE *m_buffer; + LONG m_bufferSize; + LONG m_dataLength; + LONG m_prefix; + AM_MEDIA_TYPE *m_mediaType; + REFERENCE_TIME m_sampleTimeStart; + REFERENCE_TIME m_sampleTimeEnd; + REFERENCE_TIME m_mediaTimeStart; + REFERENCE_TIME m_mediaTimeEnd; + BOOL m_syncPoint; + BOOL m_preroll; + BOOL m_discontinuity; + BOOL m_mediaTypeChanged; + }; +} + +AkVCam::MediaSample::MediaSample(IMemAllocator *memAllocator, + LONG bufferSize, + LONG align, + LONG prefix): + CUnknown(this, IID_IMediaSample) +{ + this->d = new MediaSamplePrivate; + this->d->m_memAllocator = memAllocator; + this->d->m_bufferSize = bufferSize; + this->d->m_dataLength = bufferSize; + this->d->m_prefix = prefix; + auto realSize = size_t(bufferSize + prefix + align - 1) & ~size_t(align - 1); + this->d->m_buffer = new BYTE[realSize]; + this->d->m_mediaType = nullptr; + this->d->m_sampleTimeStart = -1; + this->d->m_sampleTimeEnd = -1; + this->d->m_mediaTimeStart = -1; + this->d->m_mediaTimeEnd = -1; + this->d->m_syncPoint = S_FALSE; + this->d->m_preroll = S_FALSE; + this->d->m_discontinuity = S_FALSE; + this->d->m_mediaTypeChanged = S_FALSE; +} + +AkVCam::MediaSample::~MediaSample() +{ + delete [] this->d->m_buffer; + deleteMediaType(&this->d->m_mediaType); + delete this->d; +} + +ULONG AkVCam::MediaSample::Release() +{ + auto result = CUnknown::Release(); + this->d->m_memAllocator->ReleaseBuffer(this); + + if (!result) + delete this; + + return result; +} + +HRESULT AkVCam::MediaSample::GetPointer(BYTE **ppBuffer) +{ + AkLogMethod(); + + if (!ppBuffer) + return E_POINTER; + + *ppBuffer = this->d->m_buffer + this->d->m_prefix; + + return S_OK; +} + +LONG AkVCam::MediaSample::GetSize() +{ + AkLogMethod(); + + return this->d->m_bufferSize; +} + +HRESULT AkVCam::MediaSample::GetTime(REFERENCE_TIME *pTimeStart, + REFERENCE_TIME *pTimeEnd) +{ + AkLogMethod(); + + if (!pTimeStart || !pTimeEnd) + return E_POINTER; + + *pTimeStart = this->d->m_sampleTimeStart; + *pTimeEnd = this->d->m_sampleTimeEnd; + + if (*pTimeStart < 0) + return VFW_E_SAMPLE_TIME_NOT_SET; + + if (*pTimeEnd < 0) { + *pTimeEnd = *pTimeStart + 1; + + return VFW_S_NO_STOP_TIME; + } + + return S_OK; +} + +HRESULT AkVCam::MediaSample::SetTime(REFERENCE_TIME *pTimeStart, + REFERENCE_TIME *pTimeEnd) +{ + AkLogMethod(); + + this->d->m_sampleTimeStart = pTimeStart? *pTimeStart: -1; + this->d->m_sampleTimeEnd = pTimeEnd? *pTimeEnd: -1; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::IsSyncPoint() +{ + AkLogMethod(); + + return this->d->m_syncPoint? S_OK: S_FALSE; +} + +HRESULT AkVCam::MediaSample::SetSyncPoint(BOOL bIsSyncPoint) +{ + AkLogMethod(); + this->d->m_syncPoint = bIsSyncPoint; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::IsPreroll() +{ + AkLogMethod(); + + return this->d->m_preroll? S_OK: S_FALSE; +} + +HRESULT AkVCam::MediaSample::SetPreroll(BOOL bIsPreroll) +{ + AkLogMethod(); + this->d->m_preroll = bIsPreroll; + + return S_OK; +} + +LONG AkVCam::MediaSample::GetActualDataLength() +{ + AkLogMethod(); + + return this->d->m_dataLength; +} + +HRESULT AkVCam::MediaSample::SetActualDataLength(LONG lLen) +{ + AkLogMethod(); + + if (lLen < 0 || lLen > this->d->m_bufferSize) + return VFW_E_BUFFER_OVERFLOW; + + this->d->m_dataLength = lLen; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::GetMediaType(AM_MEDIA_TYPE **ppMediaType) +{ + AkLogMethod(); + + if (!ppMediaType) + return E_POINTER; + + *ppMediaType = nullptr; + + if (this->d->m_mediaTypeChanged == S_FALSE) + return S_FALSE; + + *ppMediaType = createMediaType(this->d->m_mediaType); + + if (!*ppMediaType) + return E_OUTOFMEMORY; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::SetMediaType(AM_MEDIA_TYPE *pMediaType) +{ + AkLogMethod(); + + if (!pMediaType) + return E_POINTER; + + if (isEqualMediaType(pMediaType, this->d->m_mediaType, true)) { + this->d->m_mediaTypeChanged = S_FALSE; + + return S_OK; + } + + deleteMediaType(&this->d->m_mediaType); + this->d->m_mediaType = createMediaType(pMediaType); + this->d->m_mediaTypeChanged = S_OK; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::IsDiscontinuity() +{ + AkLogMethod(); + + return this->d->m_discontinuity? S_OK: S_FALSE; +} + +HRESULT AkVCam::MediaSample::SetDiscontinuity(BOOL bDiscontinuity) +{ + AkLogMethod(); + this->d->m_discontinuity = bDiscontinuity; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::GetMediaTime(LONGLONG *pTimeStart, + LONGLONG *pTimeEnd) +{ + AkLogMethod(); + + if (!pTimeStart || !pTimeEnd) + return E_POINTER; + + *pTimeStart = this->d->m_mediaTimeStart; + *pTimeEnd = this->d->m_mediaTimeEnd; + + if (*pTimeStart < 0 || *pTimeEnd < 0) + return VFW_E_MEDIA_TIME_NOT_SET; + + return S_OK; +} + +HRESULT AkVCam::MediaSample::SetMediaTime(LONGLONG *pTimeStart, + LONGLONG *pTimeEnd) +{ + AkLogMethod(); + + this->d->m_mediaTimeStart = pTimeStart? *pTimeStart: -1; + this->d->m_mediaTimeEnd = pTimeEnd? *pTimeEnd: -1; + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/mediasample.h b/dshow/VirtualCamera/src/mediasample.h new file mode 100644 index 0000000..e7b27f2 --- /dev/null +++ b/dshow/VirtualCamera/src/mediasample.h @@ -0,0 +1,164 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MEDIASAMPLE_H +#define MEDIASAMPLE_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class MediaSamplePrivate; + + class MediaSample: + public IMediaSample, + public CUnknown + { + public: + MediaSample(IMemAllocator *memAllocator, + LONG bufferSize, LONG align, LONG prefix); + virtual ~MediaSample(); + + DECLARE_IUNKNOWN_NR(IID_IMediaSample) + + // IUnknown + ULONG STDMETHODCALLTYPE Release(); + + // IMediaSample + HRESULT STDMETHODCALLTYPE GetPointer(BYTE **ppBuffer); + LONG STDMETHODCALLTYPE GetSize(); + HRESULT STDMETHODCALLTYPE GetTime(REFERENCE_TIME *pTimeStart, + REFERENCE_TIME *pTimeEnd); + HRESULT STDMETHODCALLTYPE SetTime(REFERENCE_TIME *pTimeStart, + REFERENCE_TIME *pTimeEnd); + HRESULT STDMETHODCALLTYPE IsSyncPoint(); + HRESULT STDMETHODCALLTYPE SetSyncPoint(BOOL bIsSyncPoint); + HRESULT STDMETHODCALLTYPE IsPreroll(); + HRESULT STDMETHODCALLTYPE SetPreroll(BOOL bIsPreroll); + LONG STDMETHODCALLTYPE GetActualDataLength(); + HRESULT STDMETHODCALLTYPE SetActualDataLength(LONG lLen); + HRESULT STDMETHODCALLTYPE GetMediaType(AM_MEDIA_TYPE **ppMediaType); + HRESULT STDMETHODCALLTYPE SetMediaType(AM_MEDIA_TYPE *pMediaType); + HRESULT STDMETHODCALLTYPE IsDiscontinuity(); + HRESULT STDMETHODCALLTYPE SetDiscontinuity(BOOL bDiscontinuity); + HRESULT STDMETHODCALLTYPE GetMediaTime(LONGLONG *pTimeStart, + LONGLONG *pTimeEnd); + HRESULT STDMETHODCALLTYPE SetMediaTime(LONGLONG *pTimeStart, + LONGLONG *pTimeEnd); + + private: + MediaSamplePrivate *d; + }; +} + +#define DECLARE_IMEDIASAMPLE(interfaceIid) \ + DECLARE_IUNKNOWN_NR(interfaceIid) \ + \ + ULONG STDMETHODCALLTYPE Release() \ + { \ + return MediaSample::Release(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetPointer(BYTE **ppBuffer) \ + { \ + return MediaSample::GetPointer(ppBuffer); \ + } \ + \ + LONG STDMETHODCALLTYPE GetSize() \ + { \ + return MediaSample::GetSize(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetTime(REFERENCE_TIME *pTimeStart, \ + REFERENCE_TIME *pTimeEnd) \ + { \ + return MediaSample::GetTime(pTimeStart, pTimeEnd); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetTime(REFERENCE_TIME *pTimeStart, \ + REFERENCE_TIME *pTimeEnd) \ + { \ + return MediaSample::SetTime(pTimeStart, pTimeEnd); \ + } \ + \ + HRESULT STDMETHODCALLTYPE IsSyncPoint() \ + { \ + return MediaSample::IsSyncPoint(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetSyncPoint(BOOL bIsSyncPoint) \ + { \ + return MediaSample::SetSyncPoint(bIsSyncPoint); \ + } \ + \ + HRESULT STDMETHODCALLTYPE IsPreroll() \ + { \ + return MediaSample::IsPreroll(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetPreroll(BOOL bIsPreroll) \ + { \ + return MediaSample::SetPreroll(bIsPreroll); \ + } \ + \ + LONG STDMETHODCALLTYPE GetActualDataLength() \ + { \ + return MediaSample::GetActualDataLength(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetActualDataLength(LONG lLen) \ + { \ + return MediaSample::SetActualDataLength(lLen); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetMediaType(AM_MEDIA_TYPE **ppMediaType) \ + { \ + return MediaSample::GetMediaType(ppMediaType); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetMediaType(AM_MEDIA_TYPE *pMediaType) \ + { \ + return MediaSample::SetMediaType(pMediaType); \ + } \ + \ + HRESULT STDMETHODCALLTYPE IsDiscontinuity() \ + { \ + return MediaSample::IsDiscontinuity(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetDiscontinuity(BOOL bDiscontinuity) \ + { \ + return MediaSample::SetDiscontinuity(bDiscontinuity); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetMediaTime(LONGLONG *pTimeStart, \ + LONGLONG *pTimeEnd) \ + { \ + return MediaSample::GetMediaTime(pTimeStart, pTimeEnd); \ + } \ + \ + HRESULT STDMETHODCALLTYPE SetMediaTime(LONGLONG *pTimeStart, \ + LONGLONG *pTimeEnd) \ + { \ + return MediaSample::SetMediaTime(pTimeStart, pTimeEnd); \ + } + +#endif // MEDIASAMPLE_H diff --git a/dshow/VirtualCamera/src/mediasample2.cpp b/dshow/VirtualCamera/src/mediasample2.cpp new file mode 100644 index 0000000..355586e --- /dev/null +++ b/dshow/VirtualCamera/src/mediasample2.cpp @@ -0,0 +1,123 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "mediasample2.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "MediaFilter2" + +namespace AkVCam +{ + class MediaSample2Private + { + public: + DWORD m_data; + DWORD m_typeSpecificFlags; + DWORD m_sampleFlags; + DWORD m_streamId; + AM_MEDIA_TYPE *m_mediaType; + }; +} + +AkVCam::MediaSample2::MediaSample2(IMemAllocator *memAllocator, + LONG bufferSize, + LONG align, + LONG prefix): + MediaSample(memAllocator, bufferSize, align, prefix) +{ + this->d = new MediaSample2Private; + this->d->m_data = 0; + this->d->m_typeSpecificFlags = 0; + this->d->m_sampleFlags = 0; + this->d->m_streamId = 0; + this->d->m_mediaType = nullptr; +} + +AkVCam::MediaSample2::~MediaSample2() +{ + deleteMediaType(&this->d->m_mediaType); + + delete this->d; +} + +HRESULT AkVCam::MediaSample2::SetProperties(DWORD cbProperties, + const BYTE *pbProperties) +{ + AkLogMethod(); + + if (cbProperties < sizeof(AM_SAMPLE2_PROPERTIES)) + return E_INVALIDARG; + + if (!pbProperties) + return E_POINTER; + + auto properties = + reinterpret_cast(pbProperties); + + if (properties->pbBuffer || properties->cbBuffer) + return E_INVALIDARG; + + this->d->m_data = properties->cbData; + this->d->m_typeSpecificFlags = properties->dwTypeSpecificFlags; + this->d->m_sampleFlags = properties->dwSampleFlags; + this->SetDiscontinuity(this->d->m_sampleFlags & AM_SAMPLE_DATADISCONTINUITY); + this->SetSyncPoint(this->d->m_sampleFlags & AM_SAMPLE_SPLICEPOINT); + this->SetPreroll(this->d->m_sampleFlags & AM_SAMPLE_PREROLL); + this->SetActualDataLength(properties->lActual); + auto start = properties->tStart; + auto stop = properties->tStop; + this->SetTime(&start, &stop); + this->SetMediaTime(&start, &stop); + this->d->m_streamId = properties->dwStreamId; + deleteMediaType(&this->d->m_mediaType); + this->d->m_mediaType = createMediaType(properties->pMediaType); + this->SetMediaType(properties->pMediaType); + + return S_OK; +} + +HRESULT AkVCam::MediaSample2::GetProperties(DWORD cbProperties, + BYTE *pbProperties) +{ + AkLogMethod(); + + if (cbProperties < sizeof(AM_SAMPLE2_PROPERTIES)) + return E_INVALIDARG; + + if (!pbProperties) + return E_POINTER; + + auto properties = + reinterpret_cast(pbProperties); + + properties->cbData = this->d->m_data; + properties->dwTypeSpecificFlags = this->d->m_typeSpecificFlags; + properties->dwSampleFlags = this->d->m_sampleFlags; + properties->lActual = this->GetActualDataLength(); + this->GetTime(&properties->tStart, &properties->tStop); + properties->dwStreamId = this->d->m_streamId; + deleteMediaType(&this->d->m_mediaType); + this->GetMediaType(&this->d->m_mediaType); + properties->pMediaType = this->d->m_mediaType; + this->GetPointer(&properties->pbBuffer); + properties->cbBuffer = this->GetSize(); + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/mediasample2.h b/dshow/VirtualCamera/src/mediasample2.h new file mode 100644 index 0000000..98de8de --- /dev/null +++ b/dshow/VirtualCamera/src/mediasample2.h @@ -0,0 +1,51 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MEDIASAMPLE2_H +#define MEDIASAMPLE2_H + +#include "mediasample.h" + +namespace AkVCam +{ + class MediaSample2Private; + + class MediaSample2: + public IMediaSample2, + public MediaSample + { + public: + MediaSample2(IMemAllocator *memAllocator, + LONG bufferSize, LONG align, LONG prefix); + virtual ~MediaSample2(); + + DECLARE_IMEDIASAMPLE(IID_IMediaSample2) + + // IMediaSample2 + HRESULT STDMETHODCALLTYPE GetProperties(DWORD cbProperties, + BYTE *pbProperties); + HRESULT STDMETHODCALLTYPE SetProperties(DWORD cbProperties, + const BYTE *pbProperties); + + private: + MediaSample2Private *d; + }; +} + +#endif // MEDIASAMPLE2_H diff --git a/dshow/VirtualCamera/src/memallocator.cpp b/dshow/VirtualCamera/src/memallocator.cpp new file mode 100644 index 0000000..2d8d4f5 --- /dev/null +++ b/dshow/VirtualCamera/src/memallocator.cpp @@ -0,0 +1,229 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "memallocator.h" +#include "mediasample.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "MemAllocator" + +namespace AkVCam +{ + class MemAllocatorPrivate + { + public: + std::vector m_samples; + ALLOCATOR_PROPERTIES m_properties; + std::mutex m_mutex; + std::condition_variable_any m_bufferReleased; + bool m_commited; + bool m_decommiting; + }; +} + +AkVCam::MemAllocator::MemAllocator(): + CUnknown(this, IID_IMemAllocator) +{ + this->d = new MemAllocatorPrivate; + memset(&this->d->m_properties, 0, sizeof(ALLOCATOR_PROPERTIES)); + this->d->m_commited = false; + this->d->m_decommiting = false; +} + +AkVCam::MemAllocator::~MemAllocator() +{ + for (auto &sample: this->d->m_samples) + sample->Release(); + + delete this->d; +} + +HRESULT AkVCam::MemAllocator::SetProperties(ALLOCATOR_PROPERTIES *pRequest, + ALLOCATOR_PROPERTIES *pActual) +{ + AkLogMethod(); + + if (!pRequest || !pActual) + return E_POINTER; + + if (this->d->m_commited) + return VFW_E_ALREADY_COMMITTED; + + if (pRequest->cbAlign < 1) + return VFW_E_BADALIGN; + + for (auto &sample:this->d->m_samples) + if (sample->ref() > 1) + return VFW_E_BUFFERS_OUTSTANDING; + + memcpy(&this->d->m_properties, pRequest, sizeof(ALLOCATOR_PROPERTIES)); + memcpy(pActual, &this->d->m_properties, sizeof(ALLOCATOR_PROPERTIES)); + + return S_OK; +} + +HRESULT AkVCam::MemAllocator::GetProperties(ALLOCATOR_PROPERTIES *pProps) +{ + AkLogMethod(); + + if (!pProps) + return E_POINTER; + + std::lock_guard lock(this->d->m_mutex); + memcpy(pProps, &this->d->m_properties, sizeof(ALLOCATOR_PROPERTIES)); + + return S_OK; +} + +HRESULT AkVCam::MemAllocator::Commit() +{ + AkLogMethod(); + + if (this->d->m_commited) + return S_OK; + + if (this->d->m_properties.cBuffers < 1 + || this->d->m_properties.cbBuffer < 1) { + AkLoggerLog("Wrong memory allocator size"); + + return VFW_E_SIZENOTSET; + } + + this->d->m_samples.clear(); + + for (LONG i = 0; i < this->d->m_properties.cBuffers; i++) { + auto sample = + new MediaSample(this, + this->d->m_properties.cbBuffer, + this->d->m_properties.cbAlign, + this->d->m_properties.cbPrefix); + sample->AddRef(); + this->d->m_samples.push_back(sample); + } + + this->d->m_commited = true; + + return S_OK; +} + +HRESULT AkVCam::MemAllocator::Decommit() +{ + AkLogMethod(); + + if (!this->d->m_commited) + return S_OK; + + this->d->m_decommiting = true; + auto totalSamples = this->d->m_samples.size(); + size_t freeSamples = 0; + + for (size_t i = 0; i < totalSamples; i++) + if (this->d->m_samples[i]) { + if (this->d->m_samples[i]->ref() < 2) { + this->d->m_samples[i]->Release(); + this->d->m_samples[i] = nullptr; + freeSamples++; + } + } else { + freeSamples++; + } + + AkLoggerLog("Free samples: ", freeSamples, "/", totalSamples); + + if (freeSamples >= totalSamples) { + AkLoggerLog("Decommiting"); + this->d->m_samples.clear(); + this->d->m_commited = false; + this->d->m_decommiting = false; + } + + return S_OK; +} + +HRESULT AkVCam::MemAllocator::GetBuffer(IMediaSample **ppBuffer, + REFERENCE_TIME *pStartTime, + REFERENCE_TIME *pEndTime, + DWORD dwFlags) +{ + AkLogMethod(); + + if (!ppBuffer) + return E_POINTER; + + *ppBuffer = nullptr; + + if (pStartTime) + *pStartTime = 0; + + if (pEndTime) + *pEndTime = 0; + + if (!this->d->m_commited || this->d->m_decommiting) { + AkLoggerLog("Allocator not commited."); + + return VFW_E_NOT_COMMITTED; + } + + HRESULT result = S_FALSE; + + do { + for (auto &sample: this->d->m_samples) { + if (sample->ref() < 2) { + *ppBuffer = sample; + (*ppBuffer)->AddRef(); + (*ppBuffer)->GetTime(pStartTime, pEndTime); + result = S_OK; + + break; + } + } + + this->d->m_mutex.lock(); + + if (result == S_FALSE) { + if (dwFlags & AM_GBF_NOWAIT) + result = VFW_E_TIMEOUT; + else + this->d->m_bufferReleased.wait(this->d->m_mutex); + } + + this->d->m_mutex.unlock(); + } while (result == S_FALSE); + + return result; +} + +HRESULT AkVCam::MemAllocator::ReleaseBuffer(IMediaSample *pBuffer) +{ + UNUSED(pBuffer) + AkLogMethod(); + + this->d->m_mutex.lock(); + this->d->m_bufferReleased.notify_one(); + this->d->m_mutex.unlock(); + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/memallocator.h b/dshow/VirtualCamera/src/memallocator.h new file mode 100644 index 0000000..3bdcbca --- /dev/null +++ b/dshow/VirtualCamera/src/memallocator.h @@ -0,0 +1,58 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef MEMALLOCATOR_H +#define MEMALLOCATOR_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class MemAllocatorPrivate; + + class MemAllocator: + public IMemAllocator, + public CUnknown + { + public: + MemAllocator(); + virtual ~MemAllocator(); + + DECLARE_IUNKNOWN(IID_IMemAllocator) + + // IMemAllocator + HRESULT STDMETHODCALLTYPE SetProperties(ALLOCATOR_PROPERTIES *pRequest, + ALLOCATOR_PROPERTIES *pActual); + HRESULT STDMETHODCALLTYPE GetProperties(ALLOCATOR_PROPERTIES *pProps); + HRESULT STDMETHODCALLTYPE Commit(); + HRESULT STDMETHODCALLTYPE Decommit(); + HRESULT STDMETHODCALLTYPE GetBuffer(IMediaSample **ppBuffer, + REFERENCE_TIME *pStartTime, + REFERENCE_TIME *pEndTime, + DWORD dwFlags); + HRESULT STDMETHODCALLTYPE ReleaseBuffer(IMediaSample *pBuffer); + + private: + MemAllocatorPrivate *d; + }; +} + +#endif // MEMALLOCATOR_H diff --git a/dshow/VirtualCamera/src/persist.cpp b/dshow/VirtualCamera/src/persist.cpp new file mode 100644 index 0000000..52e016a --- /dev/null +++ b/dshow/VirtualCamera/src/persist.cpp @@ -0,0 +1,57 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "persist.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "Persist" + +namespace AkVCam +{ + class PersistPrivate + { + public: + CLSID m_clsid; + }; +} + +AkVCam::Persist::Persist(const IID &classCLSID): + CUnknown(this, IID_IPersist) +{ + this->d = new PersistPrivate; + this->d->m_clsid = classCLSID; +} + +AkVCam::Persist::~Persist() +{ + delete this->d; +} + +HRESULT AkVCam::Persist::GetClassID(CLSID *pClassID) +{ + AkLogMethod(); + + if (!pClassID) + return E_POINTER; + + *pClassID = this->d->m_clsid; + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/persist.h b/dshow/VirtualCamera/src/persist.h new file mode 100644 index 0000000..9b0c5cc --- /dev/null +++ b/dshow/VirtualCamera/src/persist.h @@ -0,0 +1,59 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PERSIST_H +#define PERSIST_H + +#include "cunknown.h" + +namespace AkVCam +{ + class PersistPrivate; + + class Persist: + public IPersist, + public CUnknown + { + public: + Persist(REFIID classCLSID); + virtual ~Persist(); + + DECLARE_IUNKNOWN(IID_IPersist) + + // IPersist + HRESULT STDMETHODCALLTYPE GetClassID(CLSID *pClassID); + + private: + PersistPrivate *d; + }; +} + +#define DECLARE_IPERSIST_NQ \ + DECLARE_IUNKNOWN_NQ \ + \ + HRESULT STDMETHODCALLTYPE GetClassID(CLSID *pClassID) \ + { \ + return Persist::GetClassID(pClassID); \ + } + +#define DECLARE_IPERSIST(interfaceIid) \ + DECLARE_IUNKNOWN_Q(interfaceIid) \ + DECLARE_IPERSIST_NQ + +#endif // PERSIST_H diff --git a/dshow/VirtualCamera/src/persistpropertybag.cpp b/dshow/VirtualCamera/src/persistpropertybag.cpp new file mode 100644 index 0000000..05c5863 --- /dev/null +++ b/dshow/VirtualCamera/src/persistpropertybag.cpp @@ -0,0 +1,134 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "persistpropertybag.h" +#include "basefilter.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "PersistPropertyBag" + +namespace AkVCam +{ + class PersistPropertyBagPrivate + { + public: + ComVariantMap m_properties; + }; +} + +AkVCam::PersistPropertyBag::PersistPropertyBag(const GUID &clsid, + const ComVariantMap &properties): + Persist(clsid) +{ + this->setParent(this, &IID_IPersistPropertyBag); + this->d = new PersistPropertyBagPrivate; + this->d->m_properties = properties; +} + +AkVCam::PersistPropertyBag::~PersistPropertyBag() +{ + delete this->d; +} + +HRESULT AkVCam::PersistPropertyBag::QueryInterface(const IID &riid, + void **ppvObject) +{ + AkLogMethod(); + AkLoggerLog("IID: ", AkVCam::stringFromClsid(riid)); + + if (!ppvObject) + return E_POINTER; + + *ppvObject = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) + || IsEqualIID(riid, IID_IPersistPropertyBag)) { + AkLogInterface(IPersistPropertyBag, this); + this->AddRef(); + *ppvObject = this; + + return S_OK; + } else if (IsEqualIID(riid, IID_IBaseFilter)) { + CLSID clsid; + auto result = this->GetClassID(&clsid); + + if (FAILED(result)) + return result; + + auto baseFilter = BaseFilter::create(clsid); + + if (!baseFilter) + return E_FAIL; + + AkLogInterface(IBaseFilter, baseFilter); + baseFilter->AddRef(); + *ppvObject = baseFilter; + + return S_OK; + } + + return Persist::QueryInterface(riid, ppvObject); +} + +HRESULT AkVCam::PersistPropertyBag::InitNew() +{ + AkLogMethod(); + + return S_OK; +} + +HRESULT AkVCam::PersistPropertyBag::Load(IPropertyBag *pPropBag, + IErrorLog *pErrorLog) +{ + UNUSED(pErrorLog) + AkLogMethod(); + + if (!pPropBag) + return E_POINTER; + + for (auto &prop: this->d->m_properties) { + VARIANT value; + VariantInit(&value); + + if (FAILED(pPropBag->Read(prop.first.c_str(), &value, nullptr))) + continue; + + this->d->m_properties[prop.first] = value; + } + + return S_OK; +} + +HRESULT AkVCam::PersistPropertyBag::Save(IPropertyBag *pPropBag, + BOOL fClearDirty, + BOOL fSaveAllProperties) +{ + UNUSED(fClearDirty) + UNUSED(fSaveAllProperties) + AkLogMethod(); + + if (!pPropBag) + return E_POINTER; + + for (auto &prop: this->d->m_properties) + pPropBag->Write(prop.first.c_str(), &prop.second); + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/persistpropertybag.h b/dshow/VirtualCamera/src/persistpropertybag.h new file mode 100644 index 0000000..41ef4cd --- /dev/null +++ b/dshow/VirtualCamera/src/persistpropertybag.h @@ -0,0 +1,108 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PERSISTPROPERTYBAG_H +#define PERSISTPROPERTYBAG_H + +#include +#include +#include + +#include "persist.h" + +namespace AkVCam +{ + class PersistPropertyBagPrivate; + typedef std::map ComVariantMap; + + class PersistPropertyBag: + public IPersistPropertyBag, + public Persist + { + public: + PersistPropertyBag(const GUID &clsid, + const ComVariantMap &properties={}); + virtual ~PersistPropertyBag(); + + DECLARE_IPERSIST_NQ + + // IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + void **ppvObject); + + // IPersistPropertyBag + HRESULT STDMETHODCALLTYPE InitNew(); + HRESULT STDMETHODCALLTYPE Load(IPropertyBag *pPropBag, + IErrorLog *pErrorLog); + HRESULT STDMETHODCALLTYPE Save(IPropertyBag *pPropBag, + BOOL fClearDirty, + BOOL fSaveAllProperties); + + private: + PersistPropertyBagPrivate *d; + }; +} + +#define DECLARE_IPERSISTPROPERTYBAG_Q(interfaceIid) \ + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, \ + void **ppvObject) \ + { \ + if (!ppvObject) \ + return E_POINTER; \ + \ + *ppvObject = nullptr; \ + \ + if (IsEqualIID(riid, interfaceIid)) { \ + this->AddRef(); \ + *ppvObject = this; \ + \ + return S_OK; \ + } \ + \ + return PersistPropertyBag::QueryInterface(riid, ppvObject); \ + } + +#define DECLARE_IPERSISTPROPERTYBAG_NQ \ + DECLARE_IPERSIST_NQ \ + \ + HRESULT STDMETHODCALLTYPE InitNew() \ + { \ + return PersistPropertyBag::InitNew(); \ + } \ + \ + HRESULT STDMETHODCALLTYPE Load(IPropertyBag *pPropBag, \ + IErrorLog *pErrorLog) \ + { \ + return PersistPropertyBag::Load(pPropBag, pErrorLog); \ + } \ + \ + HRESULT STDMETHODCALLTYPE Save(IPropertyBag *pPropBag, \ + BOOL fClearDirty, \ + BOOL fSaveAllProperties) \ + { \ + return PersistPropertyBag::Save(pPropBag, \ + fClearDirty, \ + fSaveAllProperties); \ + } + +#define DECLARE_IPERSISTPROPERTYBAG(interfaceIid) \ + DECLARE_IPERSISTPROPERTYBAG_Q(interfaceIid) \ + DECLARE_IPERSISTPROPERTYBAG_NQ + +#endif // PERSISTPROPERTYBAG_H diff --git a/dshow/VirtualCamera/src/pin.cpp b/dshow/VirtualCamera/src/pin.cpp new file mode 100644 index 0000000..baa167d --- /dev/null +++ b/dshow/VirtualCamera/src/pin.cpp @@ -0,0 +1,1102 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pin.h" +#include "basefilter.h" +#include "enummediatypes.h" +#include "memallocator.h" +#include "propertyset.h" +#include "pushsource.h" +#include "qualitycontrol.h" +#include "referenceclock.h" +#include "videoprocamp.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" +#include "VCamUtils/src/image/videoframe.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "Pin" + +namespace AkVCam +{ + class PinPrivate + { + public: + Pin *self; + BaseFilter *m_baseFilter; + VideoProcAmp *m_videoProcAmp; + std::wstring m_pinName; + std::wstring m_pinId; + EnumMediaTypes *m_mediaTypes; + IPin *m_connectedTo; + IMemInputPin *m_memInputPin; + IMemAllocator *m_memAllocator; + REFERENCE_TIME m_pts; + REFERENCE_TIME m_ptsDrift; + REFERENCE_TIME m_start; + REFERENCE_TIME m_stop; + double m_rate; + FILTER_STATE m_prevState; + DWORD_PTR m_adviseCookie; + HANDLE m_sendFrameEvent; + std::thread m_sendFrameThread; + std::atomic m_running; + std::mutex m_mutex; + VideoFrame m_currentFrame; + VideoFrame m_testFrame; + VideoFrame m_testFrameAdapted; + std::string m_broadcaster; + bool m_horizontalFlip; // Controlled by client + bool m_verticalFlip; + bool m_horizontalMirror; // Controlled by server + bool m_verticalMirror; + Scaling m_scaling; + AspectRatio m_aspectRatio; + bool m_swapRgb; + LONG m_brightness; + LONG m_contrast; + LONG m_saturation; + LONG m_gamma; + LONG m_hue; + LONG m_colorenable; + + void sendFrameOneShot(); + void sendFrameLoop(); + HRESULT sendFrame(); + void updateTestFrame(); + VideoFrame applyAdjusts(const VideoFrame &frame); + static void propertyChanged(void *userData, + LONG Property, + LONG lValue, + LONG Flags); + VideoFrame randomFrame(); + }; +} + +AkVCam::Pin::Pin(BaseFilter *baseFilter, + const std::vector &formats, + const std::wstring &pinName): + StreamConfig(this) +{ + this->setParent(this, &IID_IPin); + + this->d = new PinPrivate; + this->d->self = this; + this->d->m_baseFilter = baseFilter; + this->d->m_pinName = pinName; + std::wstringstream wss; + wss << L"pin(" << this << L")"; + this->d->m_pinId = wss.str(); + this->d->m_mediaTypes = new AkVCam::EnumMediaTypes(formats); + this->d->m_mediaTypes->AddRef(); + this->d->m_connectedTo = nullptr; + this->d->m_memInputPin = nullptr; + this->d->m_memAllocator = nullptr; + this->d->m_ptsDrift = -1; + this->d->m_ptsDrift = 0; + this->d->m_start = 0; + this->d->m_stop = MAXLONGLONG; + this->d->m_rate = 1.0; + this->d->m_horizontalFlip = false; + this->d->m_verticalFlip = false; + this->d->m_prevState = State_Stopped; + this->d->m_adviseCookie = 0; + this->d->m_sendFrameEvent = nullptr; + this->d->m_running = false; + this->d->m_horizontalMirror = false; + this->d->m_verticalMirror = false; + this->d->m_scaling = ScalingFast; + this->d->m_aspectRatio = AspectRatioIgnore; + this->d->m_swapRgb = false; + auto bmp = programFilesPath() + + L"\\" DSHOW_PLUGIN_NAME_L L".plugin\\share\\TestFrame.bmp"; + this->d->m_testFrame.load(std::string(bmp.begin(), bmp.end())); + + baseFilter->QueryInterface(IID_IAMVideoProcAmp, + reinterpret_cast(&this->d->m_videoProcAmp)); + + LONG flags = 0; + this->d->m_videoProcAmp->Get(VideoProcAmp_Brightness, + &this->d->m_brightness, + &flags); + this->d->m_videoProcAmp->Get(VideoProcAmp_Contrast, + &this->d->m_contrast, &flags); + this->d->m_videoProcAmp->Get(VideoProcAmp_Saturation, + &this->d->m_saturation, + &flags); + this->d->m_videoProcAmp->Get(VideoProcAmp_Gamma, + &this->d->m_gamma, + &flags); + this->d->m_videoProcAmp->Get(VideoProcAmp_Hue, + &this->d->m_hue, + &flags); + this->d->m_videoProcAmp->Get(VideoProcAmp_ColorEnable, + &this->d->m_colorenable, + &flags); + + this->d->m_videoProcAmp->connectPropertyChanged(this->d, + &PinPrivate::propertyChanged); +} + +AkVCam::Pin::~Pin() +{ + this->d->m_mediaTypes->Release(); + + if (this->d->m_connectedTo) + this->d->m_connectedTo->Release(); + + if (this->d->m_memInputPin) + this->d->m_memInputPin->Release(); + + if (this->d->m_memAllocator) + this->d->m_memAllocator->Release(); + + if (this->d->m_videoProcAmp) + this->d->m_videoProcAmp->Release(); + + delete this->d; +} + +AkVCam::BaseFilter *AkVCam::Pin::baseFilter() const +{ + AkLogMethod(); + + return this->d->m_baseFilter; +} + +void AkVCam::Pin::setBaseFilter(BaseFilter *baseFilter) +{ + AkLogMethod(); + this->d->m_baseFilter = baseFilter; +} + +HRESULT AkVCam::Pin::stateChanged(void *userData, FILTER_STATE state) +{ + auto self = reinterpret_cast(userData); + AkLoggerLog(AK_CUR_INTERFACE, "(", self, ")::", __FUNCTION__, "()"); + AkLoggerLog("Old state: ", self->d->m_prevState); + AkLoggerLog("New state: ", state); + + if (state == self->d->m_prevState) + return S_OK; + + if (self->d->m_prevState == State_Stopped) { + if (FAILED(self->d->m_memAllocator->Commit())) + return VFW_E_NOT_COMMITTED; + + self->d->updateTestFrame(); + self->d->m_currentFrame = self->d->m_testFrameAdapted; + self->d->m_pts = -1; + self->d->m_ptsDrift = 0; + + self->d->m_sendFrameEvent = + CreateSemaphore(nullptr, 1, 1, L"SendFrame"); + + self->d->m_running = true; + self->d->m_sendFrameThread = + std::thread(&PinPrivate::sendFrameLoop, self->d); + AkLoggerLog("Launching thread ", self->d->m_sendFrameThread.get_id()); + + auto clock = self->d->m_baseFilter->referenceClock(); + REFERENCE_TIME now = 0; + clock->GetTime(&now); + + AM_MEDIA_TYPE *mediaType = nullptr; + self->GetFormat(&mediaType); + auto videoFormat = formatFromMediaType(mediaType); + deleteMediaType(&mediaType); + auto fps = videoFormat.minimumFrameRate(); + auto period = REFERENCE_TIME(TIME_BASE + * fps.den() + / fps.num()); + + clock->AdvisePeriodic(now, + period, + HSEMAPHORE(self->d->m_sendFrameEvent), + &self->d->m_adviseCookie); + } else if (state == State_Stopped) { + self->d->m_running = false; + self->d->m_sendFrameThread.join(); + auto clock = self->d->m_baseFilter->referenceClock(); + clock->Unadvise(self->d->m_adviseCookie); + self->d->m_adviseCookie = 0; + CloseHandle(self->d->m_sendFrameEvent); + self->d->m_sendFrameEvent = nullptr; + self->d->m_memAllocator->Decommit(); + self->d->m_currentFrame.clear(); + self->d->m_testFrameAdapted.clear(); + } + + self->d->m_prevState = state; + + return S_OK; +} + +void AkVCam::Pin::serverStateChanged(IpcBridge::ServerState state) +{ + AkLogMethod(); + + if (state == IpcBridge::ServerStateGone) { + this->d->m_broadcaster.clear(); + this->d->m_horizontalMirror = false; + this->d->m_verticalMirror = false; + this->d->m_scaling = ScalingFast; + this->d->m_aspectRatio = AspectRatioIgnore; + this->d->m_swapRgb = false; + this->d->updateTestFrame(); + + this->d->m_mutex.lock(); + this->d->m_currentFrame = this->d->m_testFrameAdapted; + this->d->m_mutex.unlock(); + } +} + +void AkVCam::Pin::frameReady(const VideoFrame &frame) +{ + AkLogMethod(); + AkLoggerLog("Running: ", this->d->m_running); + AkLoggerLog("Broadcaster: ", this->d->m_broadcaster); + + if (!this->d->m_running) + return; + + this->d->m_mutex.lock(); + + if (!this->d->m_broadcaster.empty()) { + auto frameAdjusted = this->d->applyAdjusts(frame); + + if (frameAdjusted.format().size() > 0) + this->d->m_currentFrame = frameAdjusted; + } + + this->d->m_mutex.unlock(); +} + +void AkVCam::Pin::setBroadcasting(const std::string &broadcaster) +{ + AkLogMethod(); + AkLoggerLog("Broadcaster: ", broadcaster); + + if (this->d->m_broadcaster == broadcaster) + return; + + this->d->m_mutex.lock(); + this->d->m_broadcaster = broadcaster; + + if (broadcaster.empty()) + this->d->m_currentFrame = this->d->m_testFrameAdapted; + + this->d->m_mutex.unlock(); +} + +void AkVCam::Pin::setMirror(bool horizontalMirror, bool verticalMirror) +{ + AkLogMethod(); + + if (this->d->m_horizontalMirror == horizontalMirror + && this->d->m_verticalMirror == verticalMirror) + return; + + this->d->m_horizontalMirror = horizontalMirror; + this->d->m_verticalMirror = verticalMirror; + this->d->updateTestFrame(); +} + +void AkVCam::Pin::setScaling(Scaling scaling) +{ + AkLogMethod(); + + if (this->d->m_scaling == scaling) + return; + + this->d->m_scaling = scaling; + this->d->updateTestFrame(); +} + +void AkVCam::Pin::setAspectRatio(AspectRatio aspectRatio) +{ + AkLogMethod(); + + if (this->d->m_aspectRatio == aspectRatio) + return; + + this->d->m_aspectRatio = aspectRatio; + this->d->updateTestFrame(); +} + +void AkVCam::Pin::setSwapRgb(bool swap) +{ + AkLogMethod(); + + if (this->d->m_swapRgb == swap) + return; + + this->d->m_swapRgb = swap; + this->d->updateTestFrame(); +} + +bool AkVCam::Pin::horizontalFlip() const +{ + return this->d->m_horizontalFlip; +} + +void AkVCam::Pin::setHorizontalFlip(bool flip) +{ + this->d->m_horizontalFlip = flip; +} + +bool AkVCam::Pin::verticalFlip() const +{ + return this->d->m_verticalFlip; +} + +void AkVCam::Pin::setVerticalFlip(bool flip) +{ + this->d->m_verticalFlip = flip; +} + +HRESULT AkVCam::Pin::QueryInterface(const IID &riid, void **ppvObject) +{ + AkLogMethod(); + AkLoggerLog("IID: ", AkVCam::stringFromClsid(riid)); + + if (!ppvObject) + return E_POINTER; + + *ppvObject = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) + || IsEqualIID(riid, IID_IPin)) { + AkLogInterface(IPin, this); + this->AddRef(); + *ppvObject = this; + + return S_OK; + } else if (IsEqualIID(riid, IID_IAMStreamConfig)) { + auto streamConfig = static_cast(this); + AkLogInterface(IAMStreamConfig, streamConfig); + streamConfig->AddRef(); + *ppvObject = streamConfig; + + return S_OK; + } else if (IsEqualIID(riid, IID_IAMPushSource)) { + auto pushSource = new PushSource(this); + AkLogInterface(IAMPushSource, pushSource); + pushSource->AddRef(); + *ppvObject = pushSource; + + return S_OK; + } else if (IsEqualIID(riid, IID_IKsPropertySet)) { + auto propertySet = new PropertySet(); + AkLogInterface(IKsPropertySet, propertySet); + propertySet->AddRef(); + *ppvObject = propertySet; + + return S_OK; + } else if (IsEqualIID(riid, IID_IQualityControl)) { + auto qualityControl = new QualityControl(); + AkLogInterface(IQualityControl, qualityControl); + qualityControl->AddRef(); + *ppvObject = qualityControl; + + return S_OK; + } + + return CUnknown::QueryInterface(riid, ppvObject); +} + +HRESULT AkVCam::Pin::Connect(IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) +{ + AkLogMethod(); + AkLoggerLog("Receive pin: ", pReceivePin); + AkLoggerLog("Media type: ", stringFromMediaType(pmt)); + + if (!pReceivePin) + return E_POINTER; + + if (this->d->m_connectedTo) + return VFW_E_ALREADY_CONNECTED; + + if (this->d->m_baseFilter) { + FILTER_STATE state; + + if (SUCCEEDED(this->d->m_baseFilter->GetState(0, &state)) + && state != State_Stopped) + return VFW_E_NOT_STOPPED; + } + + PIN_DIRECTION direction = PINDIR_OUTPUT; + + // Only connect to an input pin. + if (FAILED(pReceivePin->QueryDirection(&direction)) + || direction != PINDIR_INPUT) + return VFW_E_NO_TRANSPORT; + + /* When the Filter Graph Manager calls Connect, the output pin must request + * a IMemInputPin and get a IMemAllocator interface to the input pin. + */ + IMemInputPin *memInputPin = nullptr; + + if (FAILED(pReceivePin->QueryInterface(IID_IMemInputPin, + reinterpret_cast(&memInputPin)))) { + return VFW_E_NO_TRANSPORT; + } + + AM_MEDIA_TYPE *mediaType = nullptr; + + if (pmt) { + // Try setting requested media type. + if (!containsMediaType(pmt, this->d->m_mediaTypes)) + return VFW_E_TYPE_NOT_ACCEPTED; + + mediaType = createMediaType(pmt); + } else { + // Test currently set media type. + AM_MEDIA_TYPE *mt = nullptr; + + if (SUCCEEDED(this->GetFormat(&mt)) && mt) { + if (pReceivePin->QueryAccept(mt) == S_OK) + mediaType = mt; + else + deleteMediaType(&mt); + } + + if (!mediaType) { + // Test media types supported by the input pin. + AM_MEDIA_TYPE *mt = nullptr; + IEnumMediaTypes *mediaTypes = nullptr; + + if (SUCCEEDED(pReceivePin->EnumMediaTypes(&mediaTypes))) { + mediaTypes->Reset(); + + while (mediaTypes->Next(1, &mt, nullptr) == S_OK) { + AkLoggerLog("Testing media type: ", stringFromMediaType(mt)); + + // If the mediatype match our suported mediatypes... + if (this->QueryAccept(mt) == S_OK) { + // set it. + mediaType = mt; + + break; + } + + deleteMediaType(&mt); + } + + mediaTypes->Release(); + } + } + + if (!mediaType) { + /* If none of the input media types was suitable for us, ask to + * input pin if it at least supports one of us. + */ + AM_MEDIA_TYPE *mt = nullptr; + this->d->m_mediaTypes->Reset(); + + while (this->d->m_mediaTypes->Next(1, &mt, nullptr) == S_OK) { + if (pReceivePin->QueryAccept(mt) == S_OK) { + mediaType = mt; + + break; + } + + deleteMediaType(&mt); + } + } + } + + if (!mediaType) + return VFW_E_NO_ACCEPTABLE_TYPES; + + AkLoggerLog("Setting Media Type: ", stringFromMediaType(mediaType)); + auto result = pReceivePin->ReceiveConnection(this, mediaType); + + if (FAILED(result)) { + deleteMediaType(&mediaType); + + return result; + } + + AkLoggerLog("Connection accepted by input pin"); + + // Define memory allocator requirements. + ALLOCATOR_PROPERTIES allocatorRequirements; + memset(&allocatorRequirements, 0, sizeof(ALLOCATOR_PROPERTIES)); + memInputPin->GetAllocatorRequirements(&allocatorRequirements); + auto videoFormat = formatFromMediaType(mediaType); + + if (allocatorRequirements.cBuffers < 1) + allocatorRequirements.cBuffers = 1; + + allocatorRequirements.cbBuffer = LONG(videoFormat.size()); + + if (allocatorRequirements.cbAlign < 1) + allocatorRequirements.cbAlign = 1; + + // Get a memory allocator. + IMemAllocator *memAllocator = nullptr; + + // if it fail use our own. + if (FAILED(memInputPin->GetAllocator(&memAllocator))) { + memAllocator = new MemAllocator; + memAllocator->AddRef(); + } + + ALLOCATOR_PROPERTIES actualRequirements; + memset(&actualRequirements, 0, sizeof(ALLOCATOR_PROPERTIES)); + + if (FAILED(memAllocator->SetProperties(&allocatorRequirements, + &actualRequirements))) { + memAllocator->Release(); + memInputPin->Release(); + deleteMediaType(&mediaType); + + return VFW_E_NO_TRANSPORT; + } + + if (FAILED(memInputPin->NotifyAllocator(memAllocator, S_OK))) { + memAllocator->Release(); + memInputPin->Release(); + deleteMediaType(&mediaType); + + return VFW_E_NO_TRANSPORT; + } + + if (this->d->m_memInputPin) + this->d->m_memInputPin->Release(); + + this->d->m_memInputPin = memInputPin; + + if (this->d->m_memAllocator) + this->d->m_memAllocator->Release(); + + this->d->m_memAllocator = memAllocator; + this->SetFormat(mediaType); + + if (this->d->m_connectedTo) + this->d->m_connectedTo->Release(); + + this->d->m_connectedTo = pReceivePin; + this->d->m_connectedTo->AddRef(); + this->d->m_baseFilter->connectStateChanged(this, &Pin::stateChanged); + AkLoggerLog("Connected to ", pReceivePin); + + return S_OK; +} + +HRESULT AkVCam::Pin::ReceiveConnection(IPin *pConnector, + const AM_MEDIA_TYPE *pmt) +{ + UNUSED(pConnector) + UNUSED(pmt) + AkLogMethod(); + + return VFW_E_TYPE_NOT_ACCEPTED; +} + +HRESULT AkVCam::Pin::Disconnect() +{ + AkLogMethod(); + this->d->m_baseFilter->disconnectStateChanged(this, &Pin::stateChanged); + + if (this->d->m_baseFilter) { + FILTER_STATE state; + + if (SUCCEEDED(this->d->m_baseFilter->GetState(0, &state)) + && state != State_Stopped) + return VFW_E_NOT_STOPPED; + } + + if (this->d->m_connectedTo) { + this->d->m_connectedTo->Release(); + this->d->m_connectedTo = nullptr; + } + + if (this->d->m_memInputPin) { + this->d->m_memInputPin->Release(); + this->d->m_memInputPin = nullptr; + } + + if (this->d->m_memAllocator) { + this->d->m_memAllocator->Release(); + this->d->m_memAllocator = nullptr; + } + + return S_OK; +} + +HRESULT AkVCam::Pin::ConnectedTo(IPin **pPin) +{ + AkLogMethod(); + + if (!pPin) + return E_POINTER; + + *pPin = nullptr; + + if (!this->d->m_connectedTo) + return VFW_E_NOT_CONNECTED; + + *pPin = this->d->m_connectedTo; + (*pPin)->AddRef(); + + return S_OK; +} + +HRESULT AkVCam::Pin::ConnectionMediaType(AM_MEDIA_TYPE *pmt) +{ + AkLogMethod(); + + if (!pmt) + return E_POINTER; + + memset(pmt, 0, sizeof(AM_MEDIA_TYPE)); + + if (!this->d->m_connectedTo) + return VFW_E_NOT_CONNECTED; + + AM_MEDIA_TYPE *mediaType = nullptr; + this->GetFormat(&mediaType); + copyMediaType(pmt, mediaType); + AkLoggerLog("Media Type: ", stringFromMediaType(mediaType)); + + return S_OK; +} + +HRESULT AkVCam::Pin::QueryPinInfo(PIN_INFO *pInfo) +{ + AkLogMethod(); + + if (!pInfo) + return E_POINTER; + + pInfo->pFilter = this->d->m_baseFilter; + + if (pInfo->pFilter) + pInfo->pFilter->AddRef(); + + pInfo->dir = PINDIR_OUTPUT; + memset(pInfo->achName, 0, MAX_PIN_NAME * sizeof(WCHAR)); + + if (!this->d->m_pinName.empty()) + memcpy(pInfo->achName, + this->d->m_pinName.c_str(), + (std::min)(this->d->m_pinName.size() * sizeof(WCHAR), + MAX_PIN_NAME)); + + return S_OK; +} + +HRESULT AkVCam::Pin::QueryDirection(PIN_DIRECTION *pPinDir) +{ + AkLogMethod(); + + if (!pPinDir) + return E_POINTER; + + *pPinDir = PINDIR_OUTPUT; + + return S_OK; +} + +HRESULT AkVCam::Pin::QueryId(LPWSTR *Id) +{ + AkLogMethod(); + + if (!Id) + return E_POINTER; + + auto wstrSize = (this->d->m_pinId.size() + 1) * sizeof(WCHAR); + *Id = reinterpret_cast(CoTaskMemAlloc(wstrSize)); + + if (!*Id) + return E_OUTOFMEMORY; + + memset(*Id, 0, wstrSize); + memcpy(*Id, + this->d->m_pinId.c_str(), + this->d->m_pinId.size() * sizeof(WCHAR)); + + return S_OK; +} + +HRESULT AkVCam::Pin::QueryAccept(const AM_MEDIA_TYPE *pmt) +{ + AkLogMethod(); + + if (!pmt) + return E_POINTER; + + AkLoggerLog("Accept? ", stringFromMediaType(pmt)); + + if (!containsMediaType(pmt, this->d->m_mediaTypes)) { + AkLoggerLog("NO"); + + return S_FALSE; + } + + AkLoggerLog("YES"); + + return S_OK; +} + +HRESULT AkVCam::Pin::EnumMediaTypes(IEnumMediaTypes **ppEnum) +{ + AkLogMethod(); + + if (!ppEnum) + return E_POINTER; + + *ppEnum = new AkVCam::EnumMediaTypes(this->d->m_mediaTypes->formats()); + (*ppEnum)->AddRef(); + + return S_OK; +} + +HRESULT AkVCam::Pin::QueryInternalConnections(IPin **apPin, ULONG *nPin) +{ + AkLogMethod(); + UNUSED(apPin) + UNUSED(nPin) + + return E_NOTIMPL; +} + +HRESULT AkVCam::Pin::EndOfStream() +{ + AkLogMethod(); + + return E_UNEXPECTED; +} + +HRESULT AkVCam::Pin::BeginFlush() +{ + AkLogMethod(); + + return E_UNEXPECTED; +} + +HRESULT AkVCam::Pin::EndFlush() +{ + AkLogMethod(); + + return E_UNEXPECTED; +} + +HRESULT AkVCam::Pin::NewSegment(REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate) +{ + AkLogMethod(); + this->d->m_start = tStart; + this->d->m_stop = tStop; + this->d->m_rate = dRate; + + return S_OK; +} + +void AkVCam::PinPrivate::sendFrameOneShot() +{ + AkLogMethod(); + + WaitForSingleObject(this->m_sendFrameEvent, INFINITE); + this->sendFrame(); + AkLoggerLog("Thread ", std::this_thread::get_id(), " finnished"); + this->m_running = false; +} + +void AkVCam::PinPrivate::sendFrameLoop() +{ + AkLogMethod(); + + while (this->m_running) { + WaitForSingleObject(this->m_sendFrameEvent, INFINITE); + auto result = this->sendFrame(); + + if (FAILED(result)) { + AkLoggerLog("Error sending frame: ", + result, + ": ", + stringFromResult(result)); + this->m_running = false; + + break; + } + } + + AkLoggerLog("Thread ", std::this_thread::get_id(), " finnished"); +} + +HRESULT AkVCam::PinPrivate::sendFrame() +{ + AkLogMethod(); + IMediaSample *sample = nullptr; + + if (FAILED(this->m_memAllocator->GetBuffer(&sample, + nullptr, + nullptr, + 0)) + || !sample) + return E_FAIL; + + BYTE *buffer = nullptr; + LONG size = sample->GetSize(); + + if (size < 1 || FAILED(sample->GetPointer(&buffer)) || !buffer) { + sample->Release(); + + return E_FAIL; + } + + this->m_mutex.lock(); + + if (this->m_currentFrame.format().size() > 0) { + auto copyBytes = (std::min)(size_t(size), + this->m_currentFrame.data().size()); + + if (copyBytes > 0) + memcpy(buffer, this->m_currentFrame.data().data(), copyBytes); + } else { + auto frame = this->randomFrame(); + auto copyBytes = (std::min)(size_t(size), + frame.data().size()); + + if (copyBytes > 0) + memcpy(buffer, frame.data().data(), copyBytes); + } + + this->m_mutex.unlock(); + + REFERENCE_TIME clock = 0; + this->m_baseFilter->referenceClock()->GetTime(&clock); + + AM_MEDIA_TYPE *mediaType = nullptr; + self->GetFormat(&mediaType); + auto format = formatFromMediaType(mediaType); + deleteMediaType(&mediaType); + auto fps = format.minimumFrameRate(); + auto duration = REFERENCE_TIME(TIME_BASE + * fps.den() + / fps.num()); + + if (this->m_pts < 0) { + this->m_pts = 0; + this->m_ptsDrift = this->m_pts - clock; + } else { + auto diff = clock - this->m_pts + this->m_ptsDrift; + + if (diff <= 2 * duration) { + this->m_pts = clock + this->m_ptsDrift; + } else { + this->m_pts += duration; + this->m_ptsDrift = this->m_pts - clock; + } + } + + auto startTime = this->m_pts; + auto endTime = startTime + duration; + + sample->SetTime(&startTime, &endTime); + sample->SetMediaTime(&startTime, &endTime); + sample->SetActualDataLength(size); + sample->SetDiscontinuity(false); + sample->SetSyncPoint(true); + sample->SetPreroll(false); + AkLoggerLog("Sending ", stringFromMediaSample(sample)); + auto result = this->m_memInputPin->Receive(sample); + AkLoggerLog("Frame sent"); + sample->Release(); + + return result; +} + +void AkVCam::PinPrivate::updateTestFrame() +{ + auto frame = this->applyAdjusts(this->m_testFrame); + + if (frame.format().size() < 1) + return; + + this->m_testFrameAdapted = frame; +} + +AkVCam::VideoFrame AkVCam::PinPrivate::applyAdjusts(const VideoFrame &frame) +{ + AM_MEDIA_TYPE *mediaType = nullptr; + + if (FAILED(this->self->GetFormat(&mediaType))) + return {}; + + auto format = formatFromMediaType(mediaType); + deleteMediaType(&mediaType); + FourCC fourcc = format.fourcc(); + int width = format.width(); + int height = format.height(); + + /* In Windows red and blue channels are swapped, so hack it with the + * opposite format. Endianness problem maybe? + */ + std::map fixFormat { + {PixelFormatRGB32, PixelFormatBGR32}, + {PixelFormatRGB24, PixelFormatBGR24}, + {PixelFormatRGB16, PixelFormatBGR16}, + {PixelFormatRGB15, PixelFormatBGR15}, + }; + + bool vmirror; + + if (fixFormat.count(fourcc) > 0) { + fourcc = fixFormat[fourcc]; + vmirror = this->m_verticalMirror == this->m_verticalFlip; + } else { + vmirror = this->m_verticalMirror != this->m_verticalFlip; + } + + VideoFrame newFrame; + + if (width * height > frame.format().width() * frame.format().height()) { + newFrame = + frame + .mirror(this->m_horizontalMirror != this->m_horizontalFlip, + vmirror) + .swapRgb(this->m_swapRgb) + .adjust(this->m_hue, + this->m_saturation, + this->m_brightness, + this->m_gamma, + this->m_contrast, + !this->m_colorenable) + .scaled(width, height, + this->m_scaling, + this->m_aspectRatio) + .convert(fourcc); + } else { + newFrame = + frame + .scaled(width, height, + this->m_scaling, + this->m_aspectRatio) + .mirror(this->m_horizontalMirror != this->m_horizontalFlip, + vmirror) + .swapRgb(this->m_swapRgb) + .adjust(this->m_hue, + this->m_saturation, + this->m_brightness, + this->m_gamma, + this->m_contrast, + !this->m_colorenable) + .convert(fourcc); + + } + + newFrame.format().fourcc() = format.fourcc(); + + return newFrame; +} + +void AkVCam::PinPrivate::propertyChanged(void *userData, + LONG Property, + LONG lValue, + LONG Flags) +{ + AkLoggerLog("PinPrivate::propertyChanged()"); + UNUSED(Flags) + auto self = reinterpret_cast(userData); + + switch (Property) { + case VideoProcAmp_Brightness: + self->m_brightness = lValue; + + break; + + case VideoProcAmp_Contrast: + self->m_contrast = lValue; + + break; + + case VideoProcAmp_Saturation: + self->m_saturation = lValue; + + break; + + case VideoProcAmp_Gamma: + self->m_gamma = lValue; + + break; + + case VideoProcAmp_Hue: + self->m_hue = lValue; + + break; + + case VideoProcAmp_ColorEnable: + self->m_colorenable = lValue; + break; + + default: + break; + } + + self->updateTestFrame(); + self->m_mutex.lock(); + self->m_currentFrame = self->m_testFrameAdapted; + self->m_mutex.unlock(); +} + +AkVCam::VideoFrame AkVCam::PinPrivate::randomFrame() +{ + AM_MEDIA_TYPE *mediaType = nullptr; + + if (FAILED(this->self->GetFormat(&mediaType))) + return {}; + + auto format = formatFromMediaType(mediaType); + deleteMediaType(&mediaType); + + VideoFormat rgbFormat(PixelFormatRGB24, format.width(), format.height()); + VideoData data(rgbFormat.size()); + static std::uniform_int_distribution distribution(0, 255); + static std::default_random_engine engine; + std::generate(data.begin(), data.end(), [] () { + return uint8_t(distribution(engine)); + }); + + VideoFrame rgbFrame; + rgbFrame.format() = rgbFormat; + rgbFrame.data() = data; + + return rgbFrame.adjust(this->m_hue, + this->m_saturation, + this->m_brightness, + this->m_gamma, + this->m_contrast, + !this->m_colorenable) + .convert(format.fourcc()); +} diff --git a/dshow/VirtualCamera/src/pin.h b/dshow/VirtualCamera/src/pin.h new file mode 100644 index 0000000..ca969e3 --- /dev/null +++ b/dshow/VirtualCamera/src/pin.h @@ -0,0 +1,95 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PIN_H +#define PIN_H + +#include +#include + +#include "VCamUtils/src/image/videoframetypes.h" +#include "VCamUtils/src/ipcbridge.h" +#include "streamconfig.h" + +namespace AkVCam +{ + class PinPrivate; + class BaseFilter; + class VideoFormat; + class VideoFrame; + + class Pin: + public IPin, + public StreamConfig + { + public: + Pin(BaseFilter *baseFilter=nullptr, + const std::vector &formats={}, + const std::wstring &pinName={}); + virtual ~Pin(); + + BaseFilter *baseFilter() const; + void setBaseFilter(BaseFilter *baseFilter); + static HRESULT stateChanged(void *userData, FILTER_STATE state); + void serverStateChanged(IpcBridge::ServerState state); + void frameReady(const VideoFrame &frame); + void setBroadcasting(const std::string &broadcaster); + void setMirror(bool horizontalMirror, bool verticalMirror); + void setScaling(Scaling scaling); + void setAspectRatio(AspectRatio aspectRatio); + void setSwapRgb(bool swap); + bool horizontalFlip() const; + void setHorizontalFlip(bool flip); + bool verticalFlip() const; + void setVerticalFlip(bool flip); + + DECLARE_IAMSTREAMCONFIG_NQ + + // IUNknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + void **ppvObject); + + // IPin + HRESULT STDMETHODCALLTYPE Connect(IPin *pReceivePin, + const AM_MEDIA_TYPE *pmt); + HRESULT STDMETHODCALLTYPE ReceiveConnection(IPin *pConnector, + const AM_MEDIA_TYPE *pmt); + HRESULT STDMETHODCALLTYPE Disconnect(); + HRESULT STDMETHODCALLTYPE ConnectedTo(IPin **pPin); + HRESULT STDMETHODCALLTYPE ConnectionMediaType(AM_MEDIA_TYPE *pmt); + HRESULT STDMETHODCALLTYPE QueryPinInfo(PIN_INFO *pInfo); + HRESULT STDMETHODCALLTYPE QueryDirection(PIN_DIRECTION *pPinDir); + HRESULT STDMETHODCALLTYPE QueryId(LPWSTR *Id); + HRESULT STDMETHODCALLTYPE QueryAccept(const AM_MEDIA_TYPE *pmt); + HRESULT STDMETHODCALLTYPE EnumMediaTypes(IEnumMediaTypes **ppEnum); + HRESULT STDMETHODCALLTYPE QueryInternalConnections(IPin **apPin, + ULONG *nPin); + HRESULT STDMETHODCALLTYPE EndOfStream(); + HRESULT STDMETHODCALLTYPE BeginFlush(); + HRESULT STDMETHODCALLTYPE EndFlush(); + HRESULT STDMETHODCALLTYPE NewSegment(REFERENCE_TIME tStart, + REFERENCE_TIME tStop, + double dRate); + + private: + PinPrivate *d; + }; +} + +#endif // PIN_H diff --git a/dshow/VirtualCamera/src/plugin.cpp b/dshow/VirtualCamera/src/plugin.cpp new file mode 100644 index 0000000..94ebf1e --- /dev/null +++ b/dshow/VirtualCamera/src/plugin.cpp @@ -0,0 +1,151 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "plugin.h" +#include "plugininterface.h" +#include "classfactory.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +inline AkVCam::PluginInterface *pluginInterface() +{ + static AkVCam::PluginInterface pluginInterface; + + return &pluginInterface; +} + +// Filter entry point +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + UNUSED(lpvReserved) + +#if defined(QT_DEBUG) && 0 + // Turn on lights + freopen("CONOUT$", "a", stdout); + freopen("CONOUT$", "a", stderr); + setbuf(stdout, nullptr); +#endif + + auto temp = AkVCam::tempPath(); + AkLoggerStart(std::string(temp.begin(), temp.end()) + + "\\" DSHOW_PLUGIN_NAME, "log"); + AkLoggerLog(__FUNCTION__, "()"); + + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + AkLoggerLog("Reason Attach"); + AkLoggerLog("Module file name: ", AkVCam::moduleFileName(hinstDLL)); + DisableThreadLibraryCalls(hinstDLL); + pluginInterface()->pluginHinstance() = hinstDLL; + + break; + + case DLL_PROCESS_DETACH: + AkLoggerLog("Reason Detach"); + + break; + + default: + AkLoggerLog("Reason Unknown: ", fdwReason); + + break; + } + + return TRUE; +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + AkLoggerLog(__FUNCTION__, "()"); + AkLoggerLog("CLSID: ", AkVCam::stringFromClsid(rclsid)); + AkLoggerLog("IID: ", AkVCam::stringFromClsid(rclsid)); + + if (!ppv) + return E_INVALIDARG; + + *ppv = nullptr; + + if (!IsEqualIID(riid, IID_IUnknown) + && !IsEqualIID(riid, IID_IClassFactory) + && AkVCam::cameraFromId(riid) < 0) + return CLASS_E_CLASSNOTAVAILABLE; + + auto classFactory = new AkVCam::ClassFactory(rclsid); + classFactory->AddRef(); + *ppv = classFactory; + + return S_OK; +} + +STDAPI DllCanUnloadNow() +{ + AkLoggerLog(__FUNCTION__, "()"); + + return AkVCam::ClassFactory::locked()? S_FALSE: S_OK; +} + +STDAPI DllRegisterServer() +{ + AkLoggerLog(__FUNCTION__, "()"); + + DllUnregisterServer(); + + bool ok = true; + + for (DWORD i = 0; i < AkVCam::camerasCount(); i++) { + auto description = AkVCam::cameraDescription(i); + auto path = AkVCam::cameraPath(i); + +#ifdef QT_DEBUG + auto clsid = AkVCam::createClsidFromStr(path); +#endif + + AkLoggerLog("Creating Camera"); + AkLoggerLog("\tDescription: ", std::string(description.begin(), + description.end())); + AkLoggerLog("\tPath: ", std::string(path.begin(), path.end())); + AkLoggerLog("\tCLSID: ", AkVCam::stringFromIid(clsid)); + + ok &= pluginInterface()->createDevice(path, description); + } + + return ok? S_OK: E_UNEXPECTED; +} + +STDAPI DllUnregisterServer() +{ + AkLoggerLog(__FUNCTION__, "()"); + + auto cameras = + AkVCam::listRegisteredCameras(pluginInterface()->pluginHinstance()); + + for (auto camera: cameras) { + AkLoggerLog("Deleting ", AkVCam::stringFromClsid(camera)); + pluginInterface()->destroyDevice(camera); + } + + // Unregister old virtual camera filter. + // NOTE: This code must be removed in future versions. + CLSID clsid; + CLSIDFromString(L"{41764B79-7320-5669-7274-75616C43616D}", &clsid); + AkLoggerLog("Deleting ", AkVCam::stringFromClsid(clsid)); + pluginInterface()->destroyDevice(clsid); + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/plugin.h b/dshow/VirtualCamera/src/plugin.h new file mode 100644 index 0000000..98429e2 --- /dev/null +++ b/dshow/VirtualCamera/src/plugin.h @@ -0,0 +1,32 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PLUGIN_H +#define PLUGIN_H + +#include + +// Minimum required COM interface. +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv); +STDAPI DllCanUnloadNow(); +STDAPI DllRegisterServer(); +STDAPI DllUnregisterServer(); + +#endif // PLUGIN_H diff --git a/dshow/VirtualCamera/src/plugininterface.cpp b/dshow/VirtualCamera/src/plugininterface.cpp new file mode 100644 index 0000000..08a4e26 --- /dev/null +++ b/dshow/VirtualCamera/src/plugininterface.cpp @@ -0,0 +1,386 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "plugininterface.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "PluginInterface" + +namespace AkVCam +{ + class PluginInterfacePrivate + { + public: + HINSTANCE m_pluginHinstance {nullptr}; + + LONG deleteTree(HKEY hKey, LPCTSTR lpSubKey); + }; +} + +AkVCam::PluginInterface::PluginInterface() +{ + this->d = new PluginInterfacePrivate; +} + +AkVCam::PluginInterface::~PluginInterface() +{ + delete this->d; +} + +HINSTANCE AkVCam::PluginInterface::pluginHinstance() const +{ + return this->d->m_pluginHinstance; +} + +HINSTANCE &AkVCam::PluginInterface::pluginHinstance() +{ + return this->d->m_pluginHinstance; +} + +bool AkVCam::PluginInterface::registerServer(const std::wstring &deviceId, + const std::wstring &description) const +{ + AkLogMethod(); + + // Define the layout in registry of the filter. + + auto clsid = createClsidWStrFromStr(deviceId); + auto fileName = AkVCam::moduleFileNameW(this->d->m_pluginHinstance); + std::wstring threadingModel = L"Both"; + + AkLoggerLog("CLSID: ", std::string(clsid.begin(), clsid.end())); + AkLoggerLog("Description: ", std::string(description.begin(), description.end())); + AkLoggerLog("Filename: ", std::string(fileName.begin(), fileName.end())); + + auto subkey = L"CLSID\\" + clsid; + + HKEY keyCLSID = nullptr; + HKEY keyServerType = nullptr; + LONG result = RegCreateKey(HKEY_CLASSES_ROOT, subkey.c_str(), &keyCLSID); + bool ok = false; + + if (result != ERROR_SUCCESS) + goto registerServer_failed; + + result = + RegSetValue(keyCLSID, + nullptr, + REG_SZ, + description.c_str(), + DWORD(description.size())); + + if (result != ERROR_SUCCESS) + goto registerServer_failed; + + result = RegCreateKey(keyCLSID, L"InprocServer32", &keyServerType); + + if (result != ERROR_SUCCESS) + goto registerServer_failed; + + result = + RegSetValue(keyServerType, + nullptr, + REG_SZ, + fileName.c_str(), + DWORD(fileName.size())); + + if (result != ERROR_SUCCESS) + goto registerServer_failed; + + result = + RegSetValueEx(keyServerType, + L"ThreadingModel", + 0L, + REG_SZ, + reinterpret_cast(threadingModel.c_str()), + DWORD((threadingModel.size() + 1) * sizeof(wchar_t))); + + ok = true; + +registerServer_failed: + if (keyServerType) + RegCloseKey(keyServerType); + + if (keyCLSID) + RegCloseKey(keyCLSID); + + AkLoggerLog("Result: ", stringFromResult(result)); + + return ok; +} + +void AkVCam::PluginInterface::unregisterServer(const std::string &deviceId) const +{ + AkLogMethod(); + + this->unregisterServer(createClsidFromStr(deviceId)); +} + +void AkVCam::PluginInterface::unregisterServer(const std::wstring &deviceId) const +{ + AkLogMethod(); + + this->unregisterServer(createClsidFromStr(deviceId)); +} + +void AkVCam::PluginInterface::unregisterServer(const CLSID &clsid) const +{ + AkLogMethod(); + + auto clsidStr = stringFromClsid(clsid); + AkLoggerLog("CLSID: ", clsidStr); + auto subkey = L"CLSID\\" + std::wstring(clsidStr.begin(), clsidStr.end()); + + this->d->deleteTree(HKEY_CLASSES_ROOT, subkey.c_str()); +} + +bool AkVCam::PluginInterface::registerFilter(const std::wstring &deviceId, + const std::wstring &description) const +{ + AkLogMethod(); + + auto clsid = AkVCam::createClsidFromStr(deviceId); + IFilterMapper2 *filterMapper = nullptr; + IMoniker *pMoniker = nullptr; + std::vector pinTypes { + {&MEDIATYPE_Video, &MEDIASUBTYPE_NULL} + }; + std::vector mediums; + std::vector pins { + { + REG_PINFLAG_B_OUTPUT, + 1, + UINT(pinTypes.size()), + pinTypes.data(), + UINT(mediums.size()), + mediums.data(), + &PIN_CATEGORY_CAPTURE + } + }; + REGFILTER2 regFilter; + regFilter.dwVersion = 2; + regFilter.dwMerit = MERIT_DO_NOT_USE; + regFilter.cPins2 = ULONG(pins.size()); + regFilter.rgPins2 = pins.data(); + + auto result = CoInitialize(nullptr); + bool ok = false; + + if (FAILED(result)) + goto registerFilter_failed; + + result = CoCreateInstance(CLSID_FilterMapper2, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IFilterMapper2, + reinterpret_cast(&filterMapper)); + + if (FAILED(result)) + goto registerFilter_failed; + + result = filterMapper->RegisterFilter(clsid, + description.c_str(), + &pMoniker, + &CLSID_VideoInputDeviceCategory, + nullptr, + ®Filter); + + ok = true; + +registerFilter_failed: + + if (filterMapper) + filterMapper->Release(); + + CoUninitialize(); + + AkLoggerLog("Result: ", stringFromResult(result)); + + return ok; +} + +void AkVCam::PluginInterface::unregisterFilter(const std::string &deviceId) const +{ + AkLogMethod(); + + this->unregisterFilter(AkVCam::createClsidFromStr(deviceId)); +} + +void AkVCam::PluginInterface::unregisterFilter(const std::wstring &deviceId) const +{ + AkLogMethod(); + + this->unregisterFilter(AkVCam::createClsidFromStr(deviceId)); +} + +void AkVCam::PluginInterface::unregisterFilter(const CLSID &clsid) const +{ + AkLogMethod(); + IFilterMapper2 *filterMapper = nullptr; + auto result = CoInitialize(nullptr); + + if (FAILED(result)) + goto unregisterFilter_failed; + + result = CoCreateInstance(CLSID_FilterMapper2, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IFilterMapper2, + reinterpret_cast(&filterMapper)); + + if (FAILED(result)) + goto unregisterFilter_failed; + + result = filterMapper->UnregisterFilter(&CLSID_VideoInputDeviceCategory, + nullptr, + clsid); + +unregisterFilter_failed: + + if (filterMapper) + filterMapper->Release(); + + CoUninitialize(); + AkLoggerLog("Result: ", stringFromResult(result)); +} + +bool AkVCam::PluginInterface::setDevicePath(const std::wstring &deviceId) const +{ + AkLogMethod(); + + std::wstring subKey = + L"CLSID\\" + + wstringFromIid(CLSID_VideoInputDeviceCategory) + + L"\\Instance\\" + + createClsidWStrFromStr(deviceId); + AkLoggerLog("Key: HKEY_CLASSES_ROOT"); + AkLoggerLog("SubKey: ", std::string(subKey.begin(), subKey.end())); + + HKEY hKey = nullptr; + auto result = RegOpenKeyEx(HKEY_CLASSES_ROOT, + subKey.c_str(), + 0, + KEY_ALL_ACCESS, + &hKey); + bool ok = false; + + if (result != ERROR_SUCCESS) + goto setDevicePath_failed; + + result = RegSetValueEx(hKey, + TEXT("DevicePath"), + 0, + REG_SZ, + reinterpret_cast(deviceId.c_str()), + DWORD((deviceId.size() + 1) * sizeof(wchar_t))); + + if (result != ERROR_SUCCESS) + goto setDevicePath_failed; + + ok = true; + +setDevicePath_failed: + if (hKey) + RegCloseKey(hKey); + + AkLoggerLog("Result: ", stringFromResult(result)); + + return ok; +} + +bool AkVCam::PluginInterface::createDevice(const std::wstring &deviceId, + const std::wstring &description) +{ + AkLogMethod(); + + if (!this->registerServer(deviceId, description)) + goto createDevice_failed; + + if (!this->registerFilter(deviceId, description)) + goto createDevice_failed; + + if (!this->setDevicePath(deviceId)) + goto createDevice_failed; + + return true; + +createDevice_failed: + this->destroyDevice(deviceId); + + return false; +} + +void AkVCam::PluginInterface::destroyDevice(const std::string &deviceId) +{ + AkLogMethod(); + + this->unregisterFilter(deviceId); + this->unregisterServer(deviceId); +} + +void AkVCam::PluginInterface::destroyDevice(const std::wstring &deviceId) +{ + AkLogMethod(); + + this->unregisterFilter(deviceId); + this->unregisterServer(deviceId); +} + +void AkVCam::PluginInterface::destroyDevice(const CLSID &clsid) +{ + AkLogMethod(); + + this->unregisterFilter(clsid); + this->unregisterServer(clsid); +} + +LONG AkVCam::PluginInterfacePrivate::deleteTree(HKEY hKey, LPCTSTR lpSubKey) +{ + HKEY key = nullptr; + auto result = RegOpenKeyEx(hKey, lpSubKey, 0, MAXIMUM_ALLOWED, &key); + + if (result != ERROR_SUCCESS) + return result; + + TCHAR subKey[MAX_PATH]; + DWORD subKeyLen = MAX_PATH; + FILETIME lastWrite; + + while (RegEnumKeyEx(key, + 0, + subKey, + &subKeyLen, + nullptr, + nullptr, + nullptr, + &lastWrite) == ERROR_SUCCESS) { + this->deleteTree(key, subKey); + } + + RegCloseKey(key); + RegDeleteKey(hKey, lpSubKey); + + return ERROR_SUCCESS; +} diff --git a/dshow/VirtualCamera/src/plugininterface.h b/dshow/VirtualCamera/src/plugininterface.h new file mode 100644 index 0000000..9dcb49f --- /dev/null +++ b/dshow/VirtualCamera/src/plugininterface.h @@ -0,0 +1,61 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PLUGININTERFACE_H +#define PLUGININTERFACE_H + +#include +#include + +namespace AkVCam +{ + class PluginInterfacePrivate; + + class PluginInterface + { + public: + PluginInterface(); + PluginInterface(const PluginInterface &other) = delete; + ~PluginInterface(); + + HINSTANCE pluginHinstance() const; + HINSTANCE &pluginHinstance(); + bool registerServer(const std::wstring &deviceId, + const std::wstring &description) const; + void unregisterServer(const std::string &deviceId) const; + void unregisterServer(const std::wstring &deviceId) const; + void unregisterServer(const CLSID &clsid) const; + bool registerFilter(const std::wstring &deviceId, + const std::wstring &description) const; + void unregisterFilter(const std::string &deviceId) const; + void unregisterFilter(const std::wstring &deviceId) const; + void unregisterFilter(const CLSID &clsid) const; + bool setDevicePath(const std::wstring &deviceId) const; + bool createDevice(const std::wstring &deviceId, + const std::wstring &description); + void destroyDevice(const std::string &deviceId); + void destroyDevice(const std::wstring &deviceId); + void destroyDevice(const CLSID &clsid); + + private: + PluginInterfacePrivate *d; + }; +} + +#endif // PLUGININTERFACE_H diff --git a/dshow/VirtualCamera/src/propertyset.cpp b/dshow/VirtualCamera/src/propertyset.cpp new file mode 100644 index 0000000..f1543ee --- /dev/null +++ b/dshow/VirtualCamera/src/propertyset.cpp @@ -0,0 +1,109 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include + +#include "propertyset.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "PropertySet" + +AkVCam::PropertySet::PropertySet(): + CUnknown(this, IID_IKsPropertySet) +{ + +} + +AkVCam::PropertySet::~PropertySet() +{ + +} + +HRESULT AkVCam::PropertySet::Set(const GUID &guidPropSet, + DWORD dwPropID, + LPVOID pInstanceData, + DWORD cbInstanceData, + LPVOID pPropData, + DWORD cbPropData) +{ + UNUSED(guidPropSet) + UNUSED(dwPropID) + UNUSED(pInstanceData) + UNUSED(cbInstanceData) + UNUSED(pPropData) + UNUSED(cbPropData) + AkLogMethod(); + + return E_NOTIMPL; +} + +HRESULT AkVCam::PropertySet::Get(const GUID &guidPropSet, + DWORD dwPropID, + LPVOID pInstanceData, + DWORD cbInstanceData, + LPVOID pPropData, + DWORD cbPropData, + DWORD *pcbReturned) +{ + UNUSED(pInstanceData) + UNUSED(cbInstanceData) + AkLogMethod(); + + if (!IsEqualGUID(guidPropSet, AMPROPSETID_Pin)) + return E_PROP_SET_UNSUPPORTED; + + if (dwPropID != AMPROPERTY_PIN_CATEGORY) + return E_PROP_ID_UNSUPPORTED; + + if (!pPropData && !pcbReturned) + return E_POINTER; + + if (pcbReturned) + *pcbReturned = sizeof(GUID); + + if (!pPropData) + return S_OK; + + if (cbPropData < sizeof(GUID)) + return E_UNEXPECTED; + + auto propData = reinterpret_cast(pPropData); + *propData = PIN_CATEGORY_CAPTURE; + + return S_OK; +} + +HRESULT AkVCam::PropertySet::QuerySupported(const GUID &guidPropSet, + DWORD dwPropID, + DWORD *pTypeSupport) +{ + AkLogMethod(); + + if (!IsEqualGUID(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; +} diff --git a/dshow/VirtualCamera/src/propertyset.h b/dshow/VirtualCamera/src/propertyset.h new file mode 100644 index 0000000..8273f77 --- /dev/null +++ b/dshow/VirtualCamera/src/propertyset.h @@ -0,0 +1,59 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PROPERTYSET_H +#define PROPERTYSET_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class PropertySet: + public IKsPropertySet, + public CUnknown + { + public: + PropertySet(); + virtual ~PropertySet(); + + DECLARE_IUNKNOWN(IID_IKsPropertySet) + + // IKsPropertySet + HRESULT STDMETHODCALLTYPE Set(REFGUID guidPropSet, + DWORD dwPropID, + LPVOID pInstanceData, + DWORD cbInstanceData, + LPVOID pPropData, + DWORD cbPropData); + HRESULT STDMETHODCALLTYPE Get(REFGUID guidPropSet, + DWORD dwPropID, + LPVOID pInstanceData, + DWORD cbInstanceData, + LPVOID pPropData, + DWORD cbPropData, + DWORD *pcbReturned); + HRESULT STDMETHODCALLTYPE QuerySupported(REFGUID guidPropSet, + DWORD dwPropID, + DWORD *pTypeSupport); + }; +} + +#endif // PROPERTYSET_H diff --git a/dshow/VirtualCamera/src/pushsource.cpp b/dshow/VirtualCamera/src/pushsource.cpp new file mode 100644 index 0000000..4291094 --- /dev/null +++ b/dshow/VirtualCamera/src/pushsource.cpp @@ -0,0 +1,86 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "pushsource.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "PushSource" + +AkVCam::PushSource::PushSource(IAMStreamConfig *streamConfig): + Latency(streamConfig) +{ + this->setParent(this, &IID_IAMPushSource); +} + +AkVCam::PushSource::~PushSource() +{ +} + +HRESULT AkVCam::PushSource::GetPushSourceFlags(ULONG *pFlags) +{ + AkLogMethod(); + + if (!pFlags) + return E_POINTER; + + *pFlags = 0; + + return S_OK; +} + +HRESULT AkVCam::PushSource::SetPushSourceFlags(ULONG Flags) +{ + UNUSED(Flags) + AkLogMethod(); + + return E_NOTIMPL; +} + +HRESULT AkVCam::PushSource::SetStreamOffset(REFERENCE_TIME rtOffset) +{ + UNUSED(rtOffset) + AkLogMethod(); + + return E_NOTIMPL; +} + +HRESULT AkVCam::PushSource::GetStreamOffset(REFERENCE_TIME *prtOffset) +{ + UNUSED(prtOffset) + AkLogMethod(); + + return E_NOTIMPL; +} + +HRESULT AkVCam::PushSource::GetMaxStreamOffset(REFERENCE_TIME *prtMaxOffset) +{ + UNUSED(prtMaxOffset) + AkLogMethod(); + + return E_NOTIMPL; +} + +HRESULT AkVCam::PushSource::SetMaxStreamOffset(REFERENCE_TIME rtMaxOffset) +{ + UNUSED(rtMaxOffset) + AkLogMethod(); + + return E_NOTIMPL; +} diff --git a/dshow/VirtualCamera/src/pushsource.h b/dshow/VirtualCamera/src/pushsource.h new file mode 100644 index 0000000..52fe8ad --- /dev/null +++ b/dshow/VirtualCamera/src/pushsource.h @@ -0,0 +1,47 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef PUSHSOURCE_H +#define PUSHSOURCE_H + +#include "latency.h" + +namespace AkVCam +{ + class PushSource: + public IAMPushSource, + public Latency + { + public: + PushSource(IAMStreamConfig *streamConfig); + virtual ~PushSource(); + + DECLARE_IAMLATENCY(IID_IAMPushSource) + + // IAMPushSource + HRESULT WINAPI GetPushSourceFlags(ULONG *pFlags); + HRESULT WINAPI SetPushSourceFlags(ULONG Flags); + HRESULT WINAPI SetStreamOffset(REFERENCE_TIME rtOffset); + HRESULT WINAPI GetStreamOffset(REFERENCE_TIME *prtOffset); + HRESULT WINAPI GetMaxStreamOffset(REFERENCE_TIME *prtMaxOffset); + HRESULT WINAPI SetMaxStreamOffset(REFERENCE_TIME rtMaxOffset); + }; +} + +#endif // PUSHSOURCE_H diff --git a/dshow/VirtualCamera/src/qualitycontrol.cpp b/dshow/VirtualCamera/src/qualitycontrol.cpp new file mode 100644 index 0000000..35b5b9b --- /dev/null +++ b/dshow/VirtualCamera/src/qualitycontrol.cpp @@ -0,0 +1,59 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include "qualitycontrol.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "QualityControl" + +AkVCam::QualityControl::QualityControl(): + CUnknown(this, IID_IQualityControl) +{ + +} + +AkVCam::QualityControl::~QualityControl() +{ + +} + +HRESULT AkVCam::QualityControl::Notify(IBaseFilter *pSelf, Quality q) +{ + UNUSED(q) + AkLogMethod(); + + if (!pSelf) + return E_POINTER; + + AkLoggerLog("Type: ", q.Type == Famine? "Famine": "Flood"); + AkLoggerLog("Proportion: ", q.Proportion); + AkLoggerLog("Late: ", q.Late); + AkLoggerLog("TimeStamp:", q.TimeStamp); + + return S_OK; +} + +HRESULT AkVCam::QualityControl::SetSink(IQualityControl *piqc) +{ + UNUSED(piqc) + AkLogMethod(); + + return E_NOTIMPL; +} diff --git a/dshow/VirtualCamera/src/qualitycontrol.h b/dshow/VirtualCamera/src/qualitycontrol.h new file mode 100644 index 0000000..483f28f --- /dev/null +++ b/dshow/VirtualCamera/src/qualitycontrol.h @@ -0,0 +1,45 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef QUALITYCONTROL_H +#define QUALITYCONTROL_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class QualityControl: + public IQualityControl, + public CUnknown + { + public: + QualityControl(); + virtual ~QualityControl(); + + DECLARE_IUNKNOWN(IID_IQualityControl) + + // ISpecifyPropertyPages + HRESULT STDMETHODCALLTYPE Notify(IBaseFilter *pSelf, Quality q); + HRESULT STDMETHODCALLTYPE SetSink(IQualityControl *piqc); + }; +} + +#endif // QUALITYCONTROL_H diff --git a/dshow/VirtualCamera/src/referenceclock.cpp b/dshow/VirtualCamera/src/referenceclock.cpp new file mode 100644 index 0000000..19309e8 --- /dev/null +++ b/dshow/VirtualCamera/src/referenceclock.cpp @@ -0,0 +1,306 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "referenceclock.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "ReferenceClock" + +namespace AkVCam +{ + class AdviseCookiePrivate + { + public: + IReferenceClock *m_clock; + std::thread m_thread; + std::mutex m_mutex; + std::condition_variable_any m_timeout; + std::atomic m_run {false}; + + explicit AdviseCookiePrivate(IReferenceClock *clock); + void adviseTime(REFERENCE_TIME baseTime, + REFERENCE_TIME streamTime, + HEVENT hEvent); + void adviseTimeTh(REFERENCE_TIME baseTime, + REFERENCE_TIME streamTime, + HEVENT hEvent); + void advisePeriodic(REFERENCE_TIME startTime, + REFERENCE_TIME periodTime, + HSEMAPHORE hSemaphore); + void advisePeriodicTh(REFERENCE_TIME startTime, + REFERENCE_TIME periodTime, + HSEMAPHORE hSemaphore); + void unadvise(); + }; + + class ReferenceClockPrivate + { + public: + ReferenceClock *self; + std::vector m_cookies; + REFERENCE_TIME m_lastTime {0}; + + explicit ReferenceClockPrivate(ReferenceClock *self); + void cleanup(); + }; +} + +AkVCam::ReferenceClock::ReferenceClock(): + CUnknown(this, IID_IReferenceClock) +{ + this->d = new ReferenceClockPrivate(this); +} + +AkVCam::ReferenceClock::~ReferenceClock() +{ + for (auto &cookie: this->d->m_cookies) { + auto adviseCookie = reinterpret_cast(cookie); + adviseCookie->unadvise(); + delete adviseCookie; + } + + delete this->d; +} + +HRESULT AkVCam::ReferenceClock::GetTime(REFERENCE_TIME *pTime) +{ + AkLogMethod(); + + if (!pTime) + return E_POINTER; + + *pTime = REFERENCE_TIME(TIME_BASE * timeGetTime() / 1e3); + + if (*pTime <= this->d->m_lastTime) + return S_FALSE; + + this->d->m_lastTime = *pTime; + + return S_OK; +} + +HRESULT AkVCam::ReferenceClock::AdviseTime(REFERENCE_TIME baseTime, + REFERENCE_TIME streamTime, + HEVENT hEvent, + DWORD_PTR *pdwAdviseCookie) +{ + AkLogMethod(); + this->d->cleanup(); + + if (!pdwAdviseCookie) + return E_POINTER; + + *pdwAdviseCookie = 0; + + const REFERENCE_TIME time = baseTime + streamTime; + + if (time <= 0 || time == (std::numeric_limits::max)()) + return E_INVALIDARG; + + auto adviseCookie = new AdviseCookiePrivate(this); + *pdwAdviseCookie = DWORD_PTR(adviseCookie); + this->d->m_cookies.push_back(*pdwAdviseCookie); + adviseCookie->adviseTime(baseTime, streamTime, hEvent); + + return S_OK; +} + +HRESULT AkVCam::ReferenceClock::AdvisePeriodic(REFERENCE_TIME startTime, + REFERENCE_TIME periodTime, + HSEMAPHORE hSemaphore, + DWORD_PTR *pdwAdviseCookie) +{ + AkLogMethod(); + this->d->cleanup(); + + if (!pdwAdviseCookie) + return E_POINTER; + + *pdwAdviseCookie = 0; + + if (startTime <= 0 + || periodTime <= 0 + || startTime == (std::numeric_limits::max)()) + return E_INVALIDARG; + + auto adviseCookie = new AdviseCookiePrivate(this); + adviseCookie->advisePeriodic(startTime, periodTime, hSemaphore); + *pdwAdviseCookie = DWORD_PTR(adviseCookie); + this->d->m_cookies.push_back(*pdwAdviseCookie); + + return S_OK; +} + +HRESULT AkVCam::ReferenceClock::Unadvise(DWORD_PTR dwAdviseCookie) +{ + AkLogMethod(); + + auto it = std::find(this->d->m_cookies.begin(), + this->d->m_cookies.end(), + dwAdviseCookie); + + if (it == this->d->m_cookies.end()) + return S_FALSE; + + auto adviseCookie = reinterpret_cast(*it); + adviseCookie->unadvise(); + delete adviseCookie; + this->d->m_cookies.erase(it); + this->d->cleanup(); + + return S_OK; +} + +AkVCam::AdviseCookiePrivate::AdviseCookiePrivate(IReferenceClock *clock): + m_clock(clock) +{ +} + +void AkVCam::AdviseCookiePrivate::adviseTime(REFERENCE_TIME baseTime, + REFERENCE_TIME streamTime, + HEVENT hEvent) +{ + AkLogMethod(); + + this->m_run = true; + this->m_thread = std::thread(&AdviseCookiePrivate::adviseTimeTh, + this, + baseTime, + streamTime, + hEvent); + AkLoggerLog("Launching thread ", this->m_thread.get_id()); +} + +void AkVCam::AdviseCookiePrivate::adviseTimeTh(REFERENCE_TIME baseTime, + REFERENCE_TIME streamTime, + HEVENT hEvent) +{ + AkLogMethod(); + + REFERENCE_TIME clockTime; + this->m_clock->GetTime(&clockTime); + + auto startSleep = + REFERENCE_TIME(1e3 + * (baseTime + streamTime - clockTime) + / TIME_BASE); + + if (startSleep > 0) { + std::chrono::milliseconds start(startSleep); + this->m_mutex.lock(); + this->m_timeout.wait_for(this->m_mutex, start); + this->m_mutex.unlock(); + } + + if (this->m_run) + SetEvent(HANDLE(hEvent)); + + this->m_run = false; + AkLoggerLog("Thread ", std::this_thread::get_id(), " finnished"); +} + +void AkVCam::AdviseCookiePrivate::advisePeriodic(REFERENCE_TIME startTime, + REFERENCE_TIME periodTime, + HSEMAPHORE hSemaphore) +{ + AkLogMethod(); + + this->m_run = true; + this->m_thread = std::thread(&AdviseCookiePrivate::advisePeriodicTh, + this, + startTime, + periodTime, + hSemaphore); + AkLoggerLog("Launching thread ", this->m_thread.get_id()); +} + +void AkVCam::AdviseCookiePrivate::advisePeriodicTh(REFERENCE_TIME startTime, + REFERENCE_TIME periodTime, + HSEMAPHORE hSemaphore) +{ + AkLogMethod(); + + REFERENCE_TIME clockTime; + this->m_clock->GetTime(&clockTime); + + auto startSleep = + REFERENCE_TIME(1e3 + * (startTime - clockTime) + / TIME_BASE); + + if (startSleep > 0) { + std::chrono::milliseconds start(startSleep); + this->m_mutex.lock(); + this->m_timeout.wait_for(this->m_mutex, start); + this->m_mutex.unlock(); + } + + auto periodSleep = REFERENCE_TIME(1e3 * periodTime / TIME_BASE); + std::chrono::milliseconds period(periodSleep); + + while (this->m_run) { + ReleaseSemaphore(HANDLE(hSemaphore), 1, nullptr); + this->m_mutex.lock(); + this->m_timeout.wait_for(this->m_mutex, period); + this->m_mutex.unlock(); + } + + AkLoggerLog("Thread ", std::this_thread::get_id(), " finnished"); +} + +void AkVCam::AdviseCookiePrivate::unadvise() +{ + AkLogMethod(); + + this->m_run = false; + this->m_mutex.lock(); + this->m_timeout.notify_one(); + this->m_mutex.unlock(); + this->m_thread.join(); +} + +AkVCam::ReferenceClockPrivate::ReferenceClockPrivate(ReferenceClock *self): + self(self) +{ +} + +void AkVCam::ReferenceClockPrivate::cleanup() +{ + std::vector cookies; + + for (auto &cookie: this->m_cookies) { + auto adviseCookie = reinterpret_cast(cookie); + + if (!adviseCookie->m_run) + cookies.push_back(cookie); + } + + for (auto &cookie: cookies) + this->self->Unadvise(cookie); +} diff --git a/dshow/VirtualCamera/src/referenceclock.h b/dshow/VirtualCamera/src/referenceclock.h new file mode 100644 index 0000000..3c27e3a --- /dev/null +++ b/dshow/VirtualCamera/src/referenceclock.h @@ -0,0 +1,59 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef REFERENCECLOCK_H +#define REFERENCECLOCK_H + +#include + +#include "cunknown.h" + +#define TIME_BASE 1.0e7 + +namespace AkVCam +{ + class ReferenceClockPrivate; + + class ReferenceClock: + public IReferenceClock, + public CUnknown + { + public: + ReferenceClock(); + virtual ~ReferenceClock(); + + DECLARE_IUNKNOWN(IID_IReferenceClock) + + HRESULT STDMETHODCALLTYPE GetTime(REFERENCE_TIME *pTime); + HRESULT STDMETHODCALLTYPE AdviseTime(REFERENCE_TIME baseTime, + REFERENCE_TIME streamTime, + HEVENT hEvent, + DWORD_PTR *pdwAdviseCookie); + HRESULT STDMETHODCALLTYPE AdvisePeriodic(REFERENCE_TIME startTime, + REFERENCE_TIME periodTime, + HSEMAPHORE hSemaphore, + DWORD_PTR *pdwAdviseCookie); + HRESULT STDMETHODCALLTYPE Unadvise(DWORD_PTR dwAdviseCookie); + + private: + ReferenceClockPrivate *d; + }; +} + +#endif // REFERENCECLOCK_H diff --git a/dshow/VirtualCamera/src/specifypropertypages.cpp b/dshow/VirtualCamera/src/specifypropertypages.cpp new file mode 100644 index 0000000..ef235ba --- /dev/null +++ b/dshow/VirtualCamera/src/specifypropertypages.cpp @@ -0,0 +1,91 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include + +#include "specifypropertypages.h" +#include "basefilter.h" +#include "pin.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "SpecifyPropertyPages" + +namespace AkVCam +{ + class SpecifyPropertyPagesPrivate + { + public: + IPin *m_pin; + }; +} + +AkVCam::SpecifyPropertyPages::SpecifyPropertyPages(IPin *pin): + CUnknown(this, IID_ISpecifyPropertyPages) +{ + this->d = new SpecifyPropertyPagesPrivate; + this->d->m_pin = pin; + this->d->m_pin->AddRef(); +} + +AkVCam::SpecifyPropertyPages::~SpecifyPropertyPages() +{ + this->d->m_pin->Release(); + delete this->d; +} + +HRESULT AkVCam::SpecifyPropertyPages::GetPages(CAUUID *pPages) +{ + AkLogMethod(); + + if (!pPages) + return E_POINTER; + + std::vector pages { + CLSID_VideoProcAmpPropertyPage, + }; + + IPin *pin = nullptr; + + if (SUCCEEDED(this->d->m_pin->ConnectedTo(&pin))) { + auto cpin = dynamic_cast(this->d->m_pin); + FILTER_STATE state = State_Stopped; + cpin->baseFilter()->GetState(0, &state); + + if (state == State_Stopped) + pages.push_back(CLSID_VideoStreamConfigPropertyPage); + + pin->Release(); + } + + pPages->cElems = ULONG(pages.size()); + pPages->pElems = + reinterpret_cast(CoTaskMemAlloc(sizeof(GUID) * pages.size())); + AkLoggerLog("Returning property pages:"); + + for (size_t i = 0; i < pages.size(); i++) { + memcpy(&pPages->pElems[i], &pages[i], sizeof(GUID)); + AkLoggerLog(" ", stringFromClsid(pages[i])); + } + + return S_OK; +} diff --git a/dshow/VirtualCamera/src/specifypropertypages.h b/dshow/VirtualCamera/src/specifypropertypages.h new file mode 100644 index 0000000..c48d131 --- /dev/null +++ b/dshow/VirtualCamera/src/specifypropertypages.h @@ -0,0 +1,49 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef SPECIFYPROPERTYPAGES_H +#define SPECIFYPROPERTYPAGES_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class SpecifyPropertyPagesPrivate; + + class SpecifyPropertyPages: + public ISpecifyPropertyPages, + public CUnknown + { + public: + SpecifyPropertyPages(IPin *pin); + virtual ~SpecifyPropertyPages(); + + DECLARE_IUNKNOWN(IID_ISpecifyPropertyPages) + + // ISpecifyPropertyPages + HRESULT STDMETHODCALLTYPE GetPages(CAUUID *pPages); + + private: + SpecifyPropertyPagesPrivate *d; + }; +} + +#endif // SPECIFYPROPERTYPAGES_H diff --git a/dshow/VirtualCamera/src/streamconfig.cpp b/dshow/VirtualCamera/src/streamconfig.cpp new file mode 100644 index 0000000..5f709ef --- /dev/null +++ b/dshow/VirtualCamera/src/streamconfig.cpp @@ -0,0 +1,268 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include + +#include "streamconfig.h" +#include "basefilter.h" +#include "pin.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "StreamConfig" + +namespace AkVCam +{ + class StreamConfigPrivate + { + public: + Pin *m_pin; + AM_MEDIA_TYPE *m_mediaType; + }; +} + +AkVCam::StreamConfig::StreamConfig(Pin *pin): + CUnknown(this, IID_IAMStreamConfig) +{ + this->d = new StreamConfigPrivate; + this->d->m_pin = pin; + this->d->m_mediaType = nullptr; +} + +AkVCam::StreamConfig::~StreamConfig() +{ + deleteMediaType(&this->d->m_mediaType); + delete this->d; +} + +void AkVCam::StreamConfig::setPin(Pin *pin) +{ + this->d->m_pin = pin; +} + +HRESULT AkVCam::StreamConfig::SetFormat(AM_MEDIA_TYPE *pmt) +{ + AkLogMethod(); + + if (!pmt) + return E_POINTER; + + if (this->d->m_pin) { + PIN_INFO pinInfo; + + if (SUCCEEDED(this->d->m_pin->QueryPinInfo(&pinInfo)) + && pinInfo.pFilter) { + FILTER_STATE state; + auto result = pinInfo.pFilter->GetState(0, &state); + pinInfo.pFilter->Release(); + + if (FAILED(result) || state != State_Stopped) + return VFW_E_NOT_STOPPED; + } + + if (this->d->m_pin->QueryAccept(pmt) != S_OK) + return VFW_E_INVALIDMEDIATYPE; + } + + deleteMediaType(&this->d->m_mediaType); + this->d->m_mediaType = createMediaType(pmt); + + IPin *pin = nullptr; + + if (SUCCEEDED(this->d->m_pin->ConnectedTo(&pin))) { + if (this->d->m_pin + && this->d->m_pin->baseFilter() + && this->d->m_pin->baseFilter()->filterGraph()) + this->d->m_pin->baseFilter()->filterGraph()->Reconnect(this->d->m_pin); + + pin->Release(); + } + + return S_OK; +} + +HRESULT AkVCam::StreamConfig::GetFormat(AM_MEDIA_TYPE **pmt) +{ + AkLogMethod(); + + if (!pmt) + return E_POINTER; + + *pmt = nullptr; + + if (this->d->m_mediaType) { + *pmt = createMediaType(this->d->m_mediaType); + } else { + IEnumMediaTypes *mediaTypes = nullptr; + + if (FAILED(this->d->m_pin->EnumMediaTypes(&mediaTypes))) + return E_FAIL; + + AM_MEDIA_TYPE *mediaType = nullptr; + mediaTypes->Reset(); + + if (mediaTypes->Next(1, &mediaType, nullptr) == S_OK) + *pmt = mediaType; + + mediaTypes->Release(); + } + + AkLoggerLog("MediaType: ", stringFromMediaType(*pmt)); + + return *pmt? S_OK: E_FAIL; +} + +HRESULT AkVCam::StreamConfig::GetNumberOfCapabilities(int *piCount, + int *piSize) +{ + AkLogMethod(); + + if (!piCount || !piSize) + return E_POINTER; + + *piCount = 0; + *piSize = 0; + + if (this->d->m_pin) { + IEnumMediaTypes *mediaTypes = nullptr; + + if (SUCCEEDED(this->d->m_pin->EnumMediaTypes(&mediaTypes))) { + mediaTypes->Reset(); + AM_MEDIA_TYPE *mediaType = nullptr; + + while (mediaTypes->Next(1, &mediaType, nullptr) == S_OK) { + (*piCount)++; + deleteMediaType(&mediaType); + } + + mediaTypes->Release(); + } + } + + if (*piCount) + *piSize = sizeof(VIDEO_STREAM_CONFIG_CAPS); + + return S_OK; +} + +HRESULT AkVCam::StreamConfig::GetStreamCaps(int iIndex, + AM_MEDIA_TYPE **pmt, + BYTE *pSCC) +{ + AkLogMethod(); + + if (!pmt || !pSCC) + return E_POINTER; + + *pmt = nullptr; + auto configCaps = reinterpret_cast(pSCC); + memset(configCaps, 0, sizeof(VIDEO_STREAM_CONFIG_CAPS)); + + if (iIndex < 0) + return E_INVALIDARG; + + if (this->d->m_pin) { + IEnumMediaTypes *mediaTypes = nullptr; + + if (SUCCEEDED(this->d->m_pin->EnumMediaTypes(&mediaTypes))) { + mediaTypes->Reset(); + AM_MEDIA_TYPE *mediaType = nullptr; + + for (int i = 0; + mediaTypes->Next(1, &mediaType, nullptr) == S_OK; + i++) { + if (i == iIndex) { + *pmt = mediaType; + + if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) { + auto format = reinterpret_cast(mediaType->pbFormat); + configCaps->guid = mediaType->formattype; + configCaps->VideoStandard = AnalogVideo_None; + configCaps->InputSize.cx = format->bmiHeader.biWidth; + configCaps->InputSize.cy = format->bmiHeader.biHeight; + configCaps->MinCroppingSize.cx = format->bmiHeader.biWidth; + configCaps->MinCroppingSize.cy = format->bmiHeader.biHeight; + configCaps->MaxCroppingSize.cx = format->bmiHeader.biWidth; + configCaps->MaxCroppingSize.cy = format->bmiHeader.biHeight; + configCaps->CropGranularityX = 1; + configCaps->CropGranularityY = 1; + configCaps->CropAlignX = 0; + configCaps->CropAlignY = 0; + configCaps->MinOutputSize.cx = format->bmiHeader.biWidth; + configCaps->MinOutputSize.cy = format->bmiHeader.biHeight; + configCaps->MaxOutputSize.cx = format->bmiHeader.biWidth; + configCaps->MaxOutputSize.cy = format->bmiHeader.biHeight; + configCaps->OutputGranularityX = 1; + configCaps->OutputGranularityY = 1; + configCaps->StretchTapsX = 1; + configCaps->StretchTapsY = 1; + configCaps->ShrinkTapsX = 1; + configCaps->ShrinkTapsY = 1; + configCaps->MinFrameInterval = format->AvgTimePerFrame; + configCaps->MaxFrameInterval = format->AvgTimePerFrame; + configCaps->MinBitsPerSecond = LONG(format->dwBitRate); + configCaps->MaxBitsPerSecond = LONG(format->dwBitRate); + } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) { + auto format = reinterpret_cast(mediaType->pbFormat); + configCaps->guid = mediaType->formattype; + configCaps->VideoStandard = AnalogVideo_None; + configCaps->InputSize.cx = format->bmiHeader.biWidth; + configCaps->InputSize.cy = format->bmiHeader.biHeight; + configCaps->MinCroppingSize.cx = format->bmiHeader.biWidth; + configCaps->MinCroppingSize.cy = format->bmiHeader.biHeight; + configCaps->MaxCroppingSize.cx = format->bmiHeader.biWidth; + configCaps->MaxCroppingSize.cy = format->bmiHeader.biHeight; + configCaps->CropGranularityX = 1; + configCaps->CropGranularityY = 1; + configCaps->CropAlignX = 0; + configCaps->CropAlignY = 0; + configCaps->MinOutputSize.cx = format->bmiHeader.biWidth; + configCaps->MinOutputSize.cy = format->bmiHeader.biHeight; + configCaps->MaxOutputSize.cx = format->bmiHeader.biWidth; + configCaps->MaxOutputSize.cy = format->bmiHeader.biHeight; + configCaps->OutputGranularityX = 1; + configCaps->OutputGranularityY = 1; + configCaps->StretchTapsX = 1; + configCaps->StretchTapsY = 1; + configCaps->ShrinkTapsX = 1; + configCaps->ShrinkTapsY = 1; + configCaps->MinFrameInterval = format->AvgTimePerFrame; + configCaps->MaxFrameInterval = format->AvgTimePerFrame; + configCaps->MinBitsPerSecond = LONG(format->dwBitRate); + configCaps->MaxBitsPerSecond = LONG(format->dwBitRate); + } + + break; + } + + deleteMediaType(&mediaType); + } + + mediaTypes->Release(); + } + } + + AkLoggerLog("Media Type: ", stringFromMediaType(*pmt)); + + return *pmt? S_OK: S_FALSE; +} diff --git a/dshow/VirtualCamera/src/streamconfig.h b/dshow/VirtualCamera/src/streamconfig.h new file mode 100644 index 0000000..e27c4ca --- /dev/null +++ b/dshow/VirtualCamera/src/streamconfig.h @@ -0,0 +1,88 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef STREAMCONFIG_H +#define STREAMCONFIG_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class StreamConfigPrivate; + class Pin; + + class StreamConfig: + public IAMStreamConfig, + public CUnknown + { + public: + StreamConfig(Pin *pin=nullptr); + virtual ~StreamConfig(); + + void setPin(Pin *pin); + + DECLARE_IUNKNOWN(IID_IAMStreamConfig) + + // IAMStreamConfig + HRESULT STDMETHODCALLTYPE SetFormat(AM_MEDIA_TYPE *pmt); + HRESULT STDMETHODCALLTYPE GetFormat(AM_MEDIA_TYPE **pmt); + HRESULT STDMETHODCALLTYPE GetNumberOfCapabilities(int *piCount, + int *piSize); + HRESULT STDMETHODCALLTYPE GetStreamCaps(int iIndex, + AM_MEDIA_TYPE **pmt, + BYTE *pSCC); + + private: + StreamConfigPrivate *d; + }; +} + +#define DECLARE_IAMSTREAMCONFIG_NQ \ + DECLARE_IUNKNOWN_NQ \ + \ + HRESULT STDMETHODCALLTYPE SetFormat(AM_MEDIA_TYPE *pmt) \ + { \ + return StreamConfig::SetFormat(pmt); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetFormat(AM_MEDIA_TYPE **pmt) \ + { \ + return StreamConfig::GetFormat(pmt); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetNumberOfCapabilities(int *piCount, \ + int *piSize) \ + { \ + return StreamConfig::GetNumberOfCapabilities(piCount, piSize); \ + } \ + \ + HRESULT STDMETHODCALLTYPE GetStreamCaps(int iIndex, \ + AM_MEDIA_TYPE **pmt, \ + BYTE *pSCC) \ + { \ + return StreamConfig::GetStreamCaps(iIndex, pmt, pSCC); \ + } + +#define DECLARE_IAMSTREAMCONFIG(interfaceIid) \ + DECLARE_IUNKNOWN_Q(interfaceIid) \ + DECLARE_IAMSTREAMCONFIG_NQ + +#endif // STREAMCONFIG_H diff --git a/dshow/VirtualCamera/src/videocontrol.cpp b/dshow/VirtualCamera/src/videocontrol.cpp new file mode 100644 index 0000000..682f2fb --- /dev/null +++ b/dshow/VirtualCamera/src/videocontrol.cpp @@ -0,0 +1,308 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include +#include +#include + +#include "videocontrol.h" +#include "enumpins.h" +#include "pin.h" +#include "referenceclock.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" +#include "VCamUtils/src/image/videoformat.h" + +#define AK_CUR_INTERFACE "VideoControl" + +namespace AkVCam +{ + class VideoControlPrivate + { + public: + IEnumPins *m_enumPins; + }; +} + +AkVCam::VideoControl::VideoControl(IEnumPins *enumPins): + CUnknown(this, IID_IAMVideoControl) +{ + this->d = new VideoControlPrivate; + this->d->m_enumPins = enumPins; + this->d->m_enumPins->AddRef(); +} + +AkVCam::VideoControl::~VideoControl() +{ + this->d->m_enumPins->Release(); + delete this->d; +} + +HRESULT AkVCam::VideoControl::GetCaps(IPin *pPin, LONG *pCapsFlags) +{ + AkLogMethod(); + + if (!pPin || !pCapsFlags) + return E_POINTER; + + *pCapsFlags = 0; + this->d->m_enumPins->Reset(); + HRESULT result = E_FAIL; + IPin *pin = nullptr; + + while (this->d->m_enumPins->Next(1, &pin, nullptr) == S_OK) { + if (pin == pPin) { + *pCapsFlags = VideoControlFlag_FlipHorizontal + | VideoControlFlag_FlipVertical; + result = S_OK; + pin->Release(); + + break; + } + + pin->Release(); + } + + return result; +} + +HRESULT AkVCam::VideoControl::SetMode(IPin *pPin, LONG Mode) +{ + AkLogMethod(); + + if (!pPin) + return E_POINTER; + + this->d->m_enumPins->Reset(); + HRESULT result = E_FAIL; + IPin *pin = nullptr; + + while (this->d->m_enumPins->Next(1, &pin, nullptr) == S_OK) { + if (pin == pPin) { + auto cpin = dynamic_cast(pin); + cpin->setHorizontalFlip((Mode & VideoControlFlag_FlipHorizontal) != 0); + cpin->setVerticalFlip((Mode & VideoControlFlag_FlipVertical) != 0); + result = S_OK; + pin->Release(); + + break; + } + + pin->Release(); + } + + return result; +} + +HRESULT AkVCam::VideoControl::GetMode(IPin *pPin, LONG *Mode) +{ + AkLogMethod(); + + if (!pPin || !Mode) + return E_POINTER; + + *Mode = 0; + this->d->m_enumPins->Reset(); + HRESULT result = E_FAIL; + IPin *pin = nullptr; + + while (this->d->m_enumPins->Next(1, &pin, nullptr) == S_OK) { + if (pin == pPin) { + auto cpin = dynamic_cast(pin); + + if (cpin->horizontalFlip()) + *Mode |= VideoControlFlag_FlipHorizontal; + + if (cpin->verticalFlip()) + *Mode |= VideoControlFlag_FlipVertical; + + result = S_OK; + pin->Release(); + + break; + } + + pin->Release(); + } + + return result; +} + +HRESULT AkVCam::VideoControl::GetCurrentActualFrameRate(IPin *pPin, + LONGLONG *ActualFrameRate) +{ + AkLogMethod(); + + if (!pPin || !ActualFrameRate) + return E_POINTER; + + *ActualFrameRate = 0; + this->d->m_enumPins->Reset(); + HRESULT result = E_FAIL; + IPin *pin = nullptr; + + while (this->d->m_enumPins->Next(1, &pin, nullptr) == S_OK) { + if (pin == pPin) { + IAMStreamConfig *streamConfig = nullptr; + result = pin->QueryInterface(IID_IAMStreamConfig, + reinterpret_cast(&streamConfig)); + + if (SUCCEEDED(result)) { + AM_MEDIA_TYPE *mediaType = nullptr; + result = streamConfig->GetFormat(&mediaType); + + if (SUCCEEDED(result)) { + if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) { + auto format = reinterpret_cast(mediaType->pbFormat); + *ActualFrameRate = format->AvgTimePerFrame; + } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) { + auto format = reinterpret_cast(mediaType->pbFormat); + *ActualFrameRate = format->AvgTimePerFrame; + } else { + result = E_FAIL; + } + + deleteMediaType(&mediaType); + } + + streamConfig->Release(); + } + + pin->Release(); + + break; + } + + pin->Release(); + } + + return result; +} + +HRESULT AkVCam::VideoControl::GetMaxAvailableFrameRate(IPin *pPin, + LONG iIndex, + SIZE Dimensions, + LONGLONG *MaxAvailableFrameRate) +{ + AkLogMethod(); + + if (!pPin || !MaxAvailableFrameRate) + return E_POINTER; + + *MaxAvailableFrameRate = 0; + this->d->m_enumPins->Reset(); + HRESULT result = E_FAIL; + IPin *pin = nullptr; + + while (this->d->m_enumPins->Next(1, &pin, nullptr) == S_OK) { + if (pin == pPin) { + IAMStreamConfig *streamConfig = nullptr; + result = pin->QueryInterface(IID_IAMStreamConfig, + reinterpret_cast(&streamConfig)); + + if (SUCCEEDED(result)) { + AM_MEDIA_TYPE *mediaType = nullptr; + VIDEO_STREAM_CONFIG_CAPS configCaps; + result = streamConfig->GetStreamCaps(iIndex, + &mediaType, + reinterpret_cast(&configCaps)); + + if (SUCCEEDED(result)) { + if (configCaps.MaxOutputSize.cx == Dimensions.cx + && configCaps.MaxOutputSize.cy == Dimensions.cy) { + *MaxAvailableFrameRate = configCaps.MaxFrameInterval; + } else { + result = E_FAIL; + } + + deleteMediaType(&mediaType); + } + + streamConfig->Release(); + } + + pin->Release(); + + break; + } + + pin->Release(); + } + + return result; +} + +HRESULT AkVCam::VideoControl::GetFrameRateList(IPin *pPin, + LONG iIndex, + SIZE Dimensions, + LONG *ListSize, + LONGLONG **FrameRates) +{ + AkLogMethod(); + + if (!pPin || !ListSize || !FrameRates) + return E_POINTER; + + *ListSize = 0; + *FrameRates = nullptr; + this->d->m_enumPins->Reset(); + HRESULT result = E_FAIL; + IPin *pin = nullptr; + + while (this->d->m_enumPins->Next(1, &pin, nullptr) == S_OK) { + if (pin == pPin) { + IAMStreamConfig *streamConfig = nullptr; + result = pin->QueryInterface(IID_IAMStreamConfig, + reinterpret_cast(&streamConfig)); + + if (SUCCEEDED(result)) { + AM_MEDIA_TYPE *mediaType = nullptr; + VIDEO_STREAM_CONFIG_CAPS configCaps; + result = streamConfig->GetStreamCaps(iIndex, + &mediaType, + reinterpret_cast(&configCaps)); + + if (SUCCEEDED(result)) { + if (configCaps.MaxOutputSize.cx == Dimensions.cx + && configCaps.MaxOutputSize.cy == Dimensions.cy) { + *ListSize = 1; + *FrameRates = reinterpret_cast(CoTaskMemAlloc(sizeof(LONGLONG))); + **FrameRates = configCaps.MaxFrameInterval; + } else { + result = E_FAIL; + } + + deleteMediaType(&mediaType); + } + + streamConfig->Release(); + } + + pin->Release(); + + break; + } + + pin->Release(); + } + + return result; +} diff --git a/dshow/VirtualCamera/src/videocontrol.h b/dshow/VirtualCamera/src/videocontrol.h new file mode 100644 index 0000000..e794a55 --- /dev/null +++ b/dshow/VirtualCamera/src/videocontrol.h @@ -0,0 +1,63 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef VIDEOCONTROL_H +#define VIDEOCONTROL_H + +#include + +#include "cunknown.h" + +namespace AkVCam +{ + class VideoControlPrivate; + class EnumPins; + + class VideoControl: + public IAMVideoControl, + public CUnknown + { + public: + VideoControl(IEnumPins *enumPins); + virtual ~VideoControl(); + + DECLARE_IUNKNOWN(IID_IAMVideoControl) + + // IAMVideoControl + HRESULT WINAPI GetCaps(IPin *pPin,LONG *pCapsFlags); + HRESULT WINAPI SetMode(IPin *pPin,LONG Mode); + HRESULT WINAPI GetMode(IPin *pPin,LONG *Mode); + HRESULT WINAPI GetCurrentActualFrameRate(IPin *pPin, + LONGLONG *ActualFrameRate); + HRESULT WINAPI GetMaxAvailableFrameRate(IPin *pPin, + LONG iIndex, + SIZE Dimensions, + LONGLONG *MaxAvailableFrameRate); + HRESULT WINAPI GetFrameRateList(IPin *pPin, + LONG iIndex, + SIZE Dimensions, + LONG *ListSize, + LONGLONG **FrameRates); + + private: + VideoControlPrivate *d; + }; +} + +#endif // VIDEOCONTROL_H diff --git a/dshow/VirtualCamera/src/videoprocamp.cpp b/dshow/VirtualCamera/src/videoprocamp.cpp new file mode 100644 index 0000000..a0bb9e2 --- /dev/null +++ b/dshow/VirtualCamera/src/videoprocamp.cpp @@ -0,0 +1,159 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#include +#include +#include + +#include "videoprocamp.h" +#include "PlatformUtils/src/utils.h" +#include "VCamUtils/src/utils.h" + +#define AK_CUR_INTERFACE "VideoProcAmp" + +namespace AkVCam +{ + class VideoProcAmpPrivate + { + public: + std::map m_control; + }; + + class ProcAmpPrivate + { + public: + LONG property; + LONG min; + LONG max; + LONG step; + LONG defaultValue; + LONG flags; + + inline static const std::vector &controls() + { + static const std::vector controls { + {VideoProcAmp_Brightness , -255, 255, 1, 0, VideoProcAmp_Flags_Manual}, + {VideoProcAmp_Contrast , -255, 255, 1, 0, VideoProcAmp_Flags_Manual}, + {VideoProcAmp_Saturation , -255, 255, 1, 0, VideoProcAmp_Flags_Manual}, + {VideoProcAmp_Gamma , -255, 255, 1, 0, VideoProcAmp_Flags_Manual}, + {VideoProcAmp_Hue , -359, 359, 1, 0, VideoProcAmp_Flags_Manual}, + {VideoProcAmp_ColorEnable, 0, 1, 1, 1, VideoProcAmp_Flags_Manual} + }; + + return controls; + } + + static inline const ProcAmpPrivate *byProperty(LONG property) + { + for (auto &control: controls()) + if (control.property == property) + return &control; + + return nullptr; + } + }; +} + +AkVCam::VideoProcAmp::VideoProcAmp(): + CUnknown(this, IID_IAMVideoProcAmp) +{ + this->d = new VideoProcAmpPrivate; +} + +AkVCam::VideoProcAmp::~VideoProcAmp() +{ + delete this->d; +} + +HRESULT AkVCam::VideoProcAmp::GetRange(LONG Property, + LONG *pMin, + LONG *pMax, + LONG *pSteppingDelta, + LONG *pDefault, + LONG *pCapsFlags) +{ + AkLogMethod(); + + if (!pMin || !pMax || !pSteppingDelta || !pDefault || !pCapsFlags) + return E_POINTER; + + *pMin = 0; + *pMax = 0; + *pSteppingDelta = 0; + *pDefault = 0; + *pCapsFlags = 0; + + for (auto &control: ProcAmpPrivate::controls()) + if (control.property == Property) { + *pMin = control.min; + *pMax = control.max; + *pSteppingDelta = control.step; + *pDefault = control.defaultValue; + *pCapsFlags = control.flags; + + return S_OK; + } + + return E_PROP_ID_UNSUPPORTED; +} + +HRESULT AkVCam::VideoProcAmp::Set(LONG Property, LONG lValue, LONG Flags) +{ + AkLogMethod(); + + for (auto &control: ProcAmpPrivate::controls()) + if (control.property == Property) { + if (lValue < control.min + || lValue > control.max + || Flags != control.flags) + return E_INVALIDARG; + + this->d->m_control[Property] = lValue; + AKVCAM_EMIT(this, PropertyChanged, Property, lValue, Flags) + + return S_OK; + } + + return E_PROP_ID_UNSUPPORTED; +} + +HRESULT AkVCam::VideoProcAmp::Get(LONG Property, LONG *lValue, LONG *Flags) +{ + AkLogMethod(); + + if (!lValue || !Flags) + return E_POINTER; + + *lValue = 0; + *Flags = 0; + + for (auto &control: ProcAmpPrivate::controls()) + if (control.property == Property) { + if (this->d->m_control.count(Property)) + *lValue = this->d->m_control[Property]; + else + *lValue = control.defaultValue; + + *Flags = control.flags; + + return S_OK; + } + + return E_PROP_ID_UNSUPPORTED; +} diff --git a/dshow/VirtualCamera/src/videoprocamp.h b/dshow/VirtualCamera/src/videoprocamp.h new file mode 100644 index 0000000..d365a97 --- /dev/null +++ b/dshow/VirtualCamera/src/videoprocamp.h @@ -0,0 +1,65 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +#ifndef VIDEOPROCAMP_H +#define VIDEOPROCAMP_H + +#include + +#include "cunknown.h" +#include "VCamUtils/src/utils.h" + +namespace AkVCam +{ + class VideoProcAmpPrivate; + + class VideoProcAmp: + public IAMVideoProcAmp, + public CUnknown + { + AKVCAM_SIGNAL(PropertyChanged, LONG Property, LONG lValue, LONG Flags) + + public: + VideoProcAmp(); + virtual ~VideoProcAmp(); + + DECLARE_IUNKNOWN(IID_IAMVideoProcAmp) + + // IAMVideoProcAmp + HRESULT STDMETHODCALLTYPE GetRange(LONG Property, + LONG *pMin, + LONG *pMax, + LONG *pSteppingDelta, + LONG *pDefault, + LONG *pCapsFlags); + HRESULT STDMETHODCALLTYPE Set(LONG Property, + LONG lValue, + LONG Flags); + HRESULT STDMETHODCALLTYPE Get(LONG Property, + LONG *lValue, + LONG *Flags); + + private: + VideoProcAmpPrivate *d; + + friend class VideoProcAmpPrivate; + }; +} + +#endif // VIDEOPROCAMP_H diff --git a/dshow/dshow.pri b/dshow/dshow.pri new file mode 100644 index 0000000..b1ded3b --- /dev/null +++ b/dshow/dshow.pri @@ -0,0 +1,58 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +isEmpty(DSHOW_PLUGIN_NAME): + DSHOW_PLUGIN_NAME = AkVirtualCamera +isEmpty(DSHOW_PLUGIN_ASSISTANT_NAME): + DSHOW_PLUGIN_ASSISTANT_NAME = AkVCamAssistant +isEmpty(DSHOW_PLUGIN_ASSISTANT_DESCRIPTION): + DSHOW_PLUGIN_ASSISTANT_DESCRIPTION = "Webcamoid virtual camera service" +isEmpty(DSHOW_PLUGIN_DESCRIPTION): + DSHOW_PLUGIN_DESCRIPTION = "Webcamoid Virtual Camera" +isEmpty(DSHOW_PLUGIN_DESCRIPTION_EXT): + DSHOW_PLUGIN_DESCRIPTION_EXT = "Central service for communicating between virtual cameras clients and servers" +isEmpty(DSHOW_PLUGIN_DEVICE_PREFIX): + DSHOW_PLUGIN_DEVICE_PREFIX = /akvcam/video +isEmpty(DSHOW_PLUGIN_VENDOR): + DSHOW_PLUGIN_VENDOR = "Webcamoid Project" + +DEFINES += \ + DSHOW_PLUGIN_NAME=\"\\\"$$DSHOW_PLUGIN_NAME\\\"\" \ + DSHOW_PLUGIN_NAME_L=\"L\\\"$$DSHOW_PLUGIN_NAME\\\"\" \ + DSHOW_PLUGIN_ASSISTANT_NAME=\"\\\"$$DSHOW_PLUGIN_ASSISTANT_NAME\\\"\" \ + DSHOW_PLUGIN_ASSISTANT_NAME_L=\"L\\\"$$DSHOW_PLUGIN_ASSISTANT_NAME\\\"\" \ + DSHOW_PLUGIN_ASSISTANT_DESCRIPTION=\"\\\"$$DSHOW_PLUGIN_ASSISTANT_DESCRIPTION\\\"\" \ + DSHOW_PLUGIN_ASSISTANT_DESCRIPTION_L=\"L\\\"$$DSHOW_PLUGIN_ASSISTANT_DESCRIPTION\\\"\" \ + DSHOW_PLUGIN_DESCRIPTION=\"\\\"$$DSHOW_PLUGIN_DESCRIPTION\\\"\" \ + DSHOW_PLUGIN_DESCRIPTION_L=\"L\\\"$$DSHOW_PLUGIN_DESCRIPTION\\\"\" \ + DSHOW_PLUGIN_DESCRIPTION_EXT=\"\\\"$$DSHOW_PLUGIN_DESCRIPTION_EXT\\\"\" \ + DSHOW_PLUGIN_DESCRIPTION_EXT_L=\"L\\\"$$DSHOW_PLUGIN_DESCRIPTION_EXT\\\"\" \ + DSHOW_PLUGIN_DEVICE_PREFIX=\"\\\"$$DSHOW_PLUGIN_DEVICE_PREFIX\\\"\" \ + DSHOW_PLUGIN_DEVICE_PREFIX_L=\"L\\\"$$DSHOW_PLUGIN_DEVICE_PREFIX\\\"\" \ + DSHOW_PLUGIN_VENDOR=\"\\\"$$DSHOW_PLUGIN_VENDOR\\\"\" \ + DSHOW_PLUGIN_VENDOR_L=\"L\\\"$$DSHOW_PLUGIN_VENDOR\\\"\" + +defineReplace(normalizedArch) { + arch = $$replace($$1, i386, x86) + arch = $$replace(arch, i486, x86) + arch = $$replace(arch, i586, x86) + arch = $$replace(arch, i686, x86) + arch = $$replace(arch, x86_64, x64) + + return($$arch) +} diff --git a/dshow/dshow.pro b/dshow/dshow.pro new file mode 100644 index 0000000..c2df1ed --- /dev/null +++ b/dshow/dshow.pro @@ -0,0 +1,26 @@ +# akvirtualcamera, virtual camera for Mac and Windows. +# Copyright (C) 2020 Gonzalo Exequiel Pedone +# +# akvirtualcamera 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 3 of the License, or +# (at your option) any later version. +# +# akvirtualcamera 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 akvirtualcamera. If not, see . +# +# Web-Site: http://webcamoid.github.io/ + +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = \ + PlatformUtils \ + VCamIPC \ + Assistant \ + VirtualCamera diff --git a/dshow/pspec.json b/dshow/pspec.json new file mode 100644 index 0000000..03145bc --- /dev/null +++ b/dshow/pspec.json @@ -0,0 +1,4 @@ +{ + "pluginType": "Ak.SubModule", + "type": "output" +} diff --git a/share/TestFrame/TestFrame.bmp b/share/TestFrame/TestFrame.bmp new file mode 100644 index 0000000000000000000000000000000000000000..17a52f28ef57518c861250a23bc989cdf834c487 GIT binary patch literal 921654 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. +# +# Web-Site: http://webcamoid.github.io/ + +import sys +from PyQt5 import QtWidgets, QtQml, QtQuick + + +if __name__ =='__main__': + app = QtWidgets.QApplication(sys.argv); + engine = QtQml.QQmlApplicationEngine() + engine.load("TestFrame.qml") + + def capture(): + for obj in engine.rootObjects(): + image = obj.grabWindow() + image.save("TestFrame.bmp") + obj.close() + + for obj in engine.rootObjects(): + obj.sceneGraphInitialized.connect(capture) + + app.exec_() diff --git a/share/TestFrame/TestFrame.qml b/share/TestFrame/TestFrame.qml new file mode 100644 index 0000000..f223f30 --- /dev/null +++ b/share/TestFrame/TestFrame.qml @@ -0,0 +1,194 @@ +/* akvirtualcamera, virtual camera for Mac and Windows. + * Copyright (C) 2020 Gonzalo Exequiel Pedone + * + * akvirtualcamera 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 3 of the License, or + * (at your option) any later version. + * + * akvirtualcamera 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 akvirtualcamera. If not, see . + * + * Web-Site: http://webcamoid.github.io/ + */ + +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 + +ApplicationWindow { + width: 640 + height: 480 + color: "#3f2a7e" + visible: true + + property int patternSize: 24 + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + + GridLayout { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + columns: 2 + + // Info + Image { + id: icon + width: 128 + height: width + sourceSize.width: width + sourceSize.height: height + source: "webcamoid.png" + } + + ColumnLayout { + Text { + id: programName + color: "#ffffff" + text: "Webcamoid" + font.weight: Font.Bold + font.pixelSize: 40 + } + + Text { + color: "#ffffff" + text: "The ultimate webcam suite!" + leftPadding: 24 + font.weight: Font.Bold + font.pixelSize: 0.5 * programName.font.pixelSize + } + } + + // Color pattern + GridLayout { + columns: 8 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.columnSpan: 2 + + Text { + text: "R" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + Text { + text: "G" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + Text { + text: "B" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + + Text { + text: "C" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + Text { + text: "M" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + Text { + text: "Y" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + + Text { + text: "K" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + Text { + text: "W" + color: "#ffffff" + font.pixelSize: 0.3 * programName.font.pixelSize + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + } + + // RGB + Rectangle { + color: "#ff0000" + width: patternSize + height: patternSize + } + Rectangle { + color: "#00ff00" + width: patternSize + height: patternSize + } + Rectangle { + color: "#0000ff" + width: patternSize + height: patternSize + } + + // CMY + Rectangle { + color: "#00ffff" + width: patternSize + height: patternSize + } + Rectangle { + color: "#ff00ff" + width: patternSize + height: patternSize + } + Rectangle { + color: "#ffff00" + width: patternSize + height: patternSize + } + + // BW + Rectangle { + color: "#000000" + width: patternSize + height: patternSize + } + Rectangle { + color: "#ffffff" + width: patternSize + height: patternSize + } + } + } + + // Usage + Rectangle { + width: 500 + height: 125 + color: "#00000000" + + Text { + id: usage + color: "#ffffff" + text: "This is a Webcamoid's virtual webcam device.\n" + + "Go to Webcamoid, enable virtual webcam output, select " + + "this device and play some webcam, desktop or video." + wrapMode: Text.WordWrap + anchors.fill: parent + topPadding: 8 + Layout.columnSpan: 2 + font.pixelSize: 0.45 * programName.font.pixelSize + } + } + } +} diff --git a/share/TestFrame/webcamoid.png b/share/TestFrame/webcamoid.png new file mode 100644 index 0000000000000000000000000000000000000000..930d545d71d38e83d2193bdcd4c30ec90db8046f GIT binary patch literal 18934 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