├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── VCamSample.sln ├── VCamSample ├── Resource.h ├── Tools.cpp ├── Tools.h ├── VCamSample.cpp ├── VCamSample.h ├── VCamSample.ico ├── VCamSample.rc ├── VCamSample.vcxproj ├── VCamSample.vcxproj.filters ├── WinTrace.cpp ├── WinTrace.h ├── framework.h ├── packages.config └── targetver.h └── VCamSampleSource ├── Activator.cpp ├── Activator.h ├── EnumNames.cpp ├── EnumNames.h ├── FrameGenerator.cpp ├── FrameGenerator.h ├── MFTools.cpp ├── MFTools.h ├── MediaSource.cpp ├── MediaSource.h ├── MediaStream.cpp ├── MediaStream.h ├── Tools.cpp ├── Tools.h ├── Undocumented.h ├── VCamSampleSource.def ├── VCamSampleSource.rc ├── VCamSampleSource.vcxproj ├── VCamSampleSource.vcxproj.filters ├── WinTrace.cpp ├── WinTrace.h ├── dllmain.cpp ├── framework.h ├── packages.config ├── pch.cpp ├── pch.h └── resource.h /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-2025 Simon Mourier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VCamSample 2 | This solution contains a Media Foundation Virtual Camera Sample. It works only on Windows 11 thanks to the [MFCreateVirtualCamera](https://learn.microsoft.com/en-us/windows/win32/api/mfvirtualcamera/nf-mfvirtualcamera-mfcreatevirtualcamera) API. 3 | 4 | There are two projects in the solution: 5 | 6 | * **VCamSampleSource**: the Media Source that provides RGB32 and NV12 streaming samples. 7 | * **VCamSample**: the "driver" application that does very little but calls `MFCreateVirtualCamera`. 8 | 9 | Note there's a **VCamNetSample** .NET C# port of this project available here : https://github.com/smourier/VCamNetSample 10 | 11 | To test the virtual cam: 12 | 13 | * Build in debug or release 14 | * Go to the build output and register the media source (a COM object) with a command similar to this: `regsvr32 VCamSampleSource.dll` (you *must* run this as administrator, it' not possible to register a Virtual Camera media source in `HKCU`, only in `HKLM` since it will be loaded by multiple processes) 15 | * Run the VCamSample app. 16 | * Run for example the Windows Camera app or using a Web Browser ImageCapture API 17 | 18 | You should now see something like this in the Windows Camera App 19 | 20 | ![Screenshot 2024-01-22 131726](https://github.com/smourier/VCamSample/assets/5328574/50b27acb-3cf7-4d41-9298-84f7c1358148) 21 | 22 | Something like this in Windows' Edge Web Browser, using this testing page: https://googlechrome.github.io/samples/image-capture/grab-frame-take-photo.html 23 | 24 | ![Screenshot 2024-01-22 133220](https://github.com/smourier/VCamSample/assets/5328574/1f7d34e9-5646-4f26-bc9a-534e3bc9d625) 25 | 26 | Something like this in OBS (Video Capture Device): 27 | 28 | ![image](https://github.com/smourier/VCamSample/assets/5328574/47768c63-2979-40ab-ae70-fca632b97d81) 29 | 30 | 31 | ## Notes 32 | 33 | * The media source uses `Direct2D` and `DirectWrite` to create images. It will then create Media Foundation samples from these. To create MF samples, it can use: 34 | * The GPU, if a Direct3D manager has been provided by the environment. This is the case of the Windows 11 camera app. 35 | * The CPU, if no Direct3D environment has been provided. In this case, the media source uses a WIC bitmap as a render target and it then copies the bits over to an MF sample. The ImageCapture API code embedded in Chrome or Edge, Teams, etc. is an example of such a D3D-less environment. 36 | * If you want to force CPU usage at all times, you can change the code in `MediaStream::SetD3DManager` and put the lines there in comment. 37 | 38 | * The media source provides RGB32 and NV12 formats as most setups prefer the NV12 format. Samples are initially created as RGB32 (Direct2D) and converted to NV12. To convert the samples, the media source uses two ways: 39 | * The GPU, if a Direct3D manager has been provided, using Media Foundation's [Video Processor MFT](https://learn.microsoft.com/en-us/windows/win32/medfound/video-processor-mft). 40 | * The CPU, if no Direct3D environment has been provided. In this case, the RGB to NV12 conversion is done in the code (so on the CPU). 41 | * If you want to force RGB32 mode, you can change the code in `MediaStream::Initialize` and set the media types array size to 1 (check comments in the code). 42 | 43 | * The code crrently has an issue where the virtual camera screen is shown in the preview window of apps such as Microsoft Teams, but it's not rendered to the communicating party. Not sure why it doesn't fully work yet, if you know, just ping me! 44 | 45 | ## Troubleshooting "Access Denied" on IMFVirtualCamera::Start method 46 | If you get access denied here, it's probably the same issue as here https://github.com/smourier/VCamSample/issues/1 47 | 48 | Here is a summary: 49 | 50 | * The COM object that serves as a Virtual Camera Source (here `VCamSampleSource.dll`) must be accessible by the two Windows 11 services **Frame Server** & **Frame Server Monitor** (running as `svchost.exe`). 51 | * These two services usually run as *Local Service* & *Local System* credentials respectively. 52 | * If you compile or build in a directory under your compiling user's root, for example something like `C:\Users\\source\repos\VCamSample\x64\Debug\` or somewhere restricted in some way, **it won't work** since these two services will need to access that. 53 | 54 | => So the solution is just to either copy the output directory once built (or downloaded) somewhere where everyone has access and register `VCamSampleSource.dll` from there, or copy/checkout the whole repo where everyone has access and build and register there. 55 | 56 | Also, if you downloaded the binaries from the internet, not compiled them by yourself, make sure you must remove the Mark of the Web (https://en.wikipedia.org/wiki/Mark_of_the_Web) click on "Unblock" on the .zip file you downloaded and press OK: 57 | 58 | ![image](https://github.com/smourier/VCamSample/assets/5328574/5856b780-995d-483e-83e4-1f8afd5c7b2c) 59 | 60 | ## Tracing 61 | 62 | The code output lots of interesting traces. It's quite important in this virtual camera environment because there's not just your process that's involved but at least 4: the VCamSample app, the Windows Frame Server, the Windows camera monitor, and the reader app (camera, etc.). They all load the media source COM object in-process. 63 | 64 | Tracing here doesn't use `OutputDebugString` because it's 100% old, crappy, truncating text, slow, etc. Instead it uses Event Tracing for Windows ("ETW") in "string-only" mode (the mode where it's very simple and you don't have to register painfull traces records and use complex readers...). 65 | 66 | So to read these ETW traces, use WpfTraceSpy you can download here https://github.com/smourier/TraceSpy. Configure an ETW Provider with the GUID set to `964d4572-adb9-4f3a-8170-fcbecec27467` 67 | -------------------------------------------------------------------------------- /VCamSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34408.163 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VCamSample", "VCamSample\VCamSample.vcxproj", "{184F456B-F443-4374-B41E-E37DA1B1928D}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VCamSampleSource", "VCamSampleSource\VCamSampleSource.vcxproj", "{52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|ARM64 = Debug|ARM64 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|ARM64 = Release|ARM64 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Debug|ARM64.ActiveCfg = Debug|ARM64 21 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Debug|ARM64.Build.0 = Debug|ARM64 22 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Debug|x64.ActiveCfg = Debug|x64 23 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Debug|x64.Build.0 = Debug|x64 24 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Debug|x86.ActiveCfg = Debug|Win32 25 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Debug|x86.Build.0 = Debug|Win32 26 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Release|ARM64.ActiveCfg = Release|ARM64 27 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Release|ARM64.Build.0 = Release|ARM64 28 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Release|x64.ActiveCfg = Release|x64 29 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Release|x64.Build.0 = Release|x64 30 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Release|x86.ActiveCfg = Release|Win32 31 | {184F456B-F443-4374-B41E-E37DA1B1928D}.Release|x86.Build.0 = Release|Win32 32 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Debug|ARM64.ActiveCfg = Debug|ARM64 33 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Debug|ARM64.Build.0 = Debug|ARM64 34 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Debug|x64.ActiveCfg = Debug|x64 35 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Debug|x64.Build.0 = Debug|x64 36 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Debug|x86.ActiveCfg = Debug|Win32 37 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Debug|x86.Build.0 = Debug|Win32 38 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Release|ARM64.ActiveCfg = Release|ARM64 39 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Release|ARM64.Build.0 = Release|ARM64 40 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Release|x64.ActiveCfg = Release|x64 41 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Release|x64.Build.0 = Release|x64 42 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Release|x86.ActiveCfg = Release|Win32 43 | {52FB6B93-3AA6-4369-BED4-D4BFF1F97B78}.Release|x86.Build.0 = Release|Win32 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {F92936CC-1FDA-4781-9DD6-EF1D5C166245} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /VCamSample/Resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by VCamSample.rc 4 | 5 | #define IDS_APP_TITLE 103 6 | 7 | #define IDR_MAINFRAME 128 8 | #define IDD_VCAMSAMPLE_DIALOG 102 9 | #define IDD_ABOUTBOX 103 10 | #define IDM_ABOUT 104 11 | #define IDM_EXIT 105 12 | #define IDI_VCAMSAMPLE 107 13 | #define IDI_SMALL 108 14 | #define IDC_VCAMSAMPLE 109 15 | #define IDC_MYICON 2 16 | #ifndef IDC_STATIC 17 | #define IDC_STATIC -1 18 | #endif 19 | // Next default values for new objects 20 | // 21 | #ifdef APSTUDIO_INVOKED 22 | #ifndef APSTUDIO_READONLY_SYMBOLS 23 | 24 | #define _APS_NO_MFC 130 25 | #define _APS_NEXT_RESOURCE_VALUE 129 26 | #define _APS_NEXT_COMMAND_VALUE 32771 27 | #define _APS_NEXT_CONTROL_VALUE 1000 28 | #define _APS_NEXT_SYMED_VALUE 110 29 | #endif 30 | #endif 31 | -------------------------------------------------------------------------------- /VCamSample/Tools.cpp: -------------------------------------------------------------------------------- 1 | #include "framework.h" 2 | #include "Tools.h" 3 | 4 | std::wstring to_wstring(const std::string& s) 5 | { 6 | if (s.empty()) 7 | return std::wstring(); 8 | 9 | auto ssize = (int)s.size(); 10 | auto wsize = MultiByteToWideChar(CP_THREAD_ACP, 0, s.data(), ssize, nullptr, 0); 11 | if (!wsize) 12 | return std::wstring(); 13 | 14 | std::wstring ws; 15 | ws.resize(wsize); 16 | wsize = MultiByteToWideChar(CP_THREAD_ACP, 0, s.data(), ssize, &ws[0], wsize); 17 | if (!wsize) 18 | return std::wstring(); 19 | 20 | return ws; 21 | } 22 | 23 | const std::wstring GUID_ToStringW(const GUID& guid) 24 | { 25 | wchar_t name[64]; 26 | std::ignore = StringFromGUID2(guid, name, _countof(name)); 27 | return name; 28 | } 29 | 30 | void CenterWindow(HWND hwnd, bool useCursorPos) 31 | { 32 | if (!IsWindow(hwnd)) 33 | return; 34 | 35 | RECT rc{}; 36 | GetWindowRect(hwnd, &rc); 37 | auto width = rc.right - rc.left; 38 | auto height = rc.bottom - rc.top; 39 | 40 | if (useCursorPos) 41 | { 42 | POINT pt{}; 43 | if (GetCursorPos(&pt)) 44 | { 45 | auto monitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); 46 | MONITORINFOEX mi{}; 47 | mi.cbSize = sizeof(MONITORINFOEX); 48 | if (GetMonitorInfo(monitor, &mi)) 49 | { 50 | SetWindowPos(hwnd, NULL, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2, mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2, 0, 0, SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER); 51 | return; 52 | } 53 | } 54 | } 55 | 56 | SetWindowPos(hwnd, NULL, (GetSystemMetrics(SM_CXSCREEN) - width) / 2, (GetSystemMetrics(SM_CYSCREEN) - height) / 2, 0, 0, SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER); 57 | } 58 | -------------------------------------------------------------------------------- /VCamSample/Tools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | std::wstring to_wstring(const std::string& s); 4 | const std::wstring GUID_ToStringW(const GUID& guid); 5 | 6 | void CenterWindow(HWND hwnd, bool useCursorPos = true); 7 | -------------------------------------------------------------------------------- /VCamSample/VCamSample.cpp: -------------------------------------------------------------------------------- 1 | #include "framework.h" 2 | #include "tools.h" 3 | #include "VCamSample.h" 4 | 5 | #define MAX_LOADSTRING 100 6 | 7 | // 3cad447d-f283-4af4-a3b2-6f5363309f52 8 | static GUID CLSID_VCam = { 0x3cad447d,0xf283,0x4af4,{0xa3,0xb2,0x6f,0x53,0x63,0x30,0x9f,0x52} }; 9 | 10 | HINSTANCE _instance; 11 | WCHAR _title[MAX_LOADSTRING]; 12 | WCHAR _windowClass[MAX_LOADSTRING]; 13 | wil::com_ptr_nothrow _vcam; 14 | DWORD _vcamCookie; 15 | 16 | ATOM MyRegisterClass(HINSTANCE hInstance); 17 | HWND InitInstance(HINSTANCE, int); 18 | LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 19 | INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); 20 | HRESULT RegisterVirtualCamera(); 21 | HRESULT UnregisterVirtualCamera(); 22 | 23 | int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) 24 | { 25 | UNREFERENCED_PARAMETER(hPrevInstance); 26 | UNREFERENCED_PARAMETER(lpCmdLine); 27 | 28 | // set tracing & CRT leak tracking 29 | WinTraceRegister(); 30 | WINTRACE(L"WinMain starting '%s'", GetCommandLineW()); 31 | _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); 32 | 33 | wil::SetResultLoggingCallback([](wil::FailureInfo const& failure) noexcept 34 | { 35 | wchar_t str[2048]; 36 | if (SUCCEEDED(wil::GetFailureLogString(str, _countof(str), failure))) 37 | { 38 | WinTrace(2, 0, str); // 2 => error 39 | #ifndef _DEBUG 40 | TaskDialog(nullptr, nullptr, _title, L"A fatal error has occured. Press OK to terminate.", str, TDCBF_OK_BUTTON, TD_WARNING_ICON, nullptr); 41 | #endif 42 | } 43 | }); 44 | 45 | LoadStringW(hInstance, IDS_APP_TITLE, _title, MAX_LOADSTRING); 46 | LoadStringW(hInstance, IDC_VCAMSAMPLE, _windowClass, MAX_LOADSTRING); 47 | MyRegisterClass(hInstance); 48 | auto hwnd = InitInstance(hInstance, nCmdShow); 49 | if (hwnd) 50 | { 51 | winrt::init_apartment(); 52 | if (SUCCEEDED(MFStartup(MF_VERSION))) 53 | { 54 | TASKDIALOGCONFIG config{}; 55 | config.cbSize = sizeof(TASKDIALOGCONFIG); 56 | config.hInstance = hInstance; 57 | config.hwndParent = hwnd; 58 | config.pszWindowTitle = _title; 59 | config.dwCommonButtons = TDCBF_CLOSE_BUTTON; 60 | auto hr = RegisterVirtualCamera(); 61 | if (SUCCEEDED(hr)) 62 | { 63 | config.pszMainInstruction = L"VCam was started, you can now run a program such as Windows Camera to visualize the output.\nPress Close to stop VCam and exit this program."; 64 | config.pszContent = L"This may stop VCam access for visualizing programs too."; 65 | config.pszMainIcon = TD_INFORMATION_ICON; 66 | TaskDialogIndirect(&config, nullptr, nullptr, nullptr); 67 | 68 | //auto accelerators = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_VCAMSAMPLE)); 69 | //MSG msg; 70 | //while (GetMessage(&msg, nullptr, 0, 0)) 71 | //{ 72 | // if (!TranslateAccelerator(msg.hwnd, accelerators, &msg)) 73 | // { 74 | // TranslateMessage(&msg); 75 | // DispatchMessage(&msg); 76 | // } 77 | //} 78 | 79 | UnregisterVirtualCamera(); 80 | } 81 | else 82 | { 83 | config.pszMainInstruction = L"VCam could not be started. Make sure you have registered the VCamSampleSource dll.\nPress Close to exit this program."; 84 | wchar_t text[1024]; 85 | wchar_t errorText[256]; 86 | FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, hr, 0, errorText, _countof(errorText), nullptr); 87 | wsprintf(text, L"Error 0x%08X (%u): %s", hr, hr, errorText); 88 | config.pszContent = text; 89 | 90 | config.pszMainIcon = TD_ERROR_ICON; 91 | TaskDialogIndirect(&config, nullptr, nullptr, nullptr); 92 | } 93 | 94 | _vcam.reset(); 95 | MFShutdown(); 96 | } 97 | } 98 | 99 | // cleanup & CRT leak checks 100 | _CrtDumpMemoryLeaks(); 101 | WINTRACE(L"WinMain exiting '%s'", GetCommandLineW()); 102 | WinTraceUnregister(); 103 | return 0; 104 | } 105 | 106 | HRESULT RegisterVirtualCamera() 107 | { 108 | auto clsid = GUID_ToStringW(CLSID_VCam); 109 | RETURN_IF_FAILED_MSG(MFCreateVirtualCamera( 110 | MFVirtualCameraType_SoftwareCameraSource, 111 | MFVirtualCameraLifetime_Session, 112 | MFVirtualCameraAccess_CurrentUser, 113 | _title, 114 | clsid.c_str(), 115 | nullptr, 116 | 0, 117 | &_vcam), 118 | "Failed to create virtual camera"); 119 | 120 | WINTRACE(L"RegisterVirtualCamera '%s' ok", clsid.c_str()); 121 | RETURN_IF_FAILED_MSG(_vcam->Start(nullptr), "Cannot start VCam"); 122 | WINTRACE(L"VCam was started"); 123 | return S_OK; 124 | } 125 | 126 | HRESULT UnregisterVirtualCamera() 127 | { 128 | if (!_vcam) 129 | return S_OK; 130 | 131 | // NOTE: we don't call Shutdown or this will cause 2 Shutdown calls to the media source and will prevent proper removing 132 | //auto hr = _vcam->Shutdown(); 133 | //WINTRACE(L"Shutdown VCam hr:0x%08X", hr); 134 | 135 | auto hr = _vcam->Remove(); 136 | WINTRACE(L"Remove VCam hr:0x%08X", hr); 137 | return S_OK; 138 | } 139 | 140 | ATOM MyRegisterClass(HINSTANCE instance) 141 | { 142 | WNDCLASSEXW wcex{}; 143 | wcex.cbSize = sizeof(WNDCLASSEX); 144 | wcex.style = CS_HREDRAW | CS_VREDRAW; 145 | wcex.lpfnWndProc = WndProc; 146 | wcex.hInstance = instance; 147 | wcex.hIcon = LoadIcon(instance, MAKEINTRESOURCE(IDI_VCAMSAMPLE)); 148 | wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); 149 | wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 150 | wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_VCAMSAMPLE); 151 | wcex.lpszClassName = _windowClass; 152 | wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); 153 | return RegisterClassExW(&wcex); 154 | } 155 | 156 | HWND InitInstance(HINSTANCE instance, int cmd) 157 | { 158 | _instance = instance; 159 | auto hwnd = CreateWindowW(_windowClass, _title, WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, nullptr, nullptr, instance, nullptr); 160 | if (!hwnd) 161 | return nullptr; 162 | 163 | CenterWindow(hwnd); 164 | ShowWindow(hwnd, cmd); 165 | UpdateWindow(hwnd); 166 | return hwnd; 167 | } 168 | 169 | LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 170 | { 171 | //#if _DEBUG 172 | // if (message != WM_NCMOUSEMOVE && message != WM_MOUSEMOVE && message != WM_SETCURSOR && message != WM_NCHITTEST && message != WM_MOUSELEAVE && 173 | // message != WM_GETICON && message != WM_PAINT) 174 | // { 175 | // if (message == 147 || message == 148) 176 | // { 177 | // WINTRACE("msg:%u 0x%08X (%s)", message, message, WM_ToString(message).c_str()); 178 | // } 179 | // } 180 | //#endif 181 | 182 | switch (message) 183 | { 184 | case WM_COMMAND: 185 | { 186 | auto wmId = LOWORD(wParam); 187 | switch (wmId) 188 | { 189 | case IDM_ABOUT: 190 | DialogBox(_instance, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, About); 191 | break; 192 | 193 | case IDM_EXIT: 194 | DestroyWindow(hwnd); 195 | break; 196 | default: 197 | return DefWindowProc(hwnd, message, wParam, lParam); 198 | } 199 | } 200 | break; 201 | 202 | case WM_PAINT: 203 | { 204 | PAINTSTRUCT ps; 205 | auto hdc = BeginPaint(hwnd, &ps); 206 | // TODO: Add any drawing code that uses hdc here... 207 | EndPaint(hwnd, &ps); 208 | } 209 | break; 210 | 211 | case WM_DESTROY: 212 | PostQuitMessage(0); 213 | break; 214 | 215 | default: 216 | return DefWindowProc(hwnd, message, wParam, lParam); 217 | } 218 | return 0; 219 | } 220 | 221 | INT_PTR CALLBACK About(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 222 | { 223 | UNREFERENCED_PARAMETER(lParam); 224 | switch (message) 225 | { 226 | case WM_INITDIALOG: 227 | return (INT_PTR)TRUE; 228 | 229 | case WM_COMMAND: 230 | if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) 231 | { 232 | EndDialog(hwnd, LOWORD(wParam)); 233 | return (INT_PTR)TRUE; 234 | } 235 | break; 236 | } 237 | return (INT_PTR)FALSE; 238 | } 239 | -------------------------------------------------------------------------------- /VCamSample/VCamSample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "resource.h" 4 | -------------------------------------------------------------------------------- /VCamSample/VCamSample.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smourier/VCamSample/a4dc8c0dc69daf3765d5673e8830f7769f24c9d1/VCamSample/VCamSample.ico -------------------------------------------------------------------------------- /VCamSample/VCamSample.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smourier/VCamSample/a4dc8c0dc69daf3765d5673e8830f7769f24c9d1/VCamSample/VCamSample.rc -------------------------------------------------------------------------------- /VCamSample/VCamSample.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | ARM64 8 | 9 | 10 | Debug 11 | Win32 12 | 13 | 14 | Release 15 | ARM64 16 | 17 | 18 | Release 19 | Win32 20 | 21 | 22 | Debug 23 | x64 24 | 25 | 26 | Release 27 | x64 28 | 29 | 30 | 31 | 17.0 32 | Win32Proj 33 | {184f456b-f443-4374-b41e-e37da1b1928d} 34 | VCamSample 35 | 10.0 36 | 37 | 38 | 39 | Application 40 | true 41 | v143 42 | Unicode 43 | 44 | 45 | Application 46 | false 47 | v143 48 | true 49 | Unicode 50 | 51 | 52 | Application 53 | true 54 | v143 55 | Unicode 56 | 57 | 58 | Application 59 | true 60 | v143 61 | Unicode 62 | 63 | 64 | Application 65 | false 66 | v143 67 | true 68 | Unicode 69 | 70 | 71 | Application 72 | false 73 | v143 74 | true 75 | Unicode 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 106 | true 107 | stdcpp20 108 | MultiThreadedDebug 109 | 110 | 111 | Windows 112 | true 113 | 114 | 115 | 116 | 117 | Level3 118 | true 119 | true 120 | true 121 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 122 | true 123 | stdcpp20 124 | MultiThreaded 125 | 126 | 127 | Windows 128 | true 129 | true 130 | true 131 | 132 | 133 | 134 | 135 | Level3 136 | true 137 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 138 | true 139 | stdcpp20 140 | MultiThreadedDebug 141 | 142 | 143 | Windows 144 | true 145 | 146 | 147 | 148 | 149 | Level3 150 | true 151 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 152 | true 153 | stdcpp20 154 | MultiThreadedDebug 155 | 156 | 157 | Windows 158 | true 159 | 160 | 161 | 162 | 163 | Level3 164 | true 165 | true 166 | true 167 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 168 | true 169 | stdcpp20 170 | MultiThreaded 171 | 172 | 173 | Windows 174 | true 175 | true 176 | true 177 | 178 | 179 | 180 | 181 | Level3 182 | true 183 | true 184 | true 185 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 186 | true 187 | stdcpp20 188 | MultiThreaded 189 | 190 | 191 | Windows 192 | true 193 | true 194 | true 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /VCamSample/VCamSample.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | 49 | 50 | Resource Files 51 | 52 | 53 | 54 | 55 | Resource Files 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /VCamSample/WinTrace.cpp: -------------------------------------------------------------------------------- 1 | #include "framework.h" 2 | #include "Tools.h" 3 | 4 | // we don't use OutputDebugString because it's 100% crap, truncating, slow, etc. 5 | // use WpfTraceSpy https://github.com/smourier/TraceSpy to see these traces (configure an ETW Provider with guid set to 964d4572-adb9-4f3a-8170-fcbecec27467) 6 | static GUID GUID_WinTraceProvider = { 0x964d4572,0xadb9,0x4f3a,{0x81,0x70,0xfc,0xbe,0xce,0xc2,0x74,0x67} }; 7 | 8 | REGHANDLE _traceHandle = 0; 9 | 10 | HRESULT GetTraceId(GUID* pGuid) 11 | { 12 | if (!pGuid) 13 | return E_INVALIDARG; 14 | 15 | *pGuid = GUID_WinTraceProvider; 16 | return S_OK; 17 | } 18 | 19 | ULONG WinTraceRegister() 20 | { 21 | return EventRegister(&GUID_WinTraceProvider, nullptr, nullptr, &_traceHandle); 22 | } 23 | 24 | void WinTraceUnregister() 25 | { 26 | auto h = _traceHandle; 27 | if (h) 28 | { 29 | _traceHandle = 0; 30 | EventUnregister(h); 31 | } 32 | } 33 | 34 | void WinTraceFormat(UCHAR level, ULONGLONG keyword, PCWSTR format, ...) 35 | { 36 | if (!_traceHandle) 37 | return; 38 | 39 | WCHAR szTrace[2048]; 40 | va_list args; 41 | va_start(args, format); 42 | // add '00000000:' before all traces 43 | StringCchPrintf(szTrace, (size_t)(9 + 1), L"%08X:", GetCurrentThreadId()); 44 | StringCchVPrintfW(((LPWSTR)szTrace) + 9, _countof(szTrace) - 10, format, args); 45 | va_end(args); 46 | EventWriteString(_traceHandle, level, keyword, szTrace); 47 | } 48 | 49 | void WinTraceFormat(UCHAR level, ULONGLONG keyword, PCSTR format, ...) 50 | { 51 | if (!_traceHandle) 52 | return; 53 | 54 | CHAR szTrace[2048]; 55 | va_list args; 56 | va_start(args, format); 57 | StringCchPrintfA(szTrace, (size_t)(9 + 1), "%08X:", GetCurrentThreadId()); 58 | StringCchVPrintfA(((LPSTR)szTrace) + 9, _countof(szTrace) - 10, format, args); 59 | va_end(args); 60 | EventWriteString(_traceHandle, level, keyword, to_wstring(szTrace).c_str()); 61 | } 62 | 63 | void WinTrace(UCHAR level, ULONGLONG keyword, PCSTR string) 64 | { 65 | if (!_traceHandle) 66 | return; 67 | 68 | EventWriteString(_traceHandle, level, keyword, to_wstring(string).c_str()); 69 | } 70 | 71 | void WinTrace(UCHAR level, ULONGLONG keyword, PCWSTR string) 72 | { 73 | if (!_traceHandle) 74 | return; 75 | 76 | EventWriteString(_traceHandle, level, keyword, string); 77 | } -------------------------------------------------------------------------------- /VCamSample/WinTrace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | HRESULT GetTraceId(GUID* pGuid); 4 | 5 | ULONG WinTraceRegister(); 6 | void WinTraceUnregister(); 7 | 8 | void WinTrace(UCHAR Level, ULONGLONG Keyword, PCWSTR String); 9 | void WinTraceFormat(UCHAR Level, ULONGLONG Keyword, PCWSTR pszFormat, ...); 10 | 11 | void WinTrace(UCHAR Level, ULONGLONG Keyword, PCSTR String); 12 | void WinTraceFormat(UCHAR Level, ULONGLONG Keyword, PCSTR pszFormat, ...); 13 | 14 | #ifdef _DEBUG 15 | #define WINTRACE(...) WinTraceFormat(0, 0, __VA_ARGS__) 16 | #else 17 | #define WINTRACE __noop 18 | #endif 19 | #pragma once 20 | -------------------------------------------------------------------------------- /VCamSample/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "targetver.h" 4 | 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN // exclude rarely-used stuff from Windows headers. 7 | #endif 8 | 9 | #define _CRTDBG_MAP_ALLOC 10 | #include 11 | #include 12 | #ifdef _DEBUG 13 | #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) 14 | // replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the allocations to be of _CLIENT_BLOCK type 15 | #else 16 | #define DBG_NEW new 17 | #endif 18 | 19 | // windows 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "winrt\Windows.ApplicationModel.h" 32 | 33 | // std 34 | #include 35 | #include 36 | 37 | // WIL, requires "Microsoft.Windows.ImplementationLibrary" nuget 38 | #include "wil/result.h" 39 | #include "wil/stl.h" 40 | #include "wil/win32_helpers.h" 41 | #include "wil/com.h" 42 | 43 | // C++/WinRT, requires "Microsoft.Windows.CppWinRT" nuget 44 | #include "winrt/base.h" 45 | 46 | // project globals 47 | #include "wintrace.h" 48 | 49 | #pragma comment(lib, "mfsensorgroup") 50 | #pragma comment(lib, "comctl32") 51 | #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") -------------------------------------------------------------------------------- /VCamSample/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /VCamSample/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 5 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 6 | #include 7 | -------------------------------------------------------------------------------- /VCamSampleSource/Activator.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Undocumented.h" 3 | #include "Tools.h" 4 | #include "EnumNames.h" 5 | #include "MFTools.h" 6 | #include "FrameGenerator.h" 7 | #include "MediaStream.h" 8 | #include "MediaSource.h" 9 | #include "Activator.h" 10 | 11 | HRESULT Activator::Initialize() 12 | { 13 | _source = winrt::make_self(); 14 | RETURN_IF_FAILED(SetUINT32(MF_VIRTUALCAMERA_PROVIDE_ASSOCIATED_CAMERA_SOURCES, 1)); 15 | RETURN_IF_FAILED(SetGUID(MFT_TRANSFORM_CLSID_Attribute, CLSID_VCam)); 16 | RETURN_IF_FAILED(_source->Initialize(this)); 17 | return S_OK; 18 | } 19 | 20 | // IMFActivate 21 | STDMETHODIMP Activator::ActivateObject(REFIID riid, void** ppv) 22 | { 23 | WINTRACE(L"Activator::ActivateObject '%s'", GUID_ToStringW(riid).c_str()); 24 | RETURN_HR_IF_NULL(E_POINTER, ppv); 25 | *ppv = nullptr; 26 | 27 | // use undoc'd frame server property 28 | UINT32 pid = 0; 29 | if (SUCCEEDED(GetUINT32(MF_FRAMESERVER_CLIENTCONTEXT_CLIENTPID, &pid)) && pid) 30 | { 31 | auto name = GetProcessName(pid); 32 | if (!name.empty()) 33 | { 34 | WINTRACE(L"Activator::ActivateObject client process '%s'", name.c_str()); 35 | } 36 | } 37 | RETURN_IF_FAILED_MSG(_source->QueryInterface(riid, ppv), "Activator::ActivateObject failed on IID %s", GUID_ToStringW(riid).c_str()); 38 | return S_OK; 39 | } 40 | 41 | STDMETHODIMP Activator::ShutdownObject() 42 | { 43 | WINTRACE(L"Activator::ShutdownObject"); 44 | return S_OK; 45 | } 46 | 47 | STDMETHODIMP Activator::DetachObject() 48 | { 49 | WINTRACE(L"Activator::DetachObject"); 50 | _source = nullptr; 51 | return S_OK; 52 | } 53 | -------------------------------------------------------------------------------- /VCamSampleSource/Activator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Activator : winrt::implements> 4 | { 5 | public: 6 | // IMFActivate 7 | STDMETHOD(ActivateObject(REFIID riid, void** ppv)); 8 | STDMETHOD(ShutdownObject)(); 9 | STDMETHOD(DetachObject)(); 10 | 11 | public: 12 | Activator() 13 | { 14 | SetBaseAttributesTraceName(L"ActivatorAtts"); 15 | } 16 | 17 | HRESULT Initialize(); 18 | 19 | private: 20 | #if _DEBUG 21 | int32_t query_interface_tearoff(winrt::guid const& id, void** object) const noexcept override 22 | { 23 | if (id == winrt::guid_of()) 24 | { 25 | this->m_inner->AddRef(); 26 | *object = (IMFAttributes*)this; 27 | WINTRACE(L"Activator QueryInterface IMFAttributes ok"); 28 | return S_OK; 29 | } 30 | 31 | RETURN_HR_MSG(E_NOINTERFACE, "Activator QueryInterface failed on IID %s", GUID_ToStringW(id).c_str()); 32 | } 33 | #endif 34 | 35 | private: 36 | winrt::com_ptr _source; 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /VCamSampleSource/EnumNames.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Tools.h" 3 | #include "EnumNames.h" 4 | 5 | struct DWORDAndNameW 6 | { 7 | DWORD dw; 8 | const WCHAR* name; 9 | }; 10 | 11 | struct DWORDAndNameA 12 | { 13 | DWORD dw; 14 | const CHAR* name; 15 | }; 16 | 17 | #define ID_AND_NAME_W(x) { (DWORD)x, L#x } 18 | #define ID_AND_NAME_A(x) { (DWORD)x, #x } 19 | 20 | static DWORDAndNameA __WM[] = 21 | { 22 | ID_AND_NAME_A(WM_CREATE), 23 | ID_AND_NAME_A(WM_NULL), 24 | ID_AND_NAME_A(WM_CREATE), 25 | ID_AND_NAME_A(WM_DELETEITEM), 26 | ID_AND_NAME_A(WM_DESTROY), 27 | ID_AND_NAME_A(WM_MOVE), 28 | ID_AND_NAME_A(WM_SIZE), 29 | ID_AND_NAME_A(WM_ACTIVATE), 30 | ID_AND_NAME_A(WM_SETFOCUS), 31 | ID_AND_NAME_A(WM_KILLFOCUS), 32 | ID_AND_NAME_A(WM_ENABLE), 33 | ID_AND_NAME_A(WM_SETREDRAW), 34 | ID_AND_NAME_A(WM_SETTEXT), 35 | ID_AND_NAME_A(WM_GETTEXT), 36 | ID_AND_NAME_A(WM_GETTEXTLENGTH), 37 | ID_AND_NAME_A(WM_PAINT), 38 | ID_AND_NAME_A(WM_CLOSE), 39 | ID_AND_NAME_A(WM_QUERYENDSESSION), 40 | ID_AND_NAME_A(WM_QUIT), 41 | ID_AND_NAME_A(WM_QUERYOPEN), 42 | ID_AND_NAME_A(WM_ERASEBKGND), 43 | ID_AND_NAME_A(WM_SYSCOLORCHANGE), 44 | ID_AND_NAME_A(WM_ENDSESSION), 45 | ID_AND_NAME_A(WM_SHOWWINDOW), 46 | ID_AND_NAME_A(WM_WININICHANGE), 47 | ID_AND_NAME_A(WM_DEVMODECHANGE), 48 | ID_AND_NAME_A(WM_ACTIVATEAPP), 49 | ID_AND_NAME_A(WM_FONTCHANGE), 50 | ID_AND_NAME_A(WM_TIMECHANGE), 51 | ID_AND_NAME_A(WM_CANCELMODE), 52 | ID_AND_NAME_A(WM_SETCURSOR), 53 | ID_AND_NAME_A(WM_MOUSEACTIVATE), 54 | ID_AND_NAME_A(WM_CHILDACTIVATE), 55 | ID_AND_NAME_A(WM_QUEUESYNC), 56 | ID_AND_NAME_A(WM_GETMINMAXINFO), 57 | ID_AND_NAME_A(WM_PAINTICON), 58 | ID_AND_NAME_A(WM_ICONERASEBKGND), 59 | ID_AND_NAME_A(WM_NEXTDLGCTL), 60 | ID_AND_NAME_A(WM_SPOOLERSTATUS), 61 | ID_AND_NAME_A(WM_DRAWITEM), 62 | ID_AND_NAME_A(WM_MEASUREITEM), 63 | ID_AND_NAME_A(WM_VKEYTOITEM), 64 | ID_AND_NAME_A(WM_CHARTOITEM), 65 | ID_AND_NAME_A(WM_SETFONT), 66 | ID_AND_NAME_A(WM_GETFONT), 67 | ID_AND_NAME_A(WM_SETHOTKEY), 68 | ID_AND_NAME_A(WM_GETHOTKEY), 69 | ID_AND_NAME_A(WM_QUERYDRAGICON), 70 | ID_AND_NAME_A(WM_COMPAREITEM), 71 | ID_AND_NAME_A(WM_GETOBJECT), 72 | ID_AND_NAME_A(WM_COMPACTING), 73 | ID_AND_NAME_A(WM_COMMNOTIFY), 74 | ID_AND_NAME_A(WM_WINDOWPOSCHANGING), 75 | ID_AND_NAME_A(WM_WINDOWPOSCHANGED), 76 | ID_AND_NAME_A(WM_POWER), 77 | ID_AND_NAME_A(WM_COPYDATA), 78 | ID_AND_NAME_A(WM_CANCELJOURNAL), 79 | ID_AND_NAME_A(WM_NOTIFY), 80 | ID_AND_NAME_A(WM_INPUTLANGCHANGEREQUEST), 81 | ID_AND_NAME_A(WM_INPUTLANGCHANGE), 82 | ID_AND_NAME_A(WM_TCARD), 83 | ID_AND_NAME_A(WM_HELP), 84 | ID_AND_NAME_A(WM_USERCHANGED), 85 | ID_AND_NAME_A(WM_NOTIFYFORMAT), 86 | ID_AND_NAME_A(WM_CONTEXTMENU), 87 | ID_AND_NAME_A(WM_STYLECHANGING), 88 | ID_AND_NAME_A(WM_STYLECHANGED), 89 | ID_AND_NAME_A(WM_DISPLAYCHANGE), 90 | ID_AND_NAME_A(WM_GETICON), 91 | ID_AND_NAME_A(WM_SETICON), 92 | ID_AND_NAME_A(WM_NCCREATE), 93 | ID_AND_NAME_A(WM_NCDESTROY), 94 | ID_AND_NAME_A(WM_NCCALCSIZE), 95 | ID_AND_NAME_A(WM_NCHITTEST), 96 | ID_AND_NAME_A(WM_NCPAINT), 97 | ID_AND_NAME_A(WM_NCACTIVATE), 98 | ID_AND_NAME_A(WM_GETDLGCODE), 99 | ID_AND_NAME_A(WM_NCMOUSEMOVE), 100 | ID_AND_NAME_A(WM_NCMOUSELEAVE), 101 | ID_AND_NAME_A(WM_NCLBUTTONDOWN), 102 | ID_AND_NAME_A(WM_NCLBUTTONUP), 103 | ID_AND_NAME_A(WM_NCLBUTTONDBLCLK), 104 | ID_AND_NAME_A(WM_NCRBUTTONDOWN), 105 | ID_AND_NAME_A(WM_NCRBUTTONUP), 106 | ID_AND_NAME_A(WM_NCRBUTTONDBLCLK), 107 | ID_AND_NAME_A(WM_NCMBUTTONDOWN), 108 | ID_AND_NAME_A(WM_NCMBUTTONUP), 109 | ID_AND_NAME_A(WM_NCMBUTTONDBLCLK), 110 | ID_AND_NAME_A(WM_NCXBUTTONDOWN), 111 | ID_AND_NAME_A(WM_NCXBUTTONUP), 112 | ID_AND_NAME_A(WM_NCXBUTTONDBLCLK), 113 | ID_AND_NAME_A(WM_KEYDOWN), 114 | ID_AND_NAME_A(WM_KEYUP), 115 | ID_AND_NAME_A(WM_CHAR), 116 | ID_AND_NAME_A(WM_DEADCHAR), 117 | ID_AND_NAME_A(WM_SYSKEYDOWN), 118 | ID_AND_NAME_A(WM_SYSKEYUP), 119 | ID_AND_NAME_A(WM_SYSCHAR), 120 | ID_AND_NAME_A(WM_SYSDEADCHAR), 121 | ID_AND_NAME_A(WM_IME_STARTCOMPOSITION), 122 | ID_AND_NAME_A(WM_IME_ENDCOMPOSITION), 123 | ID_AND_NAME_A(WM_IME_COMPOSITION), 124 | ID_AND_NAME_A(WM_INITDIALOG), 125 | ID_AND_NAME_A(WM_COMMAND), 126 | ID_AND_NAME_A(WM_SYSCOMMAND), 127 | ID_AND_NAME_A(WM_TIMER), 128 | ID_AND_NAME_A(WM_HSCROLL), 129 | ID_AND_NAME_A(WM_VSCROLL), 130 | ID_AND_NAME_A(WM_INITMENU), 131 | ID_AND_NAME_A(WM_INITMENUPOPUP), 132 | ID_AND_NAME_A(WM_MENUSELECT), 133 | ID_AND_NAME_A(WM_MENUCHAR), 134 | ID_AND_NAME_A(WM_ENTERIDLE), 135 | ID_AND_NAME_A(WM_UNINITMENUPOPUP), 136 | ID_AND_NAME_A(WM_CHANGEUISTATE), 137 | ID_AND_NAME_A(WM_UPDATEUISTATE), 138 | ID_AND_NAME_A(WM_QUERYUISTATE), 139 | ID_AND_NAME_A(WM_CTLCOLORMSGBOX), 140 | ID_AND_NAME_A(WM_CTLCOLOREDIT), 141 | ID_AND_NAME_A(WM_CTLCOLORLISTBOX), 142 | ID_AND_NAME_A(WM_CTLCOLORBTN), 143 | ID_AND_NAME_A(WM_CTLCOLORDLG), 144 | ID_AND_NAME_A(WM_CTLCOLORSCROLLBAR), 145 | ID_AND_NAME_A(WM_CTLCOLORSTATIC), 146 | ID_AND_NAME_A(WM_MOUSEMOVE), 147 | ID_AND_NAME_A(WM_LBUTTONDOWN), 148 | ID_AND_NAME_A(WM_LBUTTONUP), 149 | ID_AND_NAME_A(WM_LBUTTONDBLCLK), 150 | ID_AND_NAME_A(WM_RBUTTONDOWN), 151 | ID_AND_NAME_A(WM_RBUTTONUP), 152 | ID_AND_NAME_A(WM_RBUTTONDBLCLK), 153 | ID_AND_NAME_A(WM_MBUTTONDOWN), 154 | ID_AND_NAME_A(WM_MBUTTONUP), 155 | ID_AND_NAME_A(WM_MBUTTONDBLCLK), 156 | ID_AND_NAME_A(WM_XBUTTONDOWN), 157 | ID_AND_NAME_A(WM_XBUTTONUP), 158 | ID_AND_NAME_A(WM_XBUTTONDBLCLK), 159 | ID_AND_NAME_A(WM_MOUSEWHEEL), 160 | ID_AND_NAME_A(WM_MOUSEHWHEEL), 161 | ID_AND_NAME_A(WM_PARENTNOTIFY), 162 | ID_AND_NAME_A(WM_ENTERMENULOOP), 163 | ID_AND_NAME_A(WM_EXITMENULOOP), 164 | ID_AND_NAME_A(WM_NEXTMENU), 165 | ID_AND_NAME_A(WM_SIZING), 166 | ID_AND_NAME_A(WM_CAPTURECHANGED), 167 | ID_AND_NAME_A(WM_MOVING), 168 | ID_AND_NAME_A(WM_POWERBROADCAST), 169 | ID_AND_NAME_A(WM_DEVICECHANGE), 170 | ID_AND_NAME_A(WM_IME_SETCONTEXT), 171 | ID_AND_NAME_A(WM_IME_NOTIFY), 172 | ID_AND_NAME_A(WM_IME_CONTROL), 173 | ID_AND_NAME_A(WM_IME_COMPOSITIONFULL), 174 | ID_AND_NAME_A(WM_IME_SELECT), 175 | ID_AND_NAME_A(WM_IME_CHAR), 176 | ID_AND_NAME_A(WM_IME_KEYDOWN), 177 | ID_AND_NAME_A(WM_IME_KEYUP), 178 | ID_AND_NAME_A(WM_MDICREATE), 179 | ID_AND_NAME_A(WM_MDIDESTROY), 180 | ID_AND_NAME_A(WM_MDIACTIVATE), 181 | ID_AND_NAME_A(WM_MDIRESTORE), 182 | ID_AND_NAME_A(WM_MDINEXT), 183 | ID_AND_NAME_A(WM_MDIMAXIMIZE), 184 | ID_AND_NAME_A(WM_MDITILE), 185 | ID_AND_NAME_A(WM_MDICASCADE), 186 | ID_AND_NAME_A(WM_MDIICONARRANGE), 187 | ID_AND_NAME_A(WM_MDIGETACTIVE), 188 | ID_AND_NAME_A(WM_MDISETMENU), 189 | ID_AND_NAME_A(WM_ENTERSIZEMOVE), 190 | ID_AND_NAME_A(WM_EXITSIZEMOVE), 191 | ID_AND_NAME_A(WM_DROPFILES), 192 | ID_AND_NAME_A(WM_MDIREFRESHMENU), 193 | ID_AND_NAME_A(WM_POINTERDEVICECHANGE), 194 | ID_AND_NAME_A(WM_POINTERDEVICEINRANGE), 195 | ID_AND_NAME_A(WM_POINTERDEVICEOUTOFRANGE), 196 | ID_AND_NAME_A(WM_NCPOINTERUPDATE), 197 | ID_AND_NAME_A(WM_NCPOINTERDOWN), 198 | ID_AND_NAME_A(WM_NCPOINTERUP), 199 | ID_AND_NAME_A(WM_POINTERUPDATE), 200 | ID_AND_NAME_A(WM_POINTERDOWN), 201 | ID_AND_NAME_A(WM_POINTERUP), 202 | ID_AND_NAME_A(WM_POINTERENTER), 203 | ID_AND_NAME_A(WM_POINTERLEAVE), 204 | ID_AND_NAME_A(WM_POINTERACTIVATE), 205 | ID_AND_NAME_A(WM_POINTERCAPTURECHANGED), 206 | ID_AND_NAME_A(WM_TOUCHHITTESTING), 207 | ID_AND_NAME_A(WM_POINTERWHEEL), 208 | ID_AND_NAME_A(WM_POINTERHWHEEL), 209 | ID_AND_NAME_A(DM_POINTERHITTEST), 210 | ID_AND_NAME_A(WM_POINTERROUTEDTO), 211 | ID_AND_NAME_A(WM_POINTERROUTEDAWAY), 212 | ID_AND_NAME_A(WM_POINTERROUTEDRELEASED), 213 | ID_AND_NAME_A(WM_MOUSEHOVER), 214 | ID_AND_NAME_A(WM_MOUSELEAVE), 215 | ID_AND_NAME_A(WM_DPICHANGED), 216 | ID_AND_NAME_A(WM_GETDPISCALEDSIZE), 217 | ID_AND_NAME_A(WM_DPICHANGED_BEFOREPARENT), 218 | ID_AND_NAME_A(WM_DPICHANGED_AFTERPARENT), 219 | ID_AND_NAME_A(WM_CUT), 220 | ID_AND_NAME_A(WM_COPY), 221 | ID_AND_NAME_A(WM_PASTE), 222 | ID_AND_NAME_A(WM_CLEAR), 223 | ID_AND_NAME_A(WM_UNDO), 224 | ID_AND_NAME_A(WM_RENDERFORMAT), 225 | ID_AND_NAME_A(WM_RENDERALLFORMATS), 226 | ID_AND_NAME_A(WM_DESTROYCLIPBOARD), 227 | ID_AND_NAME_A(WM_DRAWCLIPBOARD), 228 | ID_AND_NAME_A(WM_PAINTCLIPBOARD), 229 | ID_AND_NAME_A(WM_VSCROLLCLIPBOARD), 230 | ID_AND_NAME_A(WM_SIZECLIPBOARD), 231 | ID_AND_NAME_A(WM_ASKCBFORMATNAME), 232 | ID_AND_NAME_A(WM_CHANGECBCHAIN), 233 | ID_AND_NAME_A(WM_HSCROLLCLIPBOARD), 234 | ID_AND_NAME_A(WM_QUERYNEWPALETTE), 235 | ID_AND_NAME_A(WM_PALETTEISCHANGING), 236 | ID_AND_NAME_A(WM_PALETTECHANGED), 237 | ID_AND_NAME_A(WM_HOTKEY), 238 | ID_AND_NAME_A(WM_PRINT), 239 | ID_AND_NAME_A(WM_PRINTCLIENT), 240 | ID_AND_NAME_A(WM_THEMECHANGED), 241 | ID_AND_NAME_A(WM_HANDHELDFIRST), 242 | ID_AND_NAME_A(WM_GETTITLEBARINFOEX), 243 | ID_AND_NAME_A(WM_HANDHELDLAST), 244 | ID_AND_NAME_A(WM_AFXFIRST), 245 | ID_AND_NAME_A(WM_AFXLAST), 246 | ID_AND_NAME_A(WM_PENWINFIRST), 247 | ID_AND_NAME_A(WM_PENWINLAST), 248 | ID_AND_NAME_A(WM_APP), 249 | ID_AND_NAME_A(WM_USER), 250 | ID_AND_NAME_A(WM_DWMCOMPOSITIONCHANGED), 251 | ID_AND_NAME_A(WM_DWMNCRENDERINGCHANGED), 252 | ID_AND_NAME_A(WM_DWMCOLORIZATIONCOLORCHANGED), 253 | ID_AND_NAME_A(WM_DWMWINDOWMAXIMIZEDCHANGE), 254 | ID_AND_NAME_A(WM_DWMSENDICONICTHUMBNAIL), 255 | ID_AND_NAME_A(WM_DWMSENDICONICLIVEPREVIEWBITMAP), 256 | }; 257 | 258 | static DWORDAndNameW __KSPROPERTY_TYPE[] 259 | { 260 | ID_AND_NAME_W(KSPROPERTY_TYPE_GET), 261 | ID_AND_NAME_W(KSPROPERTY_TYPE_GETPAYLOADSIZE), 262 | ID_AND_NAME_W(KSPROPERTY_TYPE_SET), 263 | ID_AND_NAME_W(KSPROPERTY_TYPE_GETPAYLOADSIZE), 264 | ID_AND_NAME_W(KSPROPERTY_TYPE_SETSUPPORT), 265 | ID_AND_NAME_W(KSPROPERTY_TYPE_BASICSUPPORT), 266 | ID_AND_NAME_W(KSPROPERTY_TYPE_RELATIONS), 267 | ID_AND_NAME_W(KSPROPERTY_TYPE_SERIALIZESET), 268 | ID_AND_NAME_W(KSPROPERTY_TYPE_UNSERIALIZESET), 269 | ID_AND_NAME_W(KSPROPERTY_TYPE_SERIALIZERAW), 270 | ID_AND_NAME_W(KSPROPERTY_TYPE_UNSERIALIZERAW), 271 | ID_AND_NAME_W(KSPROPERTY_TYPE_SERIALIZESIZE), 272 | ID_AND_NAME_W(KSPROPERTY_TYPE_DEFAULTVALUES), 273 | ID_AND_NAME_W(KSPROPERTY_TYPE_TOPOLOGY), 274 | ID_AND_NAME_W(KSPROPERTY_TYPE_HIGHPRIORITY), 275 | ID_AND_NAME_W(KSPROPERTY_TYPE_FSFILTERSCOPE), 276 | ID_AND_NAME_W(KSPROPERTY_TYPE_COPYPAYLOAD), 277 | }; 278 | 279 | static DWORDAndNameW __KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY[] 280 | { 281 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PHOTOMODE), 282 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PHOTOFRAMERATE), 283 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PHOTOMAXFRAMERATE), 284 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PHOTOTRIGGERTIME), 285 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_WARMSTART), 286 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_MAXVIDFPS_PHOTORES), 287 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PHOTOTHUMBNAIL), 288 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_SCENEMODE), 289 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_TORCHMODE), 290 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FLASHMODE), 291 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_OPTIMIZATIONHINT), 292 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_WHITEBALANCEMODE), 293 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_EXPOSUREMODE), 294 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FOCUSMODE), 295 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_ISO), 296 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FIELDOFVIEW), 297 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_EVCOMPENSATION), 298 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_CAMERAANGLEOFFSET), 299 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_METADATA), 300 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FOCUSPRIORITY), 301 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FOCUSSTATE), 302 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_ROI_CONFIGCAPS), 303 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_ROI_ISPCONTROL), 304 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PHOTOCONFIRMATION), 305 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_ZOOM), 306 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_MCC), 307 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_ISO_ADVANCED), 308 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_VIDEOSTABILIZATION), 309 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_VFR), 310 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FACEDETECTION), 311 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_VIDEOHDR), 312 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_HISTOGRAM), 313 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_OIS), 314 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_ADVANCEDPHOTO), 315 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_PROFILE), 316 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_FACEAUTH_MODE), 317 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_SECURE_MODE), 318 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_VIDEOTEMPORALDENOISING), 319 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_IRTORCHMODE), 320 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_RELATIVEPANELOPTIMIZATION), 321 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_EYEGAZECORRECTION), 322 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION), 323 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW_CONFIGCAPS), 324 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW), 325 | }; 326 | 327 | static DWORDAndNameW __KSPROPERTY_VIDCAP_CAMERACONTROL[] 328 | { 329 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PAN), 330 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_TILT), 331 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_ROLL), 332 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_ZOOM), 333 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXPOSURE), 334 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_IRIS), 335 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_FOCUS), 336 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_SCANMODE), 337 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PRIVACY), 338 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PANTILT), 339 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE), 340 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_TILT_RELATIVE), 341 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_ROLL_RELATIVE), 342 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_ZOOM_RELATIVE), 343 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_EXPOSURE_RELATIVE), 344 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_IRIS_RELATIVE), 345 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_FOCUS_RELATIVE), 346 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PANTILT_RELATIVE), 347 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_FOCAL_LENGTH), 348 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_AUTO_EXPOSURE_PRIORITY), 349 | }; 350 | 351 | static DWORDAndNameW __KSPROPERTY_VIDCAP_VIDEOPROCAMP[] 352 | { 353 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_BRIGHTNESS), 354 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_CONTRAST), 355 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_HUE), 356 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_SATURATION), 357 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_SHARPNESS), 358 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_GAMMA), 359 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_COLORENABLE), 360 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_WHITEBALANCE), 361 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_BACKLIGHT_COMPENSATION), 362 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_GAIN), 363 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_DIGITAL_MULTIPLIER), 364 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_DIGITAL_MULTIPLIER_LIMIT), 365 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_WHITEBALANCE_COMPONENT), 366 | ID_AND_NAME_W(KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY), 367 | }; 368 | 369 | static DWORDAndNameW __KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_PROPERTY[] 370 | { 371 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_CAPABILITY), 372 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_SET), 373 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_CLEAR), 374 | }; 375 | 376 | static DWORDAndNameW __KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST[] 377 | { 378 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST_PROPERTY_ID), 379 | }; 380 | 381 | static DWORDAndNameW __KSPROPERTY_CAMERACONTROL_IMAGE_PIN_CAPABILITY[] 382 | { 383 | ID_AND_NAME_W(KSPROPERTY_CAMERACONTROL_IMAGE_PIN_CAPABILITY_PROPERTY_ID), 384 | }; 385 | 386 | static DWORDAndNameW __KSPROPERTY_TOPOLOGY[] 387 | { 388 | ID_AND_NAME_W(KSPROPERTY_TOPOLOGY_CATEGORIES), 389 | ID_AND_NAME_W(KSPROPERTY_TOPOLOGY_NODES), 390 | ID_AND_NAME_W(KSPROPERTY_TOPOLOGY_CONNECTIONS), 391 | ID_AND_NAME_W(KSPROPERTY_TOPOLOGY_NAME), 392 | }; 393 | 394 | static DWORDAndNameW __KSPROPERTY_PIN[] 395 | { 396 | ID_AND_NAME_W(KSPROPERTY_PIN_CINSTANCES), 397 | ID_AND_NAME_W(KSPROPERTY_PIN_CTYPES), 398 | ID_AND_NAME_W(KSPROPERTY_PIN_DATAFLOW), 399 | ID_AND_NAME_W(KSPROPERTY_PIN_DATARANGES), 400 | ID_AND_NAME_W(KSPROPERTY_PIN_DATAINTERSECTION), 401 | ID_AND_NAME_W(KSPROPERTY_PIN_INTERFACES), 402 | ID_AND_NAME_W(KSPROPERTY_PIN_MEDIUMS), 403 | ID_AND_NAME_W(KSPROPERTY_PIN_COMMUNICATION), 404 | ID_AND_NAME_W(KSPROPERTY_PIN_GLOBALCINSTANCES), 405 | ID_AND_NAME_W(KSPROPERTY_PIN_NECESSARYINSTANCES), 406 | ID_AND_NAME_W(KSPROPERTY_PIN_PHYSICALCONNECTION), 407 | ID_AND_NAME_W(KSPROPERTY_PIN_CATEGORY), 408 | ID_AND_NAME_W(KSPROPERTY_PIN_NAME), 409 | ID_AND_NAME_W(KSPROPERTY_PIN_CONSTRAINEDDATARANGES), 410 | ID_AND_NAME_W(KSPROPERTY_PIN_PROPOSEDATAFORMAT), 411 | ID_AND_NAME_W(KSPROPERTY_PIN_PROPOSEDATAFORMAT2), 412 | ID_AND_NAME_W(KSPROPERTY_PIN_MODEDATAFORMATS), 413 | }; 414 | 415 | static DWORDAndNameW __KSPROPERTY_CONNECTION[] 416 | { 417 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_STATE), 418 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_PRIORITY), 419 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_DATAFORMAT), 420 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_ALLOCATORFRAMING), 421 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_PROPOSEDATAFORMAT), 422 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_ACQUIREORDERING), 423 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX), 424 | ID_AND_NAME_W(KSPROPERTY_CONNECTION_STARTAT), 425 | }; 426 | 427 | static DWORDAndNameW __MF_ATTRIBUTE_TYPE[] 428 | { 429 | ID_AND_NAME_W(MF_ATTRIBUTE_UINT32), 430 | ID_AND_NAME_W(MF_ATTRIBUTE_UINT64), 431 | ID_AND_NAME_W(MF_ATTRIBUTE_DOUBLE), 432 | ID_AND_NAME_W(MF_ATTRIBUTE_GUID), 433 | ID_AND_NAME_W(MF_ATTRIBUTE_STRING), 434 | ID_AND_NAME_W(MF_ATTRIBUTE_BLOB), 435 | ID_AND_NAME_W(MF_ATTRIBUTE_IUNKNOWN), 436 | }; 437 | 438 | static DWORDAndNameW __VARTYPE[] = 439 | { 440 | ID_AND_NAME_W(VT_EMPTY), 441 | ID_AND_NAME_W(VT_NULL), 442 | ID_AND_NAME_W(VT_I2), 443 | ID_AND_NAME_W(VT_I4), 444 | ID_AND_NAME_W(VT_R4), 445 | ID_AND_NAME_W(VT_R8), 446 | ID_AND_NAME_W(VT_CY), 447 | ID_AND_NAME_W(VT_DATE), 448 | ID_AND_NAME_W(VT_BSTR), 449 | ID_AND_NAME_W(VT_DISPATCH), 450 | ID_AND_NAME_W(VT_ERROR), 451 | ID_AND_NAME_W(VT_BOOL), 452 | ID_AND_NAME_W(VT_VARIANT), 453 | ID_AND_NAME_W(VT_UNKNOWN), 454 | ID_AND_NAME_W(VT_DECIMAL), 455 | ID_AND_NAME_W(VT_I1), 456 | ID_AND_NAME_W(VT_UI1), 457 | ID_AND_NAME_W(VT_UI2), 458 | ID_AND_NAME_W(VT_UI4), 459 | ID_AND_NAME_W(VT_I8), 460 | ID_AND_NAME_W(VT_UI8), 461 | ID_AND_NAME_W(VT_INT), 462 | ID_AND_NAME_W(VT_UINT), 463 | ID_AND_NAME_W(VT_VOID), 464 | ID_AND_NAME_W(VT_HRESULT), 465 | ID_AND_NAME_W(VT_PTR), 466 | ID_AND_NAME_W(VT_SAFEARRAY), 467 | ID_AND_NAME_W(VT_CARRAY), 468 | ID_AND_NAME_W(VT_USERDEFINED), 469 | ID_AND_NAME_W(VT_LPSTR), 470 | ID_AND_NAME_W(VT_LPWSTR), 471 | ID_AND_NAME_W(VT_RECORD), 472 | ID_AND_NAME_W(VT_INT_PTR), 473 | ID_AND_NAME_W(VT_UINT_PTR), 474 | ID_AND_NAME_W(VT_FILETIME), 475 | ID_AND_NAME_W(VT_BLOB), 476 | ID_AND_NAME_W(VT_STREAM), 477 | ID_AND_NAME_W(VT_STORAGE), 478 | ID_AND_NAME_W(VT_STREAMED_OBJECT), 479 | ID_AND_NAME_W(VT_STORED_OBJECT), 480 | ID_AND_NAME_W(VT_BLOB_OBJECT), 481 | ID_AND_NAME_W(VT_CF), 482 | ID_AND_NAME_W(VT_CLSID), 483 | ID_AND_NAME_W(VT_VERSIONED_STREAM), 484 | }; 485 | 486 | static const std::wstring ToString(DWORDAndNameW* def, UINT defLen, DWORD value, bool flags = FALSE); 487 | static const std::string ToString(DWORDAndNameA* def, UINT defLen, DWORD value, bool flags = FALSE); 488 | static const std::wstring ToString(DWORDAndNameW* def, UINT defLen, DWORD value, bool flags) 489 | { 490 | if (!def) 491 | return L"???"; 492 | 493 | std::wstring str; 494 | if (flags) 495 | { 496 | for (DWORD i = 0; i < defLen; i++) 497 | { 498 | if (def[i].dw == 0 || (value & def[i].dw) == def[i].dw) 499 | { 500 | if (!str.empty()) 501 | { 502 | str.append(L" | "); 503 | } 504 | str.append(def[i].name); 505 | } 506 | } 507 | } 508 | else 509 | { 510 | for (DWORD i = 0; i < defLen; i++) 511 | { 512 | if (value == def[i].dw) 513 | { 514 | str.append(def[i].name); 515 | break; 516 | } 517 | } 518 | } 519 | 520 | if (str.empty()) 521 | { 522 | str.append(std::to_wstring(value)); 523 | } 524 | return str; 525 | } 526 | 527 | static const std::string ToString(DWORDAndNameA* def, UINT defLen, DWORD value, bool flags) 528 | { 529 | if (!def) 530 | return "???"; 531 | 532 | std::string str; 533 | if (flags) 534 | { 535 | for (DWORD i = 0; i < defLen; i++) 536 | { 537 | if (def[i].dw == 0 || (value & def[i].dw) == def[i].dw) 538 | { 539 | if (!str.empty()) 540 | { 541 | str.append(" | "); 542 | } 543 | str.append(def[i].name); 544 | } 545 | } 546 | } 547 | else 548 | { 549 | for (DWORD i = 0; i < defLen; i++) 550 | { 551 | if (value == def[i].dw) 552 | { 553 | str.append(def[i].name); 554 | break; 555 | } 556 | } 557 | } 558 | 559 | if (str.empty()) 560 | { 561 | str.append(std::to_string(value)); 562 | } 563 | return str; 564 | } 565 | 566 | const std::wstring KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY_ToString(ULONG value) { return ToString(__KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY, sizeof(__KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY) / sizeof(DWORDAndNameA), value); } 567 | const std::wstring PROPSETID_VIDCAP_CAMERACONTROL_ToString(ULONG value) { return ToString(__KSPROPERTY_VIDCAP_CAMERACONTROL, sizeof(__KSPROPERTY_VIDCAP_CAMERACONTROL) / sizeof(DWORDAndNameA), value); } 568 | const std::wstring PROPSETID_VIDCAP_VIDEOPROCAMP_ToString(ULONG value) { return ToString(__KSPROPERTY_VIDCAP_VIDEOPROCAMP, sizeof(__KSPROPERTY_VIDCAP_VIDEOPROCAMP) / sizeof(DWORDAndNameA), value); } 569 | const std::wstring KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_PROPERTY_ToString(ULONG value) { return ToString(__KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_PROPERTY, sizeof(__KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_PROPERTY) / sizeof(DWORDAndNameA), value); } 570 | const std::wstring KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST_ToString(ULONG value) { return ToString(__KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST, sizeof(__KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST) / sizeof(DWORDAndNameA), value); } 571 | const std::wstring PROPSETID_VIDCAP_CAMERACONTROL_IMAGE_PIN_CAPABILITY_ToString(ULONG value) { return ToString(__KSPROPERTY_CAMERACONTROL_IMAGE_PIN_CAPABILITY, sizeof(__KSPROPERTY_CAMERACONTROL_IMAGE_PIN_CAPABILITY) / sizeof(DWORDAndNameA), value); } 572 | const std::wstring KSPROPERTY_TOPOLOGY_ToString(ULONG value) { return ToString(__KSPROPERTY_TOPOLOGY, sizeof(__KSPROPERTY_TOPOLOGY) / sizeof(DWORDAndNameA), value); } 573 | const std::wstring KSPROPERTY_PIN_ToString(ULONG value) { return ToString(__KSPROPERTY_PIN, sizeof(__KSPROPERTY_PIN) / sizeof(DWORDAndNameA), value); } 574 | const std::wstring KSPROPSETID_Connection_ToString(ULONG value) { return ToString(__KSPROPERTY_CONNECTION, sizeof(__KSPROPERTY_CONNECTION) / sizeof(DWORDAndNameA), value); } 575 | 576 | const std::wstring KSPROPERTY_TYPE_ToString(ULONG value) { return ToString(__KSPROPERTY_TYPE, sizeof(__KSPROPERTY_TYPE) / sizeof(DWORDAndNameA), value, true); } 577 | const std::wstring MF_ATTRIBUTE_TYPE_ToString(MF_ATTRIBUTE_TYPE value) { return ToString(__MF_ATTRIBUTE_TYPE, sizeof(__MF_ATTRIBUTE_TYPE) / sizeof(DWORDAndNameA), value); } 578 | const std::string WM_ToString(UINT msg) { return ToString(__WM, sizeof(__WM) / sizeof(DWORDAndNameA), msg); } 579 | const std::wstring VARTYPE_ToString(VARTYPE value) 580 | { 581 | auto type = value & VT_TYPEMASK; 582 | auto str = ToString(__VARTYPE, sizeof(__VARTYPE) / sizeof(DWORDAndNameW), value); 583 | if (value & VT_VECTOR) 584 | { 585 | str += L"VT_VECTOR"; 586 | } 587 | 588 | if (value & VT_ARRAY) 589 | { 590 | str += L"VT_ARRAY"; 591 | } 592 | 593 | if (value & VT_BYREF) 594 | { 595 | str += L"VT_BYREF"; 596 | } 597 | return str; 598 | } 599 | -------------------------------------------------------------------------------- /VCamSampleSource/EnumNames.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const std::string WM_ToString(UINT msg); 4 | const std::wstring VARTYPE_ToString(VARTYPE value); 5 | const std::wstring MF_ATTRIBUTE_TYPE_ToString(MF_ATTRIBUTE_TYPE value); 6 | const std::wstring KSPROPERTY_TYPE_ToString(ULONG value); 7 | const std::wstring KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY_ToString(ULONG value); 8 | const std::wstring PROPSETID_VIDCAP_CAMERACONTROL_ToString(ULONG value); 9 | const std::wstring PROPSETID_VIDCAP_VIDEOPROCAMP_ToString(ULONG value); 10 | const std::wstring PROPSETID_VIDCAP_CAMERACONTROL_IMAGE_PIN_CAPABILITY_ToString(ULONG value); 11 | const std::wstring KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_PROPERTY_ToString(ULONG value); 12 | const std::wstring KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST_ToString(ULONG value); 13 | const std::wstring KSPROPERTY_TOPOLOGY_ToString(ULONG value); 14 | const std::wstring KSPROPERTY_PIN_ToString(ULONG value); 15 | const std::wstring KSPROPSETID_Connection_ToString(ULONG value); 16 | 17 | 18 | -------------------------------------------------------------------------------- /VCamSampleSource/FrameGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Undocumented.h" 3 | #include "Tools.h" 4 | #include "EnumNames.h" 5 | #include "MFTools.h" 6 | #include "FrameGenerator.h" 7 | 8 | HRESULT FrameGenerator::EnsureRenderTarget(UINT width, UINT height) 9 | { 10 | if (!HasD3DManager()) 11 | { 12 | // create a D2D1 render target from WIC bitmap 13 | wil::com_ptr_nothrow d2d1Factory; 14 | RETURN_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, IID_PPV_ARGS(&d2d1Factory))); 15 | 16 | wil::com_ptr_nothrow wicFactory; 17 | RETURN_IF_FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&wicFactory))); 18 | 19 | RETURN_IF_FAILED(wicFactory->CreateBitmap(width, height, GUID_WICPixelFormat32bppPBGRA, WICBitmapCacheOnDemand, &_bitmap)); 20 | 21 | D2D1_RENDER_TARGET_PROPERTIES props{}; 22 | props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; 23 | props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; 24 | RETURN_IF_FAILED(d2d1Factory->CreateWicBitmapRenderTarget(_bitmap.get(), props, &_renderTarget)); 25 | 26 | RETURN_IF_FAILED(CreateRenderTargetResources(width, height)); 27 | } 28 | 29 | _prevTime = MFGetSystemTime(); 30 | _frame = 0; 31 | return S_OK; 32 | } 33 | 34 | const bool FrameGenerator::HasD3DManager() const 35 | { 36 | return _texture != nullptr; 37 | } 38 | 39 | HRESULT FrameGenerator::SetD3DManager(IUnknown* manager, UINT width, UINT height) 40 | { 41 | RETURN_HR_IF_NULL(E_POINTER, manager); 42 | RETURN_HR_IF(E_INVALIDARG, !width || !height); 43 | 44 | RETURN_IF_FAILED(manager->QueryInterface(&_dxgiManager)); 45 | RETURN_IF_FAILED(_dxgiManager->OpenDeviceHandle(&_deviceHandle)); 46 | 47 | wil::com_ptr_nothrow device; 48 | RETURN_IF_FAILED(_dxgiManager->GetVideoService(_deviceHandle, IID_PPV_ARGS(&device))); 49 | 50 | // create a texture/surface to write 51 | CD3D11_TEXTURE2D_DESC desc 52 | ( 53 | DXGI_FORMAT_B8G8R8A8_UNORM, 54 | width, 55 | height, 56 | 1, 57 | 1, 58 | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET 59 | ); 60 | RETURN_IF_FAILED(device->CreateTexture2D(&desc, nullptr, &_texture)); 61 | wil::com_ptr_nothrow surface; 62 | RETURN_IF_FAILED(_texture.copy_to(&surface)); 63 | 64 | // create a D2D1 render target from 2D GPU surface 65 | wil::com_ptr_nothrow d2d1Factory; 66 | RETURN_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, IID_PPV_ARGS(&d2d1Factory))); 67 | 68 | auto props = D2D1::RenderTargetProperties 69 | ( 70 | D2D1_RENDER_TARGET_TYPE_DEFAULT, 71 | D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED) 72 | ); 73 | RETURN_IF_FAILED(d2d1Factory->CreateDxgiSurfaceRenderTarget(surface.get(), props, &_renderTarget)); 74 | 75 | RETURN_IF_FAILED(CreateRenderTargetResources(width, height)); 76 | 77 | // create GPU RGB => NV12 converter 78 | RETURN_IF_FAILED(CoCreateInstance(CLSID_VideoProcessorMFT, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&_converter))); 79 | 80 | wil::com_ptr_nothrow atts; 81 | RETURN_IF_FAILED(_converter->GetAttributes(&atts)); 82 | TraceMFAttributes(atts.get(), L"VideoProcessorMFT"); 83 | 84 | MFT_OUTPUT_STREAM_INFO info{}; 85 | RETURN_IF_FAILED(_converter->GetOutputStreamInfo(0, &info)); 86 | WINTRACE(L"FrameGenerator::EnsureRenderTarget CLSID_VideoProcessorMFT flags:0x%08X size:%u alignment:%u", info.dwFlags, info.cbSize, info.cbAlignment); 87 | 88 | wil::com_ptr_nothrow inputType; 89 | RETURN_IF_FAILED(MFCreateMediaType(&inputType)); 90 | inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 91 | inputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); 92 | MFSetAttributeSize(inputType.get(), MF_MT_FRAME_SIZE, width, height); 93 | RETURN_IF_FAILED(_converter->SetInputType(0, inputType.get(), 0)); 94 | 95 | wil::com_ptr_nothrow outputType; 96 | RETURN_IF_FAILED(MFCreateMediaType(&outputType)); 97 | outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 98 | outputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); 99 | MFSetAttributeSize(outputType.get(), MF_MT_FRAME_SIZE, width, height); 100 | RETURN_IF_FAILED(_converter->SetOutputType(0, outputType.get(), 0)); 101 | 102 | // make sure the video processor works on GPU 103 | RETURN_IF_FAILED(_converter->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR)manager)); 104 | return S_OK; 105 | } 106 | 107 | // common to CPU & GPU 108 | HRESULT FrameGenerator::CreateRenderTargetResources(UINT width, UINT height) 109 | { 110 | assert(_renderTarget); 111 | RETURN_IF_FAILED(_renderTarget->CreateSolidColorBrush(D2D1::ColorF(1, 1, 1, 1), &_whiteBrush)); 112 | 113 | RETURN_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&_dwrite)); 114 | RETURN_IF_FAILED(_dwrite->CreateTextFormat(L"Segoe UI", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 40, L"", &_textFormat)); 115 | RETURN_IF_FAILED(_textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); 116 | RETURN_IF_FAILED(_textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); 117 | _width = width; 118 | _height = height; 119 | return S_OK; 120 | } 121 | 122 | HRESULT FrameGenerator::Generate(IMFSample* sample, REFGUID format, IMFSample** outSample) 123 | { 124 | RETURN_HR_IF_NULL(E_POINTER, sample); 125 | RETURN_HR_IF_NULL(E_POINTER, outSample); 126 | *outSample = nullptr; 127 | 128 | // render something on image common to CPU & GPU 129 | if (_renderTarget && _textFormat && _dwrite && _whiteBrush) 130 | { 131 | _renderTarget->BeginDraw(); 132 | _renderTarget->Clear(D2D1::ColorF(0, 0, 1, 1)); 133 | 134 | // draw some HSL blocks 135 | const float divisor = 20; 136 | for (UINT i = 0; i < _width / divisor; i++) 137 | { 138 | for (UINT j = 0; j < _height / divisor; j++) 139 | { 140 | wil::com_ptr_nothrow brush; 141 | auto color = HSL2RGB((float)i / (_height / divisor), 1, ((float)j / (_width / divisor))); 142 | RETURN_IF_FAILED(_renderTarget->CreateSolidColorBrush(color, &brush)); 143 | _renderTarget->FillRectangle(D2D1::Rect(i * divisor, j * divisor, (i + 1) * divisor, (j + 1) * divisor), brush.get()); 144 | } 145 | } 146 | 147 | auto radius = divisor * 2; 148 | const float padding = 1; 149 | _renderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(radius + padding, radius + padding), radius, radius), _whiteBrush.get()); 150 | _renderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(radius + padding, _height - radius - padding), radius, radius), _whiteBrush.get()); 151 | _renderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(_width - radius - padding, radius + padding), radius, radius), _whiteBrush.get()); 152 | _renderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(_width - radius - padding, _height - radius - padding), radius, radius), _whiteBrush.get()); 153 | _renderTarget->DrawRectangle(D2D1::Rect(radius, radius, _width - radius, _height - radius), _whiteBrush.get()); 154 | 155 | // draw resolution at center 156 | // note: we could optimize here and compute layout only once if text doesn't change (depending on the font, etc.) 157 | wchar_t text[127]; 158 | wchar_t fmt[15]; 159 | if (format == MFVideoFormat_NV12) 160 | { 161 | if (HasD3DManager()) 162 | { 163 | lstrcpy(fmt, L"NV12 (GPU)"); 164 | } 165 | else 166 | { 167 | lstrcpy(fmt, L"NV12 (CPU)"); 168 | } 169 | } 170 | else 171 | { 172 | if (HasD3DManager()) 173 | { 174 | lstrcpy(fmt, L"RGB32 (GPU)"); 175 | } 176 | else 177 | { 178 | lstrcpy(fmt, L"RGB32 (CPU)"); 179 | } 180 | } 181 | 182 | #define FRAMES_FOR_FPS 60 // number of frames to wait to compute fps from last measure 183 | #define NS_PER_MS 10000 184 | #define MS_PER_S 1000 185 | 186 | if (!_fps || !(_frame % FRAMES_FOR_FPS)) 187 | { 188 | auto time = MFGetSystemTime(); 189 | _fps = (UINT)(MS_PER_S * NS_PER_MS * FRAMES_FOR_FPS / (time - _prevTime)); 190 | _prevTime = time; 191 | } 192 | 193 | auto len = wsprintf(text, L"Format: %s\nFrame#: %I64i\nFps: %u\nResolution: %u x %u", fmt, _frame, _fps, _width, _height); 194 | 195 | wil::com_ptr_nothrow layout; 196 | RETURN_IF_FAILED(_dwrite->CreateTextLayout(text, len, _textFormat.get(), (FLOAT)_width, (FLOAT)_height, &layout)); 197 | 198 | _renderTarget->DrawTextLayout(D2D1::Point2F(0, 0), layout.get(), _whiteBrush.get()); 199 | _renderTarget->EndDraw(); 200 | } 201 | 202 | // build a sample using either D3D/DXGI (GPU) or WIC (CPU) 203 | wil::com_ptr_nothrow mediaBuffer; 204 | if (HasD3DManager()) 205 | { 206 | // remove all existing buffers 207 | RETURN_IF_FAILED(sample->RemoveAllBuffers()); 208 | 209 | // create a buffer from this and add to sample 210 | RETURN_IF_FAILED(MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), _texture.get(), 0, 0, &mediaBuffer)); 211 | RETURN_IF_FAILED(sample->AddBuffer(mediaBuffer.get())); 212 | 213 | // if we're on GPU & format is not RGB, convert using GPU 214 | if (format == MFVideoFormat_NV12) 215 | { 216 | assert(_converter); 217 | RETURN_IF_FAILED(_converter->ProcessInput(0, sample, 0)); 218 | 219 | // let converter build the sample for us, note it works because we gave it the D3DManager 220 | MFT_OUTPUT_DATA_BUFFER buffer = {}; 221 | DWORD status = 0; 222 | RETURN_IF_FAILED(_converter->ProcessOutput(0, 1, &buffer, &status)); 223 | *outSample = buffer.pSample; 224 | } 225 | else 226 | { 227 | sample->AddRef(); 228 | *outSample = sample; 229 | } 230 | 231 | _frame++; 232 | return S_OK; 233 | } 234 | 235 | RETURN_IF_FAILED(sample->GetBufferByIndex(0, &mediaBuffer)); 236 | wil::com_ptr_nothrow buffer2D; 237 | BYTE* scanline; 238 | LONG pitch; 239 | BYTE* start; 240 | DWORD length; 241 | RETURN_IF_FAILED(mediaBuffer->QueryInterface(IID_PPV_ARGS(&buffer2D))); 242 | RETURN_IF_FAILED(buffer2D->Lock2DSize(MF2DBuffer_LockFlags_Write, &scanline, &pitch, &start, &length)); 243 | 244 | wil::com_ptr_nothrow lock; 245 | auto hr = _bitmap->Lock(nullptr, WICBitmapLockRead, &lock); 246 | // now we're using regular COM macros because we want to be sure to unlock (or we could use try/catch) 247 | if (SUCCEEDED(hr)) 248 | { 249 | UINT w, h; 250 | hr = lock->GetSize(&w, &h); 251 | if (SUCCEEDED(hr)) 252 | { 253 | UINT wicStride; 254 | hr = lock->GetStride(&wicStride); 255 | if (SUCCEEDED(hr)) 256 | { 257 | UINT wicSize; 258 | WICInProcPointer wicPointer; 259 | hr = lock->GetDataPointer(&wicSize, &wicPointer); 260 | if (SUCCEEDED(hr)) 261 | { 262 | WINTRACE(L"WIC stride:%u WIC size:%u MF pitch:%u MF length:%u frame:%u format:%s", wicStride, wicSize, pitch, length, _frame, GUID_ToStringW(format).c_str()); 263 | if (format == MFVideoFormat_NV12) 264 | { 265 | // note we could use MF's converter too 266 | hr = RGB32ToNV12(wicPointer, wicSize, wicStride, w, h, scanline, length, pitch); 267 | } 268 | else 269 | { 270 | hr = (wicSize != length || wicStride != pitch) ? E_FAIL : S_OK; 271 | if (SUCCEEDED(hr)) 272 | { 273 | if (assert_true(wicPointer)) // WIC annotation is currently wrong on GetDataPointer wicPointer arg 274 | { 275 | CopyMemory(scanline, wicPointer, length); 276 | } 277 | } 278 | } 279 | 280 | if (SUCCEEDED(hr)) 281 | { 282 | _frame++; 283 | sample->AddRef(); 284 | *outSample = sample; 285 | } 286 | } 287 | } 288 | } 289 | lock.reset(); 290 | } 291 | 292 | buffer2D->Unlock2D(); 293 | return hr; 294 | } 295 | -------------------------------------------------------------------------------- /VCamSampleSource/FrameGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class FrameGenerator 4 | { 5 | UINT _width; 6 | UINT _height; 7 | ULONGLONG _frame; 8 | MFTIME _prevTime; 9 | UINT _fps; 10 | HANDLE _deviceHandle; 11 | wil::com_ptr_nothrow _texture; 12 | wil::com_ptr_nothrow _renderTarget; 13 | wil::com_ptr_nothrow _whiteBrush; 14 | wil::com_ptr_nothrow _textFormat; 15 | wil::com_ptr_nothrow _dwrite; 16 | wil::com_ptr_nothrow _converter; 17 | wil::com_ptr_nothrow _bitmap; 18 | wil::com_ptr_nothrow _dxgiManager; 19 | 20 | HRESULT CreateRenderTargetResources(UINT width, UINT height); 21 | 22 | public: 23 | FrameGenerator() : 24 | _width(0), 25 | _height(0), 26 | _frame(0), 27 | _fps(0), 28 | _deviceHandle(nullptr), 29 | _prevTime(MFGetSystemTime()) 30 | { 31 | 32 | } 33 | 34 | ~FrameGenerator() 35 | { 36 | if (_dxgiManager && _deviceHandle) 37 | { 38 | auto hr = _dxgiManager->CloseDeviceHandle(_deviceHandle); // don't report error at that point 39 | if (FAILED(hr)) 40 | { 41 | WINTRACE(L"FrameGenerator CloseDeviceHandle: 0x%08X", hr); 42 | } 43 | } 44 | } 45 | 46 | HRESULT SetD3DManager(IUnknown* manager, UINT width, UINT height); 47 | const bool HasD3DManager() const; 48 | HRESULT EnsureRenderTarget(UINT width, UINT height); 49 | HRESULT Generate(IMFSample* sample, REFGUID format, IMFSample** outSample); 50 | }; -------------------------------------------------------------------------------- /VCamSampleSource/MFTools.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "EnumNames.h" 3 | #include "Tools.h" 4 | #include "MFTools.h" 5 | 6 | void TraceMFAttributes(IUnknown* unknown, PCWSTR prefix) 7 | { 8 | if (!unknown) 9 | { 10 | WINTRACE(L"%s:%p is null", prefix, unknown); 11 | return; 12 | } 13 | 14 | wil::com_ptr_nothrow atts; 15 | unknown->QueryInterface(&atts); 16 | if (!atts) 17 | { 18 | WINTRACE(L"%s:%p is not an IMFAttributes", prefix, unknown); 19 | return; 20 | } 21 | 22 | UINT32 count = 0; 23 | atts->GetCount(&count); 24 | WINTRACE(L"%s:%p has %u properties", prefix, unknown, count); 25 | for (UINT32 i = 0; i < count; i++) 26 | { 27 | GUID pk; 28 | wil::unique_prop_variant pv; 29 | PropVariantInit(&pv); 30 | auto hr = atts->GetItemByIndex(i, &pk, &pv); 31 | if (SUCCEEDED(hr)) 32 | { 33 | if (pv.vt == VT_CLSID) 34 | { 35 | WINTRACE(L" %s:[%u] attribute, '%s' type %s/(0x%02X), value: '%s'", prefix, i, GUID_ToStringW(pk).c_str(), VARTYPE_ToString(pv.vt).c_str(), pv.vt, GUID_ToStringW(*pv.puuid).c_str()); 36 | } 37 | else 38 | { 39 | wil::unique_cotaskmem_ptr str; 40 | if (SUCCEEDED(PropVariantToStringAlloc(pv, wil::out_param(str)))) 41 | { 42 | WINTRACE(L" %s:[%u] attribute, '%s' type %s/(0x%02X), value: '%s'", prefix, i, GUID_ToStringW(pk).c_str(), VARTYPE_ToString(pv.vt).c_str(), pv.vt, str.get()); 43 | } 44 | else 45 | { 46 | WINTRACE(L" %s:[%u] attribute, '%s' type %s/(0x%02X) cannot be converted to string", prefix, i, GUID_ToStringW(pk).c_str(), VARTYPE_ToString(pv.vt).c_str(), pv.vt); 47 | } 48 | } 49 | } 50 | else 51 | { 52 | WINTRACE(L" %s:[%u] attribute cannot be read, hr=0x%08X", prefix, i, hr); 53 | } 54 | } 55 | } 56 | 57 | std::wstring PKSIDENTIFIER_ToString(PKSIDENTIFIER id, ULONG length) 58 | { 59 | if (!id) 60 | return L""; 61 | 62 | if (length < sizeof(KSIDENTIFIER)) 63 | return std::format(L"", length); 64 | 65 | auto flags = KSPROPERTY_TYPE_ToString(id->Flags); 66 | if (id->Set == KSPROPERTYSETID_ExtendedCameraControl) 67 | return L"KSPROPERTYSETID_ExtendedCameraControl " + KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY_ToString(id->Id) + L" " + flags; 68 | 69 | if (id->Set == PROPSETID_VIDCAP_CAMERACONTROL) 70 | return L"PROPSETID_VIDCAP_CAMERACONTROL " + PROPSETID_VIDCAP_CAMERACONTROL_ToString(id->Id) + L" " + flags; 71 | 72 | if (id->Set == PROPSETID_VIDCAP_VIDEOPROCAMP) 73 | return L"PROPSETID_VIDCAP_VIDEOPROCAMP " + PROPSETID_VIDCAP_CAMERACONTROL_ToString(id->Id) + L" " + flags; 74 | 75 | if (id->Set == KSPROPERTYSETID_PerFrameSettingControl) 76 | return L"KSPROPERTYSETID_PerFrameSettingControl " + KSPROPERTY_CAMERACONTROL_PERFRAMESETTING_PROPERTY_ToString(id->Id) + L" " + flags; 77 | 78 | if (id->Set == PROPSETID_VIDCAP_CAMERACONTROL_REGION_OF_INTEREST) 79 | return L"PROPSETID_VIDCAP_CAMERACONTROL_REGION_OF_INTEREST " + KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST_ToString(id->Id) + L" " + flags; 80 | 81 | if (id->Set == PROPSETID_VIDCAP_CAMERACONTROL_IMAGE_PIN_CAPABILITY) 82 | return L"PROPSETID_VIDCAP_CAMERACONTROL_IMAGE_PIN_CAPABILITY " + KSPROPERTY_CAMERACONTROL_REGION_OF_INTEREST_ToString(id->Id) + L" " + flags; 83 | 84 | if (id->Set == KSPROPSETID_Topology) 85 | return L"KSPROPSETID_Topology " + KSPROPERTY_TOPOLOGY_ToString(id->Id) + L" " + flags; 86 | 87 | if (id->Set == KSPROPSETID_Pin) 88 | return L"KSPROPSETID_Pin " + KSPROPERTY_PIN_ToString(id->Id) + L" " + flags; 89 | 90 | if (id->Set == KSPROPSETID_Connection) 91 | return L"KSPROPSETID_Connection " + KSPROPSETID_Connection_ToString(id->Id) + L" " + flags; 92 | 93 | return std::format(L"{} {} {}", GUID_ToStringW(id->Set), id->Id, flags); 94 | } 95 | -------------------------------------------------------------------------------- /VCamSampleSource/MFTools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void TraceMFAttributes(IUnknown* unknown, PCWSTR prefix); 4 | std::wstring PKSIDENTIFIER_ToString(PKSIDENTIFIER id, ULONG length); 5 | 6 | template 7 | struct CBaseAttributes : public IFACE 8 | { 9 | protected: 10 | wil::com_ptr_nothrow _attributes; 11 | std::wstring _trace; 12 | 13 | CBaseAttributes() : 14 | _trace(L"Atts") 15 | { 16 | THROW_IF_FAILED(MFCreateAttributes(&_attributes, 0)); 17 | } 18 | 19 | void SetBaseAttributesTraceName(std::wstring trace) 20 | { 21 | _trace = trace; 22 | } 23 | 24 | public: 25 | STDMETHODIMP GetItem(REFGUID guidKey, PROPVARIANT* value) 26 | { 27 | RETURN_HR_IF(E_INVALIDARG, !value); 28 | assert(_attributes); 29 | auto hr = _attributes->GetItem(guidKey, value); 30 | WINTRACE(L"%s:GetItem '%s' value:%s", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), PROPVARIANT_ToString(*value).c_str()); 31 | return hr; 32 | } 33 | 34 | STDMETHODIMP GetItemType(REFGUID guidKey, MF_ATTRIBUTE_TYPE* pType) 35 | { 36 | RETURN_HR_IF(E_INVALIDARG, !pType); 37 | *pType = (MF_ATTRIBUTE_TYPE)0; 38 | assert(_attributes); 39 | auto hr = _attributes->GetItemType(guidKey, pType); 40 | WINTRACE(L"%s:GetItemType '%s' type:%s hr:0x%08X", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), MF_ATTRIBUTE_TYPE_ToString(*pType).c_str(), hr); 41 | return hr; 42 | } 43 | 44 | STDMETHODIMP CompareItem(REFGUID guidKey, REFPROPVARIANT Value, BOOL* pbResult) 45 | { 46 | RETURN_HR_IF(E_INVALIDARG, !pbResult); 47 | assert(_attributes); 48 | WINTRACE(L"%s:CompareItem '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 49 | return _attributes->CompareItem(guidKey, Value, pbResult); 50 | } 51 | 52 | STDMETHODIMP Compare(IMFAttributes* pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL* pbResult) 53 | { 54 | RETURN_HR_IF(E_INVALIDARG, !pTheirs || !pbResult); 55 | assert(_attributes); 56 | WINTRACE(L"%s:Compare", _trace.c_str()); 57 | return _attributes->Compare(pTheirs, MatchType, pbResult); 58 | } 59 | 60 | STDMETHODIMP GetUINT32(REFGUID guidKey, UINT32* punValue) 61 | { 62 | RETURN_HR_IF(E_INVALIDARG, !punValue); 63 | *punValue = 0; 64 | assert(_attributes); 65 | auto hr = _attributes->GetUINT32(guidKey, punValue); 66 | WINTRACE(L"%s:GetUINT32 '%s' hr:0x%08X value:%u/0x%08X", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), hr, *punValue, *punValue); 67 | return hr; 68 | } 69 | 70 | STDMETHODIMP GetUINT64(REFGUID guidKey, UINT64* punValue) 71 | { 72 | RETURN_HR_IF(E_INVALIDARG, !punValue); 73 | *punValue = 0; 74 | assert(_attributes); 75 | auto hr = _attributes->GetUINT64(guidKey, punValue); 76 | WINTRACE(L"%s:GetUINT64 '%s' hr:0x%08X value:%I64i/0x%016X", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), hr, *punValue, *punValue); 77 | return hr; 78 | } 79 | 80 | STDMETHODIMP GetDouble(REFGUID guidKey, double* pfValue) 81 | { 82 | RETURN_HR_IF(E_INVALIDARG, !pfValue); 83 | *pfValue = 0; 84 | assert(_attributes); 85 | auto hr = _attributes->GetDouble(guidKey, pfValue); 86 | WINTRACE(L"%s:GetDouble '%s' hr:0x%08X", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), hr); 87 | return hr; 88 | } 89 | 90 | STDMETHODIMP GetGUID(REFGUID guidKey, GUID* pguidValue) 91 | { 92 | RETURN_HR_IF(E_INVALIDARG, !pguidValue); 93 | ZeroMemory(pguidValue, 16); 94 | assert(_attributes); 95 | auto hr = _attributes->GetGUID(guidKey, pguidValue); 96 | WINTRACE(L"%s:GetGUID '%s' hr:0x%08X value:'%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), hr, GUID_ToStringW(*pguidValue).c_str()); 97 | return hr; 98 | } 99 | 100 | STDMETHODIMP GetStringLength(REFGUID guidKey, UINT32* pcchLength) 101 | { 102 | RETURN_HR_IF(E_INVALIDARG, !pcchLength); 103 | *pcchLength = 0; 104 | assert(_attributes); 105 | auto hr = _attributes->GetStringLength(guidKey, pcchLength); 106 | WINTRACE(L"%s:GetStringLength '%s' len:%u", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), *pcchLength); 107 | return hr; 108 | } 109 | 110 | STDMETHODIMP GetString(REFGUID guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32* pcchLength) 111 | { 112 | assert(_attributes); 113 | WINTRACE(L"%s:GetString '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 114 | return _attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength); 115 | } 116 | 117 | STDMETHODIMP GetAllocatedString(REFGUID guidKey, LPWSTR* ppwszValue, UINT32* pcchLength) 118 | { 119 | RETURN_HR_IF(E_INVALIDARG, !ppwszValue || !pcchLength); 120 | *ppwszValue = 0; 121 | *pcchLength = 0; 122 | assert(_attributes); 123 | auto hr = _attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength); 124 | WINTRACE(L"%s:GetAllocatedString hr:0x%08X '%s' len:%u value:'%s'", _trace.c_str(), hr, GUID_ToStringW(guidKey).c_str(), *pcchLength, ppwszValue); 125 | return hr; 126 | } 127 | 128 | STDMETHODIMP GetBlobSize(REFGUID guidKey, UINT32* pcbBlobSize) 129 | { 130 | RETURN_HR_IF(E_INVALIDARG, !pcbBlobSize); 131 | assert(_attributes); 132 | WINTRACE(L"%s:GetBlobSize '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 133 | return _attributes->GetBlobSize(guidKey, pcbBlobSize); 134 | } 135 | 136 | STDMETHODIMP GetBlob(REFGUID guidKey, UINT8* pBuf, UINT32 cbBufSize, UINT32* pcbBlobSize) 137 | { 138 | assert(_attributes); 139 | WINTRACE(L"%s:GetBlob '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 140 | return _attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize); 141 | } 142 | 143 | STDMETHODIMP GetAllocatedBlob(REFGUID guidKey, UINT8** ppBuf, UINT32* pcbSize) 144 | { 145 | RETURN_HR_IF(E_INVALIDARG, !ppBuf || !pcbSize); 146 | assert(_attributes); 147 | WINTRACE(L"%s:GetAllocatedBlob '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 148 | return _attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize); 149 | } 150 | 151 | STDMETHODIMP GetUnknown(REFGUID guidKey, REFIID riid, LPVOID* ppv) 152 | { 153 | RETURN_HR_IF(E_INVALIDARG, !ppv); 154 | assert(_attributes); 155 | auto hr = _attributes->GetUnknown(guidKey, riid, ppv); 156 | WINTRACE(L"%s:GetUnknown hr:0x%08X '%s' riid:'%s' %p", _trace.c_str(), hr, GUID_ToStringW(guidKey).c_str(), GUID_ToStringW(riid).c_str(), *ppv); 157 | return hr; 158 | } 159 | 160 | STDMETHODIMP SetItem(REFGUID guidKey, REFPROPVARIANT value) 161 | { 162 | assert(_attributes); 163 | auto v = PROPVARIANT_ToString(value); 164 | WINTRACE(L"%s:SetItem '%s' value:%s", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), v.c_str()); 165 | return _attributes->SetItem(guidKey, value); 166 | } 167 | 168 | STDMETHODIMP DeleteItem(REFGUID guidKey) 169 | { 170 | assert(_attributes); 171 | WINTRACE(L"%s:DeleteItem '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 172 | return _attributes->DeleteItem(guidKey); 173 | } 174 | 175 | STDMETHODIMP DeleteAllItems() 176 | { 177 | assert(_attributes); 178 | WINTRACE(L"%s:DeleteAllItems", _trace.c_str()); 179 | return _attributes->DeleteAllItems(); 180 | } 181 | 182 | STDMETHODIMP SetUINT32(REFGUID guidKey, UINT32 value) 183 | { 184 | assert(_attributes); 185 | WINTRACE(L"%s:SetUINT32 '%s' value:%u", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), value); 186 | return _attributes->SetUINT32(guidKey, value); 187 | } 188 | 189 | STDMETHODIMP SetUINT64(REFGUID guidKey, UINT64 value) 190 | { 191 | assert(_attributes); 192 | WINTRACE(L"%s:SetUINT64 '%s' value:%I64i", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), value); 193 | return _attributes->SetUINT64(guidKey, value); 194 | } 195 | 196 | STDMETHODIMP SetDouble(REFGUID guidKey, double value) 197 | { 198 | assert(_attributes); 199 | WINTRACE(L"%s:SetDouble '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 200 | return _attributes->SetDouble(guidKey, value); 201 | } 202 | 203 | STDMETHODIMP SetGUID(REFGUID guidKey, REFGUID value) 204 | { 205 | assert(_attributes); 206 | WINTRACE(L"%s:SetGUID '%s' value:'%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), GUID_ToStringW(value).c_str()); 207 | return _attributes->SetGUID(guidKey, value); 208 | } 209 | 210 | STDMETHODIMP SetString(REFGUID guidKey, LPCWSTR value) 211 | { 212 | assert(_attributes); 213 | WINTRACE(L"%s:SetString '%s' value:'%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), value); 214 | return _attributes->SetString(guidKey, value); 215 | } 216 | 217 | STDMETHODIMP SetBlob(REFGUID guidKey, const UINT8* pBuf, UINT32 cbBufSize) 218 | { 219 | assert(_attributes); 220 | WINTRACE(L"%s:SetBlob '%s'", _trace.c_str(), GUID_ToStringW(guidKey).c_str()); 221 | return _attributes->SetBlob(guidKey, pBuf, cbBufSize); 222 | } 223 | 224 | STDMETHODIMP SetUnknown(REFGUID guidKey, IUnknown* value) 225 | { 226 | assert(_attributes); 227 | WINTRACE(L"%s:SetUnknown '%s' value:%p", _trace.c_str(), GUID_ToStringW(guidKey).c_str(), value); 228 | return _attributes->SetUnknown(guidKey, value); 229 | } 230 | 231 | STDMETHODIMP LockStore() 232 | { 233 | assert(_attributes); 234 | WINTRACE(L"%s:LockStore", _trace.c_str()); 235 | return _attributes->LockStore(); 236 | } 237 | 238 | STDMETHODIMP UnlockStore() 239 | { 240 | assert(_attributes); 241 | WINTRACE(L"%s:UnlockStore", _trace.c_str()); 242 | return _attributes->UnlockStore(); 243 | } 244 | 245 | STDMETHODIMP GetCount(UINT32* pcItems) 246 | { 247 | RETURN_HR_IF(E_INVALIDARG, !pcItems); 248 | assert(_attributes); 249 | auto hr = _attributes->GetCount(pcItems); 250 | WINTRACE(L"%s:GetCount %u hr:0x%08X", _trace.c_str(), *pcItems, hr); 251 | return hr; 252 | } 253 | 254 | STDMETHODIMP GetItemByIndex(UINT32 unIndex, GUID* pguidKey, PROPVARIANT* pValue) 255 | { 256 | assert(_attributes); 257 | WINTRACE(L"%s:GetItemByIndex %u", _trace.c_str(), unIndex); 258 | return _attributes->GetItemByIndex(unIndex, pguidKey, pValue); 259 | } 260 | 261 | STDMETHODIMP CopyAllItems(IMFAttributes* pDest) 262 | { 263 | RETURN_HR_IF(E_INVALIDARG, !pDest); 264 | assert(_attributes); 265 | WINTRACE(L"%s:CopyAllItems", _trace.c_str()); 266 | return _attributes->CopyAllItems(pDest); 267 | } 268 | 269 | HRESULT SerializeToStream(DWORD dwOptions, IStream* pStm) 270 | { 271 | RETURN_HR_IF(E_INVALIDARG, !pStm); 272 | assert(_attributes); 273 | return MFSerializeAttributesToStream(_attributes.get(), dwOptions, pStm); 274 | } 275 | 276 | HRESULT DeserializeFromStream(DWORD dwOptions, IStream* pStm) 277 | { 278 | RETURN_HR_IF(E_INVALIDARG, !pStm); 279 | assert(_attributes); 280 | return MFDeserializeAttributesFromStream(_attributes.get(), dwOptions, pStm); 281 | } 282 | 283 | HRESULT SerializeToBlob(UINT8** buffer, UINT32* size) 284 | { 285 | RETURN_HR_IF(E_INVALIDARG, !_attributes || !size); 286 | assert(_attributes); 287 | 288 | *buffer = NULL; 289 | *size = 0; 290 | 291 | UINT32 cbSize = 0; 292 | RETURN_IF_FAILED(MFGetAttributesAsBlobSize(_attributes.get(), &cbSize)); 293 | 294 | auto pBuffer = (BYTE*)CoTaskMemAlloc(cbSize); 295 | RETURN_IF_NULL_ALLOC(pBuffer); 296 | 297 | auto hr = MFGetAttributesAsBlob(_attributes.get(), pBuffer, cbSize); 298 | if (SUCCEEDED(hr)) 299 | { 300 | *buffer = pBuffer; 301 | *size = cbSize; 302 | } 303 | else 304 | { 305 | CoTaskMemFree(pBuffer); 306 | } 307 | return hr; 308 | } 309 | 310 | HRESULT DeserializeFromBlob(const UINT8* buffer, UINT size) 311 | { 312 | RETURN_HR_IF(E_INVALIDARG, !buffer || !size); 313 | assert(_attributes); 314 | return MFInitAttributesFromBlob(_attributes.get(), buffer, size); 315 | } 316 | 317 | HRESULT GetRatio(REFGUID guidKey, UINT32* pnNumerator, UINT32* punDenominator) 318 | { 319 | RETURN_HR_IF(E_INVALIDARG, !pnNumerator || !punDenominator); 320 | assert(_attributes); 321 | return MFGetAttributeRatio(_attributes.get(), guidKey, pnNumerator, punDenominator); 322 | } 323 | 324 | HRESULT SetRatio(REFGUID guidKey, UINT32 unNumerator, UINT32 unDenominator) 325 | { 326 | assert(_attributes); 327 | return MFSetAttributeRatio(_attributes.get(), guidKey, unNumerator, unDenominator); 328 | } 329 | 330 | HRESULT GetSize(REFGUID guidKey, UINT32* punWidth, UINT32* punHeight) 331 | { 332 | RETURN_HR_IF(E_INVALIDARG, !punWidth || !punWidth); 333 | assert(_attributes); 334 | return MFGetAttributeSize(_attributes.get(), guidKey, punWidth, punHeight); 335 | } 336 | 337 | HRESULT SetSize(REFGUID guidKey, UINT32 unWidth, UINT32 unHeight) 338 | { 339 | assert(_attributes); 340 | return MFSetAttributeSize(_attributes.get(), guidKey, unWidth, unHeight); 341 | } 342 | }; -------------------------------------------------------------------------------- /VCamSampleSource/MediaSource.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Undocumented.h" 3 | #include "Tools.h" 4 | #include "EnumNames.h" 5 | #include "MFTools.h" 6 | #include "FrameGenerator.h" 7 | #include "MediaStream.h" 8 | #include "MediaSource.h" 9 | 10 | HRESULT MediaSource::Initialize(IMFAttributes* attributes) 11 | { 12 | if (attributes) 13 | { 14 | RETURN_IF_FAILED(attributes->CopyAllItems(this)); 15 | } 16 | 17 | wil::com_ptr_nothrow collection; 18 | RETURN_IF_FAILED(MFCreateSensorProfileCollection(&collection)); 19 | 20 | DWORD streamId = 0; 21 | wil::com_ptr_nothrow profile; 22 | RETURN_IF_FAILED(MFCreateSensorProfile(KSCAMERAPROFILE_Legacy, 0, nullptr, &profile)); 23 | RETURN_IF_FAILED(profile->AddProfileFilter(streamId, L"((RES==;FRT<=30,1;SUT==))")); 24 | RETURN_IF_FAILED(collection->AddProfile(profile.get())); 25 | 26 | RETURN_IF_FAILED(MFCreateSensorProfile(KSCAMERAPROFILE_HighFrameRate, 0, nullptr, &profile)); 27 | RETURN_IF_FAILED(profile->AddProfileFilter(streamId, L"((RES==;FRT>=60,1;SUT==))")); 28 | RETURN_IF_FAILED(collection->AddProfile(profile.get())); 29 | RETURN_IF_FAILED(SetUnknown(MF_DEVICEMFT_SENSORPROFILE_COLLECTION, collection.get())); 30 | 31 | try 32 | { 33 | auto appInfo = winrt::Windows::ApplicationModel::AppInfo::Current(); 34 | if (appInfo) 35 | { 36 | RETURN_IF_FAILED(SetString(MF_VIRTUALCAMERA_CONFIGURATION_APP_PACKAGE_FAMILY_NAME, appInfo.PackageFamilyName().data())); 37 | } 38 | } 39 | catch (...) 40 | { 41 | WINTRACE(L"MediaSource::Initialize no AppX"); 42 | } 43 | 44 | auto streams = wil::make_unique_cotaskmem_array>(_streams.size()); 45 | for (uint32_t i = 0; i < streams.size(); i++) 46 | { 47 | wil::com_ptr_nothrow desc; 48 | RETURN_IF_FAILED(_streams[i]->GetStreamDescriptor(&desc)); 49 | streams[i] = desc.detach(); 50 | } 51 | RETURN_IF_FAILED(MFCreatePresentationDescriptor((DWORD)streams.size(), streams.get(), &_descriptor)); 52 | RETURN_IF_FAILED(MFCreateEventQueue(&_queue)); 53 | return S_OK; 54 | } 55 | 56 | int MediaSource::GetStreamIndexById(DWORD id) 57 | { 58 | for (uint32_t i = 0; i < _streams.size(); i++) 59 | { 60 | wil::com_ptr_nothrow desc; 61 | if (FAILED(_streams[i]->GetStreamDescriptor(&desc))) 62 | return -1; 63 | 64 | DWORD sid = 0; 65 | if (FAILED(desc->GetStreamIdentifier(&sid))) 66 | return -1; 67 | 68 | if (sid == id) 69 | return i; 70 | } 71 | return -1; 72 | } 73 | 74 | // IMFMediaEventGenerator 75 | STDMETHODIMP MediaSource::BeginGetEvent(IMFAsyncCallback* pCallback, IUnknown* punkState) 76 | { 77 | WINTRACE(L"MediaSource::BeginGetEvent pCallback:%p punkState:%p", pCallback, punkState); 78 | winrt::slim_lock_guard lock(_lock); 79 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 80 | 81 | RETURN_IF_FAILED(_queue->BeginGetEvent(pCallback, punkState)); 82 | return S_OK; 83 | } 84 | 85 | STDMETHODIMP MediaSource::EndGetEvent(IMFAsyncResult* pResult, IMFMediaEvent** ppEvent) 86 | { 87 | WINTRACE(L"MediaSource::EndGetEvent"); 88 | RETURN_HR_IF_NULL(E_POINTER, ppEvent); 89 | *ppEvent = nullptr; 90 | winrt::slim_lock_guard lock(_lock); 91 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 92 | 93 | RETURN_IF_FAILED(_queue->EndGetEvent(pResult, ppEvent)); 94 | return S_OK; 95 | } 96 | 97 | STDMETHODIMP MediaSource::GetEvent(DWORD dwFlags, IMFMediaEvent** ppEvent) 98 | { 99 | WINTRACE(L"MediaSource::GetEvent"); 100 | RETURN_HR_IF_NULL(E_POINTER, ppEvent); 101 | *ppEvent = nullptr; 102 | winrt::slim_lock_guard lock(_lock); 103 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 104 | 105 | RETURN_IF_FAILED(_queue->GetEvent(dwFlags, ppEvent)); 106 | return S_OK; 107 | } 108 | 109 | STDMETHODIMP MediaSource::QueueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue) 110 | { 111 | WINTRACE(L"MediaSource::QueueEvent"); 112 | winrt::slim_lock_guard lock(_lock); 113 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 114 | 115 | RETURN_IF_FAILED(_queue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue)); 116 | return S_OK; 117 | } 118 | 119 | // IMFMediaSource 120 | STDMETHODIMP MediaSource::CreatePresentationDescriptor(IMFPresentationDescriptor** ppPresentationDescriptor) 121 | { 122 | WINTRACE(L"MediaSource::CreatePresentationDescriptor"); 123 | RETURN_HR_IF_NULL(E_POINTER, ppPresentationDescriptor); 124 | *ppPresentationDescriptor = nullptr; 125 | winrt::slim_lock_guard lock(_lock); 126 | RETURN_HR_IF(MF_E_SHUTDOWN, !_descriptor); 127 | 128 | RETURN_IF_FAILED(_descriptor->Clone(ppPresentationDescriptor)); 129 | return S_OK; 130 | } 131 | 132 | STDMETHODIMP MediaSource::GetCharacteristics(DWORD* pdwCharacteristics) 133 | { 134 | WINTRACE(L"MediaSource::GetCharacteristics"); 135 | RETURN_HR_IF_NULL(E_POINTER, pdwCharacteristics); 136 | 137 | *pdwCharacteristics = MFMEDIASOURCE_IS_LIVE; 138 | return S_OK; 139 | } 140 | 141 | STDMETHODIMP MediaSource::Pause() 142 | { 143 | WINTRACE(L"MediaSource::Pause"); 144 | RETURN_HR(MF_E_INVALID_STATE_TRANSITION); 145 | } 146 | 147 | STDMETHODIMP MediaSource::Shutdown() 148 | { 149 | WINTRACE(L"MediaSource::Shutdown"); 150 | winrt::slim_lock_guard lock(_lock); 151 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 152 | 153 | LOG_IF_FAILED_MSG(_queue->Shutdown(), "Queue shutdown failed"); 154 | _queue.reset(); 155 | 156 | for (uint32_t i = 0; i < _streams.size(); i++) 157 | { 158 | _streams[i]->Shutdown(); 159 | } 160 | 161 | _descriptor.reset(); 162 | _attributes.reset(); 163 | return S_OK; 164 | } 165 | 166 | STDMETHODIMP MediaSource::Start(IMFPresentationDescriptor* pPresentationDescriptor, const GUID* pguidTimeFormat, const PROPVARIANT* pvarStartPosition) 167 | { 168 | WINTRACE(L"MediaSource::Start pPresentationDescriptor:%p pguidTimeFormat:%p pvarStartPosition:%p", pPresentationDescriptor, pguidTimeFormat, pvarStartPosition); 169 | RETURN_HR_IF_NULL(E_POINTER, pPresentationDescriptor); 170 | RETURN_HR_IF_NULL(E_POINTER, pvarStartPosition); 171 | RETURN_HR_IF_MSG(E_INVALIDARG, pguidTimeFormat && *pguidTimeFormat != GUID_NULL, "Unsupported guid time format"); 172 | winrt::slim_lock_guard lock(_lock); 173 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue || !_descriptor); 174 | 175 | DWORD count; 176 | RETURN_IF_FAILED(pPresentationDescriptor->GetStreamDescriptorCount(&count)); 177 | RETURN_HR_IF_MSG(E_INVALIDARG, count != (DWORD)_streams.size(), "Invalid number of descriptor streams"); 178 | 179 | wil::unique_prop_variant time; 180 | RETURN_IF_FAILED(InitPropVariantFromInt64(MFGetSystemTime(), &time)); 181 | 182 | for (DWORD i = 0; i < count; i++) 183 | { 184 | wil::com_ptr_nothrow desc; 185 | BOOL selected = FALSE; 186 | RETURN_IF_FAILED(pPresentationDescriptor->GetStreamDescriptorByIndex(i, &selected, &desc)); 187 | 188 | DWORD id = 0; 189 | RETURN_IF_FAILED(desc->GetStreamIdentifier(&id)); 190 | 191 | auto index = GetStreamIndexById(id); 192 | RETURN_HR_IF(E_FAIL, index < 0); 193 | 194 | BOOL thisSelected = FALSE; 195 | wil::com_ptr_nothrow thisDesc; 196 | RETURN_IF_FAILED(_descriptor->GetStreamDescriptorByIndex(index, &thisSelected, &thisDesc)); 197 | 198 | MF_STREAM_STATE state; 199 | RETURN_IF_FAILED(_streams[i]->GetStreamState(&state)); 200 | if (thisSelected && state == MF_STREAM_STATE_STOPPED ) 201 | { 202 | thisSelected = FALSE; 203 | } 204 | else if (!thisSelected && state != MF_STREAM_STATE_STOPPED) 205 | { 206 | thisSelected = TRUE; 207 | } 208 | 209 | WINTRACE(L"MediaSource::Start stream[%i] selected:%i thisSelected:%i", index, selected, thisSelected); 210 | if (selected != thisSelected) 211 | { 212 | if (selected) 213 | { 214 | RETURN_IF_FAILED(_descriptor->SelectStream(index)); 215 | 216 | wil::com_ptr_nothrow unk; 217 | RETURN_IF_FAILED(_streams[index].copy_to(&unk)); 218 | RETURN_IF_FAILED(_queue->QueueEventParamUnk(MENewStream, GUID_NULL, S_OK, unk.get())); 219 | 220 | wil::com_ptr_nothrow handler; 221 | wil::com_ptr_nothrow type; 222 | RETURN_IF_FAILED(desc->GetMediaTypeHandler(&handler)); 223 | RETURN_IF_FAILED(handler->GetCurrentMediaType(&type)); 224 | 225 | RETURN_IF_FAILED(_streams[index]->Start(type.get())); 226 | } 227 | else 228 | { 229 | RETURN_IF_FAILED(_descriptor->DeselectStream(index)); 230 | RETURN_IF_FAILED(_streams[index]->Stop()); 231 | } 232 | } 233 | } 234 | 235 | RETURN_IF_FAILED(_queue->QueueEventParamVar(MESourceStarted, GUID_NULL, S_OK, &time)); 236 | return S_OK; 237 | } 238 | 239 | STDMETHODIMP MediaSource::Stop() 240 | { 241 | WINTRACE(L"MediaSource::Stop"); 242 | winrt::slim_lock_guard lock(_lock); 243 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue || !_descriptor); 244 | 245 | wil::unique_prop_variant time; 246 | RETURN_IF_FAILED(InitPropVariantFromInt64(MFGetSystemTime(), &time)); 247 | 248 | for (DWORD i = 0; i < _streams.size(); i++) 249 | { 250 | RETURN_IF_FAILED(_streams[i]->Stop()); 251 | RETURN_IF_FAILED(_descriptor->DeselectStream(i)); 252 | } 253 | 254 | RETURN_IF_FAILED(_queue->QueueEventParamVar(MESourceStopped, GUID_NULL, S_OK, &time)); 255 | return S_OK; 256 | } 257 | 258 | // IMFMediaSourceEx 259 | STDMETHODIMP MediaSource::GetSourceAttributes(IMFAttributes** ppAttributes) 260 | { 261 | WINTRACE(L"MediaSource::GetSourceAttributes"); 262 | RETURN_HR_IF_NULL(E_POINTER, ppAttributes); 263 | winrt::slim_lock_guard lock(_lock); 264 | 265 | RETURN_IF_FAILED(QueryInterface(IID_PPV_ARGS(ppAttributes))); 266 | return S_OK; 267 | } 268 | 269 | // IMFMediaSource2 270 | STDMETHODIMP MediaSource::SetMediaType(DWORD dwStreamID, IMFMediaType* pMediaType) 271 | { 272 | WINTRACE(L"MediaSource::SetMediaType dwStreamId:%u pMediaType:%p", dwStreamID, pMediaType); 273 | RETURN_HR_IF_NULL(E_POINTER, pMediaType); 274 | winrt::slim_lock_guard lock(_lock); 275 | 276 | TraceMFAttributes(pMediaType, L"MediaType"); 277 | return S_OK; 278 | } 279 | 280 | STDMETHODIMP MediaSource::GetStreamAttributes(DWORD dwStreamIdentifier, IMFAttributes** ppAttributes) 281 | { 282 | WINTRACE(L"MediaSource::GetStreamAttributes dwStreamIdentifier:%u", dwStreamIdentifier); 283 | RETURN_HR_IF_NULL(E_POINTER, ppAttributes); 284 | *ppAttributes = nullptr; 285 | winrt::slim_lock_guard lock(_lock); 286 | 287 | RETURN_HR_IF_MSG(E_FAIL, dwStreamIdentifier >= _streams.size(), "dwStreamIdentifier %u is invalid", dwStreamIdentifier); 288 | RETURN_IF_FAILED(_streams[dwStreamIdentifier].copy_to(ppAttributes)); 289 | return S_OK; 290 | } 291 | 292 | STDMETHODIMP MediaSource::SetD3DManager(IUnknown* pManager) 293 | { 294 | WINTRACE(L"MediaSource::SetD3DManager pManager:%p", pManager); 295 | RETURN_HR_IF_NULL(E_POINTER, pManager); 296 | winrt::slim_lock_guard lock(_lock); 297 | 298 | for (DWORD i = 0; i < _streams.size(); i++) 299 | { 300 | RETURN_IF_FAILED(_streams[i]->SetD3DManager(pManager)); 301 | } 302 | return S_OK; 303 | } 304 | 305 | // IMFGetService 306 | STDMETHODIMP MediaSource::GetService(REFGUID siid, REFIID iid, LPVOID* ppvObject) 307 | { 308 | if (iid == __uuidof(IMFDeviceController) || iid == __uuidof(IMFDeviceController2)) 309 | return MF_E_UNSUPPORTED_SERVICE; 310 | 311 | WINTRACE(L"MediaSource::GetService siid '%s' iid '%s' failed", GUID_ToStringW(siid).c_str(), GUID_ToStringW(iid).c_str()); 312 | RETURN_HR(MF_E_UNSUPPORTED_SERVICE); 313 | } 314 | 315 | // IMFSampleAllocatorControl 316 | STDMETHODIMP MediaSource::SetDefaultAllocator(DWORD dwOutputStreamID, IUnknown* pAllocator) 317 | { 318 | WINTRACE(L"MediaSource::SetDefaultAllocator dwOutputStreamID:%u pAllocator:%p", dwOutputStreamID, pAllocator); 319 | RETURN_HR_IF_NULL(E_POINTER, pAllocator); 320 | winrt::slim_lock_guard lock(_lock); 321 | 322 | auto index = GetStreamIndexById(dwOutputStreamID); 323 | RETURN_HR_IF(E_FAIL, index < 0); 324 | 325 | RETURN_HR_IF_MSG(E_FAIL, index < 0 || (DWORD)index >= _streams.size(), "dwOutputStreamID %u is invalid, index:%i", dwOutputStreamID, index); 326 | RETURN_HR(_streams[index]->SetAllocator(pAllocator)); 327 | } 328 | 329 | STDMETHODIMP MediaSource::GetAllocatorUsage(DWORD dwOutputStreamID, DWORD* pdwInputStreamID, MFSampleAllocatorUsage* peUsage) 330 | { 331 | WINTRACE(L"MediaSource::GetAllocatorUsage dwOutputStreamID:%u pdwInputStreamID:%p peUsage:%p", dwOutputStreamID, pdwInputStreamID, peUsage); 332 | RETURN_HR_IF_NULL(E_POINTER, peUsage); 333 | RETURN_HR_IF_NULL(E_POINTER, pdwInputStreamID); 334 | winrt::slim_lock_guard lock(_lock); 335 | 336 | auto index = GetStreamIndexById(dwOutputStreamID); 337 | RETURN_HR_IF(E_FAIL, index < 0); 338 | 339 | RETURN_HR_IF_MSG(E_FAIL, index < 0 || (DWORD)index >= _streams.size(), "dwOutputStreamID %u is invalid, index:%i", dwOutputStreamID, index); 340 | *pdwInputStreamID = dwOutputStreamID; 341 | *peUsage = _streams[index]->GetAllocatorUsage(); 342 | return S_OK; 343 | } 344 | 345 | // IKsControl 346 | STDMETHODIMP_(NTSTATUS) MediaSource::KsProperty(PKSPROPERTY property, ULONG length, LPVOID data, ULONG dataLength, ULONG* bytesReturned) 347 | { 348 | WINTRACE(L"MediaSource::KsProperty len:%u data:%p dataLength:%u", length, data, dataLength); 349 | RETURN_HR_IF_NULL(E_POINTER, property); 350 | RETURN_HR_IF_NULL(E_POINTER, bytesReturned); 351 | winrt::slim_lock_guard lock(_lock); 352 | 353 | WINTRACE(L"MediaSource::KsProperty prop:%s", PKSIDENTIFIER_ToString(property, length).c_str()); 354 | 355 | // right now, we don't expose any property, but this is where we'll typically be asked for 356 | // 357 | // KSPROPSETID_Pin, KSPROPSETID_Topology, PROPSETID_VIDCAP_CAMERACONTROL, PROPSETID_VIDCAP_VIDEOPROCAMP 358 | // PROPSETID_VIDCAP_CAMERACONTROL_REGION_OF_INTEREST, KSPROPERTYSETID_PerFrameSettingControl, KSPROPERTYSETID_ExtendedCameraControl 359 | // 360 | // etc 361 | 362 | return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND); 363 | } 364 | 365 | STDMETHODIMP_(NTSTATUS) MediaSource::KsMethod(PKSMETHOD method, ULONG length, LPVOID data, ULONG dataLength, ULONG* bytesReturned) 366 | { 367 | WINTRACE(L"MediaSource::KsMethod len:%u data:%p dataLength:%u", length, data, dataLength); 368 | RETURN_HR_IF_NULL(E_POINTER, method); 369 | RETURN_HR_IF_NULL(E_POINTER, bytesReturned); 370 | winrt::slim_lock_guard lock(_lock); 371 | 372 | WINTRACE(L"MediaSource::KsMethod method:%s", PKSIDENTIFIER_ToString(method, length).c_str()); 373 | 374 | return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND); 375 | } 376 | 377 | STDMETHODIMP_(NTSTATUS) MediaSource::KsEvent(PKSEVENT evt, ULONG length, LPVOID data, ULONG dataLength, ULONG* bytesReturned) 378 | { 379 | WINTRACE(L"MediaSource::KsEvent evt:%p len:%u data:%p dataLength:%u", evt, length, data, dataLength); 380 | RETURN_HR_IF_NULL(E_POINTER, bytesReturned); 381 | winrt::slim_lock_guard lock(_lock); 382 | 383 | WINTRACE(L"MediaSource::KsEvent event:%s", PKSIDENTIFIER_ToString(evt, length).c_str()); 384 | return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND); 385 | } 386 | -------------------------------------------------------------------------------- /VCamSampleSource/MediaSource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct MediaStream; 4 | 5 | struct MediaSource : winrt::implements, IMFMediaSourceEx, IMFGetService, IKsControl, IMFSampleAllocatorControl> 6 | { 7 | public: 8 | // IMFMediaEventGenerator 9 | STDMETHOD(BeginGetEvent)(IMFAsyncCallback* pCallback, IUnknown* punkState); 10 | STDMETHOD(EndGetEvent)(IMFAsyncResult* pResult, IMFMediaEvent** ppEvent); 11 | STDMETHOD(GetEvent)(DWORD dwFlags, IMFMediaEvent** ppEvent); 12 | STDMETHOD(QueueEvent)(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue); 13 | 14 | // IMFMediaSource 15 | STDMETHOD(CreatePresentationDescriptor)(IMFPresentationDescriptor** ppPresentationDescriptor); 16 | STDMETHOD(GetCharacteristics)(DWORD* pdwCharacteristics); 17 | STDMETHOD(Pause)(); 18 | STDMETHOD(Shutdown)(); 19 | STDMETHOD(Start)(IMFPresentationDescriptor* pPresentationDescriptor, const GUID* pguidTimeFormat, const PROPVARIANT* pvarStartPosition); 20 | STDMETHOD(Stop)(); 21 | 22 | // IMFMediaSourceEx 23 | STDMETHOD(GetSourceAttributes)(IMFAttributes** ppAttributes); 24 | STDMETHOD(GetStreamAttributes)(DWORD dwStreamIdentifier, IMFAttributes** ppAttributes); 25 | STDMETHOD(SetD3DManager)(IUnknown* pManager); 26 | 27 | // IMFMediaSource2 : we don't currently use it, change IMFMediaSourceEx to IMFMediaSource2 to enable it 28 | STDMETHOD(SetMediaType)(DWORD dwStreamID, IMFMediaType* pMediaType); 29 | 30 | // IMFGetService 31 | STDMETHOD(GetService)(REFGUID guidService, REFIID riid, LPVOID* ppvObject); 32 | 33 | // IMFSampleAllocatorControl 34 | STDMETHOD(SetDefaultAllocator)(DWORD dwOutputStreamID, IUnknown* pAllocator); 35 | STDMETHOD(GetAllocatorUsage)(DWORD dwOutputStreamID, DWORD* pdwInputStreamID, MFSampleAllocatorUsage* peUsage); 36 | 37 | // IKsControl 38 | STDMETHOD_(NTSTATUS, KsProperty)(PKSPROPERTY Property, ULONG PropertyLength, LPVOID PropertyData, ULONG DataLength, ULONG* BytesReturned); 39 | STDMETHOD_(NTSTATUS, KsMethod)(PKSMETHOD Method, ULONG MethodLength, LPVOID MethodData, ULONG DataLength, ULONG* BytesReturned); 40 | STDMETHOD_(NTSTATUS, KsEvent)(PKSEVENT Event, ULONG EventLength, LPVOID EventData, ULONG DataLength, ULONG* BytesReturned); 41 | 42 | public: 43 | MediaSource() : 44 | _streams(_numStreams) 45 | { 46 | SetBaseAttributesTraceName(L"MediaSourceAtts"); 47 | for (auto i = 0; i < _numStreams; i++) 48 | { 49 | auto stream = winrt::make_self(); 50 | stream->Initialize(this, i); 51 | _streams[i].attach(stream.detach()); // this is needed because of wil+winrt mumbo-jumbo, as "_streams[i] = stream.detach()" just cause one extra AddRef 52 | } 53 | } 54 | 55 | HRESULT Initialize(IMFAttributes* attributes); 56 | 57 | private: 58 | #if _DEBUG 59 | int32_t query_interface_tearoff(winrt::guid const& id, void** object) const noexcept override 60 | { 61 | // undoc'd 62 | if (id == winrt::guid_of() || 63 | id == winrt::guid_of() || 64 | id == winrt::guid_of() || 65 | id == winrt::guid_of() || 66 | id == winrt::guid_of() || 67 | id == winrt::guid_of()) 68 | return E_NOINTERFACE; 69 | 70 | if (id == winrt::guid_of() || 71 | id == winrt::guid_of()) 72 | return E_NOINTERFACE; 73 | 74 | RETURN_HR_MSG(E_NOINTERFACE, "MediaSource QueryInterface failed on IID %s", GUID_ToStringW(id).c_str()); 75 | } 76 | #endif 77 | 78 | int GetStreamIndexById(DWORD id); 79 | 80 | private: 81 | const int _numStreams = 1; // 1 stream for now 82 | winrt::slim_mutex _lock; 83 | winrt::com_array> _streams; 84 | wil::com_ptr_nothrow _queue; 85 | wil::com_ptr_nothrow _descriptor; 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /VCamSampleSource/MediaStream.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Undocumented.h" 3 | #include "Tools.h" 4 | #include "EnumNames.h" 5 | #include "MFTools.h" 6 | #include "FrameGenerator.h" 7 | #include "MediaStream.h" 8 | #include "MediaSource.h" 9 | 10 | HRESULT MediaStream::Initialize(IMFMediaSource* source, int index) 11 | { 12 | RETURN_HR_IF_NULL(E_POINTER, source); 13 | _source = source; 14 | _index = index; 15 | 16 | RETURN_IF_FAILED(SetGUID(MF_DEVICESTREAM_STREAM_CATEGORY, PINNAME_VIDEO_CAPTURE)); 17 | RETURN_IF_FAILED(SetUINT32(MF_DEVICESTREAM_STREAM_ID, index)); 18 | RETURN_IF_FAILED(SetUINT32(MF_DEVICESTREAM_FRAMESERVER_SHARED, 1)); 19 | RETURN_IF_FAILED(SetUINT32(MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES, MFFrameSourceTypes::MFFrameSourceTypes_Color)); 20 | 21 | RETURN_IF_FAILED(MFCreateEventQueue(&_queue)); 22 | 23 | // set 1 here to force RGB32 only 24 | auto types = wil::make_unique_cotaskmem_array>(2); 25 | 26 | #define NUM_IMAGE_COLS 1280 // 640 27 | #define NUM_IMAGE_ROWS 960 //480 28 | 29 | wil::com_ptr_nothrow rgbType; 30 | RETURN_IF_FAILED(MFCreateMediaType(&rgbType)); 31 | rgbType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 32 | rgbType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); 33 | MFSetAttributeSize(rgbType.get(), MF_MT_FRAME_SIZE, NUM_IMAGE_COLS, NUM_IMAGE_ROWS); 34 | rgbType->SetUINT32(MF_MT_DEFAULT_STRIDE, NUM_IMAGE_COLS * 4); 35 | rgbType->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); 36 | rgbType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); 37 | MFSetAttributeRatio(rgbType.get(), MF_MT_FRAME_RATE, 30, 1); 38 | auto bitrate = (uint32_t)(NUM_IMAGE_COLS * NUM_IMAGE_ROWS * 4 * 8 * 30); 39 | rgbType->SetUINT32(MF_MT_AVG_BITRATE, bitrate); 40 | MFSetAttributeRatio(rgbType.get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); 41 | types[0] = rgbType.detach(); 42 | 43 | if (types.size() > 1) 44 | { 45 | wil::com_ptr_nothrow nv12Type; 46 | RETURN_IF_FAILED(MFCreateMediaType(&nv12Type)); 47 | nv12Type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 48 | nv12Type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); 49 | nv12Type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); 50 | nv12Type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); 51 | MFSetAttributeSize(nv12Type.get(), MF_MT_FRAME_SIZE, NUM_IMAGE_COLS, NUM_IMAGE_ROWS); 52 | nv12Type->SetUINT32(MF_MT_DEFAULT_STRIDE, (UINT)(NUM_IMAGE_COLS * 1.5)); 53 | MFSetAttributeRatio(nv12Type.get(), MF_MT_FRAME_RATE, 30, 1); 54 | // frame size * pixel bit size * framerate 55 | bitrate = (uint32_t)(NUM_IMAGE_COLS * 1.5 * NUM_IMAGE_ROWS * 8 * 30); 56 | nv12Type->SetUINT32(MF_MT_AVG_BITRATE, bitrate); 57 | MFSetAttributeRatio(nv12Type.get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); 58 | types[1] = nv12Type.detach(); 59 | } 60 | 61 | RETURN_IF_FAILED_MSG(MFCreateStreamDescriptor(_index, (DWORD)types.size(), types.get(), &_descriptor), "MFCreateStreamDescriptor failed"); 62 | 63 | wil::com_ptr_nothrow handler; 64 | RETURN_IF_FAILED(_descriptor->GetMediaTypeHandler(&handler)); 65 | TraceMFAttributes(handler.get(), L"MediaTypeHandler"); 66 | RETURN_IF_FAILED(handler->SetCurrentMediaType(types[0])); 67 | 68 | return S_OK; 69 | } 70 | 71 | HRESULT MediaStream::Start(IMFMediaType* type) 72 | { 73 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue || !_allocator); 74 | 75 | if (type) 76 | { 77 | RETURN_IF_FAILED(type->GetGUID(MF_MT_SUBTYPE, &_format)); 78 | WINTRACE(L"MediaStream::Start format: %s", GUID_ToStringW(_format).c_str()); 79 | } 80 | 81 | // at this point, set D3D manager may have not been called 82 | // so we want to create a D2D1 renter target anyway 83 | RETURN_IF_FAILED(_generator.EnsureRenderTarget(NUM_IMAGE_COLS, NUM_IMAGE_ROWS)); 84 | 85 | RETURN_IF_FAILED(_allocator->InitializeSampleAllocator(10, type)); 86 | RETURN_IF_FAILED(_queue->QueueEventParamVar(MEStreamStarted, GUID_NULL, S_OK, nullptr)); 87 | _state = MF_STREAM_STATE_RUNNING; 88 | return S_OK; 89 | } 90 | 91 | HRESULT MediaStream::Stop() 92 | { 93 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue || !_allocator); 94 | 95 | RETURN_IF_FAILED(_allocator->UninitializeSampleAllocator()); 96 | RETURN_IF_FAILED(_queue->QueueEventParamVar(MEStreamStopped, GUID_NULL, S_OK, nullptr)); 97 | _state = MF_STREAM_STATE_STOPPED; 98 | return S_OK; 99 | } 100 | 101 | MFSampleAllocatorUsage MediaStream::GetAllocatorUsage() 102 | { 103 | return MFSampleAllocatorUsage_UsesProvidedAllocator; 104 | } 105 | 106 | HRESULT MediaStream::SetAllocator(IUnknown* allocator) 107 | { 108 | RETURN_HR_IF_NULL(E_POINTER, allocator); 109 | _allocator.reset(); 110 | RETURN_HR(allocator->QueryInterface(&_allocator)); 111 | } 112 | 113 | HRESULT MediaStream::SetD3DManager(IUnknown* manager) 114 | { 115 | RETURN_HR_IF_NULL(E_POINTER, manager); 116 | 117 | // comment these 2 lines to force CPU usage 118 | RETURN_IF_FAILED(_allocator->SetDirectXManager(manager)); 119 | RETURN_IF_FAILED(_generator.SetD3DManager(manager, NUM_IMAGE_COLS, NUM_IMAGE_ROWS)); 120 | return S_OK; 121 | } 122 | 123 | void MediaStream::Shutdown() 124 | { 125 | if (_queue) 126 | { 127 | LOG_IF_FAILED_MSG(_queue->Shutdown(), "Queue shutdown failed"); 128 | _queue.reset(); 129 | } 130 | 131 | _descriptor.reset(); 132 | _source.reset(); 133 | _attributes.reset(); 134 | } 135 | 136 | // IMFMediaEventGenerator 137 | STDMETHODIMP MediaStream::BeginGetEvent(IMFAsyncCallback* pCallback, IUnknown* punkState) 138 | { 139 | //WINTRACE(L"MediaSource::BeginGetEvent"); 140 | winrt::slim_lock_guard lock(_lock); 141 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 142 | 143 | RETURN_IF_FAILED(_queue->BeginGetEvent(pCallback, punkState)); 144 | return S_OK; 145 | } 146 | 147 | STDMETHODIMP MediaStream::EndGetEvent(IMFAsyncResult* pResult, IMFMediaEvent** ppEvent) 148 | { 149 | //WINTRACE(L"MediaStream::EndGetEvent"); 150 | RETURN_HR_IF_NULL(E_POINTER, ppEvent); 151 | *ppEvent = nullptr; 152 | winrt::slim_lock_guard lock(_lock); 153 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 154 | 155 | RETURN_IF_FAILED(_queue->EndGetEvent(pResult, ppEvent)); 156 | return S_OK; 157 | } 158 | 159 | STDMETHODIMP MediaStream::GetEvent(DWORD dwFlags, IMFMediaEvent** ppEvent) 160 | { 161 | WINTRACE(L"MediaStream::GetEvent"); 162 | RETURN_HR_IF_NULL(E_POINTER, ppEvent); 163 | *ppEvent = nullptr; 164 | winrt::slim_lock_guard lock(_lock); 165 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 166 | 167 | RETURN_IF_FAILED(_queue->GetEvent(dwFlags, ppEvent)); 168 | return S_OK; 169 | } 170 | 171 | STDMETHODIMP MediaStream::QueueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue) 172 | { 173 | WINTRACE(L"MediaStream::QueueEvent"); 174 | winrt::slim_lock_guard lock(_lock); 175 | RETURN_HR_IF(MF_E_SHUTDOWN, !_queue); 176 | 177 | RETURN_IF_FAILED(_queue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue)); 178 | return S_OK; 179 | } 180 | 181 | // IMFMediaStream 182 | STDMETHODIMP MediaStream::GetMediaSource(IMFMediaSource** ppMediaSource) 183 | { 184 | WINTRACE(L"MediaSource::GetMediaSource"); 185 | RETURN_HR_IF_NULL(E_POINTER, ppMediaSource); 186 | *ppMediaSource = nullptr; 187 | RETURN_HR_IF(MF_E_SHUTDOWN, !_source); 188 | 189 | RETURN_IF_FAILED(_source.copy_to(ppMediaSource)); 190 | return S_OK; 191 | } 192 | 193 | STDMETHODIMP MediaStream::GetStreamDescriptor(IMFStreamDescriptor** ppStreamDescriptor) 194 | { 195 | WINTRACE(L"MediaStream::GetStreamDescriptor"); 196 | RETURN_HR_IF_NULL(E_POINTER, ppStreamDescriptor); 197 | *ppStreamDescriptor = nullptr; 198 | winrt::slim_lock_guard lock(_lock); 199 | RETURN_HR_IF(MF_E_SHUTDOWN, !_descriptor); 200 | 201 | RETURN_IF_FAILED(_descriptor.copy_to(ppStreamDescriptor)); 202 | return S_OK; 203 | } 204 | 205 | STDMETHODIMP MediaStream::RequestSample(IUnknown* pToken) 206 | { 207 | //WINTRACE(L"MediaStream::RequestSample pToken:%p", pToken); 208 | winrt::slim_lock_guard lock(_lock); 209 | RETURN_HR_IF(MF_E_SHUTDOWN, !_allocator || !_queue); 210 | 211 | wil::com_ptr_nothrow sample; 212 | RETURN_IF_FAILED(_allocator->AllocateSample(&sample)); 213 | RETURN_IF_FAILED(sample->SetSampleTime(MFGetSystemTime())); 214 | RETURN_IF_FAILED(sample->SetSampleDuration(333333)); 215 | 216 | // generate frame 217 | wil::com_ptr_nothrow outSample; 218 | RETURN_IF_FAILED(_generator.Generate(sample.get(), _format, &outSample)); 219 | 220 | if (pToken) 221 | { 222 | RETURN_IF_FAILED(outSample->SetUnknown(MFSampleExtension_Token, pToken)); 223 | } 224 | RETURN_IF_FAILED(_queue->QueueEventParamUnk(MEMediaSample, GUID_NULL, S_OK, outSample.get())); 225 | return S_OK; 226 | } 227 | 228 | // IMFMediaStream2 229 | STDMETHODIMP MediaStream::SetStreamState(MF_STREAM_STATE value) 230 | { 231 | WINTRACE(L"MediaStream::SetStreamState current:%u value:%u", _state, value); 232 | if (_state = value) 233 | return S_OK; 234 | switch (value) 235 | { 236 | case MF_STREAM_STATE_PAUSED: 237 | if (_state != MF_STREAM_STATE_RUNNING) 238 | RETURN_HR(MF_E_INVALID_STATE_TRANSITION); 239 | 240 | _state = value; 241 | break; 242 | 243 | case MF_STREAM_STATE_RUNNING: 244 | RETURN_IF_FAILED(Start(nullptr)); 245 | break; 246 | 247 | case MF_STREAM_STATE_STOPPED: 248 | RETURN_IF_FAILED(Stop()); 249 | break; 250 | 251 | default: 252 | RETURN_HR(MF_E_INVALID_STATE_TRANSITION); 253 | break; 254 | } 255 | return S_OK; 256 | } 257 | 258 | STDMETHODIMP MediaStream::GetStreamState(MF_STREAM_STATE* value) 259 | { 260 | WINTRACE(L"MediaStream::GetStreamState state:%u", _state); 261 | RETURN_HR_IF_NULL(E_POINTER, value); 262 | *value = _state; 263 | return S_OK; 264 | } 265 | 266 | // IKsControl 267 | STDMETHODIMP_(NTSTATUS) MediaStream::KsProperty(PKSPROPERTY property, ULONG length, LPVOID data, ULONG dataLength, ULONG* bytesReturned) 268 | { 269 | WINTRACE(L"MediaStream::KsProperty len:%u data:%p dataLength:%u", length, data, dataLength); 270 | RETURN_HR_IF_NULL(E_POINTER, property); 271 | RETURN_HR_IF_NULL(E_POINTER, bytesReturned); 272 | winrt::slim_lock_guard lock(_lock); 273 | 274 | WINTRACE(L"MediaStream::KsProperty prop:%s", PKSIDENTIFIER_ToString(property, length).c_str()); 275 | 276 | return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND); 277 | } 278 | 279 | STDMETHODIMP_(NTSTATUS) MediaStream::KsMethod(PKSMETHOD method, ULONG length, LPVOID data, ULONG dataLength, ULONG* bytesReturned) 280 | { 281 | WINTRACE(L"MediaStream::KsMethod len:%u data:%p dataLength:%u", length, data, dataLength); 282 | RETURN_HR_IF_NULL(E_POINTER, method); 283 | RETURN_HR_IF_NULL(E_POINTER, bytesReturned); 284 | winrt::slim_lock_guard lock(_lock); 285 | 286 | WINTRACE(L"MediaStream::KsMethod method:%s", PKSIDENTIFIER_ToString(method, length).c_str()); 287 | 288 | return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND); 289 | } 290 | 291 | STDMETHODIMP_(NTSTATUS) MediaStream::KsEvent(PKSEVENT evt, ULONG length, LPVOID data, ULONG dataLength, ULONG* bytesReturned) 292 | { 293 | WINTRACE(L"MediaStream::KsEvent evt:%p len:%u data:%p dataLength:%u", evt, length, data, dataLength); 294 | RETURN_HR_IF_NULL(E_POINTER, bytesReturned); 295 | winrt::slim_lock_guard lock(_lock); 296 | 297 | WINTRACE(L"MediaStream::KsEvent event:%s", PKSIDENTIFIER_ToString(evt, length).c_str()); 298 | return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND); 299 | } 300 | -------------------------------------------------------------------------------- /VCamSampleSource/MediaStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct MediaStream : winrt::implements, IMFMediaStream2, IKsControl> 4 | { 5 | public: 6 | // IMFMediaEventGenerator 7 | STDMETHOD(BeginGetEvent)(IMFAsyncCallback* pCallback, IUnknown* punkState); 8 | STDMETHOD(EndGetEvent)(IMFAsyncResult* pResult, IMFMediaEvent** ppEvent); 9 | STDMETHOD(GetEvent)(DWORD dwFlags, IMFMediaEvent** ppEvent); 10 | STDMETHOD(QueueEvent)(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue); 11 | 12 | // IMFMediaStream 13 | STDMETHOD(GetMediaSource)(IMFMediaSource** ppMediaSource); 14 | STDMETHOD(GetStreamDescriptor)(IMFStreamDescriptor** ppStreamDescriptor); 15 | STDMETHOD(RequestSample)(IUnknown* pToken); 16 | 17 | // IMFMediaStream2 18 | STDMETHOD(SetStreamState)(MF_STREAM_STATE value); 19 | STDMETHOD(GetStreamState)(MF_STREAM_STATE* value); 20 | 21 | // IKsControl 22 | STDMETHOD_(NTSTATUS, KsProperty)(PKSPROPERTY Property, ULONG PropertyLength, LPVOID PropertyData, ULONG DataLength, ULONG* BytesReturned); 23 | STDMETHOD_(NTSTATUS, KsMethod)(PKSMETHOD Method, ULONG MethodLength, LPVOID MethodData, ULONG DataLength, ULONG* BytesReturned); 24 | STDMETHOD_(NTSTATUS, KsEvent)(PKSEVENT Event, ULONG EventLength, LPVOID EventData, ULONG DataLength, ULONG* BytesReturned); 25 | 26 | public: 27 | MediaStream() : 28 | _index(0), 29 | _state(MF_STREAM_STATE_STOPPED), 30 | _format(GUID_NULL) 31 | { 32 | SetBaseAttributesTraceName(L"MediaStreamAtts"); 33 | } 34 | 35 | HRESULT Initialize(IMFMediaSource* source, int index); 36 | HRESULT SetAllocator(IUnknown* allocator); 37 | MFSampleAllocatorUsage GetAllocatorUsage(); 38 | HRESULT SetD3DManager(IUnknown* manager); 39 | HRESULT Start(IMFMediaType* type); 40 | HRESULT Stop(); 41 | void Shutdown(); 42 | 43 | private: 44 | #if _DEBUG 45 | int32_t query_interface_tearoff(winrt::guid const& id, void** object) const noexcept override 46 | { 47 | RETURN_HR_MSG(E_NOINTERFACE, "MediaStream QueryInterface failed on IID %s", GUID_ToStringW(id).c_str()); 48 | } 49 | #endif 50 | 51 | winrt::slim_mutex _lock; 52 | MF_STREAM_STATE _state; 53 | FrameGenerator _generator; 54 | GUID _format; 55 | wil::com_ptr_nothrow _descriptor; 56 | wil::com_ptr_nothrow _queue; 57 | wil::com_ptr_nothrow _source; 58 | wil::com_ptr_nothrow _allocator; 59 | int _index; 60 | }; -------------------------------------------------------------------------------- /VCamSampleSource/Tools.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Undocumented.h" 3 | #include "Tools.h" 4 | #include "EnumNames.h" 5 | 6 | std::string to_string(const std::wstring& ws) 7 | { 8 | if (ws.empty()) 9 | return std::string(); 10 | 11 | auto wsize = (int)ws.size(); 12 | auto ssize = WideCharToMultiByte(CP_THREAD_ACP, 0, ws.data(), wsize, nullptr, 0, nullptr, nullptr); 13 | if (!ssize) 14 | return std::string(); 15 | 16 | std::string s; 17 | s.resize(ssize); 18 | ssize = WideCharToMultiByte(CP_THREAD_ACP, 0, ws.data(), wsize, &s[0], ssize, nullptr, nullptr); 19 | if (!ssize) 20 | return std::string(); 21 | 22 | return s; 23 | } 24 | 25 | std::wstring to_wstring(const std::string& s) 26 | { 27 | if (s.empty()) 28 | return std::wstring(); 29 | 30 | auto ssize = (int)s.size(); 31 | auto wsize = MultiByteToWideChar(CP_THREAD_ACP, 0, s.data(), ssize, nullptr, 0); 32 | if (!wsize) 33 | return std::wstring(); 34 | 35 | std::wstring ws; 36 | ws.resize(wsize); 37 | wsize = MultiByteToWideChar(CP_THREAD_ACP, 0, s.data(), ssize, &ws[0], wsize); 38 | if (!wsize) 39 | return std::wstring(); 40 | 41 | return ws; 42 | } 43 | 44 | #define IFIID(x) if (guid == __uuidof(##x)) return L#x; 45 | #define IFGUID(x) if (guid == ##x) return L#x; 46 | 47 | const std::string GUID_ToStringA(const GUID& guid, bool resolve) { return to_string(GUID_ToStringW(guid, resolve)); } 48 | const std::wstring GUID_ToStringW(const GUID& guid, bool resolve) 49 | { 50 | if (resolve) 51 | { 52 | // list of known GUIDs we're interested in 53 | IFGUID(GUID_NULL); 54 | IFGUID(CLSID_VCam); 55 | IFGUID(PINNAME_VIDEO_CAPTURE); 56 | IFGUID(MF_DEVICESTREAM_STREAM_CATEGORY); 57 | IFGUID(MF_DEVICESTREAM_STREAM_ID); 58 | IFGUID(MF_DEVICESTREAM_FRAMESERVER_SHARED); 59 | IFGUID(MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES); 60 | IFGUID(MF_DEVICESTREAM_MULTIPLEXED_MANAGER); 61 | IFGUID(MF_DEVICEMFT_SENSORPROFILE_COLLECTION); 62 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_D3D_ADAPTERLUID); 63 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME); 64 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK); 65 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE); 66 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); 67 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY); 68 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_DEVICETYPE); 69 | IFGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_HW_SOURCE); 70 | IFGUID(MF_VIRTUALCAMERA_PROVIDE_ASSOCIATED_CAMERA_SOURCES); 71 | IFGUID(MF_VIRTUALCAMERA_CONFIGURATION_APP_PACKAGE_FAMILY_NAME); 72 | IFGUID(MF_VIRTUALCAMERA_ASSOCIATED_CAMERA_SOURCES); 73 | IFGUID(MF_CAPTURE_ENGINE_SELECTEDCAMERAPROFILE_INDEX); 74 | IFGUID(MF_CAPTURE_ENGINE_SELECTEDCAMERAPROFILE); 75 | IFGUID(MF_MEDIACAPTURE_INIT_ENABLE_MULTIPLEXOR); 76 | IFGUID(MF_FRAMESERVER_CLIENTCONTEXT_CLIENTPID); 77 | IFGUID(MF_FRAMESERVER_VCAM_CONFIGURATION_APP); 78 | IFGUID(MF_DEVICE_DSHOW_BRIDGE_FILTER); 79 | IFGUID(MF_DEVPROXY_COMPRESSED_MEDIATYPE_PASSTHROUGH_MODE); 80 | IFGUID(MF_DEVICESTREAM_ATTRIBUTE_PLUGIN_ENABLED); 81 | IFGUID(MEDIA_TELEMETRY_SESSION_ID); 82 | IFGUID(MFT_TRANSFORM_CLSID_Attribute); 83 | 84 | IFGUID(MF_MT_FRAME_SIZE); 85 | IFGUID(MF_MT_AVG_BITRATE); 86 | IFGUID(MF_MT_MAJOR_TYPE); 87 | IFGUID(MF_MT_FRAME_RATE); 88 | IFGUID(MF_MT_PIXEL_ASPECT_RATIO); 89 | IFGUID(MF_MT_ALL_SAMPLES_INDEPENDENT); 90 | IFGUID(MF_MT_INTERLACE_MODE); 91 | IFGUID(MF_MT_SUBTYPE); 92 | IFGUID(MF_MT_SUBTYPE); 93 | 94 | IFGUID(MFT_SUPPORT_3DVIDEO); 95 | IFGUID(MF_SA_D3D11_AWARE); 96 | 97 | IFGUID(KSCATEGORY_VIDEO_CAMERA); 98 | IFGUID(KSDATAFORMAT_TYPE_VIDEO); 99 | IFGUID(CLSID_VideoInputDeviceCategory); 100 | IFGUID(MFVideoFormat_RGB32); 101 | IFGUID(MFVideoFormat_NV12); 102 | 103 | IFGUID(KSPROPSETID_Pin); 104 | IFGUID(KSPROPSETID_Topology); 105 | IFGUID(KSPROPSETID_Connection); 106 | IFGUID(PROPSETID_VIDCAP_CAMERACONTROL); 107 | IFGUID(PROPSETID_VIDCAP_VIDEOPROCAMP); 108 | IFGUID(PROPSETID_VIDCAP_CAMERACONTROL_REGION_OF_INTEREST); 109 | IFGUID(PROPSETID_VIDCAP_CAMERACONTROL_IMAGE_PIN_CAPABILITY); 110 | IFGUID(KSPROPERTYSETID_PerFrameSettingControl); 111 | IFGUID(KSPROPERTYSETID_ExtendedCameraControl); 112 | 113 | IFIID(IUnknown); 114 | IFIID(IInspectable); 115 | IFIID(IClassFactory); 116 | IFIID(IPersistPropertyBag); 117 | IFIID(IUndocumented1); 118 | IFIID(INoMarshal); 119 | IFIID(IMFMediaStream2); 120 | IFIID(IKsControl); 121 | IFIID(IMFMediaSourceEx); 122 | IFIID(IMFMediaSource); 123 | IFIID(IMFMediaSource2); 124 | IFIID(IMFDeviceController); 125 | IFIID(IMFDeviceController2); 126 | IFIID(IMFDeviceTransformManager); 127 | IFIID(IMFSampleAllocatorControl); 128 | IFIID(IMFDeviceSourceInternal); 129 | IFIID(IMFDeviceSourceInternal2); 130 | IFIID(IMFCollection); 131 | IFIID(IMFRealTimeClientEx); 132 | IFIID(IMFDeviceSourceStatus); 133 | IFIID(IMFAttributes); 134 | } 135 | 136 | wchar_t name[64]; 137 | std::ignore = StringFromGUID2(guid, name, _countof(name)); 138 | return name; 139 | } 140 | 141 | const std::wstring PROPVARIANT_ToString(const PROPVARIANT& pv) 142 | { 143 | std::wstring type = std::format(L"{}(0x{:08X})", VARTYPE_ToString(pv.vt), pv.vt); 144 | wil::unique_cotaskmem_ptr str; 145 | 146 | if (pv.vt == VT_CLSID) 147 | return type + L" `" + GUID_ToStringW(*pv.puuid) + L"`"; 148 | 149 | if (SUCCEEDED(PropVariantToStringAlloc(pv, wil::out_param(str)))) 150 | return type + L" `" + str.get() + L"`"; 151 | 152 | return type; 153 | } 154 | 155 | void CenterWindow(HWND hwnd, bool useCursorPos) 156 | { 157 | if (!IsWindow(hwnd)) 158 | return; 159 | 160 | RECT rc{}; 161 | GetWindowRect(hwnd, &rc); 162 | auto width = rc.right - rc.left; 163 | auto height = rc.bottom - rc.top; 164 | 165 | if (useCursorPos) 166 | { 167 | POINT pt{}; 168 | if (GetCursorPos(&pt)) 169 | { 170 | auto monitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); 171 | MONITORINFOEX mi{}; 172 | mi.cbSize = sizeof(MONITORINFOEX); 173 | if (GetMonitorInfo(monitor, &mi)) 174 | { 175 | SetWindowPos(hwnd, NULL, mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - width) / 2, mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - height) / 2, 0, 0, SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER); 176 | return; 177 | } 178 | } 179 | } 180 | 181 | SetWindowPos(hwnd, NULL, (GetSystemMetrics(SM_CXSCREEN) - width) / 2, (GetSystemMetrics(SM_CYSCREEN) - height) / 2, 0, 0, SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER); 182 | } 183 | 184 | inline float HUE2RGB(const float p, const float q, float t) 185 | { 186 | if (t < 0) 187 | { 188 | t += 1; 189 | } 190 | 191 | if (t > 1) 192 | { 193 | t -= 1; 194 | } 195 | 196 | if (t < 1 / 6.0f) 197 | return p + (q - p) * 6 * t; 198 | 199 | if (t < 1 / 2.0f) 200 | return q; 201 | 202 | if (t < 2 / 3.0f) 203 | return p + (q - p) * (2 / 3.0f - t) * 6; 204 | 205 | return p; 206 | 207 | } 208 | 209 | D2D1_COLOR_F HSL2RGB(const float h, const float s, const float l) 210 | { 211 | D2D1_COLOR_F result; 212 | result.a = 1; 213 | 214 | if (!s) 215 | { 216 | result.r = l; 217 | result.g = l; 218 | result.b = l; 219 | } 220 | else 221 | { 222 | auto q = l < 0.5f ? l * (1 + s) : l + s - l * s; 223 | auto p = 2 * l - q; 224 | result.r = HUE2RGB(p, q, h + 1 / 3.0f); 225 | result.g = HUE2RGB(p, q, h); 226 | result.b = HUE2RGB(p, q, h - 1 / 3.0f); 227 | } 228 | return result; 229 | 230 | } 231 | 232 | const std::wstring GetProcessName(DWORD pid) 233 | { 234 | if (pid) 235 | { 236 | auto handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); 237 | if (handle) 238 | { 239 | DWORD size = 2048; 240 | std::wstring ws; 241 | ws.resize(size); 242 | QueryFullProcessImageName(handle, 0, ws.data(), &size); 243 | CloseHandle(handle); 244 | return std::format(L"{} `{}`", pid, ws); 245 | } 246 | } 247 | return L""; 248 | } 249 | 250 | const LSTATUS RegWriteKey(HKEY key, PCWSTR path, HKEY* outKey) 251 | { 252 | *outKey = nullptr; 253 | return RegCreateKeyEx(key, path, 0, nullptr, 0, KEY_WRITE, nullptr, outKey, nullptr); 254 | } 255 | 256 | const LSTATUS RegWriteValue(HKEY key, PCWSTR name, const std::wstring& value) 257 | { 258 | return RegSetValueEx(key, name, 0, REG_SZ, reinterpret_cast(value.c_str()), static_cast((value.size() + 1) * sizeof(wchar_t))); 259 | } 260 | 261 | const LSTATUS RegWriteValue(HKEY key, PCWSTR name, DWORD value) 262 | { 263 | return RegSetValueEx(key, name, 0, REG_DWORD, reinterpret_cast(&value), sizeof(value)); 264 | } 265 | 266 | static inline void RGB24ToYUY2(int r, int g, int b, BYTE* y, BYTE* u, BYTE* v) 267 | { 268 | *y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16; 269 | *u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; 270 | *v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; 271 | } 272 | 273 | static inline void RGB24ToY(int r, int g, int b, BYTE* y) 274 | { 275 | *y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16; 276 | } 277 | 278 | static inline void RGB32ToNV12(BYTE rgb1[8], BYTE rgb2[8], BYTE* y1, BYTE* y2, BYTE* uv) 279 | { 280 | RGB24ToYUY2(rgb1[2], rgb1[1], rgb1[0], y1, uv, uv + 1); 281 | RGB24ToY(rgb1[6], rgb1[5], rgb1[4], y1 + 1); 282 | RGB24ToYUY2(rgb2[2], rgb2[1], rgb2[0], y2, uv, uv + 1); 283 | RGB24ToY(rgb2[6], rgb2[5], rgb2[4], y2 + 1); 284 | }; 285 | 286 | HRESULT RGB32ToNV12(BYTE* input, ULONG inputSize, LONG inputStride, UINT width, UINT height, BYTE* output, ULONG ouputSize, LONG outputStride) 287 | { 288 | RETURN_HR_IF_NULL(E_INVALIDARG, input); 289 | RETURN_HR_IF_NULL(E_INVALIDARG, output); 290 | RETURN_HR_IF(E_UNEXPECTED, width * 4 * height > inputSize); 291 | RETURN_HR_IF(E_UNEXPECTED, width * 1.5 * height > ouputSize); 292 | 293 | for (DWORD h = 0; h < height - 1; h += 2) 294 | { 295 | auto rgb1 = h * inputStride + input; 296 | auto rgb2pRGB2 = (h + 1) * inputStride + input; 297 | auto y1 = h * outputStride + output; 298 | auto y2 = (h + 1) * outputStride + output; 299 | auto uv = (h / 2 + height) * outputStride + output; 300 | for (DWORD w = 0; w < width; w += 2) 301 | { 302 | RGB32ToNV12(rgb1, rgb2pRGB2, y1, y2, uv); 303 | rgb1 += 8; 304 | rgb2pRGB2 += 8; 305 | y1 += 2; 306 | y2 += 2; 307 | uv += 2; 308 | } 309 | } 310 | return S_OK; 311 | } 312 | -------------------------------------------------------------------------------- /VCamSampleSource/Tools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | std::string to_string(const std::wstring& ws); 4 | std::wstring to_wstring(const std::string& s); 5 | const std::wstring GUID_ToStringW(const GUID& guid, bool resolve = true); 6 | const std::string GUID_ToStringA(const GUID& guid, bool resolve = true); 7 | const std::wstring PROPVARIANT_ToString(const PROPVARIANT& pv); 8 | D2D1_COLOR_F HSL2RGB(const float h, const float s, const float l); 9 | const std::wstring GetProcessName(DWORD pid); 10 | const LSTATUS RegWriteKey(HKEY key, PCWSTR path, HKEY* outKey); 11 | const LSTATUS RegWriteValue(HKEY key, PCWSTR name, const std::wstring& value); 12 | const LSTATUS RegWriteValue(HKEY key, PCWSTR name, DWORD value); 13 | HRESULT RGB32ToNV12(BYTE* input, ULONG inputSize, LONG inputStride, UINT width, UINT height, BYTE* output, ULONG ouputSize, LONG outputStride); 14 | 15 | _Ret_range_(== , _expr) 16 | inline bool assert_true(bool _expr) 17 | { 18 | assert(_expr); 19 | return _expr; 20 | } 21 | 22 | namespace wil 23 | { 24 | template 25 | wil::unique_cotaskmem_array_ptr make_unique_cotaskmem_array(size_t numOfElements) 26 | { 27 | wil::unique_cotaskmem_array_ptr arr; 28 | auto cb = sizeof(wil::details::element_traits::type) * numOfElements; 29 | void* ptr = ::CoTaskMemAlloc(cb); 30 | if (ptr != nullptr) 31 | { 32 | ZeroMemory(ptr, cb); 33 | arr.reset(reinterpret_cast::type*>(ptr), numOfElements); 34 | } 35 | return arr; 36 | } 37 | } 38 | 39 | namespace winrt 40 | { 41 | template<> inline bool is_guid_of(guid const& id) noexcept 42 | { 43 | return is_guid_of(id); 44 | } 45 | 46 | template<> inline bool is_guid_of(guid const& id) noexcept 47 | { 48 | return is_guid_of(id); 49 | } 50 | 51 | template<> inline bool is_guid_of(guid const& id) noexcept 52 | { 53 | return is_guid_of(id); 54 | } 55 | 56 | template<> inline bool is_guid_of(guid const& id) noexcept 57 | { 58 | return is_guid_of(id); 59 | } 60 | } 61 | 62 | struct registry_traits 63 | { 64 | using type = HKEY; 65 | 66 | static void close(type value) noexcept 67 | { 68 | WINRT_VERIFY_(ERROR_SUCCESS, RegCloseKey(value)); 69 | } 70 | 71 | static constexpr type invalid() noexcept 72 | { 73 | return nullptr; 74 | } 75 | }; -------------------------------------------------------------------------------- /VCamSampleSource/Undocumented.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | DECLARE_INTERFACE_IID_(IMFDeviceController, IUnknown, "A1F58958-A5AA-412F-AF20-1B7F1242DBA0") {}; 4 | DECLARE_INTERFACE_IID_(IMFDeviceController2, IUnknown, "2032C7EF-76F6-492A-94F3-4A81F69380CC") {}; 5 | DECLARE_INTERFACE_IID_(IMFDeviceTransformManager, IUnknown, "70212999-c449-4b9d-b1a4-b358e1490121") {}; 6 | DECLARE_INTERFACE_IID_(IMFDeviceSourceInternal, IUnknown, "7F02A37E-4E81-11E0-8F3E-D057DFD72085") {}; 7 | DECLARE_INTERFACE_IID_(IMFDeviceSourceInternal2, IUnknown, "c47d95d5-9685-4bf6-b6fb-772dc58d8e3b") {}; 8 | DECLARE_INTERFACE_IID_(IMFDeviceSourceStatus, IUnknown, "43937DC1-0BE6-4ADD-8A14-9EA68FF31252") {}; 9 | DECLARE_INTERFACE_IID_(IUndocumented1, IUnknown, "9A9DAAAA-9774-4732-848E-8739655F2BA3") {}; 10 | 11 | DEFINE_GUID(MEDIA_TELEMETRY_SESSION_ID, 0x2acf1917, 0x3743, 0x41df, 0xa5, 0x64, 0xe7, 0x27, 0xa8, 0x0e, 0xa3, 0x3d); 12 | DEFINE_GUID(MF_FRAMESERVER_CLIENTCONTEXT_CLIENTPID, 0x5f8d322e, 0x0fe4, 0x43e4, 0x9e, 0x50, 0xd8, 0x3e, 0xcd, 0x9f, 0xc2, 0xb8); 13 | DEFINE_GUID(MF_DEVICE_DSHOW_BRIDGE_FILTER, 0xb2bfd8c2, 0x01e3, 0x4e29, 0xb2, 0x2f, 0xf1, 0xef, 0x98, 0x94, 0x8a, 0xbc); 14 | DEFINE_GUID(MF_DEVPROXY_COMPRESSED_MEDIATYPE_PASSTHROUGH_MODE, 0x406dfec3, 0xdc02, 0x418f, 0x93, 0xc0, 0x04, 0x10, 0x2e, 0x2c, 0x95, 0x20); 15 | DEFINE_GUID(MF_DEVICESTREAM_ATTRIBUTE_PLUGIN_ENABLED, 0xd57830e6, 0x23d5, 0x4de7, 0xbb, 0x45, 0xa7, 0x0f, 0x04, 0x17, 0xfe, 0xb8); 16 | DEFINE_GUID(MF_FRAMESERVER_VCAM_CONFIGURATION_APP, 0x22125de7, 0xd680, 0x4fb3, 0xa5, 0xdd, 0x93, 0xa0, 0x31, 0x5b, 0xbe, 0xc9); 17 | DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_DEVICETYPE, 0x44d1a9bc, 0x2999, 0x4238, 0xae, 0x43, 0x07, 0x30, 0xce, 0xb2, 0xab, 0x1b); 18 | DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_D3D_ADAPTERLUID, 0x9471213a, 0xee86, 0x4570, 0xa3, 0xba, 0x79, 0xc8, 0x5a, 0x14, 0xa7, 0xa2); 19 | DEFINE_GUID(MF_MEDIACAPTURE_INIT_ENABLE_MULTIPLEXOR, 0x1fe2c163, 0x8a52, 0x474b, 0xa5, 0x5b, 0x97, 0x93, 0x23, 0x72, 0x48, 0x6b); 20 | 21 | 22 | -------------------------------------------------------------------------------- /VCamSampleSource/VCamSampleSource.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | 3 | DllCanUnloadNow PRIVATE 4 | DllGetClassObject PRIVATE 5 | DllRegisterServer PRIVATE 6 | DllUnregisterServer PRIVATE -------------------------------------------------------------------------------- /VCamSampleSource/VCamSampleSource.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smourier/VCamSample/a4dc8c0dc69daf3765d5673e8830f7769f24c9d1/VCamSampleSource/VCamSampleSource.rc -------------------------------------------------------------------------------- /VCamSampleSource/VCamSampleSource.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | ARM64 8 | 9 | 10 | Debug 11 | Win32 12 | 13 | 14 | Release 15 | ARM64 16 | 17 | 18 | Release 19 | Win32 20 | 21 | 22 | Debug 23 | x64 24 | 25 | 26 | Release 27 | x64 28 | 29 | 30 | 31 | 17.0 32 | Win32Proj 33 | {52fb6b93-3aa6-4369-bed4-d4bff1f97b78} 34 | VCamSampleSource 35 | 10.0 36 | 37 | 38 | 39 | DynamicLibrary 40 | true 41 | v143 42 | Unicode 43 | 44 | 45 | DynamicLibrary 46 | false 47 | v143 48 | true 49 | Unicode 50 | 51 | 52 | DynamicLibrary 53 | true 54 | v143 55 | Unicode 56 | 57 | 58 | DynamicLibrary 59 | true 60 | v143 61 | Unicode 62 | 63 | 64 | DynamicLibrary 65 | false 66 | v143 67 | true 68 | Unicode 69 | 70 | 71 | DynamicLibrary 72 | false 73 | v143 74 | true 75 | Unicode 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | WIN32;_DEBUG;VCAMSAMPLESOURCE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 106 | true 107 | Use 108 | pch.h 109 | stdcpp20 110 | MultiThreadedDebug 111 | 112 | 113 | Windows 114 | true 115 | false 116 | VCamSampleSource.def 117 | 118 | 119 | 120 | 121 | Level3 122 | true 123 | true 124 | true 125 | WIN32;NDEBUG;VCAMSAMPLESOURCE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 126 | true 127 | Use 128 | pch.h 129 | stdcpp20 130 | MultiThreaded 131 | 132 | 133 | Windows 134 | true 135 | true 136 | true 137 | false 138 | VCamSampleSource.def 139 | 140 | 141 | 142 | 143 | Level3 144 | true 145 | _DEBUG;VCAMSAMPLESOURCE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 146 | true 147 | Use 148 | pch.h 149 | stdcpp20 150 | MultiThreadedDebug 151 | 152 | 153 | Windows 154 | true 155 | false 156 | VCamSampleSource.def 157 | 158 | 159 | 160 | 161 | Level3 162 | true 163 | _DEBUG;VCAMSAMPLESOURCE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 164 | true 165 | Use 166 | pch.h 167 | stdcpp20 168 | MultiThreadedDebug 169 | 170 | 171 | Windows 172 | true 173 | false 174 | VCamSampleSource.def 175 | 176 | 177 | 178 | 179 | Level3 180 | true 181 | true 182 | true 183 | NDEBUG;VCAMSAMPLESOURCE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 184 | true 185 | Use 186 | pch.h 187 | stdcpp20 188 | MultiThreaded 189 | 190 | 191 | Windows 192 | true 193 | true 194 | true 195 | false 196 | VCamSampleSource.def 197 | 198 | 199 | 200 | 201 | Level3 202 | true 203 | true 204 | true 205 | NDEBUG;VCAMSAMPLESOURCE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 206 | true 207 | Use 208 | pch.h 209 | stdcpp20 210 | MultiThreaded 211 | 212 | 213 | Windows 214 | true 215 | true 216 | true 217 | false 218 | VCamSampleSource.def 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | Create 245 | Create 246 | Create 247 | Create 248 | Create 249 | Create 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /VCamSampleSource/VCamSampleSource.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | 56 | 57 | Source Files 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | Source Files 67 | 68 | 69 | Source Files 70 | 71 | 72 | Source Files 73 | 74 | 75 | Source Files 76 | 77 | 78 | Source Files 79 | 80 | 81 | Source Files 82 | 83 | 84 | Source Files 85 | 86 | 87 | 88 | 89 | Source Files 90 | 91 | 92 | 93 | 94 | 95 | Resource Files 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /VCamSampleSource/WinTrace.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Tools.h" 3 | 4 | // we don't use OutputDebugString because it's 100% crap, truncating, slow, etc. 5 | // use WpfTraceSpy https://github.com/smourier/TraceSpy to see these traces (configure an ETW Provider with guid set to 964d4572-adb9-4f3a-8170-fcbecec27467) 6 | static GUID GUID_WinTraceProvider = { 0x964d4572,0xadb9,0x4f3a,{0x81,0x70,0xfc,0xbe,0xce,0xc2,0x74,0x67} }; 7 | 8 | REGHANDLE _traceHandle = 0; 9 | 10 | HRESULT GetTraceId(GUID* pGuid) 11 | { 12 | if (!pGuid) 13 | return E_INVALIDARG; 14 | 15 | *pGuid = GUID_WinTraceProvider; 16 | return S_OK; 17 | } 18 | 19 | ULONG WinTraceRegister() 20 | { 21 | return EventRegister(&GUID_WinTraceProvider, nullptr, nullptr, &_traceHandle); 22 | } 23 | 24 | void WinTraceUnregister() 25 | { 26 | auto h = _traceHandle; 27 | if (h) 28 | { 29 | _traceHandle = 0; 30 | EventUnregister(h); 31 | } 32 | } 33 | 34 | void WinTraceFormat(UCHAR level, ULONGLONG keyword, PCWSTR format, ...) 35 | { 36 | if (!_traceHandle) 37 | return; 38 | 39 | WCHAR szTrace[2048]; 40 | va_list args; 41 | va_start(args, format); 42 | // add '00000000:' before all traces 43 | StringCchPrintf(szTrace, (size_t)(9 + 1), L"%08X:", GetCurrentThreadId()); 44 | StringCchVPrintfW(((LPWSTR)szTrace) + 9, _countof(szTrace) - 10, format, args); 45 | va_end(args); 46 | EventWriteString(_traceHandle, level, keyword, szTrace); 47 | } 48 | 49 | void WinTraceFormat(UCHAR level, ULONGLONG keyword, PCSTR format, ...) 50 | { 51 | if (!_traceHandle) 52 | return; 53 | 54 | CHAR szTrace[2048]; 55 | va_list args; 56 | va_start(args, format); 57 | StringCchPrintfA(szTrace, (size_t)(9 + 1), "%08X:", GetCurrentThreadId()); 58 | StringCchVPrintfA(((LPSTR)szTrace) + 9, _countof(szTrace) - 10, format, args); 59 | va_end(args); 60 | EventWriteString(_traceHandle, level, keyword, to_wstring(szTrace).c_str()); 61 | } 62 | 63 | void WinTrace(UCHAR level, ULONGLONG keyword, PCSTR string) 64 | { 65 | if (!_traceHandle) 66 | return; 67 | 68 | EventWriteString(_traceHandle, level, keyword, to_wstring(string).c_str()); 69 | } 70 | 71 | void WinTrace(UCHAR level, ULONGLONG keyword, PCWSTR string) 72 | { 73 | if (!_traceHandle) 74 | return; 75 | 76 | EventWriteString(_traceHandle, level, keyword, string); 77 | } -------------------------------------------------------------------------------- /VCamSampleSource/WinTrace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | HRESULT GetTraceId(GUID* pGuid); 4 | 5 | ULONG WinTraceRegister(); 6 | void WinTraceUnregister(); 7 | 8 | void WinTrace(UCHAR Level, ULONGLONG Keyword, PCWSTR String); 9 | void WinTraceFormat(UCHAR Level, ULONGLONG Keyword, PCWSTR pszFormat, ...); 10 | 11 | void WinTrace(UCHAR Level, ULONGLONG Keyword, PCSTR String); 12 | void WinTraceFormat(UCHAR Level, ULONGLONG Keyword, PCSTR pszFormat, ...); 13 | 14 | #ifdef _DEBUG 15 | #define WINTRACE(...) WinTraceFormat(0, 0, __VA_ARGS__) 16 | #else 17 | #define WINTRACE __noop 18 | #endif 19 | #pragma once 20 | -------------------------------------------------------------------------------- /VCamSampleSource/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Undocumented.h" 3 | #include "Tools.h" 4 | #include "EnumNames.h" 5 | #include "MFTools.h" 6 | #include "FrameGenerator.h" 7 | #include "MediaStream.h" 8 | #include "MediaSource.h" 9 | #include "Activator.h" 10 | 11 | // 3cad447d-f283-4af4-a3b2-6f5363309f52 12 | GUID CLSID_VCam = { 0x3cad447d,0xf283,0x4af4,{0xa3,0xb2,0x6f,0x53,0x63,0x30,0x9f,0x52} }; 13 | HMODULE _hModule; 14 | 15 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) 16 | { 17 | switch (dwReason) 18 | { 19 | case DLL_PROCESS_ATTACH: 20 | _hModule = hModule; 21 | WinTraceRegister(); 22 | WINTRACE(L"DllMain DLL_PROCESS_ATTACH '%s'", GetCommandLine()); 23 | DisableThreadLibraryCalls(hModule); 24 | 25 | wil::SetResultLoggingCallback([](wil::FailureInfo const& failure) noexcept 26 | { 27 | wchar_t str[2048]; 28 | if (SUCCEEDED(wil::GetFailureLogString(str, _countof(str), failure))) 29 | { 30 | WinTrace(2, 0, str); // 2 => error 31 | } 32 | }); 33 | break; 34 | 35 | case DLL_PROCESS_DETACH: 36 | WINTRACE(L"DllMain DLL_PROCESS_DETACH '%s'", GetCommandLine()); 37 | WinTraceUnregister(); 38 | break; 39 | } 40 | return TRUE; 41 | } 42 | 43 | struct ClassFactory : winrt::implements 44 | { 45 | STDMETHODIMP CreateInstance(IUnknown* outer, GUID const& riid, void** result) noexcept final 46 | { 47 | RETURN_HR_IF_NULL(E_POINTER, result); 48 | *result = nullptr; 49 | if (outer) 50 | RETURN_HR(CLASS_E_NOAGGREGATION); 51 | 52 | auto vcam = winrt::make_self(); 53 | RETURN_IF_FAILED(vcam->Initialize()); 54 | auto hr = vcam->QueryInterface(riid, result); 55 | if (FAILED(hr)) 56 | { 57 | auto iid = GUID_ToStringW(riid); 58 | WINTRACE(L"ClassFactory QueryInterface failed on IID %s", iid.c_str()); 59 | } 60 | return hr; 61 | } 62 | 63 | STDMETHODIMP LockServer(BOOL) noexcept final 64 | { 65 | return S_OK; 66 | } 67 | }; 68 | 69 | __control_entrypoint(DllExport) 70 | STDAPI DllCanUnloadNow() 71 | { 72 | if (winrt::get_module_lock()) 73 | { 74 | WINTRACE(L"DllCanUnloadNow S_FALSE"); 75 | return S_FALSE; 76 | } 77 | 78 | winrt::clear_factory_cache(); 79 | WINTRACE(L"DllCanUnloadNow S_OK"); 80 | return S_OK; 81 | } 82 | 83 | _Check_return_ 84 | STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) 85 | { 86 | WINTRACE(L"DllGetClassObject rclsid:%s riid:%s", GUID_ToStringW(rclsid).c_str(), GUID_ToStringW(riid).c_str()); 87 | RETURN_HR_IF_NULL(E_POINTER, ppv); 88 | *ppv = nullptr; 89 | 90 | if (rclsid == CLSID_VCam) 91 | return winrt::make_self()->QueryInterface(riid, ppv); 92 | 93 | RETURN_HR(E_NOINTERFACE); 94 | } 95 | 96 | using registry_key = winrt::handle_type; 97 | 98 | STDAPI DllRegisterServer() 99 | { 100 | std::wstring exePath = wil::GetModuleFileNameW(_hModule).get(); 101 | WINTRACE(L"DllRegisterServer '%s'", exePath.c_str()); 102 | auto clsid = GUID_ToStringW(CLSID_VCam, false); 103 | std::wstring path = L"Software\\Classes\\CLSID\\" + clsid + L"\\InprocServer32"; 104 | 105 | // note: a vcam *must* be registered in HKEY_LOCAL_MACHINE 106 | // for the frame server to be able to talk with it. 107 | registry_key key; 108 | RETURN_IF_WIN32_ERROR(RegWriteKey(HKEY_LOCAL_MACHINE, path.c_str(), key.put())); 109 | RETURN_IF_WIN32_ERROR(RegWriteValue(key.get(), nullptr, exePath)); 110 | RETURN_IF_WIN32_ERROR(RegWriteValue(key.get(), L"ThreadingModel", L"Both")); 111 | return S_OK; 112 | } 113 | 114 | STDAPI DllUnregisterServer() 115 | { 116 | std::wstring exePath = wil::GetModuleFileNameW(_hModule).get(); 117 | WINTRACE(L"DllUnregisterServer '%s'", exePath.c_str()); 118 | auto clsid = GUID_ToStringW(CLSID_VCam, false); 119 | std::wstring path = L"Software\\Classes\\CLSID\\" + clsid; 120 | RETURN_IF_WIN32_ERROR(RegDeleteTree(HKEY_LOCAL_MACHINE, path.c_str())); 121 | return S_OK; 122 | } -------------------------------------------------------------------------------- /VCamSampleSource/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | 5 | #define _CRTDBG_MAP_ALLOC 6 | #include 7 | #include 8 | #ifdef _DEBUG 9 | #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) 10 | // replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the allocations to be of _CLIENT_BLOCK type 11 | #else 12 | #define DBG_NEW new 13 | #endif 14 | 15 | // Windows Header Files 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "winrt\Windows.ApplicationModel.h" 36 | 37 | // std 38 | #include 39 | #include 40 | 41 | // WIL, requires "Microsoft.Windows.ImplementationLibrary" nuget 42 | #include "wil/result.h" 43 | #include "wil/stl.h" 44 | #include "wil/win32_helpers.h" 45 | #include "wil/com.h" 46 | 47 | // C++/WinRT, requires "Microsoft.Windows.CppWinRT" nuget 48 | #include "winrt/base.h" 49 | 50 | // project globals 51 | #include "wintrace.h" 52 | 53 | #pragma comment(lib, "mfsensorgroup") 54 | // 3cad447d-f283-4af4-a3b2-6f5363309f52 55 | extern GUID CLSID_VCam; 56 | 57 | -------------------------------------------------------------------------------- /VCamSampleSource/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /VCamSampleSource/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /VCamSampleSource/pch.h: -------------------------------------------------------------------------------- 1 | #ifndef PCH_H 2 | #define PCH_H 3 | 4 | #include "framework.h" 5 | 6 | #endif //PCH_H 7 | -------------------------------------------------------------------------------- /VCamSampleSource/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by VCamSampleSource.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | --------------------------------------------------------------------------------