├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support-request.md └── workflows │ └── build.yml ├── .gitignore ├── Brewfile ├── CMakeLists.txt ├── CONTRIBUTING.md ├── COPYING ├── README.md ├── assets └── test-card.pcvd ├── ci ├── ci_includes.cmd.in ├── ci_includes.sh.in └── macos │ ├── certs.p12 │ └── package-macos.sh ├── cmake └── Modules │ ├── FindLibobs.cmake │ └── ObsPluginHelpers.cmake ├── data └── locale │ └── en-US.ini ├── installer └── installer-macOS.pkgproj.in └── src ├── common └── MachProtocol.h ├── dal-plugin ├── CMakeLists.txt ├── Defines.h.in ├── Info.plist ├── Logging.h ├── OBSDALCMSampleBufferUtils.h ├── OBSDALCMSampleBufferUtils.mm ├── OBSDALDevice.h ├── OBSDALDevice.mm ├── OBSDALMachClient.h ├── OBSDALMachClient.mm ├── OBSDALObjectStore.h ├── OBSDALObjectStore.mm ├── OBSDALPlugIn.h ├── OBSDALPlugIn.mm ├── OBSDALPlugInInterface.h ├── OBSDALPlugInInterface.mm ├── OBSDALPlugInMain.mm ├── OBSDALStream.h ├── OBSDALStream.mm ├── OBSDALTestCard.h └── OBSDALTestCard.mm └── obs-plugin ├── CMakeLists.txt ├── Defines.h.in ├── MachServer.h ├── MachServer.mm ├── data └── locale │ └── en-US.ini └── plugin-main.mm /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS version: [e.g. macOS Catalina 10.15.4] 28 | - Application: [e.g. Chrome, Zoom] 29 | - Virtualcam version: [e.g. 1.3.1] 30 | - OBS version: [e.g. 25.0.8] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered (if applicable)** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: If you need additional help 4 | title: "[Support]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the issue** 11 | A clear and concise description of what the problem is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Behavior** 21 | A clear and concise description of what happened. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS version: [e.g. macOS Catalina 10.15.4] 28 | - Application (if applicable): [e.g. Chrome, Safari] 29 | - Virtualcam version: [e.g. 1.0.0] 30 | - OBS version: [e.g. 25.0.8] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and (maybe) Release 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | - pkg-build 9 | tags: 10 | - "v*" 11 | 12 | jobs: 13 | build: 14 | env: 15 | MACOSX_DEPLOYMENT_TARGET: 10.13 16 | MACOS_DEPS_VERSION: '2020-08-30' 17 | QT_VERSION: '5.14.1' 18 | obs-studio-ref: 26.0.2 19 | runs-on: macos-latest 20 | 21 | steps: 22 | - uses: maxim-lobanov/setup-xcode@v1 23 | with: 24 | xcode-version: latest-stable 25 | 26 | - name: Checkout obs-mac-virtualcam 27 | uses: actions/checkout@v2 28 | with: 29 | path: obs-mac-virtualcam 30 | 31 | - name: 'Restore pre-built dependencies from cache' 32 | id: deps-cache 33 | uses: actions/cache@v2.1.2 34 | env: 35 | CACHE_NAME: 'deps-cache' 36 | with: 37 | path: /tmp/obsdeps 38 | key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.MACOS_DEPS_VERSION }} 39 | 40 | - name: 'Restore pre-built Qt dependency from cache' 41 | id: deps-qt-cache 42 | uses: actions/cache@v2.1.2 43 | env: 44 | CACHE_NAME: 'deps-qt-cache' 45 | with: 46 | path: /tmp/obsdeps 47 | key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.MACOS_DEPS_VERSION }} 48 | 49 | - name: 'Install prerequisite: Pre-built dependencies' 50 | if: steps.deps-cache.outputs.cache-hit != 'true' 51 | shell: bash 52 | run: | 53 | curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz 54 | tar -xf ./macos-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp" 55 | 56 | - name: 'Install prerequisite: Pre-built dependency Qt' 57 | if: steps.deps-qt-cache.outputs.cache-hit != 'true' 58 | shell: bash 59 | run: | 60 | curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-qt-${{ env.QT_VERSION }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz 61 | tar -xf ./macos-qt-${{ env.QT_VERSION }}-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp" 62 | xattr -r -d com.apple.quarantine /tmp/obsdeps 63 | 64 | - name: Cache OBS Build 65 | id: cache-obs-build 66 | uses: actions/cache@v1 67 | with: 68 | path: obs-studio 69 | key: obs-studio-${{ env.obs-studio-ref }}-macOS${{ env.MACOSX_DEPLOYMENT_TARGET }} 70 | 71 | - name: Checkout OBS 72 | if: steps.cache-obs-build.outputs.cache-hit != 'true' 73 | uses: actions/checkout@v2 74 | with: 75 | repository: obsproject/obs-studio 76 | path: obs-studio 77 | submodules: recursive 78 | ref: ${{ env.obs-studio-ref }} 79 | 80 | - name: Build OBS 81 | if: steps.cache-obs-build.outputs.cache-hit != 'true' 82 | run: | 83 | mkdir -p obs-studio/build 84 | cd obs-studio/build 85 | 86 | cmake -DQTDIR="/tmp/obsdeps" -DSWIGDIR="/tmp/obsdeps" -DDepsPath="/tmp/obsdeps" -DDISABLE_PYTHON=ON .. 87 | make -j 88 | 89 | - name: Build the OBS plugin 90 | run: | 91 | mkdir obs-mac-virtualcam/build 92 | cd obs-mac-virtualcam/build 93 | 94 | cmake -DLIBOBS_INCLUDE_DIR:STRING=$GITHUB_WORKSPACE/obs-studio/libobs \ 95 | -DLIBOBS_LIB:STRING=$GITHUB_WORKSPACE/obs-studio/build/libobs/libobs.dylib \ 96 | -DOBS_FRONTEND_LIB:STRING=$GITHUB_WORKSPACE/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib \ 97 | -DQTDIR:STRING=/tmp/obsdeps .. 98 | 99 | make -j obs-plugin 100 | 101 | - name: Build the DAL plugin 102 | run: | 103 | cd obs-mac-virtualcam/build 104 | 105 | cmake -DLIBOBS_INCLUDE_DIR:STRING=$GITHUB_WORKSPACE/obs-studio/libobs \ 106 | -DLIBOBS_LIB:STRING=$GITHUB_WORKSPACE/obs-studio/build/libobs/libobs.dylib \ 107 | -DOBS_FRONTEND_LIB:STRING=$GITHUB_WORKSPACE/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib \ 108 | -DQTDIR:STRING=/tmp/obsdeps -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" .. 109 | 110 | make -j dal-plugin 111 | 112 | - name: Fix runtime QT deps 113 | run: | 114 | install_name_tool \ 115 | -change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets \ 116 | @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ 117 | -change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui \ 118 | @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ 119 | -change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore \ 120 | @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ 121 | obs-mac-virtualcam/build/src/obs-plugin/obs-mac-virtualcam.so 122 | 123 | - name: Copy artifacts 124 | run: | 125 | mkdir -p artifacts 126 | 127 | cp -r obs-mac-virtualcam/build/src/dal-plugin/obs-mac-virtualcam.plugin artifacts/ 128 | cp obs-mac-virtualcam/build/src/obs-plugin/obs-mac-virtualcam.so artifacts/ 129 | 130 | - name: Upload Raw PlugIn Artifacts 131 | uses: actions/upload-artifact@v1 132 | with: 133 | name: raw-plugins 134 | path: artifacts 135 | 136 | - name: Install Apple certificates 137 | if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/v') 138 | uses: apple-actions/import-codesign-certs@v1 139 | with: 140 | p12-file-base64: ${{ secrets.CERTIFICATES_P12 }} 141 | p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }} 142 | 143 | - name: Package Plugin 144 | if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/v') 145 | run: ./ci/macos/package-macos.sh 146 | working-directory: ./obs-mac-virtualcam 147 | env: 148 | RELEASE_MODE: "True" 149 | CODE_SIGNING_IDENTITY: John Boiles 150 | INSTALLER_SIGNING_IDENTITY: John Boiles 151 | AC_USERNAME: ${{ secrets.NOTARAZATION_USERNAME }} 152 | AC_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }} 153 | AC_PROVIDER_SHORTNAME: ${{ secrets.NOTARIZATION_PROVIDER_SHORTNAME }} 154 | 155 | - name: Upload .pkg Installer 156 | uses: actions/upload-artifact@v1 157 | if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/v') 158 | with: 159 | name: pkg-installer 160 | path: obs-mac-virtualcam/release 161 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | xcode/ 3 | .DS_Store 4 | *.generated.* 5 | /installer/Output/ 6 | Brewfile.lock.json 7 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew 'ffmpeg' 2 | brew 'x264' 3 | brew 'qt5' 4 | brew 'cmake' 5 | brew 'mbedtls' 6 | brew 'swig' 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project(obs-mac-virtualcam VERSION 1.3.1) 4 | set(PLUGIN_AUTHOR "John Boiles") 5 | set(MACOS_BUNDLEID "com.johnboiles.obs-mac-virtualcam") 6 | 7 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++11") 9 | 10 | configure_file( 11 | installer/installer-macOS.pkgproj.in 12 | ../installer/installer-macOS.generated.pkgproj 13 | ) 14 | 15 | configure_file( 16 | ci/ci_includes.sh.in 17 | ../ci/ci_includes.generated.sh 18 | ) 19 | configure_file( 20 | ci/ci_includes.cmd.in 21 | ../ci/ci_includes.generated.cmd 22 | ) 23 | 24 | add_subdirectory(src/obs-plugin) 25 | add_subdirectory(src/dal-plugin) 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Please take a look [this](https://github.com/johnboiles/obs-mac-virtualcam/wiki/Contributing) wiki page to see our notes for contributors. 3 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OBS (macOS) Virtual Camera (ARCHIVED) 🎥 2 | 3 | ![Build](https://github.com/johnboiles/obs-mac-virtualcam/workflows/Build%20and%20(maybe)%20Release/badge.svg) 4 | 5 | **ATTENTION: STARTING WITH OBS Studio 26.1, THIS PLUGIN IS NOW A PART OF THE OFFICIAL OBS PACKAGE 🎉. Development will now happen on the [OBS Studio GitHub](https://github.com/obsproject/obs-studio). Running this plugin along-side the built-in distribution does not work. If you can, update to OBS 26.1!** 6 | 7 | **ATTENTION: Before updating to OBS Studio 26.1, make sure to remove this plugin using the [uninstall instructions](https://github.com/johnboiles/obs-mac-virtualcam#uninstalling).** While it worked for most, some users have reported problems when updating to OBS Studio 26.1 with the plugin installed. You will likely also need to restart any host software (e.g. Chrome, Zoom, etc) after installing OBS Studio 26.1 and starting the virtual camera before the new plugin will work. 8 | 9 | Creates a virtual webcam device from the output of [OBS Studio](https://obsproject.com/). Especially useful for streaming smooth, composited video into Zoom, Hangouts, Jitsi etc. Like [CatxFish/obs-virtual-cam](https://github.com/CatxFish/obs-virtual-cam) but for macOS. 10 | 11 | ![Mar-28-2020 01-55-07](https://user-images.githubusercontent.com/218876/77819715-279b8700-709a-11ea-8885-aa15051665ee.gif) 12 | 13 | This code was spun out of [this OBS Project RFC](https://github.com/obsproject/rfcs/pull/15) which was itself spun out of [this issue](https://github.com/obsproject/obs-studio/issues/2568) from [@tobi](https://github.com/tobi). The goal for this, being merged into the core OBS codebase, has been reached 🤞. 14 | 15 | ## Donating 💸 16 | 17 | Consider sending some money in the direction of the [OBS Project](https://obsproject.com/contribute) via [Open Collective](https://opencollective.com/obsproject/contribute), [Patreon](https://patreon.com/OBSProject), or [PayPal](https://www.paypal.me/obsproject). Obviously, without OBS, this plugin would not be very useful! Hugh "Jim" Bailey is OBS Project's full-time lead developer and project maintainer. This money helps him continue to work on OBS! 18 | 19 | If, after you donate to the OBS Project, you also want to send some cash my way that's appreciated too! Feel free to [Buy Me a Coffee](https://www.buymeacoffee.com/johnboiles) or [PayPal me](https://paypal.me/johnboiles). 20 | 21 | Buy Me A Coffee 22 | 23 | ## Known Issues 24 | 25 | * Zoom prior to version 5.1.1 disabled virtual cameras by default. Please update to the latest (5.2.1 at time of writing) to re-enable virtual camera. Start the virtual camera before starting the Zoom application. 26 | * Slack, Webex, Skype and probably some other applications have disabled virtual cameras by default via [application restrictions](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation?language=objc). Check out [the wiki](https://github.com/johnboiles/obs-mac-virtualcam/wiki/Compatibility) to see if your app is supported. Please [edit the wiki](https://github.com/johnboiles/obs-mac-virtualcam/wiki/Compatibility/_edit) if you try other software that we should include in that list. In most cases you can work around these restrictions by [re-codesigning those applications](https://github.com/johnboiles/obs-mac-virtualcam/wiki/Compatibility#apps-dont-allow-dal-plugins). 27 | * Photo Booth and FaceTime do not support virtual cameras as of macOS 10.14 Mojave since they disallow loading any plugin that's not provided by Apple. Photo Booth can simply be duplicated and renamed (e.g. `Photo Booth copy`) and it will work. There is no known workaroud for FaceTime. 28 | * You _may_ need to restart your computer after installing new versions of this plugin (not sure why 🤷‍♂️). 29 | 30 | See also the open [issues](https://github.com/johnboiles/obs-mac-virtualcam/issues) for other reported issues. In case you need help or think you found a bug, see [this](https://github.com/johnboiles/obs-mac-virtualcam#Discussion--Support). 31 | 32 | ## Installing 33 | *If you are using OBS Studio 26.1 or newer, the virtual camera is already part of OBS Studio. In that case, **DO NOT** install this plugin!* 34 | 35 | * Download and install the latest version of OBS from the [official website](https://obsproject.com). 36 | * Download the latest `.pkg` installer on the [Releases page](https://github.com/johnboiles/obs-mac-virtualcam/releases) 37 | * Run the `.pkg` installer (entering your password when required) 38 | * If you're already running OBS, make sure to restart it. 39 | * Restart any app that was running during the installation process that is supposed to pick up the camera. 40 | * To start the virtual camera, go (in OBS) to `Tools`→`Start Virtual Camera`. 41 | 42 | Your OBS video should now show up in the target app! 43 | 44 | ## Uninstalling 45 | 46 | You can easily uninstall this plugin by deleting the OBS plugin (in `/Library/Application\ Support/obs-studio/plugins/`) and the DAL plugin (in `/Library/CoreMediaIO/Plug-Ins/DAL/`). 47 | 48 | ```bash 49 | sudo rm -rf /Library/CoreMediaIO/Plug-Ins/DAL/obs-mac-virtualcam.plugin 50 | sudo rm -rf /Library/Application\ Support/obs-studio/plugins/obs-mac-virtualcam 51 | ``` 52 | 53 | ## Discussion / Support 54 | 55 | If you are using the version the virtual camera that comes shipped with OBS Studio 26.1, the official place for questions is the `#macos-support` channel in the [OBS Studio Discord](https://discord.gg/obsproject). 56 | If you are still using this plugin, the official place for discussion and chat is in the `#plugins-and-tools` channel in the [OBS Studio Discord](https://discord.gg/obsproject). For questions or troubleshooting, ping @gxalpha#3486 and attach the OBS log, screenshots, and/or crash logs (from Console.app). 57 | 58 | ## Reporting Issues / Bugs / Improvements 59 | 60 | > 🚀 Wonder How to contribute? Have look at our [notes for contributors](https://github.com/johnboiles/obs-mac-virtualcam/wiki/Contributing). There are ways non-technical or minimally-technical folks can contribute too! 61 | 62 | This plugin is now archived. If you are having an issue there's a good chance someone has already run into the same thing. Please search through the [issues](https://github.com/johnboiles/obs-mac-virtualcam/issues) before reporting a new one. 63 | If you are using the version the virtual camera that comes shipped with OBS Studio 26.1, also see the issues on the [OBS Studio GitHub](https://github.com/obsproject/obs-studio/issues) and create new issues there. 64 | 65 | If you still believe you have found an unreported issue related to this plugin, please open an issue! When you do, include any relevant terminal log, Console.app log, crash log, screen recording and/or screenshots. The more information you can provide, the better! 66 | 67 | ## Development 68 | 69 | Please help me make this thing not janky! See the [this wiki page](https://github.com/johnboiles/obs-mac-virtualcam/wiki/Developing) for build instructions and tips & tricks for developing. 70 | 71 | ## License 72 | 73 | As the goal of this repo was to get merged into [obsproject/obs-studio](https://github.com/obsproject/obs-studio/), the license for this code mirrors the GPLv2 license for that project. 74 | -------------------------------------------------------------------------------- /assets/test-card.pcvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnboiles/obs-mac-virtualcam/3cfcbf333cb610fe16887f6e7d50c4c669bff70f/assets/test-card.pcvd -------------------------------------------------------------------------------- /ci/ci_includes.cmd.in: -------------------------------------------------------------------------------- 1 | set PluginName=@CMAKE_PROJECT_NAME@ 2 | set PluginVersion=@CMAKE_PROJECT_VERSION@ 3 | -------------------------------------------------------------------------------- /ci/ci_includes.sh.in: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME="@CMAKE_PROJECT_NAME@" 2 | PLUGIN_VERSION="@CMAKE_PROJECT_VERSION@" 3 | MACOS_BUNDLEID="@MACOS_BUNDLEID@" 4 | -------------------------------------------------------------------------------- /ci/macos/certs.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnboiles/obs-mac-virtualcam/3cfcbf333cb610fe16887f6e7d50c4c669bff70f/ci/macos/certs.p12 -------------------------------------------------------------------------------- /ci/macos/package-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | script_dir=$(dirname "$0") 6 | source "$script_dir/../ci_includes.generated.sh" 7 | 8 | OSTYPE=$(uname) 9 | 10 | if [ "${OSTYPE}" != "Darwin" ]; then 11 | echo "[Error] macOS package script can be run on Darwin-type OS only." 12 | exit 1 13 | fi 14 | 15 | echo "=> Preparing package build" 16 | export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)" 17 | 18 | GIT_HASH=$(git rev-parse --short HEAD) 19 | GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}') 20 | 21 | PKG_VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG" 22 | 23 | FILENAME_UNSIGNED="$PLUGIN_NAME-$PKG_VERSION-Unsigned.pkg" 24 | FILENAME="$PLUGIN_NAME-$PKG_VERSION.pkg" 25 | 26 | # echo "=> Modifying $PLUGIN_NAME.so" 27 | # install_name_tool \ 28 | # -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \ 29 | # @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ 30 | # -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \ 31 | # @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ 32 | # -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \ 33 | # @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ 34 | # ./build/src/obs-plugin/obs-mac-virtualcam.so 35 | 36 | # Check if replacement worked 37 | echo "=> Dependencies for $PLUGIN_NAME" 38 | otool -L ./build/src/obs-plugin/obs-mac-virtualcam.so 39 | 40 | if [[ "$RELEASE_MODE" == "True" ]]; then 41 | echo "=> Signing plugin binary: $PLUGIN_NAME.so" 42 | codesign --sign "$CODE_SIGNING_IDENTITY" ./build/src/obs-plugin/obs-mac-virtualcam.so 43 | codesign --sign "$CODE_SIGNING_IDENTITY" --force --deep ./build/src/dal-plugin/obs-mac-virtualcam.plugin 44 | else 45 | echo "=> Skipped plugin codesigning" 46 | fi 47 | 48 | # Fetch and install Packages app 49 | # =!= NOTICE =!= 50 | # Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist 51 | # =!= NOTICE =!= 52 | echo "=> Installing Packaging app (might require password due to 'sudo').." 53 | curl -o './Packages.pkg' --retry-connrefused -s --retry-delay 1 'https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg' 54 | sudo installer -pkg ./Packages.pkg -target / 55 | 56 | echo "=> Actual package build" 57 | packagesbuild ./installer/installer-macOS.generated.pkgproj 58 | 59 | echo "=> Renaming $PLUGIN_NAME.pkg to $FILENAME" 60 | mv ./release/$PLUGIN_NAME.pkg ./release/$FILENAME_UNSIGNED 61 | 62 | if [[ "$RELEASE_MODE" == "True" ]]; then 63 | echo "=> Signing installer: $FILENAME" 64 | productsign \ 65 | --sign "$INSTALLER_SIGNING_IDENTITY" \ 66 | ./release/$FILENAME_UNSIGNED \ 67 | ./release/$FILENAME 68 | rm ./release/$FILENAME_UNSIGNED 69 | 70 | echo "=> Submitting installer $FILENAME for notarization" 71 | zip -r ./release/$FILENAME.zip ./release/$FILENAME 72 | UPLOAD_RESULT=$(xcrun altool \ 73 | --notarize-app \ 74 | --primary-bundle-id "$MACOS_BUNDLEID" \ 75 | --username "$AC_USERNAME" \ 76 | --password "$AC_PASSWORD" \ 77 | --asc-provider "$AC_PROVIDER_SHORTNAME" \ 78 | --file "./release/$FILENAME.zip") 79 | rm ./release/$FILENAME.zip 80 | 81 | REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}') 82 | echo "Request UUID: $REQUEST_UUID" 83 | 84 | echo "=> Wait for notarization result" 85 | # Pieces of code borrowed from rednoah/notarized-app 86 | while sleep 30 && date; do 87 | CHECK_RESULT=$(xcrun altool \ 88 | --notarization-info "$REQUEST_UUID" \ 89 | --username "$AC_USERNAME" \ 90 | --password "$AC_PASSWORD" \ 91 | --asc-provider "$AC_PROVIDER_SHORTNAME") 92 | echo $CHECK_RESULT 93 | 94 | if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then 95 | echo "=> Staple ticket to installer: $FILENAME" 96 | xcrun stapler staple ./release/$FILENAME 97 | break 98 | fi 99 | done 100 | else 101 | echo "=> Skipped installer codesigning and notarization" 102 | fi -------------------------------------------------------------------------------- /cmake/Modules/FindLibobs.cmake: -------------------------------------------------------------------------------- 1 | # This module can be copied and used by external plugins for OBS 2 | # 3 | # Once done these will be defined: 4 | # 5 | # LIBOBS_FOUND 6 | # LIBOBS_INCLUDE_DIRS 7 | # LIBOBS_LIBRARIES 8 | 9 | find_package(PkgConfig QUIET) 10 | if (PKG_CONFIG_FOUND) 11 | pkg_check_modules(_OBS QUIET obs libobs) 12 | endif() 13 | 14 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 15 | set(_lib_suffix 64) 16 | else() 17 | set(_lib_suffix 32) 18 | endif() 19 | 20 | if(DEFINED CMAKE_BUILD_TYPE) 21 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 22 | set(_build_type_base "debug") 23 | else() 24 | set(_build_type_base "release") 25 | endif() 26 | endif() 27 | 28 | find_path(LIBOBS_INCLUDE_DIR 29 | NAMES obs.h 30 | HINTS 31 | ENV obsPath${_lib_suffix} 32 | ENV obsPath 33 | ${obsPath} 34 | PATHS 35 | /usr/include /usr/local/include /opt/local/include /sw/include 36 | PATH_SUFFIXES 37 | libobs 38 | ) 39 | 40 | function(find_obs_lib base_name repo_build_path lib_name) 41 | string(TOUPPER "${base_name}" base_name_u) 42 | 43 | if(DEFINED _build_type_base) 44 | set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}") 45 | set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") 46 | endif() 47 | 48 | find_library(${base_name_u}_LIB 49 | NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} 50 | HINTS 51 | ENV obsPath${_lib_suffix} 52 | ENV obsPath 53 | ${obsPath} 54 | ${_${base_name_u}_LIBRARY_DIRS} 55 | PATHS 56 | /usr/lib /usr/local/lib /opt/local/lib /sw/lib 57 | PATH_SUFFIXES 58 | lib${_lib_suffix} lib 59 | libs${_lib_suffix} libs 60 | bin${_lib_suffix} bin 61 | ../lib${_lib_suffix} ../lib 62 | ../libs${_lib_suffix} ../libs 63 | ../bin${_lib_suffix} ../bin 64 | # base repo non-msvc-specific search paths 65 | ${_build_type_${repo_build_path}} 66 | ${_build_type_${repo_build_path}${_lib_suffix}} 67 | build/${repo_build_path} 68 | build${_lib_suffix}/${repo_build_path} 69 | # base repo msvc-specific search paths on windows 70 | build${_lib_suffix}/${repo_build_path}/Debug 71 | build${_lib_suffix}/${repo_build_path}/RelWithDebInfo 72 | build/${repo_build_path}/Debug 73 | build/${repo_build_path}/RelWithDebInfo 74 | ) 75 | endfunction() 76 | 77 | find_obs_lib(LIBOBS libobs obs) 78 | 79 | if(MSVC) 80 | find_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads) 81 | endif() 82 | 83 | include(FindPackageHandleStandardArgs) 84 | find_package_handle_standard_args(Libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR) 85 | mark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB) 86 | 87 | if(LIBOBS_FOUND) 88 | if(MSVC) 89 | if (NOT DEFINED W32_PTHREADS_LIB) 90 | message(FATAL_ERROR "Could not find the w32-pthreads library" ) 91 | endif() 92 | 93 | set(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads) 94 | endif() 95 | 96 | set(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR}) 97 | set(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB}) 98 | include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake) 99 | 100 | # allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg) 101 | if(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES) 102 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LIBOBS_INCLUDE_DIR}/../cmake/Modules/") 103 | set(INCLUDED_LIBOBS_CMAKE_MODULES true) 104 | endif() 105 | else() 106 | message(FATAL_ERROR "Could not find the libobs library" ) 107 | endif() 108 | -------------------------------------------------------------------------------- /cmake/Modules/ObsPluginHelpers.cmake: -------------------------------------------------------------------------------- 1 | # Functions for generating external plugins 2 | 3 | set(EXTERNAL_PLUGIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/rundir") 4 | 5 | # Fix XCode includes to ignore warnings on system includes 6 | function(target_include_directories_system _target) 7 | if(XCODE) 8 | foreach(_arg ${ARGN}) 9 | if("${_arg}" STREQUAL "PRIVATE" OR "${_arg}" STREQUAL "PUBLIC" OR "${_arg}" STREQUAL "INTERFACE") 10 | set(_scope ${_arg}) 11 | else() 12 | target_compile_options(${_target} ${_scope} -isystem${_arg}) 13 | endif() 14 | endforeach() 15 | else() 16 | target_include_directories(${_target} SYSTEM ${_scope} ${ARGN}) 17 | endif() 18 | endfunction() 19 | 20 | function(install_external_plugin_data_internal target source_dir target_dir) 21 | install(DIRECTORY ${source_dir}/ 22 | DESTINATION "${target}/${target_dir}" 23 | USE_SOURCE_PERMISSIONS) 24 | add_custom_command(TARGET ${target} POST_BUILD 25 | COMMAND "${CMAKE_COMMAND}" -E copy_directory 26 | "${CMAKE_CURRENT_SOURCE_DIR}/${source_dir}" "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$/${target}/${target_dir}" 27 | VERBATIM) 28 | endfunction() 29 | 30 | # Installs data 31 | # 'target' is the destination target project being installed to 32 | # 'data_loc' specifies the directory of the data 33 | function(install_external_plugin_data target data_loc) 34 | install_external_plugin_data_internal(${target} ${data_loc} "data") 35 | endfunction() 36 | 37 | # Installs data in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac. 38 | # 'target' is the destination target project being installed to 39 | # 'data_loc' specifies the directory of the data being installed 40 | function(install_external_plugin_arch_data target data_loc) 41 | if(APPLE) 42 | set(_bit_suffix "") 43 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 44 | set(_bit_suffix "/64bit") 45 | else() 46 | set(_bit_suffix "/32bit") 47 | endif() 48 | 49 | install_external_plugin_data_internal(${target} ${data_loc} "data${_bit_suffix}") 50 | endfunction() 51 | 52 | # Installs data in the target's bin directory 53 | # 'target' is the destination target project being installed to 54 | # 'data_loc' specifies the directory of the data being installed 55 | function(install_external_plugin_data_to_bin target data_loc) 56 | if(APPLE) 57 | set(_bit_suffix "") 58 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 59 | set(_bit_suffix "/64bit") 60 | else() 61 | set(_bit_suffix "/32bit") 62 | endif() 63 | 64 | install_external_plugin_data_internal(${target} ${data_loc} "bin${_bit_suffix}") 65 | endfunction() 66 | 67 | # Installs an additional binary to a target 68 | # 'target' is the destination target project being installed to 69 | # 'additional_target' specifies the additional binary 70 | function(install_external_plugin_additional target additional_target) 71 | if(APPLE) 72 | set(_bit_suffix "") 73 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 74 | set(_bit_suffix "64bit/") 75 | else() 76 | set(_bit_suffix "32bit/") 77 | endif() 78 | 79 | set_target_properties(${additional_target} PROPERTIES 80 | PREFIX "") 81 | 82 | install(TARGETS ${additional_target} 83 | LIBRARY DESTINATION "bin" 84 | RUNTIME DESTINATION "bin") 85 | add_custom_command(TARGET ${additional_target} POST_BUILD 86 | COMMAND "${CMAKE_COMMAND}" -E copy 87 | "$" 88 | "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$/${target}/bin/${_bit_suffix}$" 89 | VERBATIM) 90 | endfunction() 91 | 92 | # Installs the binary of the target 93 | # 'target' is the target project being installed 94 | function(install_external_plugin target) 95 | install_external_plugin_additional(${target} ${target}) 96 | endfunction() 97 | 98 | # Installs the binary and data of the target 99 | # 'target' is the destination target project being installed to 100 | function(install_external_plugin_with_data target data_loc) 101 | install_external_plugin(${target}) 102 | install_external_plugin_data(${target} ${data_loc}) 103 | endfunction() 104 | 105 | # Installs an additional binary to the data of a target 106 | # 'target' is the destination target project being installed to 107 | # 'additional_target' specifies the additional binary 108 | function(install_external_plugin_bin_to_data target additional_target) 109 | install(TARGETS ${additional_target} 110 | LIBRARY DESTINATION "data" 111 | RUNTIME DESTINATION "data") 112 | add_custom_command(TARGET ${additional_target} POST_BUILD 113 | COMMAND "${CMAKE_COMMAND}" -E copy 114 | "$" 115 | "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$/${target}/data/$" 116 | VERBATIM) 117 | endfunction() 118 | 119 | # Installs an additional binary in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac. 120 | # 'target' is the destination target project being installed to 121 | # 'additional_target' specifies the additional binary 122 | function(install_external_plugin_bin_to_arch_data target additional_target) 123 | if(APPLE) 124 | set(_bit_suffix "") 125 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 126 | set(_bit_suffix "/64bit") 127 | else() 128 | set(_bit_suffix "/32bit") 129 | endif() 130 | 131 | install(TARGETS ${additional_target} 132 | LIBRARY DESTINATION "data${_bit_suffix}" 133 | RUNTIME DESTINATION "data${_bit_suffix}") 134 | add_custom_command(TARGET ${additional_target} POST_BUILD 135 | COMMAND "${CMAKE_COMMAND}" -E copy 136 | "$" 137 | "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$/${target}/data${_bit_suffix}/$" 138 | VERBATIM) 139 | endfunction() 140 | 141 | # Installs an additional file in an architecture-specific data directory on windows/linux (data/32bit or data/64bit). Does not apply for mac. 142 | # 'target' is the destination target project being installed to 143 | # 'additional_target' specifies the additional binary 144 | function(install_external_plugin_data_file_to_arch_data target additional_target file_target) 145 | if(APPLE) 146 | set(_bit_suffix "") 147 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 148 | set(_bit_suffix "/64bit") 149 | else() 150 | set(_bit_suffix "/32bit") 151 | endif() 152 | 153 | get_filename_component(file_target_name ${file_target} NAME) 154 | 155 | install(TARGETS ${additional_target} 156 | LIBRARY DESTINATION "data${_bit_suffix}" 157 | RUNTIME DESTINATION "data${_bit_suffix}") 158 | add_custom_command(TARGET ${additional_target} POST_BUILD 159 | COMMAND "${CMAKE_COMMAND}" -E copy 160 | "${file_target}" 161 | "${EXTERNAL_PLUGIN_OUTPUT_DIR}/$/${target}/data${_bit_suffix}/${file_target_name}" 162 | VERBATIM) 163 | endfunction() 164 | -------------------------------------------------------------------------------- /data/locale/en-US.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnboiles/obs-mac-virtualcam/3cfcbf333cb610fe16887f6e7d50c4c669bff70f/data/locale/en-US.ini -------------------------------------------------------------------------------- /installer/installer-macOS.pkgproj.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PROJECT 6 | 7 | PACKAGE_FILES 8 | 9 | DEFAULT_INSTALL_LOCATION 10 | / 11 | HIERARCHY 12 | 13 | CHILDREN 14 | 15 | 16 | CHILDREN 17 | 18 | 19 | CHILDREN 20 | 21 | 22 | CHILDREN 23 | 24 | 25 | CHILDREN 26 | 27 | 28 | CHILDREN 29 | 30 | 31 | CHILDREN 32 | 33 | 34 | CHILDREN 35 | 36 | GID 37 | 80 38 | PATH 39 | ../build/src/obs-plugin/@CMAKE_PROJECT_NAME@.so 40 | PATH_TYPE 41 | 1 42 | PERMISSIONS 43 | 493 44 | TYPE 45 | 3 46 | UID 47 | 0 48 | 49 | 50 | GID 51 | 80 52 | PATH 53 | bin 54 | PATH_TYPE 55 | 0 56 | PERMISSIONS 57 | 493 58 | TYPE 59 | 2 60 | UID 61 | 0 62 | 63 | 64 | CHILDREN 65 | 66 | GID 67 | 80 68 | PATH 69 | ../data 70 | PATH_TYPE 71 | 1 72 | PERMISSIONS 73 | 493 74 | TYPE 75 | 3 76 | UID 77 | 0 78 | 79 | 80 | GID 81 | 80 82 | PATH 83 | @CMAKE_PROJECT_NAME@ 84 | PATH_TYPE 85 | 0 86 | PERMISSIONS 87 | 493 88 | TYPE 89 | 2 90 | UID 91 | 0 92 | 93 | 94 | GID 95 | 80 96 | PATH 97 | plugins 98 | PATH_TYPE 99 | 0 100 | PERMISSIONS 101 | 493 102 | TYPE 103 | 2 104 | UID 105 | 0 106 | 107 | 108 | GID 109 | 80 110 | PATH 111 | obs-studio 112 | PATH_TYPE 113 | 0 114 | PERMISSIONS 115 | 493 116 | TYPE 117 | 2 118 | UID 119 | 0 120 | 121 | 122 | GID 123 | 80 124 | PATH 125 | Application Support 126 | PATH_TYPE 127 | 0 128 | PERMISSIONS 129 | 493 130 | TYPE 131 | 1 132 | UID 133 | 0 134 | 135 | 136 | CHILDREN 137 | 138 | 139 | CHILDREN 140 | 141 | 142 | CHILDREN 143 | 144 | 145 | CHILDREN 146 | 147 | GID 148 | 80 149 | PATH 150 | ../build/src/dal-plugin/obs-mac-virtualcam.plugin 151 | PATH_TYPE 152 | 1 153 | PERMISSIONS 154 | 493 155 | TYPE 156 | 3 157 | UID 158 | 0 159 | 160 | 161 | GID 162 | 80 163 | PATH 164 | DAL 165 | PATH_TYPE 166 | 0 167 | PERMISSIONS 168 | 493 169 | TYPE 170 | 2 171 | UID 172 | 0 173 | 174 | 175 | GID 176 | 80 177 | PATH 178 | Plug-Ins 179 | PATH_TYPE 180 | 0 181 | PERMISSIONS 182 | 493 183 | TYPE 184 | 2 185 | UID 186 | 0 187 | 188 | 189 | GID 190 | 80 191 | PATH 192 | CoreMediaIO 193 | PATH_TYPE 194 | 0 195 | PERMISSIONS 196 | 493 197 | TYPE 198 | 1 199 | UID 200 | 0 201 | 202 | 203 | CHILDREN 204 | 205 | GID 206 | 0 207 | PATH 208 | Automator 209 | PATH_TYPE 210 | 0 211 | PERMISSIONS 212 | 493 213 | TYPE 214 | 1 215 | UID 216 | 0 217 | 218 | 219 | CHILDREN 220 | 221 | GID 222 | 0 223 | PATH 224 | Documentation 225 | PATH_TYPE 226 | 0 227 | PERMISSIONS 228 | 493 229 | TYPE 230 | 1 231 | UID 232 | 0 233 | 234 | 235 | CHILDREN 236 | 237 | GID 238 | 0 239 | PATH 240 | Extensions 241 | PATH_TYPE 242 | 0 243 | PERMISSIONS 244 | 493 245 | TYPE 246 | 1 247 | UID 248 | 0 249 | 250 | 251 | CHILDREN 252 | 253 | GID 254 | 0 255 | PATH 256 | Filesystems 257 | PATH_TYPE 258 | 0 259 | PERMISSIONS 260 | 493 261 | TYPE 262 | 1 263 | UID 264 | 0 265 | 266 | 267 | CHILDREN 268 | 269 | GID 270 | 0 271 | PATH 272 | Frameworks 273 | PATH_TYPE 274 | 0 275 | PERMISSIONS 276 | 493 277 | TYPE 278 | 1 279 | UID 280 | 0 281 | 282 | 283 | CHILDREN 284 | 285 | GID 286 | 0 287 | PATH 288 | Input Methods 289 | PATH_TYPE 290 | 0 291 | PERMISSIONS 292 | 493 293 | TYPE 294 | 1 295 | UID 296 | 0 297 | 298 | 299 | CHILDREN 300 | 301 | GID 302 | 0 303 | PATH 304 | Internet Plug-Ins 305 | PATH_TYPE 306 | 0 307 | PERMISSIONS 308 | 493 309 | TYPE 310 | 1 311 | UID 312 | 0 313 | 314 | 315 | CHILDREN 316 | 317 | GID 318 | 0 319 | PATH 320 | LaunchAgents 321 | PATH_TYPE 322 | 0 323 | PERMISSIONS 324 | 493 325 | TYPE 326 | 1 327 | UID 328 | 0 329 | 330 | 331 | CHILDREN 332 | 333 | GID 334 | 0 335 | PATH 336 | LaunchDaemons 337 | PATH_TYPE 338 | 0 339 | PERMISSIONS 340 | 493 341 | TYPE 342 | 1 343 | UID 344 | 0 345 | 346 | 347 | CHILDREN 348 | 349 | GID 350 | 0 351 | PATH 352 | PreferencePanes 353 | PATH_TYPE 354 | 0 355 | PERMISSIONS 356 | 493 357 | TYPE 358 | 1 359 | UID 360 | 0 361 | 362 | 363 | CHILDREN 364 | 365 | GID 366 | 0 367 | PATH 368 | Preferences 369 | PATH_TYPE 370 | 0 371 | PERMISSIONS 372 | 493 373 | TYPE 374 | 1 375 | UID 376 | 0 377 | 378 | 379 | CHILDREN 380 | 381 | GID 382 | 80 383 | PATH 384 | Printers 385 | PATH_TYPE 386 | 0 387 | PERMISSIONS 388 | 493 389 | TYPE 390 | 1 391 | UID 392 | 0 393 | 394 | 395 | CHILDREN 396 | 397 | GID 398 | 0 399 | PATH 400 | PrivilegedHelperTools 401 | PATH_TYPE 402 | 0 403 | PERMISSIONS 404 | 493 405 | TYPE 406 | 1 407 | UID 408 | 0 409 | 410 | 411 | CHILDREN 412 | 413 | GID 414 | 0 415 | PATH 416 | QuickLook 417 | PATH_TYPE 418 | 0 419 | PERMISSIONS 420 | 493 421 | TYPE 422 | 1 423 | UID 424 | 0 425 | 426 | 427 | CHILDREN 428 | 429 | GID 430 | 0 431 | PATH 432 | QuickTime 433 | PATH_TYPE 434 | 0 435 | PERMISSIONS 436 | 493 437 | TYPE 438 | 1 439 | UID 440 | 0 441 | 442 | 443 | CHILDREN 444 | 445 | GID 446 | 0 447 | PATH 448 | Screen Savers 449 | PATH_TYPE 450 | 0 451 | PERMISSIONS 452 | 493 453 | TYPE 454 | 1 455 | UID 456 | 0 457 | 458 | 459 | CHILDREN 460 | 461 | GID 462 | 0 463 | PATH 464 | Scripts 465 | PATH_TYPE 466 | 0 467 | PERMISSIONS 468 | 493 469 | TYPE 470 | 1 471 | UID 472 | 0 473 | 474 | 475 | CHILDREN 476 | 477 | GID 478 | 0 479 | PATH 480 | Services 481 | PATH_TYPE 482 | 0 483 | PERMISSIONS 484 | 493 485 | TYPE 486 | 1 487 | UID 488 | 0 489 | 490 | 491 | CHILDREN 492 | 493 | GID 494 | 0 495 | PATH 496 | Widgets 497 | PATH_TYPE 498 | 0 499 | PERMISSIONS 500 | 493 501 | TYPE 502 | 1 503 | UID 504 | 0 505 | 506 | 507 | GID 508 | 0 509 | PATH 510 | Library 511 | PATH_TYPE 512 | 0 513 | PERMISSIONS 514 | 493 515 | TYPE 516 | 1 517 | UID 518 | 0 519 | 520 | 521 | CHILDREN 522 | 523 | 524 | CHILDREN 525 | 526 | GID 527 | 0 528 | PATH 529 | Shared 530 | PATH_TYPE 531 | 0 532 | PERMISSIONS 533 | 1023 534 | TYPE 535 | 1 536 | UID 537 | 0 538 | 539 | 540 | GID 541 | 80 542 | PATH 543 | Users 544 | PATH_TYPE 545 | 0 546 | PERMISSIONS 547 | 493 548 | TYPE 549 | 1 550 | UID 551 | 0 552 | 553 | 554 | GID 555 | 0 556 | PATH 557 | / 558 | PATH_TYPE 559 | 0 560 | PERMISSIONS 561 | 493 562 | TYPE 563 | 1 564 | UID 565 | 0 566 | 567 | PAYLOAD_TYPE 568 | 0 569 | VERSION 570 | 4 571 | 572 | PACKAGE_SCRIPTS 573 | 574 | RESOURCES 575 | 576 | 577 | PACKAGE_SETTINGS 578 | 579 | AUTHENTICATION 580 | 1 581 | CONCLUSION_ACTION 582 | 0 583 | IDENTIFIER 584 | @MACOS_BUNDLEID@ 585 | OVERWRITE_PERMISSIONS 586 | 587 | VERSION 588 | @CMAKE_PROJECT_VERSION@ 589 | 590 | PROJECT_COMMENTS 591 | 592 | NOTES 593 | 594 | PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M 595 | IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv 596 | c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l 597 | cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 598 | IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 599 | ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp 600 | dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u 601 | dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD 602 | b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuMTMiPgo8c3R5bGUg 603 | dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 604 | Pgo8L2JvZHk+CjwvaHRtbD4K 605 | 606 | 607 | PROJECT_SETTINGS 608 | 609 | BUILD_PATH 610 | 611 | PATH 612 | ../release 613 | PATH_TYPE 614 | 1 615 | 616 | EXCLUDED_FILES 617 | 618 | 619 | PATTERNS_ARRAY 620 | 621 | 622 | REGULAR_EXPRESSION 623 | 624 | STRING 625 | .DS_Store 626 | TYPE 627 | 0 628 | 629 | 630 | PROTECTED 631 | 632 | PROXY_NAME 633 | Remove .DS_Store files 634 | PROXY_TOOLTIP 635 | Remove ".DS_Store" files created by the Finder. 636 | STATE 637 | 638 | 639 | 640 | PATTERNS_ARRAY 641 | 642 | 643 | REGULAR_EXPRESSION 644 | 645 | STRING 646 | .pbdevelopment 647 | TYPE 648 | 0 649 | 650 | 651 | PROTECTED 652 | 653 | PROXY_NAME 654 | Remove .pbdevelopment files 655 | PROXY_TOOLTIP 656 | Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. 657 | STATE 658 | 659 | 660 | 661 | PATTERNS_ARRAY 662 | 663 | 664 | REGULAR_EXPRESSION 665 | 666 | STRING 667 | CVS 668 | TYPE 669 | 1 670 | 671 | 672 | REGULAR_EXPRESSION 673 | 674 | STRING 675 | .cvsignore 676 | TYPE 677 | 0 678 | 679 | 680 | REGULAR_EXPRESSION 681 | 682 | STRING 683 | .cvspass 684 | TYPE 685 | 0 686 | 687 | 688 | REGULAR_EXPRESSION 689 | 690 | STRING 691 | .svn 692 | TYPE 693 | 1 694 | 695 | 696 | REGULAR_EXPRESSION 697 | 698 | STRING 699 | .git 700 | TYPE 701 | 1 702 | 703 | 704 | REGULAR_EXPRESSION 705 | 706 | STRING 707 | .gitignore 708 | TYPE 709 | 0 710 | 711 | 712 | PROTECTED 713 | 714 | PROXY_NAME 715 | Remove SCM metadata 716 | PROXY_TOOLTIP 717 | Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. 718 | STATE 719 | 720 | 721 | 722 | PATTERNS_ARRAY 723 | 724 | 725 | REGULAR_EXPRESSION 726 | 727 | STRING 728 | classes.nib 729 | TYPE 730 | 0 731 | 732 | 733 | REGULAR_EXPRESSION 734 | 735 | STRING 736 | designable.db 737 | TYPE 738 | 0 739 | 740 | 741 | REGULAR_EXPRESSION 742 | 743 | STRING 744 | info.nib 745 | TYPE 746 | 0 747 | 748 | 749 | PROTECTED 750 | 751 | PROXY_NAME 752 | Optimize nib files 753 | PROXY_TOOLTIP 754 | Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. 755 | STATE 756 | 757 | 758 | 759 | PATTERNS_ARRAY 760 | 761 | 762 | REGULAR_EXPRESSION 763 | 764 | STRING 765 | Resources Disabled 766 | TYPE 767 | 1 768 | 769 | 770 | PROTECTED 771 | 772 | PROXY_NAME 773 | Remove Resources Disabled folders 774 | PROXY_TOOLTIP 775 | Remove "Resources Disabled" folders. 776 | STATE 777 | 778 | 779 | 780 | SEPARATOR 781 | 782 | 783 | 784 | NAME 785 | @CMAKE_PROJECT_NAME@ 786 | 787 | 788 | TYPE 789 | 1 790 | VERSION 791 | 2 792 | 793 | 794 | -------------------------------------------------------------------------------- /src/common/MachProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // MachProtocol.m 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 5/5/20. 6 | // 7 | 8 | #define MACH_SERVICE_NAME "com.johnboiles.obs-mac-virtualcam.server" 9 | 10 | typedef enum { 11 | //! Initial connect message sent from the client to the server to initate a connection 12 | MachMsgIdConnect = 1, 13 | //! Message containing data for a frame 14 | MachMsgIdFrame = 2, 15 | //! Indicates the server is going to stop sending frames 16 | MachMsgIdStop = 3, 17 | } MachMsgId; 18 | -------------------------------------------------------------------------------- /src/dal-plugin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(dal-plugin) 2 | 3 | find_library(AVFOUNDATION AVFoundation) 4 | find_library(COCOA Cocoa) 5 | find_library(COREFOUNDATION CoreFoundation) 6 | find_library(COREMEDIA CoreMedia) 7 | find_library(COREVIDEO CoreVideo) 8 | find_library(COCOA Cocoa) 9 | find_library(COREMEDIAIO CoreMediaIO) 10 | find_library(IOSURFACE IOSurface) 11 | find_library(IOKIT IOKit) 12 | 13 | configure_file( 14 | Defines.h.in 15 | ../../../src/dal-plugin/Defines.generated.h 16 | ) 17 | 18 | # Possible we could remove osme of these 19 | include_directories(${AVFOUNDATION} 20 | ${COCOA} 21 | ${COREFOUNDATION} 22 | ${COREMEDIA} 23 | ${COREVIDEO} 24 | ${COREMEDIAIO} 25 | ${COCOA} 26 | ${IOSURFACE} 27 | ./ 28 | ../common) 29 | 30 | set(dal-plugin_SOURCES 31 | Defines.generated.h 32 | Logging.h 33 | OBSDALPlugInMain.mm 34 | OBSDALPlugInInterface.h 35 | OBSDALPlugInInterface.mm 36 | OBSDALObjectStore.h 37 | OBSDALObjectStore.mm 38 | OBSDALPlugIn.h 39 | OBSDALPlugIn.mm 40 | OBSDALDevice.h 41 | OBSDALDevice.mm 42 | OBSDALStream.h 43 | OBSDALStream.mm 44 | OBSDALCMSampleBufferUtils.h 45 | OBSDALCMSampleBufferUtils.mm 46 | OBSDALMachClient.h 47 | OBSDALMachClient.mm 48 | OBSDALTestCard.h 49 | OBSDALTestCard.mm 50 | ../common/MachProtocol.h) 51 | 52 | add_library(dal-plugin MODULE 53 | ${dal-plugin_SOURCES} 54 | ${dal-plugin_HEADERS}) 55 | 56 | set_target_properties(dal-plugin 57 | PROPERTIES 58 | BUNDLE TRUE 59 | OUTPUT_NAME "obs-mac-virtualcam" 60 | COMPILE_FLAGS "-std=gnu++14 -stdlib=libc++ -fobjc-arc -fobjc-weak") 61 | 62 | target_link_libraries(dal-plugin 63 | ${AVFOUNDATION} 64 | ${COCOA} 65 | ${COREFOUNDATION} 66 | ${COREMEDIA} 67 | ${COREVIDEO} 68 | ${COREMEDIAIO} 69 | ${IOSURFACE} 70 | ${IOKIT}) 71 | 72 | # Workaround because building from Xcode likes to put things in Debug and Release directories 73 | # TODO: This only supports Debug 74 | if (XCODE) 75 | set(TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Debug") 76 | else (XCODE) 77 | set(TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}") 78 | endif (XCODE) 79 | 80 | add_custom_command(TARGET dal-plugin 81 | POST_BUILD 82 | COMMAND rm -rf ${TARGET_DIR}/obs-mac-virtualcam.plugin || true 83 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${TARGET_DIR}/obs-mac-virtualcam.bundle ${TARGET_DIR}/obs-mac-virtualcam.plugin 84 | COMMENT "Rename bundle to plugin" 85 | ) 86 | 87 | # Note: Xcode seems to run a command `builtin-infoPlistUtility` to generate the Info.plist, but I'm 88 | # not sure where to find that binary. If we had access to it, the command would look something like: 89 | # builtin-infoPlistUtility ${PROJECT_SOURCE_DIR}/../common/CoreMediaIO/DeviceAbstractionLayer/Devices/Sample/PlugIn/SampleVCam-Info.plist -producttype com.apple.product-type.bundle -expandbuildsettings -platform macosx -o mac-virtualcam.bundle/Contents/Info.plist 90 | # Instead, just copy in one that was already generated from Xcode. 91 | add_custom_command(TARGET dal-plugin 92 | POST_BUILD 93 | COMMAND cp ${PROJECT_SOURCE_DIR}/Info.plist ${TARGET_DIR}/obs-mac-virtualcam.plugin/Contents/Info.plist 94 | DEPENDS {PROJECT_SOURCE_DIR}/Info.plist 95 | COMMENT "Copy in Info.plist" 96 | ) 97 | 98 | add_custom_command(TARGET dal-plugin 99 | POST_BUILD 100 | COMMAND /usr/bin/codesign --force --deep --sign - --timestamp=none ${TARGET_DIR}/obs-mac-virtualcam.plugin 101 | COMMENT "Codesign plugin" 102 | ) 103 | -------------------------------------------------------------------------------- /src/dal-plugin/Defines.h.in: -------------------------------------------------------------------------------- 1 | // 2 | // Defines.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 5/27/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #define PLUGIN_NAME @"@CMAKE_PROJECT_NAME@" 21 | #define PLUGIN_VERSION @"@CMAKE_PROJECT_VERSION@" 22 | -------------------------------------------------------------------------------- /src/dal-plugin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | obs-mac-virtualcam 9 | CFBundleIdentifier 10 | com.johnboiles.obs-mac-virtualcam.dal-plugin 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | OBS Virtual Camera 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.3.1 19 | CFBundleVersion 20 | 1.3.1 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFPlugInFactories 26 | 27 | 35FDFF29-BFCF-4644-AB77-B759DE932ABE 28 | OBSDALPlugInMain 29 | 30 | CFPlugInTypes 31 | 32 | 30010C1C-93BF-11D8-8B5B-000A95AF9C6A 33 | 34 | 35FDFF29-BFCF-4644-AB77-B759DE932ABE 35 | 36 | 37 | LSMinimumSystemVersion 38 | 10.13 39 | CMIOHardwareAssistantServiceNames 40 | 41 | com.johnboiles.obs-mac-virtualcam.server 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/dal-plugin/Logging.h: -------------------------------------------------------------------------------- 1 | // 2 | // Logging.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #ifndef Logging_h 21 | #define Logging_h 22 | 23 | #include "Defines.generated.h" 24 | 25 | #define DLog(fmt, ...) NSLog((PLUGIN_NAME @"(DAL): " fmt), ##__VA_ARGS__) 26 | #define DLogFunc(fmt, ...) NSLog((PLUGIN_NAME @"(DAL): %s " fmt), __FUNCTION__, ##__VA_ARGS__) 27 | #define VLog(fmt, ...) 28 | #define VLogFunc(fmt, ...) 29 | #define ELog(fmt, ...) DLog(fmt, ##__VA_ARGS__) 30 | 31 | #endif /* Logging_h */ 32 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALCMSampleBufferUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALCMSampleBufferUtils.h 3 | // dal-plugin 4 | // 5 | // Created by John Boiles on 5/8/20. 6 | // 7 | 8 | #include 9 | 10 | OSStatus OBSDALCMSampleBufferCreateFromData(NSSize size, CMSampleTimingInfo timingInfo, UInt64 sequenceNumber, NSData *data, CMSampleBufferRef *sampleBuffer); 11 | 12 | OSStatus OBSDALCMSampleBufferCreateFromDataNoCopy(NSSize size, CMSampleTimingInfo timingInfo, UInt64 sequenceNumber, NSData *data, CMSampleBufferRef *sampleBuffer); 13 | 14 | CMSampleTimingInfo OBSDALCMSampleTimingInfoForTimestamp(uint64_t timestampNanos, uint32_t fpsNumerator, uint32_t fpsDenominator); 15 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALCMSampleBufferUtils.mm: -------------------------------------------------------------------------------- 1 | // 2 | // CMSampleBufferUtils.m 3 | // dal-plugin 4 | // 5 | // Created by John Boiles on 5/8/20. 6 | // 7 | 8 | #import "OBSDALCMSampleBufferUtils.h" 9 | 10 | #include "Logging.h" 11 | 12 | /*! 13 | OBSDALCMSampleBufferCreateFromData 14 | 15 | Creates a CMSampleBuffer by copying bytes from NSData into a CVPixelBuffer. 16 | */ 17 | OSStatus OBSDALCMSampleBufferCreateFromData(NSSize size, CMSampleTimingInfo timingInfo, UInt64 sequenceNumber, NSData *data, CMSampleBufferRef *sampleBuffer) { 18 | OSStatus err = noErr; 19 | 20 | // Create an empty pixel buffer 21 | CVPixelBufferRef pixelBuffer; 22 | err = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_422YpCbCr8, nil, &pixelBuffer); 23 | if (err != noErr) { 24 | DLog(@"CVPixelBufferCreate err %d", err); 25 | return err; 26 | } 27 | 28 | // Generate the video format description from that pixel buffer 29 | CMFormatDescriptionRef format; 30 | err = CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &format); 31 | if (err != noErr) { 32 | DLog(@"CMVideoFormatDescriptionCreateForImageBuffer err %d", err); 33 | return err; 34 | } 35 | 36 | // Copy memory into the pixel buffer 37 | CVPixelBufferLockBaseAddress(pixelBuffer, 0); 38 | uint8_t *dest = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); 39 | uint8_t *src = (uint8_t *)data.bytes; 40 | 41 | size_t destBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); 42 | size_t srcBytesPerRow = size.width * 2; 43 | 44 | // Sometimes CVPixelBufferCreate will create a pixelbuffer that's a different 45 | // size than necessary to hold the frame (probably for some optimization reason). 46 | // If that is the case this will do a row-by-row copy into the buffer. 47 | if (destBytesPerRow == srcBytesPerRow) { 48 | memcpy(dest, src, data.length); 49 | } else { 50 | for (int line = 0; line < size.height; line++) { 51 | memcpy(dest, src, srcBytesPerRow); 52 | src += srcBytesPerRow; 53 | dest += destBytesPerRow; 54 | } 55 | } 56 | 57 | CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); 58 | 59 | err = CMIOSampleBufferCreateForImageBuffer( 60 | kCFAllocatorDefault, 61 | pixelBuffer, 62 | format, 63 | &timingInfo, 64 | sequenceNumber, 65 | 0, 66 | sampleBuffer 67 | ); 68 | CFRelease(format); 69 | CFRelease(pixelBuffer); 70 | 71 | if (err != noErr) { 72 | DLog(@"CMIOSampleBufferCreateForImageBuffer err %d", err); 73 | return err; 74 | } 75 | 76 | return noErr; 77 | } 78 | 79 | static void releaseNSData(void *o, void *block, size_t size) { 80 | NSData *data = (__bridge_transfer NSData *)o; 81 | data = nil; // Assuming ARC is enabled 82 | } 83 | 84 | 85 | // From https://stackoverflow.com/questions/26158253/how-to-create-a-cmblockbufferref-from-nsdata 86 | OSStatus createReadonlyBlockBuffer(CMBlockBufferRef *result, NSData *data) { 87 | CMBlockBufferCustomBlockSource blockSource = { 88 | .version = kCMBlockBufferCustomBlockSourceVersion, 89 | .AllocateBlock = NULL, 90 | .FreeBlock = &releaseNSData, 91 | .refCon = (__bridge_retained void *)data, 92 | }; 93 | return CMBlockBufferCreateWithMemoryBlock(NULL, (void *)data.bytes, data.length, NULL, &blockSource, 0, data.length, 0, result); 94 | } 95 | 96 | /*! 97 | OBSDALCMSampleBufferCreateFromDataNoCopy 98 | 99 | Creates a CMSampleBuffer by using the bytes directly from NSData (without copying them). 100 | Seems to mostly work but does not work at full resolution in OBS for some reason (which prevents loopback testing). 101 | */ 102 | OSStatus OBSDALCMSampleBufferCreateFromDataNoCopy(NSSize size, CMSampleTimingInfo timingInfo, UInt64 sequenceNumber, NSData *data, CMSampleBufferRef *sampleBuffer) { 103 | OSStatus err = noErr; 104 | 105 | CMBlockBufferRef dataBuffer; 106 | createReadonlyBlockBuffer(&dataBuffer, data); 107 | 108 | // Magic format properties snagged from https://github.com/lvsti/CoreMediaIO-DAL-Example/blob/0392cbf27ed33425a1a5bd9f495b2ccec8f20501/Sources/Extras/CoreMediaIO/DeviceAbstractionLayer/Devices/Sample/PlugIn/CMIO_DP_Sample_Stream.cpp#L830 109 | NSDictionary *extensions = @{ 110 | @"com.apple.cmio.format_extension.video.only_has_i_frames": @YES, 111 | (__bridge NSString *)kCMFormatDescriptionExtension_FieldCount: @1, 112 | (__bridge NSString *)kCMFormatDescriptionExtension_ColorPrimaries: (__bridge NSString *)kCMFormatDescriptionColorPrimaries_SMPTE_C, 113 | (__bridge NSString *)kCMFormatDescriptionExtension_TransferFunction: (__bridge NSString *)kCMFormatDescriptionTransferFunction_ITU_R_709_2, 114 | (__bridge NSString *)kCMFormatDescriptionExtension_YCbCrMatrix: (__bridge NSString *)kCMFormatDescriptionYCbCrMatrix_ITU_R_601_4, 115 | (__bridge NSString *)kCMFormatDescriptionExtension_BytesPerRow: @(size.width * 2), 116 | (__bridge NSString *)kCMFormatDescriptionExtension_FormatName: @"Component Video - CCIR-601 uyvy", 117 | (__bridge NSString *)kCMFormatDescriptionExtension_Version: @2, 118 | }; 119 | 120 | CMFormatDescriptionRef format; 121 | err = CMVideoFormatDescriptionCreate(NULL, kCMVideoCodecType_422YpCbCr8, size.width, size.height, (__bridge CFDictionaryRef)extensions, &format); 122 | if (err != noErr) { 123 | DLog(@"CMVideoFormatDescriptionCreate err %d", err); 124 | return err; 125 | } 126 | 127 | size_t dataSize = data.length; 128 | err = CMIOSampleBufferCreate(kCFAllocatorDefault, dataBuffer, format, 1, 1, &timingInfo, 1, &dataSize, sequenceNumber, 0, sampleBuffer); 129 | CFRelease(format); 130 | CFRelease(dataBuffer); 131 | 132 | if (err != noErr) { 133 | DLog(@"CMIOSampleBufferCreate err %d", err); 134 | return err; 135 | } 136 | 137 | return noErr; 138 | } 139 | 140 | CMSampleTimingInfo OBSDALCMSampleTimingInfoForTimestamp(uint64_t timestampNanos, uint32_t fpsNumerator, uint32_t fpsDenominator) { 141 | // The timing here is quite important. For frames to be delivered correctly and successfully be recorded by apps 142 | // like QuickTime Player, we need to be accurate in both our timestamps _and_ have a sensible scale. Using large 143 | // timestamps and scales like mach_absolute_time() and NSEC_PER_SEC will work for display, but will error out 144 | // when trying to record. 145 | // 146 | // 600 is a commmon default in Apple's docs https://developer.apple.com/documentation/avfoundation/avmutablemovie/1390622-timescale 147 | CMTimeScale scale = 600; 148 | CMSampleTimingInfo timing; 149 | timing.duration = CMTimeMake(fpsDenominator * scale, fpsNumerator * scale); 150 | timing.presentationTimeStamp = CMTimeMake((timestampNanos / (double)NSEC_PER_SEC) * scale, scale); 151 | timing.decodeTimeStamp = kCMTimeInvalid; 152 | return timing; 153 | } 154 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALDevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALDevice.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import 21 | 22 | #import "OBSDALObjectStore.h" 23 | 24 | NS_ASSUME_NONNULL_BEGIN 25 | 26 | @interface OBSDALDevice : NSObject 27 | 28 | @property CMIOObjectID objectId; 29 | @property CMIOObjectID pluginId; 30 | @property CMIOObjectID streamId; 31 | 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALDevice.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALDevice.mm 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import "OBSDALDevice.h" 21 | 22 | #import 23 | #include 24 | 25 | #import "OBSDALPlugIn.h" 26 | #import "Logging.h" 27 | 28 | @interface OBSDALDevice () 29 | @property BOOL excludeNonDALAccess; 30 | @property pid_t masterPid; 31 | @end 32 | 33 | 34 | @implementation OBSDALDevice 35 | 36 | // Note that the DAL's API calls HasProperty before calling GetPropertyDataSize. This means that it can be assumed that address is valid for the property involved. 37 | - (UInt32)getPropertyDataSizeWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData { 38 | 39 | switch (address.mSelector) { 40 | case kCMIOObjectPropertyName: 41 | return sizeof(CFStringRef); 42 | case kCMIOObjectPropertyManufacturer: 43 | return sizeof(CFStringRef); 44 | case kCMIOObjectPropertyElementCategoryName: 45 | return sizeof(CFStringRef); 46 | case kCMIOObjectPropertyElementNumberName: 47 | return sizeof(CFStringRef); 48 | case kCMIODevicePropertyPlugIn: 49 | return sizeof(CMIOObjectID); 50 | case kCMIODevicePropertyDeviceUID: 51 | return sizeof(CFStringRef); 52 | case kCMIODevicePropertyModelUID: 53 | return sizeof(CFStringRef); 54 | case kCMIODevicePropertyTransportType: 55 | return sizeof(UInt32); 56 | case kCMIODevicePropertyDeviceIsAlive: 57 | return sizeof(UInt32); 58 | case kCMIODevicePropertyDeviceHasChanged: 59 | return sizeof(UInt32); 60 | case kCMIODevicePropertyDeviceIsRunning: 61 | return sizeof(UInt32); 62 | case kCMIODevicePropertyDeviceIsRunningSomewhere: 63 | return sizeof(UInt32); 64 | case kCMIODevicePropertyDeviceCanBeDefaultDevice: 65 | return sizeof(UInt32); 66 | case kCMIODevicePropertyHogMode: 67 | return sizeof(pid_t); 68 | case kCMIODevicePropertyLatency: 69 | return sizeof(UInt32); 70 | case kCMIODevicePropertyStreams: 71 | // Only one stream 72 | return sizeof(CMIOStreamID) * 1; 73 | case kCMIODevicePropertyStreamConfiguration: 74 | // Only one stream 75 | return sizeof(UInt32) + (sizeof(UInt32) * 1); 76 | case kCMIODevicePropertyExcludeNonDALAccess: 77 | return sizeof(UInt32); 78 | case kCMIODevicePropertyCanProcessAVCCommand: 79 | return sizeof(Boolean); 80 | case kCMIODevicePropertyCanProcessRS422Command: 81 | return sizeof(Boolean); 82 | case kCMIODevicePropertyLinkedCoreAudioDeviceUID: 83 | return sizeof(CFStringRef); 84 | case kCMIODevicePropertyDeviceMaster: 85 | return sizeof(pid_t); 86 | default: 87 | DLog(@"OBSDALDevice unhandled getPropertyDataSizeWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 88 | }; 89 | 90 | return 0; 91 | } 92 | 93 | // Note that the DAL's API calls HasProperty before calling GetPropertyData. This means that it can be assumed that address is valid for the property involved. 94 | - (void)getPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData dataSize:(UInt32)dataSize dataUsed:(nonnull UInt32 *)dataUsed data:(nonnull void *)data { 95 | 96 | switch (address.mSelector) { 97 | case kCMIOObjectPropertyName: 98 | *static_cast(data) = CFSTR("OBS Virtual Camera"); 99 | *dataUsed = sizeof(CFStringRef); 100 | break; 101 | case kCMIOObjectPropertyManufacturer: 102 | *static_cast(data) = CFSTR("John Boiles"); 103 | *dataUsed = sizeof(CFStringRef); 104 | break; 105 | case kCMIOObjectPropertyElementCategoryName: 106 | *static_cast(data) = CFSTR("Virtual Camera"); 107 | *dataUsed = sizeof(CFStringRef); 108 | break; 109 | case kCMIOObjectPropertyElementNumberName: 110 | *static_cast(data) = CFSTR("0001"); 111 | *dataUsed = sizeof(CFStringRef); 112 | break; 113 | case kCMIODevicePropertyPlugIn: 114 | *static_cast(data) = self.pluginId; 115 | *dataUsed = sizeof(CMIOObjectID); 116 | break; 117 | case kCMIODevicePropertyDeviceUID: 118 | *static_cast(data) = CFSTR("obs-virtual-cam-device"); 119 | *dataUsed = sizeof(CFStringRef); 120 | break; 121 | case kCMIODevicePropertyModelUID: 122 | *static_cast(data) = CFSTR("obs-virtual-cam-model"); 123 | *dataUsed = sizeof(CFStringRef); 124 | break; 125 | case kCMIODevicePropertyTransportType: 126 | *static_cast(data) = kIOAudioDeviceTransportTypeBuiltIn; 127 | *dataUsed = sizeof(UInt32); 128 | break; 129 | case kCMIODevicePropertyDeviceIsAlive: 130 | *static_cast(data) = 1; 131 | *dataUsed = sizeof(UInt32); 132 | break; 133 | case kCMIODevicePropertyDeviceHasChanged: 134 | *static_cast(data) = 0; 135 | *dataUsed = sizeof(UInt32); 136 | break; 137 | case kCMIODevicePropertyDeviceIsRunning: 138 | *static_cast(data) = 1; 139 | *dataUsed = sizeof(UInt32); 140 | break; 141 | case kCMIODevicePropertyDeviceIsRunningSomewhere: 142 | *static_cast(data) = 1; 143 | *dataUsed = sizeof(UInt32); 144 | break; 145 | case kCMIODevicePropertyDeviceCanBeDefaultDevice: 146 | *static_cast(data) = 1; 147 | *dataUsed = sizeof(UInt32); 148 | break; 149 | case kCMIODevicePropertyHogMode: 150 | *static_cast(data) = -1; 151 | *dataUsed = sizeof(pid_t); 152 | break; 153 | case kCMIODevicePropertyLatency: 154 | *static_cast(data) = 0; 155 | *dataUsed = sizeof(UInt32); 156 | break; 157 | case kCMIODevicePropertyStreams: 158 | *static_cast(data) = self.streamId; 159 | *dataUsed = sizeof(CMIOObjectID); 160 | break; 161 | case kCMIODevicePropertyStreamConfiguration: 162 | DLog(@"TODO kCMIODevicePropertyStreamConfiguration"); 163 | break; 164 | case kCMIODevicePropertyExcludeNonDALAccess: 165 | *static_cast(data) = self.excludeNonDALAccess ? 1 : 0; 166 | *dataUsed = sizeof(UInt32); 167 | break; 168 | case kCMIODevicePropertyCanProcessAVCCommand: 169 | *static_cast(data) = false; 170 | *dataUsed = sizeof(Boolean); 171 | break; 172 | case kCMIODevicePropertyCanProcessRS422Command: 173 | *static_cast(data) = false; 174 | *dataUsed = sizeof(Boolean); 175 | break; 176 | case kCMIODevicePropertyDeviceMaster: 177 | *static_cast(data) = self.masterPid; 178 | *dataUsed = sizeof(pid_t); 179 | break; 180 | default: 181 | DLog(@"OBSDALDevice unhandled getPropertyDataWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 182 | *dataUsed = 0; 183 | break; 184 | }; 185 | } 186 | 187 | - (BOOL)hasPropertyWithAddress:(CMIOObjectPropertyAddress)address { 188 | switch (address.mSelector) { 189 | case kCMIOObjectPropertyName: 190 | case kCMIOObjectPropertyManufacturer: 191 | case kCMIOObjectPropertyElementCategoryName: 192 | case kCMIOObjectPropertyElementNumberName: 193 | case kCMIODevicePropertyPlugIn: 194 | case kCMIODevicePropertyDeviceUID: 195 | case kCMIODevicePropertyModelUID: 196 | case kCMIODevicePropertyTransportType: 197 | case kCMIODevicePropertyDeviceIsAlive: 198 | case kCMIODevicePropertyDeviceHasChanged: 199 | case kCMIODevicePropertyDeviceIsRunning: 200 | case kCMIODevicePropertyDeviceIsRunningSomewhere: 201 | case kCMIODevicePropertyDeviceCanBeDefaultDevice: 202 | case kCMIODevicePropertyHogMode: 203 | case kCMIODevicePropertyLatency: 204 | case kCMIODevicePropertyStreams: 205 | case kCMIODevicePropertyExcludeNonDALAccess: 206 | case kCMIODevicePropertyCanProcessAVCCommand: 207 | case kCMIODevicePropertyCanProcessRS422Command: 208 | case kCMIODevicePropertyDeviceMaster: 209 | return true; 210 | case kCMIODevicePropertyStreamConfiguration: 211 | case kCMIODevicePropertyLinkedCoreAudioDeviceUID: 212 | return false; 213 | default: 214 | DLog(@"OBSDALDevice unhandled hasPropertyWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 215 | return false; 216 | }; 217 | } 218 | 219 | - (BOOL)isPropertySettableWithAddress:(CMIOObjectPropertyAddress)address { 220 | switch (address.mSelector) { 221 | case kCMIOObjectPropertyName: 222 | case kCMIOObjectPropertyManufacturer: 223 | case kCMIOObjectPropertyElementCategoryName: 224 | case kCMIOObjectPropertyElementNumberName: 225 | case kCMIODevicePropertyPlugIn: 226 | case kCMIODevicePropertyDeviceUID: 227 | case kCMIODevicePropertyModelUID: 228 | case kCMIODevicePropertyTransportType: 229 | case kCMIODevicePropertyDeviceIsAlive: 230 | case kCMIODevicePropertyDeviceHasChanged: 231 | case kCMIODevicePropertyDeviceIsRunning: 232 | case kCMIODevicePropertyDeviceIsRunningSomewhere: 233 | case kCMIODevicePropertyDeviceCanBeDefaultDevice: 234 | case kCMIODevicePropertyHogMode: 235 | case kCMIODevicePropertyLatency: 236 | case kCMIODevicePropertyStreams: 237 | case kCMIODevicePropertyStreamConfiguration: 238 | case kCMIODevicePropertyCanProcessAVCCommand: 239 | case kCMIODevicePropertyCanProcessRS422Command: 240 | case kCMIODevicePropertyLinkedCoreAudioDeviceUID: 241 | return false; 242 | case kCMIODevicePropertyExcludeNonDALAccess: 243 | case kCMIODevicePropertyDeviceMaster: 244 | return true; 245 | default: 246 | DLog(@"OBSDALDevice unhandled isPropertySettableWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 247 | return false; 248 | }; 249 | } 250 | 251 | - (void)setPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData dataSize:(UInt32)dataSize data:(nonnull const void *)data { 252 | 253 | switch (address.mSelector) { 254 | case kCMIODevicePropertyExcludeNonDALAccess: 255 | self.excludeNonDALAccess = (*static_cast(data) != 0); 256 | break; 257 | case kCMIODevicePropertyDeviceMaster: 258 | self.masterPid = *static_cast(data); 259 | break; 260 | default: 261 | DLog(@"OBSDALDevice unhandled setPropertyDataWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 262 | break; 263 | }; 264 | } 265 | 266 | @end 267 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALMachClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALMachClient.h 3 | // dal-plugin 4 | // 5 | // Created by John Boiles on 5/5/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @protocol OBSDALMachClientDelegate 13 | 14 | - (void)receivedFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp fpsNumerator:(uint32_t)fpsNumerator fpsDenominator:(uint32_t)fpsDenominator frameData:(NSData *)frameData; 15 | - (void)receivedStop; 16 | 17 | @end 18 | 19 | 20 | @interface OBSDALMachClient : NSObject 21 | 22 | @property (nullable, weak) id delegate; 23 | 24 | - (BOOL)isServerAvailable; 25 | 26 | - (BOOL)connectToServer; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALMachClient.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALMachClient.m 3 | // dal-plugin 4 | // 5 | // Created by John Boiles on 5/5/20. 6 | // 7 | 8 | #import "OBSDALMachClient.h" 9 | #import "MachProtocol.h" 10 | #import "Logging.h" 11 | 12 | @interface OBSDALMachClient () { 13 | NSPort *_receivePort; 14 | } 15 | @end 16 | 17 | 18 | @implementation OBSDALMachClient 19 | 20 | - (void)dealloc { 21 | DLogFunc(@""); 22 | _receivePort.delegate = nil; 23 | } 24 | 25 | - (NSPort *)serverPort { 26 | // See note in MachServer.mm and don't judge me 27 | #pragma clang diagnostic push 28 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 29 | return [[NSMachBootstrapServer sharedInstance] portForName:@MACH_SERVICE_NAME]; 30 | #pragma clang diagnostic pop 31 | } 32 | 33 | - (BOOL)isServerAvailable { 34 | return [self serverPort] != nil; 35 | } 36 | 37 | - (NSPort *)receivePort { 38 | if (_receivePort == nil) { 39 | NSPort *receivePort = [NSMachPort port]; 40 | _receivePort = receivePort; 41 | _receivePort.delegate = self; 42 | __weak typeof(self) weakSelf = self; 43 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 44 | NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 45 | [runLoop addPort:receivePort forMode:NSDefaultRunLoopMode]; 46 | // weakSelf should become nil when this object gets destroyed 47 | while(weakSelf) { 48 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 49 | } 50 | DLog(@"Shutting down receive run loop"); 51 | }); 52 | DLog(@"Initialized mach port %d for receiving", ((NSMachPort *)_receivePort).machPort); 53 | } 54 | return _receivePort; 55 | } 56 | 57 | - (BOOL)connectToServer { 58 | DLogFunc(@""); 59 | 60 | NSPort *sendPort = [self serverPort]; 61 | if (sendPort == nil) { 62 | ELog(@"Unable to connect to server port"); 63 | return NO; 64 | } 65 | 66 | NSPortMessage *message = [[NSPortMessage alloc] initWithSendPort:sendPort receivePort:self.receivePort components:nil]; 67 | message.msgid = MachMsgIdConnect; 68 | 69 | NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5.0]; 70 | if (![message sendBeforeDate:timeout]) { 71 | ELog(@"sendBeforeDate failed"); 72 | return NO; 73 | } 74 | 75 | return YES; 76 | } 77 | 78 | - (void)handlePortMessage:(NSPortMessage *)message { 79 | VLogFunc(@""); 80 | NSArray *components = message.components; 81 | switch (message.msgid) { 82 | case MachMsgIdConnect: 83 | DLog(@"Received connect response"); 84 | break; 85 | case MachMsgIdFrame: 86 | VLog(@"Received frame message"); 87 | if (components.count >= 6) { 88 | CGFloat width; 89 | [components[0] getBytes:&width length:sizeof(width)]; 90 | CGFloat height; 91 | [components[1] getBytes:&height length:sizeof(height)]; 92 | uint64_t timestamp; 93 | [components[2] getBytes:×tamp length:sizeof(timestamp)]; 94 | VLog(@"Received frame data: %fx%f (%llu)", width, height, timestamp); 95 | NSData *frameData = components[3]; 96 | uint32_t fpsNumerator; 97 | [components[4] getBytes:&fpsNumerator length:sizeof(fpsNumerator)]; 98 | uint32_t fpsDenominator; 99 | [components[5] getBytes:&fpsDenominator length:sizeof(fpsDenominator)]; 100 | [self.delegate receivedFrameWithSize:NSMakeSize(width, height) timestamp:timestamp fpsNumerator:fpsNumerator fpsDenominator:fpsDenominator frameData:frameData]; 101 | } 102 | break; 103 | case MachMsgIdStop: 104 | DLog(@"Received stop message"); 105 | [self.delegate receivedStop]; 106 | break; 107 | default: 108 | ELog(@"Received unexpected response msgid %u", (unsigned)message.msgid); 109 | break; 110 | } 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALObjectStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALObjectStore.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import 21 | #import 22 | 23 | NS_ASSUME_NONNULL_BEGIN 24 | 25 | @protocol CMIOObject 26 | 27 | - (BOOL)hasPropertyWithAddress:(CMIOObjectPropertyAddress)address; 28 | - (BOOL)isPropertySettableWithAddress:(CMIOObjectPropertyAddress)address; 29 | - (UInt32)getPropertyDataSizeWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(const void*)qualifierData; 30 | - (void)getPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(const void*)qualifierData dataSize:(UInt32)dataSize dataUsed:(UInt32 *)dataUsed data:(void *)data; 31 | - (void)setPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(const void*)qualifierData dataSize:(UInt32)dataSize data:(const void*)data; 32 | 33 | @end 34 | 35 | @interface OBSDALObjectStore : NSObject 36 | 37 | + (OBSDALObjectStore *)SharedOBSDALObjectStore; 38 | 39 | + (NSObject *)GetObjectWithId:(CMIOObjectID)objectId; 40 | 41 | + (NSString *)StringFromPropertySelector:(CMIOObjectPropertySelector)selector; 42 | 43 | + (BOOL)IsBridgedTypeForSelector:(CMIOObjectPropertySelector)selector; 44 | 45 | - (NSObject *)getObject:(CMIOObjectID)objectID; 46 | 47 | - (void)setObject:(id)object forObjectId:(CMIOObjectID)objectId; 48 | 49 | @end 50 | 51 | NS_ASSUME_NONNULL_END 52 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALObjectStore.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALObjectStore.mm 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import "OBSDALObjectStore.h" 21 | 22 | @interface OBSDALObjectStore () 23 | @property NSMutableDictionary *objectMap; 24 | @end 25 | 26 | @implementation OBSDALObjectStore 27 | 28 | // 4-byte selectors to string for easy debugging 29 | + (NSString *)StringFromPropertySelector:(CMIOObjectPropertySelector)selector { 30 | switch (selector) { 31 | case kCMIODevicePropertyPlugIn: 32 | return @"kCMIODevicePropertyPlugIn"; 33 | case kCMIODevicePropertyDeviceUID: 34 | return @"kCMIODevicePropertyDeviceUID"; 35 | case kCMIODevicePropertyModelUID: 36 | return @"kCMIODevicePropertyModelUID"; 37 | case kCMIODevicePropertyTransportType: 38 | return @"kCMIODevicePropertyTransportType"; 39 | case kCMIODevicePropertyDeviceIsAlive: 40 | return @"kCMIODevicePropertyDeviceIsAlive"; 41 | case kCMIODevicePropertyDeviceHasChanged: 42 | return @"kCMIODevicePropertyDeviceHasChanged"; 43 | case kCMIODevicePropertyDeviceIsRunning: 44 | return @"kCMIODevicePropertyDeviceIsRunning"; 45 | case kCMIODevicePropertyDeviceIsRunningSomewhere: 46 | return @"kCMIODevicePropertyDeviceIsRunningSomewhere"; 47 | case kCMIODevicePropertyDeviceCanBeDefaultDevice: 48 | return @"kCMIODevicePropertyDeviceCanBeDefaultDevice"; 49 | case kCMIODevicePropertyHogMode: 50 | return @"kCMIODevicePropertyHogMode"; 51 | case kCMIODevicePropertyLatency: 52 | return @"kCMIODevicePropertyLatency"; 53 | case kCMIODevicePropertyStreams: 54 | return @"kCMIODevicePropertyStreams"; 55 | case kCMIODevicePropertyStreamConfiguration: 56 | return @"kCMIODevicePropertyStreamConfiguration"; 57 | case kCMIODevicePropertyDeviceMaster: 58 | return @"kCMIODevicePropertyDeviceMaster"; 59 | case kCMIODevicePropertyExcludeNonDALAccess: 60 | return @"kCMIODevicePropertyExcludeNonDALAccess"; 61 | case kCMIODevicePropertyClientSyncDiscontinuity: 62 | return @"kCMIODevicePropertyClientSyncDiscontinuity"; 63 | case kCMIODevicePropertySMPTETimeCallback: 64 | return @"kCMIODevicePropertySMPTETimeCallback"; 65 | case kCMIODevicePropertyCanProcessAVCCommand: 66 | return @"kCMIODevicePropertyCanProcessAVCCommand"; 67 | case kCMIODevicePropertyAVCDeviceType: 68 | return @"kCMIODevicePropertyAVCDeviceType"; 69 | case kCMIODevicePropertyAVCDeviceSignalMode: 70 | return @"kCMIODevicePropertyAVCDeviceSignalMode"; 71 | case kCMIODevicePropertyCanProcessRS422Command: 72 | return @"kCMIODevicePropertyCanProcessRS422Command"; 73 | case kCMIODevicePropertyLinkedCoreAudioDeviceUID: 74 | return @"kCMIODevicePropertyLinkedCoreAudioDeviceUID"; 75 | case kCMIODevicePropertyVideoDigitizerComponents: 76 | return @"kCMIODevicePropertyVideoDigitizerComponents"; 77 | case kCMIODevicePropertySuspendedByUser: 78 | return @"kCMIODevicePropertySuspendedByUser"; 79 | case kCMIODevicePropertyLinkedAndSyncedCoreAudioDeviceUID: 80 | return @"kCMIODevicePropertyLinkedAndSyncedCoreAudioDeviceUID"; 81 | case kCMIODevicePropertyIIDCInitialUnitSpace: 82 | return @"kCMIODevicePropertyIIDCInitialUnitSpace"; 83 | case kCMIODevicePropertyIIDCCSRData: 84 | return @"kCMIODevicePropertyIIDCCSRData"; 85 | case kCMIODevicePropertyCanSwitchFrameRatesWithoutFrameDrops: 86 | return @"kCMIODevicePropertyCanSwitchFrameRatesWithoutFrameDrops"; 87 | case kCMIODevicePropertyLocation: 88 | return @"kCMIODevicePropertyLocation"; 89 | case kCMIODevicePropertyDeviceHasStreamingError: 90 | return @"kCMIODevicePropertyDeviceHasStreamingError"; 91 | case kCMIODevicePropertyScopeInput: 92 | return @"kCMIODevicePropertyScopeInput"; 93 | case kCMIODevicePropertyScopeOutput: 94 | return @"kCMIODevicePropertyScopeOutput"; 95 | case kCMIODevicePropertyScopePlayThrough: 96 | return @"kCMIODevicePropertyScopePlayThrough"; 97 | case kCMIOObjectPropertyClass: 98 | return @"kCMIOObjectPropertyClass"; 99 | case kCMIOObjectPropertyOwner: 100 | return @"kCMIOObjectPropertyOwner"; 101 | case kCMIOObjectPropertyCreator: 102 | return @"kCMIOObjectPropertyCreator"; 103 | case kCMIOObjectPropertyName: 104 | return @"kCMIOObjectPropertyName"; 105 | case kCMIOObjectPropertyManufacturer: 106 | return @"kCMIOObjectPropertyManufacturer"; 107 | case kCMIOObjectPropertyElementName: 108 | return @"kCMIOObjectPropertyElementName"; 109 | case kCMIOObjectPropertyElementCategoryName: 110 | return @"kCMIOObjectPropertyElementCategoryName"; 111 | case kCMIOObjectPropertyElementNumberName: 112 | return @"kCMIOObjectPropertyElementNumberName"; 113 | case kCMIOObjectPropertyOwnedObjects: 114 | return @"kCMIOObjectPropertyOwnedObjects"; 115 | case kCMIOObjectPropertyListenerAdded: 116 | return @"kCMIOObjectPropertyListenerAdded"; 117 | case kCMIOObjectPropertyListenerRemoved: 118 | return @"kCMIOObjectPropertyListenerRemoved"; 119 | case kCMIOStreamPropertyDirection: 120 | return @"kCMIOStreamPropertyDirection"; 121 | case kCMIOStreamPropertyTerminalType: 122 | return @"kCMIOStreamPropertyTerminalType"; 123 | case kCMIOStreamPropertyStartingChannel: 124 | return @"kCMIOStreamPropertyStartingChannel"; 125 | // Same value as kCMIODevicePropertyLatency 126 | // case kCMIOStreamPropertyLatency: 127 | // return @"kCMIOStreamPropertyLatency"; 128 | case kCMIOStreamPropertyFormatDescription: 129 | return @"kCMIOStreamPropertyFormatDescription"; 130 | case kCMIOStreamPropertyFormatDescriptions: 131 | return @"kCMIOStreamPropertyFormatDescriptions"; 132 | case kCMIOStreamPropertyStillImage: 133 | return @"kCMIOStreamPropertyStillImage"; 134 | case kCMIOStreamPropertyStillImageFormatDescriptions: 135 | return @"kCMIOStreamPropertyStillImageFormatDescriptions"; 136 | case kCMIOStreamPropertyFrameRate: 137 | return @"kCMIOStreamPropertyFrameRate"; 138 | case kCMIOStreamPropertyMinimumFrameRate: 139 | return @"kCMIOStreamPropertyMinimumFrameRate"; 140 | case kCMIOStreamPropertyFrameRates: 141 | return @"kCMIOStreamPropertyFrameRates"; 142 | case kCMIOStreamPropertyFrameRateRanges: 143 | return @"kCMIOStreamPropertyFrameRateRanges"; 144 | case kCMIOStreamPropertyNoDataTimeoutInMSec: 145 | return @"kCMIOStreamPropertyNoDataTimeoutInMSec"; 146 | case kCMIOStreamPropertyDeviceSyncTimeoutInMSec: 147 | return @"kCMIOStreamPropertyDeviceSyncTimeoutInMSec"; 148 | case kCMIOStreamPropertyNoDataEventCount: 149 | return @"kCMIOStreamPropertyNoDataEventCount"; 150 | case kCMIOStreamPropertyOutputBufferUnderrunCount: 151 | return @"kCMIOStreamPropertyOutputBufferUnderrunCount"; 152 | case kCMIOStreamPropertyOutputBufferRepeatCount: 153 | return @"kCMIOStreamPropertyOutputBufferRepeatCount"; 154 | case kCMIOStreamPropertyOutputBufferQueueSize: 155 | return @"kCMIOStreamPropertyOutputBufferQueueSize"; 156 | case kCMIOStreamPropertyOutputBuffersRequiredForStartup: 157 | return @"kCMIOStreamPropertyOutputBuffersRequiredForStartup"; 158 | case kCMIOStreamPropertyOutputBuffersNeededForThrottledPlayback: 159 | return @"kCMIOStreamPropertyOutputBuffersNeededForThrottledPlayback"; 160 | case kCMIOStreamPropertyFirstOutputPresentationTimeStamp: 161 | return @"kCMIOStreamPropertyFirstOutputPresentationTimeStamp"; 162 | case kCMIOStreamPropertyEndOfData: 163 | return @"kCMIOStreamPropertyEndOfData"; 164 | case kCMIOStreamPropertyClock: 165 | return @"kCMIOStreamPropertyClock"; 166 | case kCMIOStreamPropertyCanProcessDeckCommand: 167 | return @"kCMIOStreamPropertyCanProcessDeckCommand"; 168 | case kCMIOStreamPropertyDeck: 169 | return @"kCMIOStreamPropertyDeck"; 170 | case kCMIOStreamPropertyDeckFrameNumber: 171 | return @"kCMIOStreamPropertyDeckFrameNumber"; 172 | case kCMIOStreamPropertyDeckDropness: 173 | return @"kCMIOStreamPropertyDeckDropness"; 174 | case kCMIOStreamPropertyDeckThreaded: 175 | return @"kCMIOStreamPropertyDeckThreaded"; 176 | case kCMIOStreamPropertyDeckLocal: 177 | return @"kCMIOStreamPropertyDeckLocal"; 178 | case kCMIOStreamPropertyDeckCueing: 179 | return @"kCMIOStreamPropertyDeckCueing"; 180 | case kCMIOStreamPropertyInitialPresentationTimeStampForLinkedAndSyncedAudio: 181 | return @"kCMIOStreamPropertyInitialPresentationTimeStampForLinkedAndSyncedAudio"; 182 | case kCMIOStreamPropertyScheduledOutputNotificationProc: 183 | return @"kCMIOStreamPropertyScheduledOutputNotificationProc"; 184 | case kCMIOStreamPropertyPreferredFormatDescription: 185 | return @"kCMIOStreamPropertyPreferredFormatDescription"; 186 | case kCMIOStreamPropertyPreferredFrameRate: 187 | return @"kCMIOStreamPropertyPreferredFrameRate"; 188 | case kCMIOControlPropertyScope: 189 | return @"kCMIOControlPropertyScope"; 190 | case kCMIOControlPropertyElement: 191 | return @"kCMIOControlPropertyElement"; 192 | case kCMIOControlPropertyVariant: 193 | return @"kCMIOControlPropertyVariant"; 194 | case kCMIOHardwarePropertyProcessIsMaster: 195 | return @"kCMIOHardwarePropertyProcessIsMaster"; 196 | case kCMIOHardwarePropertyIsInitingOrExiting: 197 | return @"kCMIOHardwarePropertyIsInitingOrExiting"; 198 | case kCMIOHardwarePropertyDevices: 199 | return @"kCMIOHardwarePropertyDevices"; 200 | case kCMIOHardwarePropertyDefaultInputDevice: 201 | return @"kCMIOHardwarePropertyDefaultInputDevice"; 202 | case kCMIOHardwarePropertyDefaultOutputDevice: 203 | return @"kCMIOHardwarePropertyDefaultOutputDevice"; 204 | case kCMIOHardwarePropertyDeviceForUID: 205 | return @"kCMIOHardwarePropertyDeviceForUID"; 206 | case kCMIOHardwarePropertySleepingIsAllowed: 207 | return @"kCMIOHardwarePropertySleepingIsAllowed"; 208 | case kCMIOHardwarePropertyUnloadingIsAllowed: 209 | return @"kCMIOHardwarePropertyUnloadingIsAllowed"; 210 | case kCMIOHardwarePropertyPlugInForBundleID: 211 | return @"kCMIOHardwarePropertyPlugInForBundleID"; 212 | case kCMIOHardwarePropertyUserSessionIsActiveOrHeadless: 213 | return @"kCMIOHardwarePropertyUserSessionIsActiveOrHeadless"; 214 | case kCMIOHardwarePropertySuspendedBySystem: 215 | return @"kCMIOHardwarePropertySuspendedBySystem"; 216 | case kCMIOHardwarePropertyAllowScreenCaptureDevices: 217 | return @"kCMIOHardwarePropertyAllowScreenCaptureDevices"; 218 | case kCMIOHardwarePropertyAllowWirelessScreenCaptureDevices: 219 | return @"kCMIOHardwarePropertyAllowWirelessScreenCaptureDevices"; 220 | default: 221 | uint8_t *chars = (uint8_t *)&selector; 222 | return [NSString stringWithFormat:@"Unknown selector: %c%c%c%c", chars[0], chars[1], chars[2], chars[3]]; 223 | } 224 | } 225 | 226 | + (BOOL)IsBridgedTypeForSelector:(CMIOObjectPropertySelector)selector { 227 | switch (selector) { 228 | case kCMIOObjectPropertyName: 229 | case kCMIOObjectPropertyManufacturer: 230 | case kCMIOObjectPropertyElementName: 231 | case kCMIOObjectPropertyElementCategoryName: 232 | case kCMIOObjectPropertyElementNumberName: 233 | case kCMIODevicePropertyDeviceUID: 234 | case kCMIODevicePropertyModelUID: 235 | case kCMIOStreamPropertyFormatDescriptions: 236 | case kCMIOStreamPropertyFormatDescription: 237 | case kCMIOStreamPropertyClock: 238 | return YES; 239 | default: 240 | return NO; 241 | } 242 | } 243 | 244 | + (OBSDALObjectStore *)SharedOBSDALObjectStore { 245 | static OBSDALObjectStore *sOBSDALObjectStore = nil; 246 | static dispatch_once_t sOnceToken; 247 | dispatch_once(&sOnceToken, ^{ 248 | sOBSDALObjectStore = [[self alloc] init]; 249 | }); 250 | return sOBSDALObjectStore; 251 | } 252 | 253 | + (NSObject *)GetObjectWithId:(CMIOObjectID)objectId { 254 | return [[OBSDALObjectStore SharedOBSDALObjectStore] getObject:objectId]; 255 | } 256 | 257 | - (id)init { 258 | if (self = [super init]) { 259 | self.objectMap = [[NSMutableDictionary alloc] init]; 260 | } 261 | return self; 262 | } 263 | 264 | - (NSObject *)getObject:(CMIOObjectID)objectID { 265 | return self.objectMap[@(objectID)]; 266 | } 267 | 268 | - (void)setObject:(id)object forObjectId:(CMIOObjectID)objectId { 269 | self.objectMap[@(objectId)] = object; 270 | } 271 | 272 | @end 273 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALPlugIn.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALPlugIn.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/9/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import 21 | #import 22 | 23 | #import "OBSDALObjectStore.h" 24 | #import "OBSDALMachClient.h" 25 | #import "OBSDALStream.h" 26 | 27 | #define kTestCardWidthKey @"obs-mac-virtualcam-test-card-width" 28 | #define kTestCardHeightKey @"obs-mac-virtualcam-test-card-height" 29 | #define kTestCardFPSKey @"obs-mac-virtualcam-test-card-fps" 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | @interface OBSDALPlugIn : NSObject 34 | 35 | @property CMIOObjectID objectId; 36 | @property (readonly) OBSDALMachClient *machClient; 37 | @property OBSDALStream *stream; 38 | 39 | + (OBSDALPlugIn *)SharedOBSDALPlugIn; 40 | 41 | - (void)initialize; 42 | 43 | - (void)teardown; 44 | 45 | - (void)startStream; 46 | 47 | - (void)stopStream; 48 | 49 | @end 50 | 51 | NS_ASSUME_NONNULL_END 52 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALPlugIn.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALPlugIn.mm 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/9/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import "OBSDALPlugIn.h" 21 | 22 | #import 23 | 24 | #import "Logging.h" 25 | 26 | 27 | typedef enum { 28 | OBSDALPlugInStateNotStarted = 0, 29 | OBSDALPlugInStateWaitingForServer, 30 | OBSDALPlugInStateReceivingFrames, 31 | } OBSDALPlugInState; 32 | 33 | 34 | @interface OBSDALPlugIn () { 35 | //! Serial queue for all state changes that need to be concerned with thread safety 36 | dispatch_queue_t _stateQueue; 37 | 38 | //! Repeated timer for driving the mach server re-connection 39 | dispatch_source_t _machConnectTimer; 40 | 41 | //! Timeout timer when we haven't received frames for 5s 42 | dispatch_source_t _timeoutTimer; 43 | } 44 | @property OBSDALPlugInState state; 45 | @property OBSDALMachClient *machClient; 46 | 47 | @end 48 | 49 | 50 | @implementation OBSDALPlugIn 51 | 52 | + (OBSDALPlugIn *)SharedOBSDALPlugIn { 53 | static OBSDALPlugIn *sOBSDALPlugIn = nil; 54 | static dispatch_once_t sOnceToken; 55 | dispatch_once(&sOnceToken, ^{ 56 | sOBSDALPlugIn = [[self alloc] init]; 57 | }); 58 | return sOBSDALPlugIn; 59 | } 60 | 61 | - (instancetype)init { 62 | if (self = [super init]) { 63 | _stateQueue = dispatch_queue_create("com.johnboiles.obs-mac-virtualcam.dal.state", DISPATCH_QUEUE_SERIAL); 64 | 65 | _timeoutTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _stateQueue); 66 | __weak typeof(self) weakSelf = self; 67 | dispatch_source_set_event_handler(_timeoutTimer, ^{ 68 | if (weakSelf.state == OBSDALPlugInStateReceivingFrames) { 69 | DLog(@"No frames received for 5s, restarting connection"); 70 | [self stopStream]; 71 | [self startStream]; 72 | } 73 | }); 74 | 75 | _machClient = [[OBSDALMachClient alloc] init]; 76 | _machClient.delegate = self; 77 | 78 | _machConnectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _stateQueue); 79 | dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 0); 80 | uint64_t intervalTime = (int64_t)(1 * NSEC_PER_SEC); 81 | dispatch_source_set_timer(_machConnectTimer, startTime, intervalTime, 0); 82 | dispatch_source_set_event_handler(_machConnectTimer, ^{ 83 | if (![[weakSelf machClient] isServerAvailable]) { 84 | DLog(@"Server is not available"); 85 | } else if (weakSelf.state == OBSDALPlugInStateWaitingForServer) { 86 | DLog(@"Attempting connection"); 87 | [[weakSelf machClient] connectToServer]; 88 | } 89 | }); 90 | } 91 | return self; 92 | } 93 | 94 | - (void)startStream { 95 | DLogFunc(@""); 96 | dispatch_async(_stateQueue, ^{ 97 | if (_state == OBSDALPlugInStateNotStarted) { 98 | dispatch_resume(_machConnectTimer); 99 | [self.stream startServingDefaultFrames]; 100 | _state = OBSDALPlugInStateWaitingForServer; 101 | } 102 | }); 103 | } 104 | 105 | - (void)stopStream { 106 | DLogFunc(@""); 107 | dispatch_async(_stateQueue, ^{ 108 | if (_state == OBSDALPlugInStateWaitingForServer) { 109 | dispatch_suspend(_machConnectTimer); 110 | [self.stream stopServingDefaultFrames]; 111 | } else if (_state == OBSDALPlugInStateReceivingFrames) { 112 | // TODO: Disconnect from the mach server? 113 | dispatch_suspend(_timeoutTimer); 114 | } 115 | _state = OBSDALPlugInStateNotStarted; 116 | }); 117 | } 118 | 119 | - (void)initialize { 120 | } 121 | 122 | - (void)teardown { 123 | } 124 | 125 | #pragma mark - CMIOObject 126 | 127 | - (BOOL)hasPropertyWithAddress:(CMIOObjectPropertyAddress)address { 128 | switch (address.mSelector) { 129 | case kCMIOObjectPropertyName: 130 | return true; 131 | default: 132 | DLog(@"OBSDALPlugIn unhandled hasPropertyWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 133 | return false; 134 | }; 135 | } 136 | 137 | - (BOOL)isPropertySettableWithAddress:(CMIOObjectPropertyAddress)address { 138 | switch (address.mSelector) { 139 | case kCMIOObjectPropertyName: 140 | return false; 141 | default: 142 | DLog(@"OBSDALPlugIn unhandled isPropertySettableWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 143 | return false; 144 | }; 145 | } 146 | 147 | - (UInt32)getPropertyDataSizeWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(const void*)qualifierData { 148 | switch (address.mSelector) { 149 | case kCMIOObjectPropertyName: 150 | return sizeof(CFStringRef); 151 | default: 152 | DLog(@"OBSDALPlugIn unhandled getPropertyDataSizeWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 153 | return 0; 154 | }; 155 | } 156 | 157 | - (void)getPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData dataSize:(UInt32)dataSize dataUsed:(nonnull UInt32 *)dataUsed data:(nonnull void *)data { 158 | switch (address.mSelector) { 159 | case kCMIOObjectPropertyName: 160 | *static_cast(data) = CFSTR("OBS Virtual Camera Plugin"); 161 | *dataUsed = sizeof(CFStringRef); 162 | return; 163 | default: 164 | DLog(@"OBSDALPlugIn unhandled getPropertyDataWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 165 | return; 166 | }; 167 | } 168 | 169 | - (void)setPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData dataSize:(UInt32)dataSize data:(nonnull const void *)data { 170 | DLog(@"OBSDALPlugIn unhandled setPropertyDataWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 171 | } 172 | 173 | #pragma mark - OBSDALMachClientDelegate 174 | 175 | - (void)receivedFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp fpsNumerator:(uint32_t)fpsNumerator fpsDenominator:(uint32_t)fpsDenominator frameData:(NSData *)frameData { 176 | dispatch_sync(_stateQueue, ^{ 177 | if (_state == OBSDALPlugInStateWaitingForServer) { 178 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 179 | [defaults setInteger:size.width forKey:kTestCardWidthKey]; 180 | [defaults setInteger:size.height forKey:kTestCardHeightKey]; 181 | double fps = (double)fpsNumerator/(double)fpsDenominator; 182 | [defaults setDouble:fps forKey:kTestCardFPSKey]; 183 | DLog(@"Saving frame info %dx%d fps=%d/%d=%f", size.width, size.height, fpsNumerator, fpsDenominator, fps); 184 | 185 | dispatch_suspend(_machConnectTimer); 186 | [self.stream stopServingDefaultFrames]; 187 | dispatch_resume(_timeoutTimer); 188 | _state = OBSDALPlugInStateReceivingFrames; 189 | } 190 | }); 191 | 192 | // Add 5 more seconds onto the timeout timer 193 | dispatch_source_set_timer(_timeoutTimer, dispatch_time(DISPATCH_TIME_NOW, 5.0 * NSEC_PER_SEC), 5.0 * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10); 194 | 195 | [self.stream queueFrameWithSize:size timestamp:timestamp fpsNumerator:fpsNumerator fpsDenominator:fpsDenominator frameData:frameData]; 196 | } 197 | 198 | - (void)receivedStop { 199 | DLogFunc(@"Restarting connection"); 200 | [self stopStream]; 201 | [self startStream]; 202 | } 203 | 204 | @end 205 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALPlugInInterface.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALPlugInInterface.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/9/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import 21 | 22 | // The static singleton of the plugin interface 23 | CMIOHardwarePlugInRef OBSDALPlugInRef(); 24 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALPlugInInterface.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALPlugInInterface.mm 3 | // obs-mac-virtualcam 4 | // 5 | // This file implements the CMIO DAL plugin interface 6 | // 7 | // Created by John Boiles on 4/9/20. 8 | // 9 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU General Public License as published by 11 | // the Free Software Foundation, either version 2 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU General Public License for more details. 18 | // 19 | // You should have received a copy of the GNU General Public License 20 | // along with obs-mac-virtualcam. If not, see . 21 | 22 | #import "OBSDALPlugInInterface.h" 23 | 24 | #import 25 | 26 | #import "OBSDALPlugIn.h" 27 | #import "OBSDALDevice.h" 28 | #import "OBSDALStream.h" 29 | #import "Logging.h" 30 | 31 | #pragma mark Plug-In Operations 32 | 33 | static UInt32 sRefCount = 0; 34 | 35 | ULONG HardwarePlugIn_AddRef(CMIOHardwarePlugInRef self) { 36 | sRefCount += 1; 37 | DLogFunc(@"sRefCount now = %d", sRefCount); 38 | return sRefCount; 39 | } 40 | 41 | ULONG HardwarePlugIn_Release(CMIOHardwarePlugInRef self) { 42 | sRefCount -= 1; 43 | DLogFunc(@"sRefCount now = %d", sRefCount); 44 | return sRefCount; 45 | } 46 | 47 | HRESULT HardwarePlugIn_QueryInterface(CMIOHardwarePlugInRef self, REFIID uuid, LPVOID* interface) { 48 | DLogFunc(@""); 49 | 50 | if (!interface) { 51 | DLogFunc(@"Received an empty interface"); 52 | return E_POINTER; 53 | } 54 | 55 | // Set the returned interface to NULL in case the UUIDs don't match 56 | *interface = NULL; 57 | 58 | // Create a CoreFoundation UUIDRef for the requested interface. 59 | CFUUIDRef cfUuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, uuid); 60 | CFStringRef uuidString = CFUUIDCreateString(NULL, cfUuid); 61 | CFStringRef hardwarePluginUuid = CFUUIDCreateString(NULL, kCMIOHardwarePlugInInterfaceID); 62 | 63 | if (CFEqual(uuidString, hardwarePluginUuid)) { 64 | // Return the interface; 65 | sRefCount += 1; 66 | *interface = OBSDALPlugInRef(); 67 | return kCMIOHardwareNoError; 68 | } else { 69 | DLogFunc(@"ERR Queried for some weird UUID %@", uuidString); 70 | } 71 | 72 | return E_NOINTERFACE; 73 | } 74 | 75 | // I think this is deprecated, seems that HardwarePlugIn_InitializeWithObjectID gets called instead 76 | OSStatus HardwarePlugIn_Initialize(CMIOHardwarePlugInRef self) { 77 | DLogFunc(@"ERR self=%p", self); 78 | return kCMIOHardwareUnspecifiedError; 79 | } 80 | 81 | OSStatus HardwarePlugIn_InitializeWithObjectID(CMIOHardwarePlugInRef self, CMIOObjectID objectID) { 82 | DLogFunc(@"self=%p", self); 83 | 84 | OSStatus error = kCMIOHardwareNoError; 85 | 86 | OBSDALPlugIn *plugIn = [OBSDALPlugIn SharedOBSDALPlugIn]; 87 | plugIn.objectId = objectID; 88 | [[OBSDALObjectStore SharedOBSDALObjectStore] setObject:plugIn forObjectId:objectID]; 89 | 90 | OBSDALDevice *device = [[OBSDALDevice alloc] init]; 91 | CMIOObjectID deviceId; 92 | error = CMIOObjectCreate(OBSDALPlugInRef(), kCMIOObjectSystemObject, kCMIODeviceClassID, &deviceId); 93 | if (error != noErr) { 94 | DLog(@"CMIOObjectCreate Error %d", error); 95 | return error; 96 | } 97 | device.objectId = deviceId; 98 | device.pluginId = objectID; 99 | [[OBSDALObjectStore SharedOBSDALObjectStore] setObject:device forObjectId:deviceId]; 100 | 101 | OBSDALStream *stream = [[OBSDALStream alloc] init]; 102 | CMIOObjectID streamId; 103 | error = CMIOObjectCreate(OBSDALPlugInRef(), deviceId, kCMIOStreamClassID, &streamId); 104 | if (error != noErr) { 105 | DLog(@"CMIOObjectCreate Error %d", error); 106 | return error; 107 | } 108 | stream.objectId = streamId; 109 | [[OBSDALObjectStore SharedOBSDALObjectStore] setObject:stream forObjectId:streamId]; 110 | device.streamId = streamId; 111 | plugIn.stream = stream; 112 | 113 | // Tell the system about the OBSDALDevice 114 | error = CMIOObjectsPublishedAndDied(OBSDALPlugInRef(), kCMIOObjectSystemObject, 1, &deviceId, 0, 0); 115 | if (error != kCMIOHardwareNoError) { 116 | DLog(@"CMIOObjectsPublishedAndDied plugin/device Error %d", error); 117 | return error; 118 | } 119 | 120 | // Tell the system about the OBSDALStream 121 | error = CMIOObjectsPublishedAndDied(OBSDALPlugInRef(), deviceId, 1, &streamId, 0, 0); 122 | if (error != kCMIOHardwareNoError) { 123 | DLog(@"CMIOObjectsPublishedAndDied device/stream Error %d", error); 124 | return error; 125 | } 126 | 127 | return error; 128 | } 129 | 130 | OSStatus HardwarePlugIn_Teardown(CMIOHardwarePlugInRef self) { 131 | DLogFunc(@"self=%p", self); 132 | 133 | OSStatus error = kCMIOHardwareNoError; 134 | 135 | OBSDALPlugIn *plugIn = [OBSDALPlugIn SharedOBSDALPlugIn]; 136 | [plugIn teardown]; 137 | 138 | return error; 139 | } 140 | 141 | #pragma mark CMIOObject Operations 142 | 143 | void HardwarePlugIn_ObjectShow(CMIOHardwarePlugInRef self, CMIOObjectID objectID) { 144 | DLogFunc(@"self=%p", self); 145 | } 146 | 147 | Boolean HardwarePlugIn_ObjectHasProperty(CMIOHardwarePlugInRef self, CMIOObjectID objectID, const CMIOObjectPropertyAddress* address) { 148 | 149 | NSObject *object = [OBSDALObjectStore GetObjectWithId:objectID]; 150 | 151 | if (object == nil) { 152 | DLogFunc(@"ERR nil object"); 153 | return false; 154 | } 155 | 156 | Boolean answer = [object hasPropertyWithAddress:*address]; 157 | 158 | // Disabling Noisy logs 159 | // DLogFunc(@"%@(%d) %@ self=%p hasProperty=%d", NSStringFromClass([object class]), objectID, [OBSDALObjectStore StringFromPropertySelector:address->mSelector], self, answer); 160 | 161 | return answer; 162 | } 163 | 164 | OSStatus HardwarePlugIn_ObjectIsPropertySettable(CMIOHardwarePlugInRef self, CMIOObjectID objectID, const CMIOObjectPropertyAddress* address, Boolean* isSettable) { 165 | 166 | NSObject *object = [OBSDALObjectStore GetObjectWithId:objectID]; 167 | 168 | if (object == nil) { 169 | DLogFunc(@"ERR nil object"); 170 | return kCMIOHardwareBadObjectError; 171 | } 172 | 173 | *isSettable = [object isPropertySettableWithAddress:*address]; 174 | 175 | DLogFunc(@"%@(%d) %@ self=%p settable=%d", NSStringFromClass([object class]), objectID, [OBSDALObjectStore StringFromPropertySelector:address->mSelector], self, *isSettable); 176 | 177 | return kCMIOHardwareNoError; 178 | } 179 | 180 | OSStatus HardwarePlugIn_ObjectGetPropertyDataSize(CMIOHardwarePlugInRef self, CMIOObjectID objectID, const CMIOObjectPropertyAddress* address, UInt32 qualifierDataSize, const void* qualifierData, UInt32* dataSize) { 181 | 182 | NSObject *object = [OBSDALObjectStore GetObjectWithId:objectID]; 183 | 184 | if (object == nil) { 185 | DLogFunc(@"ERR nil object"); 186 | return kCMIOHardwareBadObjectError; 187 | } 188 | 189 | *dataSize = [object getPropertyDataSizeWithAddress:*address qualifierDataSize:qualifierDataSize qualifierData:qualifierData]; 190 | 191 | // Disabling Noisy logs 192 | // DLogFunc(@"%@(%d) %@ self=%p size=%d", NSStringFromClass([object class]), objectID, [OBSDALObjectStore StringFromPropertySelector:address->mSelector], self, *dataSize); 193 | 194 | return kCMIOHardwareNoError; 195 | } 196 | 197 | OSStatus HardwarePlugIn_ObjectGetPropertyData(CMIOHardwarePlugInRef self, CMIOObjectID objectID, const CMIOObjectPropertyAddress* address, UInt32 qualifierDataSize, const void* qualifierData, UInt32 dataSize, UInt32* dataUsed, void* data) { 198 | 199 | NSObject *object = [OBSDALObjectStore GetObjectWithId:objectID]; 200 | 201 | if (object == nil) { 202 | DLogFunc(@"ERR nil object"); 203 | return kCMIOHardwareBadObjectError; 204 | } 205 | 206 | [object getPropertyDataWithAddress:*address qualifierDataSize:qualifierDataSize qualifierData:qualifierData dataSize:dataSize dataUsed:dataUsed data:data]; 207 | 208 | // Disabling Noisy logs 209 | // if ([OBSDALObjectStore IsBridgedTypeForSelector:address->mSelector]) { 210 | // id dataObj = (__bridge NSObject *)*static_cast(data); 211 | // DLogFunc(@"%@(%d) %@ self=%p data(id)=%@", NSStringFromClass([object class]), objectID, [OBSDALObjectStore StringFromPropertySelector:address->mSelector], self, dataObj); 212 | // } else { 213 | // UInt32 *dataInt = (UInt32 *)data; 214 | // DLogFunc(@"%@(%d) %@ self=%p data(int)=%d", NSStringFromClass([object class]), objectID, [OBSDALObjectStore StringFromPropertySelector:address->mSelector], self, *dataInt); 215 | // } 216 | 217 | return kCMIOHardwareNoError; 218 | } 219 | 220 | OSStatus HardwarePlugIn_ObjectSetPropertyData(CMIOHardwarePlugInRef self, CMIOObjectID objectID, const CMIOObjectPropertyAddress* address, UInt32 qualifierDataSize, const void* qualifierData, UInt32 dataSize, const void* data) { 221 | 222 | NSObject *object = [OBSDALObjectStore GetObjectWithId:objectID]; 223 | 224 | if (object == nil) { 225 | DLogFunc(@"ERR nil object"); 226 | return kCMIOHardwareBadObjectError; 227 | } 228 | 229 | UInt32 *dataInt = (UInt32 *)data; 230 | DLogFunc(@"%@(%d) %@ self=%p data(int)=%d", NSStringFromClass([object class]), objectID, [OBSDALObjectStore StringFromPropertySelector:address->mSelector], self, *dataInt); 231 | 232 | [object setPropertyDataWithAddress:*address qualifierDataSize:qualifierDataSize qualifierData:qualifierData dataSize:dataSize data:data]; 233 | 234 | return kCMIOHardwareNoError; 235 | } 236 | 237 | #pragma mark CMIOStream Operations 238 | OSStatus HardwarePlugIn_StreamCopyBufferQueue(CMIOHardwarePlugInRef self, CMIOStreamID streamID, CMIODeviceStreamQueueAlteredProc queueAlteredProc, void* queueAlteredRefCon, CMSimpleQueueRef* queue) { 239 | 240 | OBSDALStream *stream = (OBSDALStream *)[OBSDALObjectStore GetObjectWithId:streamID]; 241 | 242 | if (stream == nil) { 243 | DLogFunc(@"ERR nil object"); 244 | return kCMIOHardwareBadObjectError; 245 | } 246 | 247 | *queue = [stream copyBufferQueueWithAlteredProc:queueAlteredProc alteredRefCon:queueAlteredRefCon]; 248 | 249 | DLogFunc(@"%@ (id=%d) self=%p queue=%@", stream, streamID, self, (__bridge NSObject *)*queue); 250 | 251 | return kCMIOHardwareNoError; 252 | } 253 | 254 | #pragma mark CMIODevice Operations 255 | OSStatus HardwarePlugIn_DeviceStartStream(CMIOHardwarePlugInRef self, CMIODeviceID deviceID, CMIOStreamID streamID) { 256 | DLogFunc(@"self=%p device=%d stream=%d", self, deviceID, streamID); 257 | 258 | OBSDALStream *stream = (OBSDALStream *)[OBSDALObjectStore GetObjectWithId:streamID]; 259 | 260 | if (stream == nil) { 261 | DLogFunc(@"ERR nil object"); 262 | return kCMIOHardwareBadObjectError; 263 | } 264 | 265 | [[OBSDALPlugIn SharedOBSDALPlugIn] startStream]; 266 | 267 | return kCMIOHardwareNoError; 268 | } 269 | 270 | OSStatus HardwarePlugIn_DeviceSuspend(CMIOHardwarePlugInRef self, CMIODeviceID deviceID) { 271 | DLogFunc(@"self=%p", self); 272 | return kCMIOHardwareNoError; 273 | } 274 | 275 | OSStatus HardwarePlugIn_DeviceResume(CMIOHardwarePlugInRef self, CMIODeviceID deviceID) { 276 | DLogFunc(@"self=%p", self); 277 | return kCMIOHardwareNoError; 278 | } 279 | 280 | OSStatus HardwarePlugIn_DeviceStopStream(CMIOHardwarePlugInRef self, CMIODeviceID deviceID, CMIOStreamID streamID) { 281 | DLogFunc(@"self=%p device=%d stream=%d", self, deviceID, streamID); 282 | 283 | OBSDALStream *stream = (OBSDALStream *)[OBSDALObjectStore GetObjectWithId:streamID]; 284 | 285 | if (stream == nil) { 286 | DLogFunc(@"ERR nil object"); 287 | return kCMIOHardwareBadObjectError; 288 | } 289 | 290 | [[OBSDALPlugIn SharedOBSDALPlugIn] stopStream]; 291 | 292 | return kCMIOHardwareNoError; 293 | } 294 | 295 | OSStatus HardwarePlugIn_DeviceProcessAVCCommand(CMIOHardwarePlugInRef self, CMIODeviceID deviceID, CMIODeviceAVCCommand* ioAVCCommand) { 296 | DLogFunc(@"self=%p", self); 297 | return kCMIOHardwareNoError; 298 | } 299 | 300 | OSStatus HardwarePlugIn_DeviceProcessRS422Command(CMIOHardwarePlugInRef self, CMIODeviceID deviceID, CMIODeviceRS422Command* ioRS422Command) { 301 | DLogFunc(@"self=%p", self); 302 | return kCMIOHardwareNoError; 303 | } 304 | 305 | OSStatus HardwarePlugIn_StreamDeckPlay(CMIOHardwarePlugInRef self, CMIOStreamID streamID) { 306 | DLogFunc(@"self=%p", self); 307 | return kCMIOHardwareIllegalOperationError; 308 | } 309 | 310 | OSStatus HardwarePlugIn_StreamDeckStop(CMIOHardwarePlugInRef self,CMIOStreamID streamID) { 311 | DLogFunc(@"self=%p", self); 312 | return kCMIOHardwareIllegalOperationError; 313 | } 314 | 315 | OSStatus HardwarePlugIn_StreamDeckJog(CMIOHardwarePlugInRef self, CMIOStreamID streamID, SInt32 speed) { 316 | DLogFunc(@"self=%p", self); 317 | return kCMIOHardwareIllegalOperationError; 318 | } 319 | 320 | OSStatus HardwarePlugIn_StreamDeckCueTo(CMIOHardwarePlugInRef self, CMIOStreamID streamID, Float64 requestedTimecode, Boolean playOnCue) { 321 | DLogFunc(@"self=%p", self); 322 | return kCMIOHardwareIllegalOperationError; 323 | } 324 | 325 | static CMIOHardwarePlugInInterface sInterface = { 326 | // Padding for COM 327 | NULL, 328 | 329 | // IUnknown Routines 330 | (HRESULT (*)(void*, CFUUIDBytes, void**))HardwarePlugIn_QueryInterface, 331 | (ULONG (*)(void*))HardwarePlugIn_AddRef, 332 | (ULONG (*)(void*))HardwarePlugIn_Release, 333 | 334 | // DAL Plug-In Routines 335 | HardwarePlugIn_Initialize, 336 | HardwarePlugIn_InitializeWithObjectID, 337 | HardwarePlugIn_Teardown, 338 | HardwarePlugIn_ObjectShow, 339 | HardwarePlugIn_ObjectHasProperty, 340 | HardwarePlugIn_ObjectIsPropertySettable, 341 | HardwarePlugIn_ObjectGetPropertyDataSize, 342 | HardwarePlugIn_ObjectGetPropertyData, 343 | HardwarePlugIn_ObjectSetPropertyData, 344 | HardwarePlugIn_DeviceSuspend, 345 | HardwarePlugIn_DeviceResume, 346 | HardwarePlugIn_DeviceStartStream, 347 | HardwarePlugIn_DeviceStopStream, 348 | HardwarePlugIn_DeviceProcessAVCCommand, 349 | HardwarePlugIn_DeviceProcessRS422Command, 350 | HardwarePlugIn_StreamCopyBufferQueue, 351 | HardwarePlugIn_StreamDeckPlay, 352 | HardwarePlugIn_StreamDeckStop, 353 | HardwarePlugIn_StreamDeckJog, 354 | HardwarePlugIn_StreamDeckCueTo 355 | }; 356 | 357 | static CMIOHardwarePlugInInterface* sInterfacePtr = &sInterface; 358 | static CMIOHardwarePlugInRef sOBSDALPlugInRef = &sInterfacePtr; 359 | 360 | CMIOHardwarePlugInRef OBSDALPlugInRef() { 361 | return sOBSDALPlugInRef; 362 | } 363 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALPlugInMain.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALPlugInMain.mm 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/9/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import 21 | 22 | #import "OBSDALPlugInInterface.h" 23 | #import "Logging.h" 24 | #import "Defines.generated.h" 25 | 26 | //! OBSDALPlugInMain is the entrypoint for the plugin 27 | extern "C" { 28 | void* OBSDALPlugInMain(CFAllocatorRef allocator, CFUUIDRef requestedTypeUUID) { 29 | DLogFunc(@"version=%@", PLUGIN_VERSION); 30 | if (!CFEqual(requestedTypeUUID, kCMIOHardwarePlugInTypeID)) { 31 | return 0; 32 | } 33 | 34 | return OBSDALPlugInRef(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALStream.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import 21 | 22 | #import "OBSDALObjectStore.h" 23 | 24 | NS_ASSUME_NONNULL_BEGIN 25 | 26 | @interface OBSDALStream : NSObject 27 | 28 | @property CMIOStreamID objectId; 29 | 30 | - (instancetype _Nonnull)init; 31 | 32 | - (CMSimpleQueueRef)copyBufferQueueWithAlteredProc:(CMIODeviceStreamQueueAlteredProc)alteredProc alteredRefCon:(void *)alteredRefCon; 33 | 34 | - (void)startServingDefaultFrames; 35 | 36 | - (void)stopServingDefaultFrames; 37 | 38 | - (void)queueFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp fpsNumerator:(uint32_t)fpsNumerator fpsDenominator:(uint32_t)fpsDenominator frameData:(NSData *)frameData; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALStream.mm: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALStream.mm 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 4/10/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #import "OBSDALStream.h" 21 | 22 | #import 23 | #import 24 | #include 25 | 26 | #import "Logging.h" 27 | #import "OBSDALCMSampleBufferUtils.h" 28 | #import "OBSDALTestCard.h" 29 | #import "OBSDALPlugIn.h" 30 | 31 | @interface OBSDALStream () { 32 | CMSimpleQueueRef _queue; 33 | CFTypeRef _clock; 34 | NSImage *_testCardImage; 35 | dispatch_source_t _frameDispatchSource; 36 | NSSize _testCardSize; 37 | Float64 _fps; 38 | } 39 | 40 | @property CMIODeviceStreamQueueAlteredProc alteredProc; 41 | @property void * alteredRefCon; 42 | @property (readonly) CMSimpleQueueRef queue; 43 | @property (readonly) CFTypeRef clock; 44 | @property UInt64 sequenceNumber; 45 | @property (readonly) NSImage *testCardImage; 46 | @property (readonly) NSSize testCardSize; 47 | @property (readonly) Float64 fps; 48 | 49 | @end 50 | 51 | 52 | @implementation OBSDALStream 53 | 54 | #define DEFAULT_FPS 30.0 55 | #define DEFAULT_WIDTH 1280 56 | #define DEFAULT_HEIGHT 720 57 | 58 | - (instancetype _Nonnull)init { 59 | self = [super init]; 60 | if (self) { 61 | _frameDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, 62 | dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); 63 | __weak typeof(self) wself = self; 64 | dispatch_source_set_event_handler(_frameDispatchSource, ^{ 65 | [wself fillFrame]; 66 | }); 67 | } 68 | return self; 69 | } 70 | 71 | - (void)dealloc { 72 | DLog(@"OBSDALStream Dealloc"); 73 | CMIOStreamClockInvalidate(_clock); 74 | CFRelease(_clock); 75 | _clock = NULL; 76 | CFRelease(_queue); 77 | _queue = NULL; 78 | dispatch_suspend(_frameDispatchSource); 79 | } 80 | 81 | - (void)startServingDefaultFrames { 82 | DLogFunc(@""); 83 | _testCardImage = nil; 84 | _testCardSize = NSZeroSize; 85 | _fps = 0; 86 | dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 0); 87 | uint64_t intervalTime = (int64_t)(NSEC_PER_SEC / self.fps); 88 | dispatch_source_set_timer(_frameDispatchSource, startTime, intervalTime, 0); 89 | dispatch_resume(_frameDispatchSource); 90 | } 91 | 92 | - (void)stopServingDefaultFrames { 93 | DLogFunc(@""); 94 | dispatch_suspend(_frameDispatchSource); 95 | } 96 | 97 | - (CMSimpleQueueRef)queue { 98 | if (_queue == NULL) { 99 | // Allocate a one-second long queue, which we can use our FPS constant for. 100 | OSStatus err = CMSimpleQueueCreate(kCFAllocatorDefault, self.fps, &_queue); 101 | if (err != noErr) { 102 | DLog(@"Err %d in CMSimpleQueueCreate", err); 103 | } 104 | } 105 | return _queue; 106 | } 107 | 108 | - (CFTypeRef)clock { 109 | if (_clock == NULL) { 110 | OSStatus err = CMIOStreamClockCreate(kCFAllocatorDefault, CFSTR("obs-mac-virtualcam::OBSDALStream::clock"), (__bridge void *)self, CMTimeMake(1, 10), 100, 10, &_clock); 111 | if (err != noErr) { 112 | DLog(@"Error %d from CMIOStreamClockCreate", err); 113 | } 114 | } 115 | return _clock; 116 | } 117 | 118 | - (NSSize)testCardSize { 119 | if (NSEqualSizes(_testCardSize, NSZeroSize)) { 120 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 121 | int width = [[defaults objectForKey:kTestCardWidthKey] integerValue]; 122 | int height = [[defaults objectForKey:kTestCardHeightKey] integerValue]; 123 | if (width == 0 || height == 0) { 124 | _testCardSize = NSMakeSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 125 | DLog(@"Setting default resolution %dx%d", DEFAULT_WIDTH, DEFAULT_HEIGHT); 126 | } else { 127 | _testCardSize = NSMakeSize(width, height); 128 | DLog(@"Loaded resolution from NSUserDefaults %dx%d", width, height); 129 | } 130 | } 131 | return _testCardSize; 132 | } 133 | 134 | - (Float64)fps { 135 | if (_fps == 0) { 136 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 137 | double fps = [[defaults objectForKey:kTestCardFPSKey] doubleValue]; 138 | if (isnan(fps) || fps <= 0 || fps > 120) { 139 | DLog(@"Setting default framerate %f", fps); 140 | _fps = DEFAULT_FPS; 141 | } else { 142 | DLog(@"Loaded framerate from NSUserDefaults %f", fps); 143 | _fps = fps; 144 | } 145 | } 146 | return _fps; 147 | } 148 | 149 | - (NSImage *)testCardImage { 150 | if (_testCardImage == nil) { 151 | _testCardImage = OBSDALImageOfTestCardWithSize(self.testCardSize); 152 | } 153 | return _testCardImage; 154 | } 155 | 156 | - (CMSimpleQueueRef)copyBufferQueueWithAlteredProc:(CMIODeviceStreamQueueAlteredProc)alteredProc alteredRefCon:(void *)alteredRefCon { 157 | self.alteredProc = alteredProc; 158 | self.alteredRefCon = alteredRefCon; 159 | 160 | // Retain this since it's a copy operation 161 | CFRetain(self.queue); 162 | 163 | return self.queue; 164 | } 165 | 166 | - (CVPixelBufferRef)createPixelBufferWithTestAnimation { 167 | int width = self.testCardSize.width; 168 | int height = self.testCardSize.height; 169 | 170 | NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: 171 | [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, 172 | [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil]; 173 | CVPixelBufferRef pxbuffer = NULL; 174 | CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer); 175 | 176 | NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); 177 | 178 | CVPixelBufferLockBaseAddress(pxbuffer, 0); 179 | void *pxdata = CVPixelBufferGetBaseAddressOfPlane(pxbuffer, 0); 180 | NSParameterAssert(pxdata != NULL); 181 | 182 | CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); 183 | CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 8, CVPixelBufferGetBytesPerRowOfPlane(pxbuffer, 0), rgbColorSpace, kCGImageAlphaPremultipliedFirst | kCGImageByteOrder32Big); 184 | NSParameterAssert(context); 185 | 186 | NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]; 187 | [NSGraphicsContext setCurrentContext:nsContext]; 188 | 189 | NSRect rect = NSMakeRect(0, 0, self.testCardImage.size.width, self.testCardImage.size.height); 190 | CGImageRef image = [self.testCardImage CGImageForProposedRect:&rect context:nsContext hints:nil]; 191 | CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); 192 | 193 | OBSDALDrawDialWithFrame(NSMakeRect(0, 0, width, height), (int(self.fps) - self.sequenceNumber % int(self.fps)) * 360 / int(self.fps)); 194 | 195 | CGContextRelease(context); 196 | 197 | CVPixelBufferUnlockBaseAddress(pxbuffer, 0); 198 | 199 | return pxbuffer; 200 | } 201 | 202 | - (void)fillFrame { 203 | if (CMSimpleQueueGetFullness(self.queue) >= 1.0) { 204 | DLog(@"Queue is full, bailing out"); 205 | return; 206 | } 207 | 208 | CVPixelBufferRef pixelBuffer = [self createPixelBufferWithTestAnimation]; 209 | 210 | uint64_t hostTime = mach_absolute_time(); 211 | CMSampleTimingInfo timingInfo = OBSDALCMSampleTimingInfoForTimestamp(hostTime, self.fps, 1); 212 | 213 | OSStatus err = CMIOStreamClockPostTimingEvent(timingInfo.presentationTimeStamp, hostTime, true, self.clock); 214 | if (err != noErr) { 215 | DLog(@"CMIOStreamClockPostTimingEvent err %d", err); 216 | } 217 | 218 | CMFormatDescriptionRef format; 219 | CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &format); 220 | 221 | self.sequenceNumber = CMIOGetNextSequenceNumber(self.sequenceNumber); 222 | 223 | CMSampleBufferRef buffer; 224 | err = CMIOSampleBufferCreateForImageBuffer( 225 | kCFAllocatorDefault, 226 | pixelBuffer, 227 | format, 228 | &timingInfo, 229 | self.sequenceNumber, 230 | kCMIOSampleBufferNoDiscontinuities, 231 | &buffer 232 | ); 233 | CFRelease(pixelBuffer); 234 | CFRelease(format); 235 | if (err != noErr) { 236 | DLog(@"CMIOSampleBufferCreateForImageBuffer err %d", err); 237 | } 238 | 239 | CMSimpleQueueEnqueue(self.queue, buffer); 240 | 241 | // Inform the clients that the queue has been altered 242 | if (self.alteredProc != NULL) { 243 | (self.alteredProc)(self.objectId, buffer, self.alteredRefCon); 244 | } 245 | } 246 | 247 | - (void)queueFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp fpsNumerator:(uint32_t)fpsNumerator fpsDenominator:(uint32_t)fpsDenominator frameData:(NSData *)frameData { 248 | if (CMSimpleQueueGetFullness(self.queue) >= 1.0) { 249 | DLog(@"Queue is full, bailing out"); 250 | return; 251 | } 252 | OSStatus err = noErr; 253 | 254 | CMSampleTimingInfo timingInfo = OBSDALCMSampleTimingInfoForTimestamp(timestamp, fpsNumerator, fpsDenominator); 255 | 256 | err = CMIOStreamClockPostTimingEvent(timingInfo.presentationTimeStamp, mach_absolute_time(), true, self.clock); 257 | if (err != noErr) { 258 | DLog(@"CMIOStreamClockPostTimingEvent err %d", err); 259 | } 260 | 261 | self.sequenceNumber = CMIOGetNextSequenceNumber(self.sequenceNumber); 262 | 263 | CMSampleBufferRef sampleBuffer; 264 | OBSDALCMSampleBufferCreateFromData(size, timingInfo, self.sequenceNumber, frameData, &sampleBuffer); 265 | CMSimpleQueueEnqueue(self.queue, sampleBuffer); 266 | 267 | // Inform the clients that the queue has been altered 268 | if (self.alteredProc != NULL) { 269 | (self.alteredProc)(self.objectId, sampleBuffer, self.alteredRefCon); 270 | } 271 | } 272 | 273 | - (CMVideoFormatDescriptionRef)getFormatDescription { 274 | CMVideoFormatDescriptionRef formatDescription; 275 | OSStatus err = CMVideoFormatDescriptionCreate(kCFAllocatorDefault, kCMVideoCodecType_422YpCbCr8, self.testCardSize.width, self.testCardSize.height, NULL, &formatDescription); 276 | if (err != noErr) { 277 | DLog(@"Error %d from CMVideoFormatDescriptionCreate", err); 278 | } 279 | return formatDescription; 280 | } 281 | 282 | #pragma mark - CMIOObject 283 | 284 | - (UInt32)getPropertyDataSizeWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData { 285 | switch (address.mSelector) { 286 | case kCMIOStreamPropertyInitialPresentationTimeStampForLinkedAndSyncedAudio: 287 | return sizeof(CMTime); 288 | case kCMIOStreamPropertyOutputBuffersNeededForThrottledPlayback: 289 | return sizeof(UInt32); 290 | case kCMIOObjectPropertyName: 291 | return sizeof(CFStringRef); 292 | case kCMIOObjectPropertyManufacturer: 293 | return sizeof(CFStringRef); 294 | case kCMIOObjectPropertyElementName: 295 | return sizeof(CFStringRef); 296 | case kCMIOObjectPropertyElementCategoryName: 297 | return sizeof(CFStringRef); 298 | case kCMIOObjectPropertyElementNumberName: 299 | return sizeof(CFStringRef); 300 | case kCMIOStreamPropertyDirection: 301 | return sizeof(UInt32); 302 | case kCMIOStreamPropertyTerminalType: 303 | return sizeof(UInt32); 304 | case kCMIOStreamPropertyStartingChannel: 305 | return sizeof(UInt32); 306 | case kCMIOStreamPropertyLatency: 307 | return sizeof(UInt32); 308 | case kCMIOStreamPropertyFormatDescriptions: 309 | return sizeof(CFArrayRef); 310 | case kCMIOStreamPropertyFormatDescription: 311 | return sizeof(CMFormatDescriptionRef); 312 | case kCMIOStreamPropertyFrameRateRanges: 313 | return sizeof(AudioValueRange); 314 | case kCMIOStreamPropertyFrameRate: 315 | case kCMIOStreamPropertyFrameRates: 316 | return sizeof(Float64); 317 | case kCMIOStreamPropertyMinimumFrameRate: 318 | return sizeof(Float64); 319 | case kCMIOStreamPropertyClock: 320 | return sizeof(CFTypeRef); 321 | default: 322 | DLog(@"OBSDALStream unhandled getPropertyDataSizeWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 323 | return 0; 324 | }; 325 | } 326 | 327 | - (void)getPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData dataSize:(UInt32)dataSize dataUsed:(nonnull UInt32 *)dataUsed data:(nonnull void *)data { 328 | switch (address.mSelector) { 329 | case kCMIOObjectPropertyName: 330 | *static_cast(data) = CFSTR("OBS Virtual Camera"); 331 | *dataUsed = sizeof(CFStringRef); 332 | break; 333 | case kCMIOObjectPropertyElementName: 334 | *static_cast(data) = CFSTR("OBS Virtual Camera OBSDALStream Element"); 335 | *dataUsed = sizeof(CFStringRef); 336 | break; 337 | case kCMIOObjectPropertyManufacturer: 338 | case kCMIOObjectPropertyElementCategoryName: 339 | case kCMIOObjectPropertyElementNumberName: 340 | case kCMIOStreamPropertyTerminalType: 341 | case kCMIOStreamPropertyStartingChannel: 342 | case kCMIOStreamPropertyLatency: 343 | case kCMIOStreamPropertyInitialPresentationTimeStampForLinkedAndSyncedAudio: 344 | case kCMIOStreamPropertyOutputBuffersNeededForThrottledPlayback: 345 | break; 346 | case kCMIOStreamPropertyDirection: 347 | *static_cast(data) = 1; 348 | *dataUsed = sizeof(UInt32); 349 | break; 350 | case kCMIOStreamPropertyFormatDescriptions: 351 | *static_cast(data) = (__bridge_retained CFArrayRef)[NSArray arrayWithObject:(__bridge_transfer NSObject *)[self getFormatDescription]]; 352 | *dataUsed = sizeof(CFArrayRef); 353 | break; 354 | case kCMIOStreamPropertyFormatDescription: 355 | *static_cast(data) = [self getFormatDescription]; 356 | *dataUsed = sizeof(CMVideoFormatDescriptionRef); 357 | break; 358 | case kCMIOStreamPropertyFrameRateRanges: 359 | AudioValueRange range; 360 | range.mMinimum = self.fps; 361 | range.mMaximum = self.fps; 362 | *static_cast(data) = range; 363 | *dataUsed = sizeof(AudioValueRange); 364 | break; 365 | case kCMIOStreamPropertyFrameRate: 366 | case kCMIOStreamPropertyFrameRates: 367 | *static_cast(data) = self.fps; 368 | *dataUsed = sizeof(Float64); 369 | break; 370 | case kCMIOStreamPropertyMinimumFrameRate: 371 | *static_cast(data) = self.fps; 372 | *dataUsed = sizeof(Float64); 373 | break; 374 | case kCMIOStreamPropertyClock: 375 | *static_cast(data) = self.clock; 376 | // This one was incredibly tricky and cost me many hours to find. It seems that DAL expects 377 | // the clock to be retained when returned. It's unclear why, and that seems inconsistent 378 | // with other properties that don't have the same behavior. But this is what Apple's sample 379 | // code does. 380 | // https://github.com/lvsti/CoreMediaIO-DAL-Example/blob/0392cb/Sources/Extras/CoreMediaIO/OBSDALDeviceAbstractionLayer/OBSDALDevices/DP/Properties/CMIO_DP_Property_Clock.cpp#L75 381 | CFRetain(*static_cast(data)); 382 | *dataUsed = sizeof(CFTypeRef); 383 | break; 384 | default: 385 | DLog(@"OBSDALStream unhandled getPropertyDataWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 386 | *dataUsed = 0; 387 | }; 388 | } 389 | 390 | - (BOOL)hasPropertyWithAddress:(CMIOObjectPropertyAddress)address { 391 | switch (address.mSelector){ 392 | case kCMIOObjectPropertyName: 393 | case kCMIOObjectPropertyElementName: 394 | case kCMIOStreamPropertyFormatDescriptions: 395 | case kCMIOStreamPropertyFormatDescription: 396 | case kCMIOStreamPropertyFrameRateRanges: 397 | case kCMIOStreamPropertyFrameRate: 398 | case kCMIOStreamPropertyFrameRates: 399 | case kCMIOStreamPropertyMinimumFrameRate: 400 | case kCMIOStreamPropertyClock: 401 | return true; 402 | case kCMIOObjectPropertyManufacturer: 403 | case kCMIOObjectPropertyElementCategoryName: 404 | case kCMIOObjectPropertyElementNumberName: 405 | case kCMIOStreamPropertyDirection: 406 | case kCMIOStreamPropertyTerminalType: 407 | case kCMIOStreamPropertyStartingChannel: 408 | case kCMIOStreamPropertyLatency: 409 | case kCMIOStreamPropertyInitialPresentationTimeStampForLinkedAndSyncedAudio: 410 | case kCMIOStreamPropertyOutputBuffersNeededForThrottledPlayback: 411 | DLog(@"TODO: %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 412 | return false; 413 | default: 414 | DLog(@"OBSDALStream unhandled hasPropertyWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 415 | return false; 416 | }; 417 | } 418 | 419 | - (BOOL)isPropertySettableWithAddress:(CMIOObjectPropertyAddress)address { 420 | DLog(@"OBSDALStream unhandled isPropertySettableWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 421 | return false; 422 | } 423 | 424 | - (void)setPropertyDataWithAddress:(CMIOObjectPropertyAddress)address qualifierDataSize:(UInt32)qualifierDataSize qualifierData:(nonnull const void *)qualifierData dataSize:(UInt32)dataSize data:(nonnull const void *)data { 425 | DLog(@"OBSDALStream unhandled setPropertyDataWithAddress for %@", [OBSDALObjectStore StringFromPropertySelector:address.mSelector]); 426 | } 427 | 428 | @end 429 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALTestCard.h: -------------------------------------------------------------------------------- 1 | // 2 | // OBSDALTestCard.h 3 | // dal-plugin 4 | // 5 | // Created by John Boiles on 5/8/20. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | void OBSDALDrawTestCardWithFrame(CGContextRef context, NSRect frame); 12 | void OBSDALDrawDialWithFrame(NSRect frame, CGFloat rotation); 13 | 14 | NSImage *OBSDALImageOfTestCardWithSize(NSSize imageSize); 15 | -------------------------------------------------------------------------------- /src/dal-plugin/OBSDALTestCard.mm: -------------------------------------------------------------------------------- 1 | // 2 | // TestCard.m 3 | // dal-plugin 4 | // 5 | // Created by John Boiles on 5/8/20. 6 | // 7 | 8 | #import "OBSDALTestCard.h" 9 | 10 | #import 11 | #import "Defines.generated.h" 12 | 13 | // This code was generated by Trial version of PaintCode, therefore cannot be used for commercial purposes. 14 | // http://www.paintcodeapp.com 15 | 16 | void OBSDALDrawTestCardWithFrame(CGContextRef context, NSRect frame) 17 | { 18 | if (context == NULL) { 19 | context = NSGraphicsContext.currentContext.CGContext; 20 | } 21 | 22 | BOOL showText = (frame.size.width >= 1280 && frame.size.height >= 720); 23 | 24 | CGFloat centerWidth = floor(frame.size.width * 0.70840 - 0.25) - floor(frame.size.width * 0.29199 - 0.25); 25 | NSRect center = NSMakeRect(NSMinX(frame) + floor(frame.size.width * 0.29199 - 0.25) + 0.75, NSMinY(frame) + floor((frame.size.height - centerWidth) / 2), centerWidth, centerWidth); 26 | NSString *versionText = PLUGIN_VERSION; 27 | 28 | // Paste in PaintCode code below 29 | 30 | //// Color Declarations 31 | NSColor* fillColor = [NSColor colorWithRed: 0.125 green: 0.176 blue: 0.435 alpha: 1]; 32 | NSColor* fillColor2 = [NSColor colorWithRed: 0.086 green: 0.141 blue: 0.345 alpha: 1]; 33 | NSColor* fillColor3 = [NSColor colorWithRed: 0.047 green: 0.086 blue: 0.2 alpha: 1]; 34 | NSColor* strokeColor = [NSColor colorWithRed: 1 green: 1 blue: 1 alpha: 1]; 35 | NSColor* fillColor4 = [NSColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.62]; 36 | NSColor* fillColor5 = [NSColor colorWithRed: 0.188 green: 0.18 blue: 0.192 alpha: 1]; 37 | NSColor* fillColor6 = [NSColor colorWithRed: 0.769 green: 0.761 blue: 0.769 alpha: 1]; 38 | NSColor* textForeground = [NSColor colorWithRed: 1 green: 1 blue: 1 alpha: 1]; 39 | NSColor* fillColor7 = [NSColor colorWithRed: 1 green: 1 blue: 1 alpha: 1]; 40 | NSColor* fillColor8 = [NSColor colorWithRed: 0 green: 0 blue: 0.753 alpha: 1]; 41 | NSColor* fillColor9 = [NSColor colorWithRed: 0 green: 0.753 blue: 0 alpha: 1]; 42 | NSColor* fillColor10 = [NSColor colorWithRed: 0.753 green: 0 blue: 0 alpha: 1]; 43 | 44 | 45 | //// Subframes 46 | NSRect background = NSMakeRect(NSMinX(frame), NSMinY(frame), frame.size.width, frame.size.height); 47 | // NSRect center = NSMakeRect(NSMinX(frame) + floor(frame.size.width * 0.29199 - 0.25) + 0.75, NSMinY(frame) + floor(frame.size.height * 0.12917 + 0.5), floor(frame.size.width * 0.70840 - 0.25) - floor(frame.size.width * 0.29199 - 0.25), floor(frame.size.height * 0.86944 + 0.5) - floor(frame.size.height * 0.12917 + 0.5)); 48 | NSRect regularText = NSMakeRect(NSMinX(frame) + floor((frame.size.width - 274.23) * 0.04061 - 0.34) + 0.84, NSMinY(frame) + floor((frame.size.height - 352.53) * 0.42711 - 0.45) + 0.95, 274.23, 352.53); 49 | NSRect rGB = NSMakeRect(NSMinX(frame), NSMinY(frame) - 0.98, floor((frame.size.width) * 0.03223 + 0.24) + 0.26, floor((frame.size.height + 0.98) * 0.02185 - 1.25) + 1.75); 50 | NSRect topRight = NSMakeRect(NSMinX(frame) + frame.size.width - 93.46, NSMinY(frame) + 0.04, 93.42, 93.52); 51 | NSRect bottomLeft = NSMakeRect(NSMinX(frame), NSMinY(frame) + frame.size.height - 93.55, 93.42, 93.51); 52 | 53 | 54 | //// Background 55 | { 56 | //// Rectangle Drawing 57 | NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRect: NSMakeRect(NSMinX(background) + floor(background.size.width * 0.00000 + 0.5), NSMinY(background) + floor(background.size.height * 0.00000 + 0.5), floor(background.size.width * 1.00000 + 0.5) - floor(background.size.width * 0.00000 + 0.5), floor(background.size.height * 0.24874 + 0.41) - floor(background.size.height * 0.00000 + 0.5) + 0.09)]; 58 | [fillColor setFill]; 59 | [rectanglePath fill]; 60 | 61 | 62 | //// Rectangle 2 Drawing 63 | NSBezierPath* rectangle2Path = [NSBezierPath bezierPathWithRect: NSMakeRect(NSMinX(background) + floor(background.size.width * 0.00000 + 0.5), NSMinY(background) + floor(background.size.height * 0.24874 + 0.41) + 0.09, floor(background.size.width * 1.00000 + 0.5) - floor(background.size.width * 0.00000 + 0.5), floor(background.size.height * 1.00000 + 0.5) - floor(background.size.height * 0.24874 + 0.41) - 0.09)]; 64 | [fillColor setFill]; 65 | [rectangle2Path fill]; 66 | 67 | 68 | //// Bezier Drawing 69 | NSBezierPath* bezierPath = [NSBezierPath bezierPath]; 70 | [bezierPath moveToPoint: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 0.49645 * background.size.height)]; 71 | [bezierPath curveToPoint: NSMakePoint(NSMinX(background) + 0.71753 * background.size.width, NSMinY(background) + 0.62275 * background.size.height) controlPoint1: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 0.48916 * background.size.height) controlPoint2: NSMakePoint(NSMinX(background) + 0.71753 * background.size.width, NSMinY(background) + 0.62275 * background.size.height)]; 72 | [bezierPath curveToPoint: NSMakePoint(NSMinX(background) + 0.00000 * background.size.width, NSMinY(background) + 0.28446 * background.size.height) controlPoint1: NSMakePoint(NSMinX(background) + 0.71753 * background.size.width, NSMinY(background) + 0.62275 * background.size.height) controlPoint2: NSMakePoint(NSMinX(background) + 0.15141 * background.size.width, NSMinY(background) + 0.36204 * background.size.height)]; 73 | [bezierPath lineToPoint: NSMakePoint(NSMinX(background) + 0.00000 * background.size.width, NSMinY(background) + 0.24823 * background.size.height)]; 74 | [bezierPath lineToPoint: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 0.24823 * background.size.height)]; 75 | [bezierPath lineToPoint: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 0.49645 * background.size.height)]; 76 | [bezierPath closePath]; 77 | [fillColor2 setFill]; 78 | [bezierPath fill]; 79 | 80 | 81 | //// Bezier 2 Drawing 82 | NSBezierPath* bezier2Path = [NSBezierPath bezierPath]; 83 | [bezier2Path moveToPoint: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 0.49645 * background.size.height)]; 84 | [bezier2Path lineToPoint: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 1.00000 * background.size.height)]; 85 | [bezier2Path lineToPoint: NSMakePoint(NSMinX(background) + 0.00000 * background.size.width, NSMinY(background) + 1.00000 * background.size.height)]; 86 | [bezier2Path lineToPoint: NSMakePoint(NSMinX(background) + 0.00000 * background.size.width, NSMinY(background) + 0.78019 * background.size.height)]; 87 | [bezier2Path lineToPoint: NSMakePoint(NSMinX(background) + 1.00000 * background.size.width, NSMinY(background) + 0.49645 * background.size.height)]; 88 | [bezier2Path closePath]; 89 | [fillColor3 setFill]; 90 | [bezier2Path fill]; 91 | } 92 | 93 | 94 | //// Center 95 | { 96 | //// Oval Drawing 97 | NSBezierPath* ovalPath = [NSBezierPath bezierPathWithOvalInRect: NSMakeRect(NSMinX(center) + floor(center.size.width * 0.00000 + 0.5), NSMinY(center) + floor(center.size.height * 0.00000 + 0.5), floor(center.size.width * 1.00000 + 0.5) - floor(center.size.width * 0.00000 + 0.5), floor(center.size.height * 1.00000 + 0.5) - floor(center.size.height * 0.00000 + 0.5))]; 98 | [fillColor4 setFill]; 99 | [ovalPath fill]; 100 | [strokeColor setStroke]; 101 | ovalPath.lineWidth = 2; 102 | [ovalPath stroke]; 103 | 104 | 105 | //// Rectangle 3 Drawing 106 | NSBezierPath* rectangle3Path = [NSBezierPath bezierPathWithRect: NSMakeRect(NSMinX(center) + floor(center.size.width * 0.15572 + 0.5), NSMinY(center) + floor(center.size.height * 0.15572 + 0.5), floor(center.size.width * 0.84428 + 0.5) - floor(center.size.width * 0.15572 + 0.5), floor(center.size.height * 0.84428 + 0.5) - floor(center.size.height * 0.15572 + 0.5))]; 107 | [strokeColor setStroke]; 108 | rectangle3Path.lineWidth = 2; 109 | [rectangle3Path stroke]; 110 | 111 | 112 | //// Oval 2 Drawing 113 | NSBezierPath* oval2Path = [NSBezierPath bezierPathWithOvalInRect: NSMakeRect(NSMinX(center) + floor(center.size.width * 0.37715 + 0.48) + 0.02, NSMinY(center) + floor(center.size.height * 0.37715 + 0.48) + 0.02, floor(center.size.width * 0.62285 - 0.48) - floor(center.size.width * 0.37715 + 0.48) + 0.96, floor(center.size.height * 0.62285 - 0.48) - floor(center.size.height * 0.37715 + 0.48) + 0.96)]; 114 | [fillColor5 setFill]; 115 | [oval2Path fill]; 116 | [strokeColor setStroke]; 117 | oval2Path.lineWidth = 3.47; 118 | [oval2Path stroke]; 119 | 120 | 121 | //// Bezier 3 Drawing 122 | NSBezierPath* bezier3Path = [NSBezierPath bezierPath]; 123 | [bezier3Path moveToPoint: NSMakePoint(NSMinX(center) + 0.43774 * center.size.width, NSMinY(center) + 0.43569 * center.size.height)]; 124 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.43777 * center.size.width, NSMinY(center) + 0.43553 * center.size.height)]; 125 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.47053 * center.size.width, NSMinY(center) + 0.39283 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.44169 * center.size.width, NSMinY(center) + 0.41708 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.45373 * center.size.width, NSMinY(center) + 0.40139 * center.size.height)]; 126 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.46134 * center.size.width, NSMinY(center) + 0.40126 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.46749 * center.size.width, NSMinY(center) + 0.39584 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.46401 * center.size.width, NSMinY(center) + 0.39809 * center.size.height)]; 127 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.46106 * center.size.width, NSMinY(center) + 0.40157 * center.size.height)]; 128 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.44863 * center.size.width, NSMinY(center) + 0.44638 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.45011 * center.size.width, NSMinY(center) + 0.41373 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.44551 * center.size.width, NSMinY(center) + 0.43032 * center.size.height)]; 129 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.49915 * center.size.width, NSMinY(center) + 0.48885 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.45226 * center.size.width, NSMinY(center) + 0.46965 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.47476 * center.size.width, NSMinY(center) + 0.48919 * center.size.height)]; 130 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.54568 * center.size.width, NSMinY(center) + 0.46244 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.51803 * center.size.width, NSMinY(center) + 0.48969 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.53644 * center.size.width, NSMinY(center) + 0.47885 * center.size.height)]; 131 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.54557 * center.size.width, NSMinY(center) + 0.46243 * center.size.height)]; 132 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.59607 * center.size.width, NSMinY(center) + 0.48985 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.56578 * center.size.width, NSMinY(center) + 0.46314 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.58447 * center.size.width, NSMinY(center) + 0.47329 * center.size.height)]; 133 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.60657 * center.size.width, NSMinY(center) + 0.51871 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.60175 * center.size.width, NSMinY(center) + 0.49825 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.60626 * center.size.width, NSMinY(center) + 0.50827 * center.size.height)]; 134 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.60649 * center.size.width, NSMinY(center) + 0.51837 * center.size.height)]; 135 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.58067 * center.size.width, NSMinY(center) + 0.48533 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.60272 * center.size.width, NSMinY(center) + 0.50431 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.59340 * center.size.width, NSMinY(center) + 0.49239 * center.size.height)]; 136 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.58087 * center.size.width, NSMinY(center) + 0.48544 * center.size.height)]; 137 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.50993 * center.size.width, NSMinY(center) + 0.50579 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.55566 * center.size.width, NSMinY(center) + 0.47147 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.52390 * center.size.width, NSMinY(center) + 0.48058 * center.size.height)]; 138 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.50541 * center.size.width, NSMinY(center) + 0.51670 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.50801 * center.size.width, NSMinY(center) + 0.50924 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.50649 * center.size.width, NSMinY(center) + 0.51291 * center.size.height)]; 139 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.50550 * center.size.width, NSMinY(center) + 0.51637 * center.size.height)]; 140 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.50977 * center.size.width, NSMinY(center) + 0.55699 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.50161 * center.size.width, NSMinY(center) + 0.52995 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.50314 * center.size.width, NSMinY(center) + 0.54451 * center.size.height)]; 141 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.50940 * center.size.width, NSMinY(center) + 0.55691 * center.size.height)]; 142 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.46769 * center.size.width, NSMinY(center) + 0.58611 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.50026 * center.size.width, NSMinY(center) + 0.57211 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.48510 * center.size.width, NSMinY(center) + 0.58273 * center.size.height)]; 143 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.42781 * center.size.width, NSMinY(center) + 0.58115 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.45419 * center.size.width, NSMinY(center) + 0.58894 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.44020 * center.size.width, NSMinY(center) + 0.58679 * center.size.height)]; 144 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.46195 * center.size.width, NSMinY(center) + 0.58102 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.43890 * center.size.width, NSMinY(center) + 0.58437 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.45094 * center.size.width, NSMinY(center) + 0.58490 * center.size.height)]; 145 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.46219 * center.size.width, NSMinY(center) + 0.58094 * center.size.height)]; 146 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.49448 * center.size.width, NSMinY(center) + 0.54921 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.47719 * center.size.width, NSMinY(center) + 0.57578 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.48905 * center.size.width, NSMinY(center) + 0.56412 * center.size.height)]; 147 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.48799 * center.size.width, NSMinY(center) + 0.50104 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.50027 * center.size.width, NSMinY(center) + 0.53367 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.49801 * center.size.width, NSMinY(center) + 0.51472 * center.size.height)]; 148 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.48819 * center.size.width, NSMinY(center) + 0.50132 * center.size.height)]; 149 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.45603 * center.size.width, NSMinY(center) + 0.48003 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.48055 * center.size.width, NSMinY(center) + 0.49041 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.46906 * center.size.width, NSMinY(center) + 0.48280 * center.size.height)]; 150 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.44359 * center.size.width, NSMinY(center) + 0.47857 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.45180 * center.size.width, NSMinY(center) + 0.47927 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.44769 * center.size.width, NSMinY(center) + 0.47895 * center.size.height)]; 151 | [bezier3Path curveToPoint: NSMakePoint(NSMinX(center) + 0.43794 * center.size.width, NSMinY(center) + 0.43581 * center.size.height) controlPoint1: NSMakePoint(NSMinX(center) + 0.43705 * center.size.width, NSMinY(center) + 0.46540 * center.size.height) controlPoint2: NSMakePoint(NSMinX(center) + 0.43465 * center.size.width, NSMinY(center) + 0.45011 * center.size.height)]; 152 | [bezier3Path lineToPoint: NSMakePoint(NSMinX(center) + 0.43774 * center.size.width, NSMinY(center) + 0.43569 * center.size.height)]; 153 | [bezier3Path closePath]; 154 | [fillColor6 setFill]; 155 | [bezier3Path fill]; 156 | } 157 | 158 | 159 | if (showText) 160 | { 161 | //// MirroredText 162 | { 163 | [NSGraphicsContext saveGraphicsState]; 164 | CGContextTranslateCTM(context, NSMinX(frame) + 0.96057 * frame.size.width, NSMinY(frame) + 0.42824 * frame.size.height); 165 | CGContextScaleCTM(context, -1, 1); 166 | 167 | 168 | 169 | //// Label Drawing 170 | NSRect labelRect = NSMakeRect(-0.15, -30.85, 264.59, 40); 171 | NSMutableParagraphStyle* labelStyle = [[NSMutableParagraphStyle alloc] init]; 172 | labelStyle.alignment = NSTextAlignmentLeft; 173 | NSDictionary* labelFontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: labelStyle}; 174 | 175 | [@"OBS Virtual Cam " drawInRect: NSOffsetRect(labelRect, 0, 0) withAttributes: labelFontAttributes]; 176 | 177 | 178 | //// Label 2 Drawing 179 | NSRect label2Rect = NSMakeRect(-0.15, 7.75, 264.68, 40); 180 | NSMutableParagraphStyle* label2Style = [[NSMutableParagraphStyle alloc] init]; 181 | label2Style.alignment = NSTextAlignmentLeft; 182 | NSDictionary* label2FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label2Style}; 183 | 184 | [@"is inactive." drawInRect: NSOffsetRect(label2Rect, 0, 0) withAttributes: label2FontAttributes]; 185 | 186 | 187 | //// Label 3 Drawing 188 | NSRect label3Rect = NSMakeRect(-0.15, 84.95, 245.51, 39); 189 | NSMutableParagraphStyle* label3Style = [[NSMutableParagraphStyle alloc] init]; 190 | label3Style.alignment = NSTextAlignmentLeft; 191 | NSDictionary* label3FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label3Style}; 192 | 193 | [@"Choose Tools > " drawInRect: NSOffsetRect(label3Rect, 0, 0) withAttributes: label3FontAttributes]; 194 | 195 | 196 | //// Label 4 Drawing 197 | NSRect label4Rect = NSMakeRect(-0.15, 123.55, 269.53, 39); 198 | NSMutableParagraphStyle* label4Style = [[NSMutableParagraphStyle alloc] init]; 199 | label4Style.alignment = NSTextAlignmentLeft; 200 | NSDictionary* label4FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label4Style}; 201 | 202 | [@"Start Virtual " drawInRect: NSOffsetRect(label4Rect, 0, 0) withAttributes: label4FontAttributes]; 203 | 204 | 205 | //// Label 5 Drawing 206 | NSRect label5Rect = NSMakeRect(-0.15, 162.15, 126.45, 39); 207 | NSMutableParagraphStyle* label5Style = [[NSMutableParagraphStyle alloc] init]; 208 | label5Style.alignment = NSTextAlignmentLeft; 209 | NSDictionary* label5FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label5Style}; 210 | 211 | [@"Camera." drawInRect: NSOffsetRect(label5Rect, 0, 0) withAttributes: label5FontAttributes]; 212 | 213 | 214 | //// Label 6 Drawing 215 | NSRect label6Rect = NSMakeRect(-0.15, -152.38, 296.53, 81); 216 | NSMutableParagraphStyle* label6Style = [[NSMutableParagraphStyle alloc] init]; 217 | label6Style.alignment = NSTextAlignmentLeft; 218 | NSDictionary* label6FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 66], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label6Style}; 219 | 220 | [@"Mirrored" drawInRect: NSOffsetRect(label6Rect, 0, 0) withAttributes: label6FontAttributes]; 221 | 222 | 223 | 224 | [NSGraphicsContext restoreGraphicsState]; 225 | } 226 | 227 | 228 | //// RegularText 229 | { 230 | //// Label 7 Drawing 231 | NSRect label7Rect = NSMakeRect(NSMinX(regularText) + 4.7, NSMinY(regularText) + 121.53, 264.59, 40); 232 | NSMutableParagraphStyle* label7Style = [[NSMutableParagraphStyle alloc] init]; 233 | label7Style.alignment = NSTextAlignmentLeft; 234 | NSDictionary* label7FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label7Style}; 235 | 236 | [@"OBS Virtual Cam " drawInRect: NSOffsetRect(label7Rect, 0, 0) withAttributes: label7FontAttributes]; 237 | 238 | 239 | //// Label 8 Drawing 240 | NSRect label8Rect = NSMakeRect(NSMinX(regularText) + 4.7, NSMinY(regularText) + 160.13, 269.46, 39); 241 | NSMutableParagraphStyle* label8Style = [[NSMutableParagraphStyle alloc] init]; 242 | label8Style.alignment = NSTextAlignmentLeft; 243 | NSDictionary* label8FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label8Style}; 244 | 245 | [@"is inactive." drawInRect: NSOffsetRect(label8Rect, 0, 0) withAttributes: label8FontAttributes]; 246 | 247 | 248 | //// Label 9 Drawing 249 | NSRect label9Rect = NSMakeRect(NSMinX(regularText) + 4.7, NSMinY(regularText) + 236.33, 245.51, 39); 250 | NSMutableParagraphStyle* label9Style = [[NSMutableParagraphStyle alloc] init]; 251 | label9Style.alignment = NSTextAlignmentLeft; 252 | NSDictionary* label9FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label9Style}; 253 | 254 | [@"Choose Tools > " drawInRect: NSOffsetRect(label9Rect, 0, 0) withAttributes: label9FontAttributes]; 255 | 256 | 257 | //// Label 10 Drawing 258 | NSRect label10Rect = NSMakeRect(NSMinX(regularText) + 4.7, NSMinY(regularText) + 274.93, 269.53, 39); 259 | NSMutableParagraphStyle* label10Style = [[NSMutableParagraphStyle alloc] init]; 260 | label10Style.alignment = NSTextAlignmentLeft; 261 | NSDictionary* label10FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label10Style}; 262 | 263 | [@"Start Virtual " drawInRect: NSOffsetRect(label10Rect, 0, 0) withAttributes: label10FontAttributes]; 264 | 265 | 266 | //// Label 11 Drawing 267 | NSRect label11Rect = NSMakeRect(NSMinX(regularText) + 4.7, NSMinY(regularText) + 313.53, 126.45, 39); 268 | NSMutableParagraphStyle* label11Style = [[NSMutableParagraphStyle alloc] init]; 269 | label11Style.alignment = NSTextAlignmentLeft; 270 | NSDictionary* label11FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 32], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label11Style}; 271 | 272 | [@"Camera." drawInRect: NSOffsetRect(label11Rect, 0, 0) withAttributes: label11FontAttributes]; 273 | 274 | 275 | //// Label 12 Drawing 276 | NSRect label12Rect = NSMakeRect(NSMinX(regularText), NSMinY(regularText), 248.31, 81); 277 | NSMutableParagraphStyle* label12Style = [[NSMutableParagraphStyle alloc] init]; 278 | label12Style.alignment = NSTextAlignmentLeft; 279 | NSDictionary* label12FontAttributes = @{NSFontAttributeName: [NSFont fontWithName: @"Helvetica-Bold" size: 66], NSForegroundColorAttributeName: textForeground, NSParagraphStyleAttributeName: label12Style}; 280 | 281 | [@"Regular" drawInRect: NSOffsetRect(label12Rect, 0, 0) withAttributes: label12FontAttributes]; 282 | } 283 | } 284 | 285 | 286 | //// RGB 287 | { 288 | //// Blue Drawing 289 | NSBezierPath* bluePath = [NSBezierPath bezierPathWithRect: NSMakeRect(NSMinX(rGB) + floor(rGB.size.width * 0.67475 - 0.34) + 0.84, NSMinY(rGB) + floor(rGB.size.height * 0.00000 + 0.5), floor(rGB.size.width * 1.00000 + 0.24) - floor(rGB.size.width * 0.67475 - 0.34) - 0.58, floor(rGB.size.height * 1.00000 - 0.25) - floor(rGB.size.height * 0.00000 + 0.5) + 0.75)]; 290 | [fillColor8 setFill]; 291 | [bluePath fill]; 292 | 293 | 294 | //// Green Drawing 295 | NSBezierPath* greenPath = [NSBezierPath bezierPathWithRect: NSMakeRect(NSMinX(rGB) + floor(rGB.size.width * 0.32525 + 0.08) + 0.42, NSMinY(rGB) + floor(rGB.size.height * 0.00000 + 0.5), floor(rGB.size.width * 0.67475 - 0.34) - floor(rGB.size.width * 0.32525 + 0.08) + 0.42, floor(rGB.size.height * 1.00000 - 0.25) - floor(rGB.size.height * 0.00000 + 0.5) + 0.75)]; 296 | [fillColor9 setFill]; 297 | [greenPath fill]; 298 | 299 | 300 | //// Red Drawing 301 | NSBezierPath* redPath = [NSBezierPath bezierPathWithRect: NSMakeRect(NSMinX(rGB) + floor(rGB.size.width * 0.00000 + 0.5), NSMinY(rGB) + floor(rGB.size.height * 0.00000 + 0.5), floor(rGB.size.width * 0.32525 + 0.08) - floor(rGB.size.width * 0.00000 + 0.5) + 0.42, floor(rGB.size.height * 1.00000 - 0.25) - floor(rGB.size.height * 0.00000 + 0.5) + 0.75)]; 302 | [fillColor10 setFill]; 303 | [redPath fill]; 304 | } 305 | 306 | 307 | //// TopRight 308 | { 309 | //// Bezier 7 Drawing 310 | NSBezierPath* bezier7Path = [NSBezierPath bezierPath]; 311 | [bezier7Path moveToPoint: NSMakePoint(NSMinX(topRight) + 31.28, NSMinY(topRight) + 54.38)]; 312 | [bezier7Path curveToPoint: NSMakePoint(NSMinX(topRight) + 23.82, NSMinY(topRight) + 61.91) controlPoint1: NSMakePoint(NSMinX(topRight) + 26.23, NSMinY(topRight) + 55.91) controlPoint2: NSMakePoint(NSMinX(topRight) + 25.34, NSMinY(topRight) + 56.81)]; 313 | [bezier7Path lineToPoint: NSMakePoint(NSMinX(topRight), NSMinY(topRight) + 61.91)]; 314 | [bezier7Path lineToPoint: NSMakePoint(NSMinX(topRight) + 30.91, NSMinY(topRight))]; 315 | [bezier7Path lineToPoint: NSMakePoint(NSMinX(topRight) + 31.28, NSMinY(topRight) + 0.08)]; 316 | [bezier7Path lineToPoint: NSMakePoint(NSMinX(topRight) + 31.28, NSMinY(topRight) + 54.38)]; 317 | [bezier7Path closePath]; 318 | [fillColor7 setFill]; 319 | [bezier7Path fill]; 320 | 321 | 322 | //// Bezier 8 Drawing 323 | NSBezierPath* bezier8Path = [NSBezierPath bezierPath]; 324 | [bezier8Path moveToPoint: NSMakePoint(NSMinX(topRight) + 93.42, NSMinY(topRight) + 62.55)]; 325 | [bezier8Path lineToPoint: NSMakePoint(NSMinX(topRight) + 31.61, NSMinY(topRight) + 93.52)]; 326 | [bezier8Path lineToPoint: NSMakePoint(NSMinX(topRight) + 31.61, NSMinY(topRight) + 69.52)]; 327 | [bezier8Path curveToPoint: NSMakePoint(NSMinX(topRight) + 38.98, NSMinY(topRight) + 62.1) controlPoint1: NSMakePoint(NSMinX(topRight) + 35.92, NSMinY(topRight) + 68.97) controlPoint2: NSMakePoint(NSMinX(topRight) + 38.42, NSMinY(topRight) + 66.47)]; 328 | [bezier8Path lineToPoint: NSMakePoint(NSMinX(topRight) + 93.28, NSMinY(topRight) + 62.1)]; 329 | [bezier8Path lineToPoint: NSMakePoint(NSMinX(topRight) + 93.42, NSMinY(topRight) + 62.55)]; 330 | [bezier8Path closePath]; 331 | [fillColor7 setFill]; 332 | [bezier8Path fill]; 333 | 334 | 335 | //// Bezier 9 Drawing 336 | NSBezierPath* bezier9Path = [NSBezierPath bezierPath]; 337 | [bezier9Path moveToPoint: NSMakePoint(NSMinX(topRight) + 31.54, NSMinY(topRight) + 65.21)]; 338 | [bezier9Path curveToPoint: NSMakePoint(NSMinX(topRight) + 28.38, NSMinY(topRight) + 62.07) controlPoint1: NSMakePoint(NSMinX(topRight) + 29.66, NSMinY(topRight) + 64.99) controlPoint2: NSMakePoint(NSMinX(topRight) + 28.39, NSMinY(topRight) + 64.01)]; 339 | [bezier9Path lineToPoint: NSMakePoint(NSMinX(topRight) + 28.38, NSMinY(topRight) + 62.05)]; 340 | [bezier9Path curveToPoint: NSMakePoint(NSMinX(topRight) + 31.52, NSMinY(topRight) + 59) controlPoint1: NSMakePoint(NSMinX(topRight) + 28.41, NSMinY(topRight) + 60.34) controlPoint2: NSMakePoint(NSMinX(topRight) + 29.82, NSMinY(topRight) + 58.97)]; 341 | [bezier9Path curveToPoint: NSMakePoint(NSMinX(topRight) + 34.56, NSMinY(topRight) + 61.94) controlPoint1: NSMakePoint(NSMinX(topRight) + 33.15, NSMinY(topRight) + 59.03) controlPoint2: NSMakePoint(NSMinX(topRight) + 34.47, NSMinY(topRight) + 60.31)]; 342 | [bezier9Path curveToPoint: NSMakePoint(NSMinX(topRight) + 31.54, NSMinY(topRight) + 65.21) controlPoint1: NSMakePoint(NSMinX(topRight) + 34.65, NSMinY(topRight) + 63.82) controlPoint2: NSMakePoint(NSMinX(topRight) + 33.42, NSMinY(topRight) + 64.86)]; 343 | [bezier9Path closePath]; 344 | [fillColor7 setFill]; 345 | [bezier9Path fill]; 346 | } 347 | 348 | 349 | //// BottomLeft 350 | { 351 | //// Bezier 4 Drawing 352 | NSBezierPath* bezier4Path = [NSBezierPath bezierPath]; 353 | [bezier4Path moveToPoint: NSMakePoint(NSMinX(bottomLeft) + 62.14, NSMinY(bottomLeft) + 39.13)]; 354 | [bezier4Path curveToPoint: NSMakePoint(NSMinX(bottomLeft) + 69.6, NSMinY(bottomLeft) + 31.6) controlPoint1: NSMakePoint(NSMinX(bottomLeft) + 67.18, NSMinY(bottomLeft) + 37.6) controlPoint2: NSMakePoint(NSMinX(bottomLeft) + 68.08, NSMinY(bottomLeft) + 36.71)]; 355 | [bezier4Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 93.42, NSMinY(bottomLeft) + 31.6)]; 356 | [bezier4Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 62.51, NSMinY(bottomLeft) + 93.51)]; 357 | [bezier4Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 62.14, NSMinY(bottomLeft) + 93.43)]; 358 | [bezier4Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 62.14, NSMinY(bottomLeft) + 39.13)]; 359 | [bezier4Path closePath]; 360 | [fillColor7 setFill]; 361 | [bezier4Path fill]; 362 | 363 | 364 | //// Bezier 5 Drawing 365 | NSBezierPath* bezier5Path = [NSBezierPath bezierPath]; 366 | [bezier5Path moveToPoint: NSMakePoint(NSMinX(bottomLeft), NSMinY(bottomLeft) + 30.96)]; 367 | [bezier5Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 61.81, NSMinY(bottomLeft))]; 368 | [bezier5Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 61.81, NSMinY(bottomLeft) + 24.02)]; 369 | [bezier5Path curveToPoint: NSMakePoint(NSMinX(bottomLeft) + 54.44, NSMinY(bottomLeft) + 31.44) controlPoint1: NSMakePoint(NSMinX(bottomLeft) + 57.49, NSMinY(bottomLeft) + 24.57) controlPoint2: NSMakePoint(NSMinX(bottomLeft) + 54.99, NSMinY(bottomLeft) + 27.07)]; 370 | [bezier5Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 0.14, NSMinY(bottomLeft) + 31.44)]; 371 | [bezier5Path lineToPoint: NSMakePoint(NSMinX(bottomLeft), NSMinY(bottomLeft) + 30.96)]; 372 | [bezier5Path closePath]; 373 | [fillColor7 setFill]; 374 | [bezier5Path fill]; 375 | 376 | 377 | //// Bezier 6 Drawing 378 | NSBezierPath* bezier6Path = [NSBezierPath bezierPath]; 379 | [bezier6Path moveToPoint: NSMakePoint(NSMinX(bottomLeft) + 61.88, NSMinY(bottomLeft) + 28.3)]; 380 | [bezier6Path curveToPoint: NSMakePoint(NSMinX(bottomLeft) + 65.04, NSMinY(bottomLeft) + 31.45) controlPoint1: NSMakePoint(NSMinX(bottomLeft) + 63.75, NSMinY(bottomLeft) + 28.53) controlPoint2: NSMakePoint(NSMinX(bottomLeft) + 65.03, NSMinY(bottomLeft) + 29.51)]; 381 | [bezier6Path lineToPoint: NSMakePoint(NSMinX(bottomLeft) + 65.04, NSMinY(bottomLeft) + 31.47)]; 382 | [bezier6Path curveToPoint: NSMakePoint(NSMinX(bottomLeft) + 61.89, NSMinY(bottomLeft) + 34.52) controlPoint1: NSMakePoint(NSMinX(bottomLeft) + 65.01, NSMinY(bottomLeft) + 33.18) controlPoint2: NSMakePoint(NSMinX(bottomLeft) + 63.6, NSMinY(bottomLeft) + 34.55)]; 383 | [bezier6Path curveToPoint: NSMakePoint(NSMinX(bottomLeft) + 58.86, NSMinY(bottomLeft) + 31.58) controlPoint1: NSMakePoint(NSMinX(bottomLeft) + 60.27, NSMinY(bottomLeft) + 34.49) controlPoint2: NSMakePoint(NSMinX(bottomLeft) + 58.95, NSMinY(bottomLeft) + 33.21)]; 384 | [bezier6Path curveToPoint: NSMakePoint(NSMinX(bottomLeft) + 61.88, NSMinY(bottomLeft) + 28.3) controlPoint1: NSMakePoint(NSMinX(bottomLeft) + 58.77, NSMinY(bottomLeft) + 29.7) controlPoint2: NSMakePoint(NSMinX(bottomLeft) + 60, NSMinY(bottomLeft) + 28.66)]; 385 | [bezier6Path closePath]; 386 | [fillColor7 setFill]; 387 | [bezier6Path fill]; 388 | } 389 | 390 | 391 | //// Text Drawing 392 | NSRect textRect = NSMakeRect(NSMinX(frame) + floor(frame.size.width * 0.42422 + 0.5), NSMinY(frame) + frame.size.height - 20, floor(frame.size.width * 0.57656 + 0.5) - floor(frame.size.width * 0.42422 + 0.5), 21); 393 | NSMutableParagraphStyle* textStyle = [[NSMutableParagraphStyle alloc] init]; 394 | textStyle.alignment = NSTextAlignmentCenter; 395 | NSDictionary* textFontAttributes = @{NSFontAttributeName: [NSFont systemFontOfSize: 14], NSForegroundColorAttributeName: fillColor6, NSParagraphStyleAttributeName: textStyle}; 396 | 397 | CGFloat textTextHeight = [versionText boundingRectWithSize: textRect.size options: NSStringDrawingUsesLineFragmentOrigin attributes: textFontAttributes].size.height; 398 | NSRect textTextRect = NSMakeRect(NSMinX(textRect), NSMinY(textRect) + (textRect.size.height - textTextHeight) / 2, textRect.size.width, textTextHeight); 399 | [NSGraphicsContext saveGraphicsState]; 400 | NSRectClip(textRect); 401 | [versionText drawInRect: NSOffsetRect(textTextRect, 0, -0.5) withAttributes: textFontAttributes]; 402 | [NSGraphicsContext restoreGraphicsState]; 403 | } 404 | 405 | void OBSDALDrawDialWithFrame(NSRect frame, CGFloat rotation) 406 | { 407 | //// General Declarations 408 | CGContextRef context = NSGraphicsContext.currentContext.CGContext; 409 | 410 | //// Oval 3 Drawing 411 | NSBezierPath* oval3Path = [NSBezierPath bezierPathWithOvalInRect: NSMakeRect(NSMinX(frame) + frame.size.width - 133, NSMinY(frame) + 30, 98, 98)]; 412 | [NSColor.grayColor setFill]; 413 | [oval3Path fill]; 414 | 415 | 416 | //// Bezier 10 Drawing 417 | [NSGraphicsContext saveGraphicsState]; 418 | CGContextTranslateCTM(context, NSMaxX(frame) - 83.5, NSMinY(frame) + 79.5); 419 | CGContextRotateCTM(context, rotation * M_PI/180); 420 | 421 | NSBezierPath* bezier10Path = [NSBezierPath bezierPath]; 422 | [bezier10Path moveToPoint: NSMakePoint(-0, -0)]; 423 | [bezier10Path lineToPoint: NSMakePoint(-0, 48)]; 424 | [NSColor.blackColor setStroke]; 425 | bezier10Path.lineWidth = 2; 426 | [bezier10Path stroke]; 427 | 428 | [NSGraphicsContext restoreGraphicsState]; 429 | } 430 | 431 | 432 | NSImage *OBSDALImageOfTestCardWithSize(NSSize imageSize) 433 | { 434 | return [NSImage imageWithSize: imageSize flipped: YES drawingHandler: ^(__unused NSRect dstRect) 435 | { 436 | OBSDALDrawTestCardWithFrame(nil, NSMakeRect(0, 0, imageSize.width, imageSize.height)); 437 | return YES; 438 | }]; 439 | } 440 | -------------------------------------------------------------------------------- /src/obs-plugin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(obs-plugin) 2 | 3 | find_package(Libobs REQUIRED) 4 | 5 | find_library(AVFOUNDATION AVFoundation) 6 | find_library(COCOA Cocoa) 7 | find_library(COREFOUNDATION CoreFoundation) 8 | find_library(COREMEDIA CoreMedia) 9 | find_library(COREVIDEO CoreVideo) 10 | find_library(COCOA Cocoa) 11 | find_library(COREMEDIAIO CoreMediaIO) 12 | find_library(IOSURFACE IOSurface) 13 | find_library(IOKIT IOKit) 14 | 15 | set(CMAKE_PREFIX_PATH "${QTDIR}") 16 | find_package(Qt5Core REQUIRED) 17 | find_package(Qt5Widgets REQUIRED) 18 | 19 | configure_file( 20 | Defines.h.in 21 | ../../../src/obs-plugin/Defines.generated.h 22 | ) 23 | 24 | include_directories(${AVFOUNDATION} 25 | ${COCOA} 26 | ${COREFOUNDATION} 27 | ${COREMEDIA} 28 | ${COREVIDEO} 29 | ${COREMEDIAIO} 30 | ${COCOA} 31 | ${IOSURFACE} 32 | ${LIBOBS_INCLUDE_DIR} 33 | "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" 34 | ${Qt5Core_INCLUDES} 35 | ${Qt5Widgets_INCLUDES} 36 | ./ 37 | ../common) 38 | 39 | set(obs-plugin_SOURCES 40 | Defines.generated.h 41 | plugin-main.mm 42 | MachServer.h 43 | MachServer.mm 44 | ../common/MachProtocol.h) 45 | 46 | add_library(obs-plugin MODULE 47 | ${obs-plugin_SOURCES} 48 | ${obs-plugin_HEADERS}) 49 | 50 | target_link_libraries(obs-plugin 51 | ${AVFOUNDATION} 52 | ${COCOA} 53 | ${COREFOUNDATION} 54 | ${COREMEDIA} 55 | ${COREVIDEO} 56 | ${COREMEDIAIO} 57 | ${IOSURFACE} 58 | ${IOKIT} 59 | ${LIBOBS_LIB} 60 | ${OBS_FRONTEND_LIB} 61 | Qt5::Core 62 | Qt5::Widgets) 63 | 64 | set_target_properties(obs-plugin PROPERTIES 65 | PREFIX "" 66 | OUTPUT_NAME "obs-mac-virtualcam" 67 | COMPILE_FLAGS "-std=gnu++14 -stdlib=libc++ -fobjc-arc -fobjc-weak" 68 | ) 69 | -------------------------------------------------------------------------------- /src/obs-plugin/Defines.h.in: -------------------------------------------------------------------------------- 1 | // 2 | // Defines.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 5/27/20. 6 | // 7 | // obs-mac-virtualcam is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 2 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // obs-mac-virtualcam is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU General Public License 18 | // along with obs-mac-virtualcam. If not, see . 19 | 20 | #define PLUGIN_NAME "@CMAKE_PROJECT_NAME@" 21 | #define PLUGIN_VERSION "@CMAKE_PROJECT_VERSION@" 22 | 23 | #define blog(level, msg, ...) blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) 24 | -------------------------------------------------------------------------------- /src/obs-plugin/MachServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MachServer.h 3 | // obs-mac-virtualcam 4 | // 5 | // Created by John Boiles on 5/5/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface MachServer : NSObject 13 | 14 | - (void)run; 15 | 16 | /*! 17 | Will eventually be used for sending frames to all connected clients 18 | */ 19 | - (void)sendFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp fpsNumerator:(uint32_t)fpsNumerator fpsDenominator:(uint32_t)fpsDenominator frameBytes:(uint8_t *)frameBytes; 20 | 21 | - (void)stop; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /src/obs-plugin/MachServer.mm: -------------------------------------------------------------------------------- 1 | // 2 | // MachServer.m 3 | // mac-virtualcam 4 | // 5 | // Created by John Boiles on 5/5/20. 6 | // 7 | 8 | #import "MachServer.h" 9 | #import 10 | #include 11 | #include "MachProtocol.h" 12 | #include "Defines.generated.h" 13 | 14 | @interface MachServer () 15 | @property NSPort *port; 16 | @property NSMutableSet *clientPorts; 17 | @property NSRunLoop *runLoop; 18 | @end 19 | 20 | 21 | @implementation MachServer 22 | 23 | - (id)init { 24 | if (self = [super init]) { 25 | self.clientPorts = [[NSMutableSet alloc] init]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)dealloc { 31 | blog(LOG_DEBUG, "tearing down MachServer"); 32 | [self.runLoop removePort:self.port forMode:NSDefaultRunLoopMode]; 33 | [self.port invalidate]; 34 | self.port.delegate = nil; 35 | } 36 | 37 | - (void)run { 38 | if (self.port != nil) { 39 | blog(LOG_DEBUG, "mach server already running!"); 40 | return; 41 | } 42 | 43 | // It's a bummer this is deprecated. The replacement, NSXPCConnection, seems to require 44 | // an assistant process that lives inside the .app bundle. This would be more modern, but adds 45 | // complexity and I think makes it impossible to just run the `obs` binary from the commandline. 46 | // So let's stick with NSMachBootstrapServer at least until it fully goes away. 47 | // At that point we can decide between NSXPCConnection and using the CoreFoundation versions of 48 | // these APIs (which are, interestingly, not deprecated) 49 | #pragma clang diagnostic push 50 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 51 | self.port = [[NSMachBootstrapServer sharedInstance] servicePortWithName:@MACH_SERVICE_NAME]; 52 | #pragma clang diagnostic pop 53 | if (self.port == nil) { 54 | // This probably means another instance is running. 55 | blog(LOG_ERROR, "Unable to open mach server port."); 56 | return; 57 | } 58 | 59 | self.port.delegate = self; 60 | 61 | self.runLoop = [NSRunLoop currentRunLoop]; 62 | [self.runLoop addPort:self.port forMode:NSDefaultRunLoopMode]; 63 | 64 | blog(LOG_DEBUG, "mach server running!"); 65 | } 66 | 67 | - (void)handlePortMessage:(NSPortMessage *)message { 68 | switch (message.msgid) { 69 | case MachMsgIdConnect: 70 | if (message.sendPort != nil) { 71 | blog(LOG_DEBUG, "mach server received connect message from port %d!", ((NSMachPort *)message.sendPort).machPort); 72 | [self.clientPorts addObject:message.sendPort]; 73 | } 74 | break; 75 | default: 76 | blog(LOG_ERROR, "Unexpected mach message ID %u", (unsigned)message.msgid); 77 | break; 78 | } 79 | } 80 | 81 | - (void)sendMessageToClientsWithMsgId:(uint32_t)msgId components:(nullable NSArray *)components { 82 | if ([self.clientPorts count] <= 0) { 83 | return; 84 | } 85 | 86 | NSMutableSet *removedPorts = [NSMutableSet set]; 87 | 88 | for (NSPort *port in self.clientPorts) { 89 | @try { 90 | NSPortMessage *message = [[NSPortMessage alloc] initWithSendPort:port receivePort:nil components:components]; 91 | message.msgid = msgId; 92 | if (![message sendBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]) { 93 | blog(LOG_DEBUG, "failed to send message to %d, removing it from the clients!", ((NSMachPort *)port).machPort); 94 | [removedPorts addObject:port]; 95 | } 96 | } @catch (NSException *exception) { 97 | blog(LOG_DEBUG, "failed to send message (exception) to %d, removing it from the clients!", ((NSMachPort *)port).machPort); 98 | [removedPorts addObject:port]; 99 | } 100 | } 101 | 102 | // Remove dead ports if necessary 103 | [self.clientPorts minusSet:removedPorts]; 104 | } 105 | 106 | - (void)sendFrameWithSize:(NSSize)size timestamp:(uint64_t)timestamp fpsNumerator:(uint32_t)fpsNumerator fpsDenominator:(uint32_t)fpsDenominator frameBytes:(uint8_t *)frameBytes { 107 | if ([self.clientPorts count] <= 0) { 108 | return; 109 | } 110 | 111 | @autoreleasepool { 112 | CGFloat width = size.width; 113 | NSData *widthData = [NSData dataWithBytes:&width length:sizeof(width)]; 114 | CGFloat height = size.height; 115 | NSData *heightData = [NSData dataWithBytes:&height length:sizeof(height)]; 116 | NSData *timestampData = [NSData dataWithBytes:×tamp length:sizeof(timestamp)]; 117 | NSData *fpsNumeratorData = [NSData dataWithBytes:&fpsNumerator length:sizeof(fpsNumerator)]; 118 | NSData *fpsDenominatorData = [NSData dataWithBytes:&fpsDenominator length:sizeof(fpsDenominator)]; 119 | 120 | // NOTE: I'm not totally sure about the safety of dataWithBytesNoCopy in this context. 121 | // Seems like there could potentially be an issue if the frameBuffer went away before the 122 | // mach message finished sending. But it seems to be working and avoids a memory copy. Alternately 123 | // we could do something like 124 | // NSData *frameData = [NSData dataWithBytes:(void *)frameBytes length:size.width * size.height * 2]; 125 | NSData *frameData = [NSData dataWithBytesNoCopy:(void *)frameBytes length:size.width * size.height * 2 freeWhenDone:NO]; 126 | [self sendMessageToClientsWithMsgId:MachMsgIdFrame components:@[widthData, heightData, timestampData, frameData, fpsNumeratorData, fpsDenominatorData]]; 127 | } 128 | } 129 | 130 | - (void)stop { 131 | blog(LOG_DEBUG, "sending stop message to %lu clients", self.clientPorts.count); 132 | [self sendMessageToClientsWithMsgId:MachMsgIdStop components:nil]; 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /src/obs-plugin/data/locale/en-US.ini: -------------------------------------------------------------------------------- 1 | UnsupportedResolution_Title="Unsupported resolution" 2 | UnsupportedResolution_Main="Your output resolution not supported. Please use one of the following:" 3 | VirtualCamera_Start="Start Virtual Camera" 4 | VirtualCamera_Stop="Stop Virtual Camera" 5 | Plugin_Name="macOS Virtual Webcam" 6 | -------------------------------------------------------------------------------- /src/obs-plugin/plugin-main.mm: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "MachServer.h" 10 | #include "Defines.generated.h" 11 | 12 | OBS_DECLARE_MODULE() 13 | OBS_MODULE_USE_DEFAULT_LOCALE("mac-virtualcam", "en-US") 14 | MODULE_EXPORT const char *obs_module_description(void) 15 | { 16 | return "macOS virtual webcam output"; 17 | } 18 | 19 | obs_output_t *output; 20 | obs_video_info videoInfo; 21 | // Tools menu action for starting and stopping the virtual camera 22 | QAction *action; 23 | static MachServer *sMachServer; 24 | 25 | static const char *virtualcam_output_get_name(void *type_data) 26 | { 27 | (void)type_data; 28 | return obs_module_text("macOS Virtual Webcam"); 29 | } 30 | 31 | // This is a dummy pointer so we have something to return from virtualcam_output_create 32 | static void *data = &data; 33 | 34 | static void *virtualcam_output_create(obs_data_t *settings, obs_output_t *output) 35 | { 36 | blog(LOG_DEBUG, "output_create"); 37 | sMachServer = [[MachServer alloc] init]; 38 | return data; 39 | } 40 | 41 | static void virtualcam_output_destroy(void *data) 42 | { 43 | blog(LOG_DEBUG, "output_destroy"); 44 | sMachServer = nil; 45 | } 46 | 47 | static bool virtualcam_output_start(void *data) 48 | { 49 | blog(LOG_DEBUG, "output_start"); 50 | 51 | [sMachServer run]; 52 | 53 | obs_get_video_info(&videoInfo); 54 | 55 | struct video_scale_info conversion = {}; 56 | conversion.format = VIDEO_FORMAT_UYVY; 57 | conversion.width = videoInfo.output_width; 58 | conversion.height = videoInfo.output_height; 59 | obs_output_set_video_conversion(output, &conversion); 60 | if (!obs_output_begin_data_capture(output, 0)) { 61 | return false; 62 | } 63 | 64 | return true; 65 | } 66 | 67 | static void virtualcam_output_stop(void *data, uint64_t ts) 68 | { 69 | blog(LOG_DEBUG, "output_stop"); 70 | obs_output_end_data_capture(output); 71 | [sMachServer stop]; 72 | } 73 | 74 | static void virtualcam_output_raw_video(void *data, struct video_data *frame) 75 | { 76 | uint8_t *outData = frame->data[0]; 77 | if (frame->linesize[0] != (videoInfo.output_width * 2)) { 78 | blog(LOG_ERROR, "unexpected frame->linesize (expected:%d actual:%d)", (videoInfo.output_width * 2), frame->linesize[0]); 79 | } 80 | 81 | CGFloat width = videoInfo.output_width; 82 | CGFloat height = videoInfo.output_height; 83 | 84 | [sMachServer sendFrameWithSize:NSMakeSize(width, height) timestamp:frame->timestamp fpsNumerator:videoInfo.fps_num fpsDenominator:videoInfo.fps_den frameBytes:outData]; 85 | } 86 | 87 | struct obs_output_info virtualcam_output_info = { 88 | .id = "virtualcam_output", 89 | .flags = OBS_OUTPUT_VIDEO, 90 | .get_name = virtualcam_output_get_name, 91 | .create = virtualcam_output_create, 92 | .destroy = virtualcam_output_destroy, 93 | .start = virtualcam_output_start, 94 | .stop = virtualcam_output_stop, 95 | .raw_video = virtualcam_output_raw_video, 96 | }; 97 | 98 | bool obs_module_load(void) 99 | { 100 | blog(LOG_INFO, "version=%s", PLUGIN_VERSION); 101 | 102 | QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window(); 103 | action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("Start Virtual Camera")); 104 | auto menu_cb = [] 105 | { 106 | if (obs_output_active(output)) { 107 | action->setText(obs_module_text("Start Virtual Camera")); 108 | obs_output_stop(output); 109 | obs_output_release(output); 110 | output = NULL; 111 | } else { 112 | action->setText(obs_module_text("Stop Virtual Camera")); 113 | OBSData settings; 114 | output = obs_output_create("virtualcam_output", "virtualcam_output", settings, NULL); 115 | obs_output_start(output); 116 | } 117 | }; 118 | action->connect(action, &QAction::triggered, menu_cb); 119 | 120 | obs_register_output(&virtualcam_output_info); 121 | return true; 122 | } 123 | 124 | --------------------------------------------------------------------------------