├── .gitattributes ├── .gitignore ├── README.md ├── ScreenCapture.exe.recipe ├── ScreenCapture.sln ├── ScreenCapture.vcxproj ├── ScreenCapture.vcxproj.filters ├── capture.hpp ├── main.cpp ├── stdafx.cpp └── stdafx.h /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.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/master/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 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScreenCapture single header library for Windows 2 | 3 | Article: https://www.codeproject.com/Articles/5256890/ScreenCapture-Single-header-DirectX-library 4 | 5 | Features: 6 | * DirectX hardware screen capture and encoding 7 | * Audio capture and mixing, multiple audio sources, can capture from speakers 8 | * HDR support 9 | * H264/H265/VP80/VP90/MP3/FLAC/AAC support 10 | * Easy interface 11 | 12 | ```C++ 13 | int wmain() 14 | { 15 | CoInitializeEx(0, COINIT_APARTMENTTHREADED); 16 | MFStartup(MF_VERSION); 17 | std::cout << "Capturing screen for 10 seconds..."; 18 | DESKTOPCAPTUREPARAMS dp; 19 | dp.f = L"capture.mp4"; 20 | dp.EndMS = 10000; 21 | DesktopCapture(dp); 22 | std::cout << "Done.\r\n"; 23 | return 0; 24 | } 25 | ``` 26 | 27 | Where the DESKTOPCAPTUREPARAMS is this structure: 28 | 29 | 30 | ```C++ 31 | struct DESKTOPCAPTUREPARAMS 32 | { 33 | bool HasVideo = 1; 34 | bool HasAudio = 1; 35 | std::vector>> AudioFrom; 36 | GUID VIDEO_ENCODING_FORMAT = MFVideoFormat_H264; 37 | GUID AUDIO_ENCODING_FORMAT = MFAudioFormat_MP3; 38 | std::wstring f; 39 | std::function Streamer; 40 | std::function Framer; 41 | std::function PrepareAttributes; 42 | 43 | int fps = 25; 44 | int NumThreads = 0; 45 | int Qu = -1; 46 | int vbrm = 0; 47 | int vbrq = 0; 48 | int BR = 4000; 49 | int NCH = 2; 50 | int SR = 44100; 51 | int ABR = 192; 52 | bool Cursor = true; 53 | RECT rx = { 0,0,0,0 }; 54 | HWND hWnd = 0; 55 | IDXGIAdapter1* ad = 0; 56 | UINT nOutput = 0; 57 | 58 | unsigned long long StartMS = 0; // 0, none 59 | unsigned long long EndMS = 0; // 0, none 60 | bool MustEnd = false; 61 | bool Pause = false; 62 | }; 63 | ``` 64 | Where: 65 | 66 | * HasVideo = 1 -> You are capturing video. If this is set, the output file must be an MP4 or an ASF regardless of if you have audio or not. 67 | * HasAudio = 1 -> You are capturing audio. If this is set and you do not have a video, the output file must be an MP3 or FLAC. For AAC, you must use MP4. 68 | * AudioFrom = a vector of which audio devices you want to capture. Each element is a tuple of the device unique ID (as returned by the enumeration, see VISTAMIXERS::EnumVistaMixers()) and a vector of the channels you want to record from. 69 | 70 | The library can also record from a playback device (like your speakers) in loopback. You can specify multiple sources of recording and the library will mix them all into the final audio stream. 71 | 72 | * VIDEO_ENCODING_FORMAT -> One of MFVideoFormat_H264, MFVideoFormat_HEVC, MFVideoFormat_VP90, MFVideoFormat_VP80. If your display is HDR, use MFVideoFormat_HEVC._ 73 | * AUDIO_ENCODING_FORMAT -> One of MFAudioFormat_MP3 or MFAudioFormat_FLAC or MFAudioFormat_AAC. MP3 and AAC support only 44100/48000 2 channel output. 74 | * f -> target file name (MP3/FLAC/AAC for audio only, MP4/ASF else) 75 | * fps -> Frames per second 76 | * NumThreads -> Threads for the video encoder, 0 default. Can be 0-16. 77 | * Qu -> If >= 0 and <= 0, Quality Vs Speed video factor 78 | * vbrm and vbrq -> If 2, then vbrq is a quality value between 0 and 100 (BR is ignored) 79 | * BR -> Video bitrate in KBps, default 4000. If vbrm is 2, BR is ignored 80 | * NCH -> Audio output channels 81 | * SR -> Audio output sample rate 82 | * ABR -> Audio bitrate in Kbps for MP3 83 | * Cursor -> true to capture the cursor. Ignored if HDR. 84 | * rx -> If not {0}, capture this specific rect only 85 | * hWnd -> If not {0}, capture this HWND only. If HWND is 0 and rx = {0}, the entire screen is captured 86 | * ad -> If not 0, specifies which adapter you want to capture if you have more than 1 adapter 87 | * nOutput -> The index of the monitor to capture. 0 is the first monitor. For multiple monitors, this specifies the monitor. 88 | * EndMS -> If not 0, the library stops when EndMs milliseconds have been captured. Else you have to stop the library by setting "MustEnd" to true 89 | * MustEnd -> Set to true for the library to stop capturing 90 | * Pause -> If true, capture is paused 91 | 92 | 93 | If you add the Streamer callback, f can be empty. In this case, you have a MP4 or ASF stream in your callback. 94 | If you add the Framer callback, the library captures a screenshot (DWORD array of RGBA (or DXGI_FORMAT_R16G16B16A16_FLOAT for HDR) to the callback) until the callback returns S_OK,in which the library returns. 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ScreenCapture.exe.recipe: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | F:\TP2\sc\ScreenCapture\ScreenCapture.exe 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ScreenCapture.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScreenCapture", "ScreenCapture.vcxproj", "{8977CEFB-978B-4B5C-943D-75C312B7F72D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Debug|x64.ActiveCfg = Debug|x64 17 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Debug|x64.Build.0 = Debug|x64 18 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Debug|x86.ActiveCfg = Debug|Win32 19 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Debug|x86.Build.0 = Debug|Win32 20 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Release|x64.ActiveCfg = Release|x64 21 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Release|x64.Build.0 = Release|x64 22 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Release|x86.ActiveCfg = Release|Win32 23 | {8977CEFB-978B-4B5C-943D-75C312B7F72D}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {0ACEB5E4-2F1C-4037-B12A-29D3628D3760} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ScreenCapture.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {8977cefb-978b-4b5c-943d-75c312b7f72d} 23 | Win32Proj 24 | ScreenCapture 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v143 32 | Unicode 33 | 34 | 35 | Application 36 | true 37 | v143 38 | Unicode 39 | 40 | 41 | Application 42 | false 43 | v143 44 | true 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v143 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | true 72 | .\ 73 | .\ 74 | *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi 75 | true 76 | 77 | 78 | true 79 | .\ 80 | .\ 81 | *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi 82 | true 83 | 84 | 85 | false 86 | .\ 87 | .\ 88 | *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi 89 | true 90 | 91 | 92 | false 93 | .\ 94 | .\ 95 | *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi 96 | true 97 | 98 | 99 | 100 | Use 101 | Level4 102 | Disabled 103 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 104 | true 105 | ProgramDatabase 106 | true 107 | Async 108 | MultiThreadedDebug 109 | StreamingSIMDExtensions2 110 | true 111 | stdcpp17 112 | 113 | 114 | Console 115 | true 116 | true 117 | 118 | 119 | _X86_;_UNICODE;UNICODE;%(PreprocessorDefinitions) 120 | 121 | 122 | 123 | 124 | Use 125 | Level4 126 | Disabled 127 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 128 | true 129 | true 130 | Async 131 | MultiThreadedDebug 132 | StreamingSIMDExtensions2 133 | true 134 | stdcpp17 135 | 136 | 137 | Console 138 | true 139 | true 140 | 141 | 142 | _X64_;_UNICODE;UNICODE;%(PreprocessorDefinitions) 143 | 144 | 145 | 146 | 147 | Level4 148 | Use 149 | Full 150 | true 151 | true 152 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 153 | true 154 | true 155 | Async 156 | MultiThreaded 157 | StreamingSIMDExtensions2 158 | true 159 | stdcpp17 160 | 161 | 162 | Console 163 | true 164 | true 165 | true 166 | true 167 | 168 | 169 | _X86_;_UNICODE;UNICODE;%(PreprocessorDefinitions) 170 | 171 | 172 | 173 | 174 | Level4 175 | Use 176 | Full 177 | true 178 | true 179 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 180 | true 181 | true 182 | Async 183 | MultiThreaded 184 | StreamingSIMDExtensions2 185 | true 186 | stdcpp17 187 | 188 | 189 | Console 190 | true 191 | true 192 | true 193 | true 194 | 195 | 196 | _X64_;_UNICODE;UNICODE;%(PreprocessorDefinitions) 197 | 198 | 199 | 200 | 201 | 202 | Create 203 | Create 204 | Create 205 | Create 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /ScreenCapture.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Code 6 | 7 | 8 | Code 9 | 10 | 11 | 12 | 13 | {f39967f6-f5d7-4585-8037-d7a7d3d33408} 14 | 15 | 16 | 17 | 18 | Code 19 | 20 | 21 | Code 22 | 23 | 24 | 25 | 26 | Code 27 | 28 | 29 | -------------------------------------------------------------------------------- /capture.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #undef min 5 | #undef max 6 | 7 | 8 | 9 | // {1884E6DC-2AE3-4499-A3D7-1A27E3B2AC39} 10 | inline static const GUID MyFakeFmt = 11 | { 0x1884e6dc, 0x2ae3, 0x4499, { 0xa3, 0xd7, 0x1a, 0x27, 0xe3, 0xb2, 0xac, 0x39 } }; 12 | 13 | // {7F478D2D-9EBF-4B9A-B2F3-83800263BB86} 14 | inline static const GUID MFVideoFormat_RGB10 = 15 | { 0x7f478d2d, 0x9ebf, 0x4b9a, { 0xb2, 0xf3, 0x83, 0x80, 0x2, 0x63, 0xbb, 0x86 } }; 16 | 17 | 18 | 19 | #ifdef TURBO_PLAY_SC 20 | #include "..\\ca\\vistamixers.hpp" 21 | #else 22 | 23 | 24 | class AHANDLE 25 | { 26 | private: 27 | HANDLE hX = INVALID_HANDLE_VALUE; 28 | 29 | public: 30 | 31 | AHANDLE() 32 | { 33 | hX = INVALID_HANDLE_VALUE; 34 | } 35 | ~AHANDLE() 36 | { 37 | Close(); 38 | } 39 | AHANDLE(const AHANDLE& h) 40 | { 41 | DuplicateHandle(GetCurrentProcess(), h.hX, GetCurrentProcess(), &hX, 0, 0, DUPLICATE_SAME_ACCESS); 42 | } 43 | AHANDLE(AHANDLE&& h) 44 | { 45 | hX = h.hX; 46 | h.hX = INVALID_HANDLE_VALUE; 47 | } 48 | AHANDLE(HANDLE hY) 49 | { 50 | hX = hY; 51 | } 52 | AHANDLE& operator =(const AHANDLE& h) 53 | { 54 | Close(); 55 | DuplicateHandle(GetCurrentProcess(), h.hX, GetCurrentProcess(), &hX, 0, 0, DUPLICATE_SAME_ACCESS); 56 | return *this; 57 | } 58 | AHANDLE& operator =(AHANDLE&& h) 59 | { 60 | Close(); 61 | hX = h.hX; 62 | h.hX = INVALID_HANDLE_VALUE; 63 | return *this; 64 | } 65 | 66 | void Close() 67 | { 68 | if (hX != INVALID_HANDLE_VALUE) 69 | CloseHandle(hX); 70 | hX = INVALID_HANDLE_VALUE; 71 | } 72 | 73 | operator bool() const 74 | { 75 | if (hX == INVALID_HANDLE_VALUE) 76 | return false; 77 | return true; 78 | } 79 | operator HANDLE() const 80 | { 81 | return hX; 82 | } 83 | 84 | 85 | }; 86 | 87 | struct REBUFFERLOCK 88 | { 89 | std::recursive_mutex* mm; 90 | REBUFFERLOCK(std::recursive_mutex& m) 91 | { 92 | mm = &m; 93 | m.lock(); 94 | } 95 | ~REBUFFERLOCK() 96 | { 97 | mm->unlock(); 98 | } 99 | }; 100 | 101 | 102 | 103 | template 104 | class MIXBUFFER 105 | { 106 | T* m = 0; 107 | unsigned int count = 0; 108 | public: 109 | 110 | friend struct REBUFFER; 111 | 112 | unsigned int Count() { return count; } 113 | MIXBUFFER(T* f = 0) 114 | { 115 | Set(f); 116 | } 117 | void Set(T* f) 118 | { 119 | m = f; 120 | } 121 | void Reset(unsigned long long samples) 122 | { 123 | if (m) 124 | memset(m, 0, (size_t)(samples * sizeof(T))); 125 | count = 0; 126 | } 127 | void Put(T* data, unsigned long long samples, bool ForceIfEmpty = false, float VOut = 1.0f) 128 | { 129 | if (!m) 130 | return; 131 | if (!samples) 132 | return; 133 | 134 | // If null, do nothing 135 | if (ForceIfEmpty == false && data) 136 | { 137 | bool Silence = true; 138 | for (unsigned long long ismp = 0; ismp < samples; ismp++) 139 | { 140 | if (data[ismp] > 0.001f || data[ismp] < -0.001f) 141 | { 142 | Silence = false; 143 | break; 144 | } 145 | } 146 | if (Silence) 147 | return; 148 | } 149 | if (ForceIfEmpty == false && !data) 150 | return; 151 | 152 | if (data) 153 | { 154 | if (count == 0 && VOut >= 0.99f && VOut <= 1.01f) 155 | memcpy(m, data, (size_t)(samples * sizeof(T))); 156 | else 157 | { 158 | for (unsigned long long s = 0; s < samples; s++) 159 | { 160 | m[s] += data[s] * VOut; 161 | } 162 | } 163 | } 164 | else 165 | { 166 | if (count == 0) 167 | memset(m, 0, (size_t)(samples * sizeof(T))); 168 | else 169 | { 170 | } 171 | } 172 | count++; 173 | } 174 | T* Fin(unsigned long long samples, float* A = 0) 175 | { 176 | if (count <= 1 && A == 0) 177 | { 178 | count = 0; 179 | return m; 180 | } 181 | if (!m) 182 | return m; 183 | 184 | float V = 0; 185 | for (unsigned long long s = 0; s < samples; s++) 186 | { 187 | if (count > 0) 188 | m[s] /= (float)count; 189 | if (A) 190 | { 191 | auto ff = fabs(m[s]); 192 | if (ff <= 1.0f && ff > V) 193 | V = ff; 194 | } 195 | } 196 | if (A && samples) 197 | *A = V; 198 | count = 0; 199 | return m; 200 | } 201 | 202 | }; 203 | 204 | template 205 | inline T Peak(T* d, size_t s) 206 | { 207 | T mm = 0; 208 | for (size_t i = 0; i < s; i++) 209 | { 210 | auto fa = fabs(d[i]); 211 | if (fa > mm) 212 | mm = fa; 213 | } 214 | return mm; 215 | } 216 | 217 | 218 | 219 | 220 | struct REBUFFER 221 | { 222 | std::recursive_mutex m; 223 | std::vector d; 224 | AHANDLE Has = CreateEvent(0, TRUE, 0, 0); 225 | MIXBUFFER mb; 226 | 227 | void FinMix(size_t sz, float* A = 0) 228 | { 229 | mb.Fin(sz / sizeof(float), A); 230 | } 231 | 232 | size_t PushX(const char* dd, size_t sz, float* A = 0, float V = 1.0f) 233 | { 234 | REBUFFERLOCK l(m); 235 | auto s = d.size(); 236 | d.resize(s + sz); 237 | if (dd) 238 | memcpy(d.data() + s, dd, sz); 239 | else 240 | memset(d.data() + s, 0, sz); 241 | 242 | char* a1 = d.data(); 243 | a1 += s; 244 | mb.Set((float*)a1); 245 | mb.count = 1; 246 | 247 | SetEvent(Has); 248 | 249 | float* b = (float*)(d.data() + s); 250 | if (V > 1.01f || V < 0.99f) 251 | { 252 | auto st = sz / sizeof(float); 253 | for (size_t i = 0; i < st; i++) 254 | b[i] *= V; 255 | } 256 | if (A) 257 | { 258 | *A = Peak(b, sz / sizeof(float)); 259 | } 260 | 261 | 262 | return s + sz; 263 | } 264 | 265 | size_t Av() 266 | { 267 | REBUFFERLOCK l(m); 268 | return d.size(); 269 | } 270 | 271 | size_t PopX(char* trg, size_t sz, DWORD wi = 0, bool NR = false) 272 | { 273 | if (wi) 274 | WaitForSingleObject(Has, wi); 275 | REBUFFERLOCK l(m); 276 | if (sz >= d.size()) 277 | sz = d.size(); 278 | if (sz == 0) 279 | return 0; 280 | if (trg) 281 | memcpy(trg, d.data(), sz); 282 | if (NR == false) 283 | d.erase(d.begin(), d.begin() + sz); 284 | if (d.size() == 0) 285 | ResetEvent(Has); 286 | return sz; 287 | } 288 | 289 | 290 | void Clear() 291 | { 292 | REBUFFERLOCK l(m); 293 | d.clear(); 294 | } 295 | }; 296 | 297 | template 298 | class READYBUFF 299 | { 300 | public: 301 | 302 | std::vector g; 303 | 304 | void Add(T* d, size_t sz) 305 | { 306 | auto t = g.size(); 307 | g.resize(t + sz); 308 | memcpy(g.data() + t, d, sz * sizeof(T)); 309 | } 310 | 311 | bool Next(T* d, size_t sz) 312 | { 313 | if (g.empty()) 314 | return false; 315 | if (!sz) 316 | return false; 317 | memcpy(d, g.data(), sz * sizeof(T)); 318 | 319 | if (g.size() < sz) 320 | g.clear(); 321 | else 322 | g.erase(g.begin(), g.begin() + sz); 323 | return true; 324 | } 325 | 326 | }; 327 | 328 | 329 | inline HRESULT MFTrs(DWORD, DWORD iid, DWORD ood, CComPtr trs, CComPtr s, IMFSample** sx) 330 | { 331 | if (!trs) 332 | return E_FAIL; 333 | auto hr = s ? trs->ProcessInput(iid, s, 0) : S_OK; 334 | if (SUCCEEDED(hr) || hr == 0xC00D36B5) 335 | { 336 | bool FX = false; 337 | if (hr == 0xC00D36B5) 338 | FX = true; 339 | 340 | MFT_INPUT_STREAM_INFO six = {}; 341 | trs->GetInputStreamInfo(iid, &six); 342 | 343 | MFT_OUTPUT_STREAM_INFO si = {}; 344 | CComPtr bb; 345 | if (si.cbSize == 0) 346 | trs->GetOutputStreamInfo(ood, &si); 347 | CComPtr pSample2; 348 | if ((si.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) == 0) 349 | { 350 | if (si.cbSize == 0) 351 | { 352 | return E_FAIL; 353 | } 354 | MFCreateSample(&pSample2); 355 | MFCreateMemoryBuffer(si.cbSize, &bb); 356 | pSample2->AddBuffer(bb); 357 | } 358 | 359 | MFT_OUTPUT_DATA_BUFFER db = { 0 }; 360 | db.dwStreamID = ood; 361 | db.pSample = pSample2; 362 | if (si.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) 363 | db.pSample = 0; 364 | 365 | DWORD st = 0; 366 | hr = trs->ProcessOutput(0, 1, &db, &st); 367 | if (hr == 0xC00D6D72 && FX) 368 | { 369 | hr = trs->ProcessInput(iid, s, 0); 370 | hr = trs->ProcessOutput(0, 1, &db, &st); 371 | } 372 | if (FAILED(hr)) 373 | return hr; 374 | if (db.pEvents) 375 | db.pEvents->Release(); 376 | if (!db.pSample) 377 | return E_FAIL; 378 | if (sx) 379 | { 380 | *sx = db.pSample; 381 | if ((si.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) == 0) 382 | (*sx)->AddRef(); 383 | 384 | } 385 | return S_OK; 386 | } 387 | return hr; 388 | } 389 | 390 | struct VISTAMIXER 391 | { 392 | std::wstring id; // id 393 | std::wstring name; // friendly 394 | DWORD st = 0; // state 395 | bool CanCapture = 0; 396 | bool CanPlay = 0; 397 | int Mode = 0; // 0 none, 1 shared, 2 exclusive 398 | 399 | std::map> maps; 400 | 401 | int NumChannels(AUDCLNT_SHAREMODE s) 402 | { 403 | auto mo = maps.find(s); 404 | if (mo == maps.end()) 405 | return 0; 406 | if (mo->second.empty()) 407 | return 0; 408 | return mo->second.begin()->second.Format.nChannels; 409 | } 410 | void test(CComPtr ac, bool AllShared) 411 | { 412 | if (ac == 0) 413 | { 414 | CComPtr deviceEnumerator = 0; 415 | auto hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator); 416 | if (!deviceEnumerator) 417 | return; 418 | 419 | CComPtr d; 420 | deviceEnumerator->GetDevice(id.c_str(), &d); 421 | if (!d) 422 | return; 423 | hr = d->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&ac); 424 | if (!ac) 425 | return; 426 | } 427 | 428 | // Mix format 429 | WAVEFORMATEXTENSIBLE* ext = 0; 430 | ac->GetMixFormat((WAVEFORMATEX**)&ext); 431 | if (!ext) 432 | return; 433 | 434 | maps[AUDCLNT_SHAREMODE_SHARED][ext->Format.nSamplesPerSec] = *ext; 435 | WAVEFORMATEXTENSIBLE wfx = *ext; 436 | CoTaskMemFree(ext); 437 | 438 | if (AllShared) 439 | { 440 | for (DWORD sr : { 22050, 44100, 48000, 88200, 96000, 192000 }) 441 | { 442 | if (ext->Format.nSamplesPerSec != sr) 443 | { 444 | auto wfx2 = *ext; 445 | wfx2.Format.nSamplesPerSec = sr; 446 | wfx2.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; 447 | maps[AUDCLNT_SHAREMODE_SHARED][sr] = wfx2; 448 | } 449 | } 450 | } 451 | 452 | // And the exclusives 453 | wfx.Format.wFormatTag = WAVE_FORMAT_PCM; 454 | wfx.Format.cbSize = 0; 455 | wfx.Format.wBitsPerSample = 16; 456 | wfx.Format.nBlockAlign = wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8; 457 | for (auto sr : { 22050,44100,48000,88200,96000,192000 }) 458 | { 459 | wfx.Format.nSamplesPerSec = sr; 460 | wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; 461 | auto hr = ac->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX*)&wfx, 0); 462 | if (hr == S_OK) 463 | maps[AUDCLNT_SHAREMODE_EXCLUSIVE][wfx.Format.nSamplesPerSec] = wfx; 464 | } 465 | } 466 | 467 | bool CanSR(AUDCLNT_SHAREMODE sm, std::vector>& sampleRates) 468 | { 469 | auto mo = maps.find(sm); 470 | if (mo == maps.end()) 471 | return false; 472 | if (mo->second.empty()) 473 | return false; 474 | for (auto& s : sampleRates) 475 | { 476 | int sr = std::get<0>(s); 477 | auto m2 = mo->second.find(sr); 478 | if (m2 == mo->second.end()) 479 | { 480 | std::get<1>(s) = 0; 481 | std::get<2>(s) = 0; 482 | if (sm == AUDCLNT_SHAREMODE_SHARED) 483 | { 484 | std::get<1>(s) = 1; 485 | std::get<2>(s) = mo->second[0].Format.nChannels; 486 | } 487 | continue; 488 | } 489 | std::get<1>(s) = 1; 490 | std::get<2>(s) = m2->second.Format.nChannels; 491 | } 492 | return true; 493 | } 494 | 495 | }; 496 | 497 | 498 | DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING 499 | inline void EnumVistaMixers(std::vector& vistamixers) 500 | { 501 | vistamixers.clear(); 502 | 503 | // Windows Vista+ Mixers as well 504 | CComPtr deviceEnumerator = 0; 505 | CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator); 506 | if (deviceEnumerator) 507 | { 508 | CComPtr e = 0; 509 | 510 | // Capture 511 | deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATEMASK_ALL, &e); 512 | if (e) 513 | { 514 | UINT c = 0; 515 | e->GetCount(&c); 516 | for (unsigned int i = 0; i < c; i++) 517 | { 518 | CComPtr d = 0; 519 | e->Item(i, &d); 520 | if (d) 521 | { 522 | VISTAMIXER vm; 523 | LPWSTR id = 0; 524 | d->GetId(&id); 525 | if (id) 526 | { 527 | vm.id = id; 528 | CoTaskMemFree(id); 529 | } 530 | d->GetState(&vm.st); 531 | 532 | CComPtr st = 0; 533 | d->OpenPropertyStore(STGM_READ, &st); 534 | if (st) 535 | { 536 | PROPVARIANT pv; 537 | PropVariantInit(&pv); 538 | st->GetValue(PKEY_Device_FriendlyName, &pv); 539 | if (pv.pwszVal) 540 | vm.name = pv.pwszVal; 541 | else 542 | vm.name = L"Record Device"; 543 | PropVariantClear(&pv); 544 | } 545 | 546 | vm.CanCapture = true; 547 | vm.CanPlay = false; 548 | CComPtr ac = 0; 549 | d->Activate(__uuidof(IAudioClient), CLSCTX_ALL, 0, (void**)&ac); 550 | if (ac) 551 | { 552 | vm.test(ac, 0); 553 | ac.Release(); 554 | vm.Mode = 0; 555 | vistamixers.push_back(vm); 556 | } 557 | } 558 | } 559 | } 560 | 561 | // Render 562 | e.Release(); 563 | deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &e); 564 | if (e) 565 | { 566 | UINT c = 0; 567 | e->GetCount(&c); 568 | for (unsigned int i = 0; i < c; i++) 569 | { 570 | CComPtr d = 0; 571 | e->Item(i, &d); 572 | if (d) 573 | { 574 | VISTAMIXER vm; 575 | LPWSTR id = 0; 576 | d->GetId(&id); 577 | if (id) 578 | { 579 | vm.id = id; 580 | CoTaskMemFree(id); 581 | } 582 | d->GetState(&vm.st); 583 | 584 | CComPtr st = 0; 585 | d->OpenPropertyStore(STGM_READ, &st); 586 | if (st) 587 | { 588 | PROPVARIANT pv; 589 | PropVariantInit(&pv); 590 | st->GetValue(PKEY_Device_FriendlyName, &pv); 591 | if (pv.pwszVal) 592 | vm.name = pv.pwszVal; 593 | else 594 | vm.name = L"Playback Device"; 595 | PropVariantClear(&pv); 596 | } 597 | 598 | vm.CanCapture = false; 599 | vm.CanPlay = true; 600 | CComPtr ac = 0; 601 | d->Activate(__uuidof(IAudioClient), CLSCTX_ALL, 0, (void**)&ac); 602 | if (ac) 603 | { 604 | vm.test(ac, 0); 605 | ac.Release(); 606 | vm.Mode = 0; 607 | vistamixers.push_back(vm); 608 | } 609 | d.Release(); 610 | } 611 | } 612 | } 613 | 614 | } 615 | 616 | 617 | 618 | 619 | } 620 | 621 | 622 | 623 | 624 | 625 | #endif 626 | 627 | class CAPTURE 628 | { 629 | public: 630 | 631 | DXGI_FORMAT InHDR = DXGI_FORMAT_UNKNOWN; 632 | HRESULT CreateDirect3DDevice(IDXGIAdapter1* g) 633 | { 634 | HRESULT hr = S_OK; 635 | 636 | // Driver types supported 637 | D3D_DRIVER_TYPE DriverTypes[] = 638 | { 639 | D3D_DRIVER_TYPE_HARDWARE, 640 | D3D_DRIVER_TYPE_WARP, 641 | D3D_DRIVER_TYPE_REFERENCE, 642 | }; 643 | UINT NumDriverTypes = ARRAYSIZE(DriverTypes); 644 | 645 | // Feature levels supported 646 | D3D_FEATURE_LEVEL FeatureLevels[] = 647 | { 648 | D3D_FEATURE_LEVEL_11_0, 649 | D3D_FEATURE_LEVEL_10_1, 650 | D3D_FEATURE_LEVEL_10_0, 651 | D3D_FEATURE_LEVEL_9_3, 652 | D3D_FEATURE_LEVEL_9_2, 653 | D3D_FEATURE_LEVEL_9_1 654 | }; 655 | UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels); 656 | 657 | D3D_FEATURE_LEVEL FeatureLevel; 658 | 659 | // Create device 660 | for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex) 661 | { 662 | hr = D3D11CreateDevice(g, DriverTypes[DriverTypeIndex], nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, FeatureLevels, NumFeatureLevels, 663 | D3D11_SDK_VERSION, &device, &FeatureLevel, &context); 664 | if (SUCCEEDED(hr)) 665 | { 666 | // Device creation success, no need to loop anymore 667 | break; 668 | } 669 | } 670 | if (FAILED(hr)) 671 | return hr; 672 | 673 | return S_OK; 674 | } 675 | 676 | 677 | 678 | std::vector buf; 679 | CComPtr device; 680 | CComPtr context; 681 | CComPtr lDeskDupl; 682 | CComPtr lGDIImage; 683 | CComPtr lDestImage; 684 | DXGI_OUTDUPL_DESC lOutputDuplDesc = {}; 685 | 686 | static void GetAdapters(std::vector>& a) 687 | { 688 | CComPtr df; 689 | CreateDXGIFactory1(__uuidof(IDXGIFactory1),(void**)&df); 690 | a.clear(); 691 | if (!df) 692 | return; 693 | int L = 0; 694 | for (;;) 695 | { 696 | CComPtr lDxgiAdapter; 697 | df->EnumAdapters1(L, &lDxgiAdapter); 698 | if (!lDxgiAdapter) 699 | break; 700 | L++; 701 | a.push_back(lDxgiAdapter); 702 | } 703 | return; 704 | } 705 | 706 | bool Get(IDXGIResource* lDesktopResource,bool Curs,RECT* rcx = 0) 707 | { 708 | // QI for ID3D11Texture2D 709 | CComPtr lAcquiredDesktopImage; 710 | if (!lDesktopResource) 711 | return 0; 712 | auto hr = lDesktopResource->QueryInterface(IID_PPV_ARGS(&lAcquiredDesktopImage)); 713 | if (!lAcquiredDesktopImage) 714 | return 0; 715 | lDesktopResource = 0; 716 | 717 | 718 | if (InHDR != DXGI_FORMAT_UNKNOWN || Curs == 0) 719 | { 720 | // No Cursor support 721 | context->CopyResource(lDestImage, lAcquiredDesktopImage); 722 | } 723 | else 724 | { 725 | // Copy image into GDI drawing texture 726 | context->CopyResource(lGDIImage, lAcquiredDesktopImage); 727 | 728 | // Draw cursor image into GDI drawing texture 729 | CComPtr lIDXGISurface1; 730 | 731 | lIDXGISurface1 = lGDIImage; 732 | 733 | if (!lIDXGISurface1) 734 | return 0; 735 | 736 | CURSORINFO lCursorInfo = { 0 }; 737 | lCursorInfo.cbSize = sizeof(lCursorInfo); 738 | auto lBoolres = GetCursorInfo(&lCursorInfo); 739 | if (lBoolres == TRUE) 740 | { 741 | if (lCursorInfo.flags == CURSOR_SHOWING && Curs) 742 | { 743 | auto lCursorPosition = lCursorInfo.ptScreenPos; 744 | // auto lCursorSize = lCursorInfo.cbSize; 745 | HDC lHDC; 746 | lIDXGISurface1->GetDC(FALSE, &lHDC); 747 | DrawIconEx( 748 | lHDC, 749 | lCursorPosition.x, 750 | lCursorPosition.y, 751 | lCursorInfo.hCursor, 752 | 0, 753 | 0, 754 | 0, 755 | 0, 756 | DI_NORMAL | DI_DEFAULTSIZE); 757 | lIDXGISurface1->ReleaseDC(nullptr); 758 | } 759 | } 760 | 761 | // Copy image into CPU access texture 762 | context->CopyResource(lDestImage, lGDIImage); 763 | } 764 | 765 | 766 | // Copy from CPU access texture to bitmap buffer 767 | D3D11_MAPPED_SUBRESOURCE resource; 768 | UINT subresource = D3D11CalcSubresource(0, 0, 0); 769 | hr = context->Map(lDestImage, subresource, D3D11_MAP_READ_WRITE, 0, &resource); 770 | if (FAILED(hr)) 771 | return 0; 772 | 773 | int multi = 4; 774 | if (InHDR == DXGI_FORMAT_R16G16B16A16_FLOAT) 775 | multi = 8; // 64-bit 776 | 777 | auto sz = lOutputDuplDesc.ModeDesc.Width 778 | * lOutputDuplDesc.ModeDesc.Height * multi; 779 | auto sz2 = sz; 780 | buf.resize(sz); 781 | if (rcx) 782 | { 783 | sz2 = (rcx->right - rcx->left) * (rcx->bottom - rcx->top) * multi; 784 | buf.resize(sz2); 785 | sz = sz2; 786 | } 787 | 788 | UINT lBmpRowPitch = lOutputDuplDesc.ModeDesc.Width * multi; 789 | if (rcx) 790 | lBmpRowPitch = (rcx->right - rcx->left) * multi; 791 | UINT lRowPitch = std::min(lBmpRowPitch, resource.RowPitch); 792 | 793 | BYTE* sptr = reinterpret_cast(resource.pData); 794 | BYTE* dptr = buf.data() + sz - lBmpRowPitch; 795 | if (rcx) 796 | sptr += rcx->left * multi; 797 | for (size_t h = 0; h < lOutputDuplDesc.ModeDesc.Height; ++h) 798 | { 799 | if (rcx && h < (size_t)rcx->top) 800 | { 801 | sptr += resource.RowPitch; 802 | continue; 803 | } 804 | if (rcx && h >= (size_t)rcx->bottom) 805 | break; 806 | memcpy_s(dptr, lBmpRowPitch, sptr, lRowPitch); 807 | sptr += resource.RowPitch; 808 | dptr -= lBmpRowPitch; 809 | } 810 | context->Unmap(lDestImage, subresource); 811 | return 1; 812 | } 813 | 814 | bool Prepare(UINT Output = 0) 815 | { 816 | // Get DXGI device 817 | CComPtr lDxgiDevice; 818 | lDxgiDevice = device; 819 | if (!lDxgiDevice) 820 | return 0; 821 | 822 | // Get DXGI adapter 823 | HRESULT hr = 0; 824 | 825 | CComPtr lDxgiAdapter; 826 | hr = lDxgiDevice->GetParent( 827 | __uuidof(IDXGIAdapter), 828 | reinterpret_cast(&lDxgiAdapter)); 829 | 830 | if (FAILED(hr)) 831 | return 0; 832 | 833 | lDxgiDevice = 0; 834 | 835 | // Get output 836 | CComPtr lDxgiOutput; 837 | hr = lDxgiAdapter->EnumOutputs(Output, &lDxgiOutput); 838 | if (FAILED(hr)) 839 | return 0; 840 | 841 | lDxgiAdapter = 0; 842 | 843 | DXGI_OUTPUT_DESC lOutputDesc; 844 | hr = lDxgiOutput->GetDesc(&lOutputDesc); 845 | 846 | // QI for Output 1 847 | CComPtr lDxgiOutput1; 848 | lDxgiOutput1 = lDxgiOutput; 849 | if (!lDxgiOutput1) 850 | return 0; 851 | 852 | lDxgiOutput = 0; 853 | 854 | // Create desktop duplication 855 | hr = lDxgiOutput1->DuplicateOutput( 856 | device, 857 | &lDeskDupl); 858 | 859 | if (FAILED(hr)) 860 | return 0; 861 | 862 | lDxgiOutput1 = 0; 863 | 864 | // Create GUI drawing texture 865 | lDeskDupl->GetDesc(&lOutputDuplDesc); 866 | D3D11_TEXTURE2D_DESC desc = {}; 867 | desc.Width = lOutputDuplDesc.ModeDesc.Width; 868 | desc.Height = lOutputDuplDesc.ModeDesc.Height; 869 | desc.Format = lOutputDuplDesc.ModeDesc.Format; 870 | desc.ArraySize = 1; 871 | desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_RENDER_TARGET; 872 | InHDR = DXGI_FORMAT_UNKNOWN; 873 | if (desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT) // HDR, no GDI compatible 874 | { 875 | InHDR = desc.Format; 876 | } 877 | else 878 | desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE; 879 | desc.SampleDesc.Count = 1; 880 | desc.SampleDesc.Quality = 0; 881 | desc.MipLevels = 1; 882 | desc.CPUAccessFlags = 0; 883 | desc.Usage = D3D11_USAGE_DEFAULT; 884 | lGDIImage = 0; 885 | hr = device->CreateTexture2D(&desc, NULL, &lGDIImage); 886 | if (FAILED(hr)) 887 | { 888 | // desc.MiscFlags = 0; 889 | // hr = device->CreateTexture2D(&desc, NULL, &lGDIImage); 890 | } 891 | if (FAILED(hr)) 892 | return 0; 893 | 894 | if (lGDIImage == nullptr) 895 | return 0; 896 | 897 | // Create CPU access texture 898 | desc.Width = lOutputDuplDesc.ModeDesc.Width; 899 | desc.Height = lOutputDuplDesc.ModeDesc.Height; 900 | desc.Format = lOutputDuplDesc.ModeDesc.Format; 901 | desc.ArraySize = 1; 902 | desc.BindFlags = 0; 903 | desc.MiscFlags = 0; 904 | desc.SampleDesc.Count = 1; 905 | desc.SampleDesc.Quality = 0; 906 | desc.MipLevels = 1; 907 | desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE; 908 | desc.Usage = D3D11_USAGE_STAGING; 909 | lDestImage = 0; 910 | hr = device->CreateTexture2D(&desc, NULL, &lDestImage); 911 | if (FAILED(hr)) 912 | return 0; 913 | 914 | if (lDestImage == nullptr) 915 | return 0; 916 | 917 | return 1; 918 | } 919 | 920 | 921 | CComPtr wbfact; 922 | HRESULT Convert(void* f, int wi, int he,std::vector& out,GUID from = GUID_WICPixelFormat128bppPRGBAFloat) 923 | { 924 | if (!wbfact) 925 | CoCreateInstance(CLSID_WICImagingFactory2, 0, CLSCTX_INPROC_SERVER, 926 | __uuidof(IWICImagingFactory2), (void**)&wbfact); 927 | 928 | int multi = 4; 929 | if (from == GUID_WICPixelFormat128bppPRGBAFloat) 930 | multi = 16; 931 | if (from == GUID_WICPixelFormat64bppRGBAHalf) 932 | multi = 8; 933 | CComPtr b; 934 | wbfact->CreateBitmapFromMemory(wi, he, from, wi * multi, wi * he * multi, (BYTE*)f, &b); 935 | if (!b) 936 | return E_FAIL; 937 | 938 | 939 | CComPtr wf; 940 | wbfact->CreateFormatConverter(&wf); 941 | 942 | if (!wf) 943 | return E_FAIL; 944 | auto hr = wf->Initialize(b, GUID_WICPixelFormat32bppRGBA1010102, WICBitmapDitherTypeNone, 0, 0, WICBitmapPaletteTypeCustom); 945 | if (FAILED(hr)) 946 | return E_FAIL; 947 | 948 | CComPtr wic; 949 | wbfact->CreateBitmapFromSource(wf, WICBitmapCacheOnLoad, &wic); 950 | if (!wic) 951 | return E_FAIL; 952 | 953 | WICRect wr = { 0,0,(INT)wi,(INT)he }; 954 | out.resize(wi * he); 955 | hr = wic->CopyPixels(&wr, wi * 4, (UINT)out.size() * 4, (BYTE*)out.data()); 956 | 957 | return hr; 958 | } 959 | 960 | }; 961 | 962 | 963 | 964 | 965 | 966 | 967 | struct DESKTOPCAPTUREPARAMS 968 | { 969 | bool HasVideo = 1; 970 | bool HasAudio = 1; 971 | std::vector>> AudioFrom; 972 | GUID VIDEO_ENCODING_FORMAT = MFVideoFormat_H264; 973 | GUID AUDIO_ENCODING_FORMAT = MFAudioFormat_MP3; 974 | std::wstring f; 975 | void* cb = 0; 976 | std::function Streamer; 977 | std::function Framer; 978 | std::function PrepareAttributes; 979 | int fps = 25; 980 | int NumThreads = 0; 981 | int Qu = -1; 982 | int vbrm = 0; 983 | int vbrq = 0; 984 | int BR = 4000; 985 | int NCH = 2; 986 | int SR = 44100; 987 | int ABR = 192; 988 | bool Cursor = true; 989 | RECT rx = { 0,0,0,0 }; 990 | HWND hWnd = 0; 991 | IDXGIAdapter1* ad = 0; 992 | UINT nOutput = 0; 993 | 994 | unsigned long long StartMS = 0; // 0, none 995 | unsigned long long EndMS = 0; // 0, none 996 | bool MustEnd = false; 997 | bool Pause = false; 998 | }; 999 | 1000 | 1001 | struct VectorStreamX2 : public IMFByteStream 1002 | { 1003 | ULONG r = 1; 1004 | std::vector d; 1005 | size_t p = 0; 1006 | 1007 | // IUnknown 1008 | virtual HRESULT STDMETHODCALLTYPE QueryInterface( 1009 | /* [in] */ REFIID riid, 1010 | /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) 1011 | { 1012 | if (riid == __uuidof(IUnknown) || riid == __uuidof(IMFByteStream)) 1013 | { 1014 | *ppvObject = (IStream*)this; 1015 | r++; 1016 | return S_OK; 1017 | } 1018 | return E_NOINTERFACE; 1019 | } 1020 | 1021 | virtual ULONG STDMETHODCALLTYPE AddRef(void) 1022 | { 1023 | return ++r; 1024 | } 1025 | 1026 | virtual ULONG STDMETHODCALLTYPE Release(void) 1027 | { 1028 | return --r; 1029 | } 1030 | 1031 | HRESULT __stdcall BeginRead( 1032 | BYTE* pb, 1033 | ULONG cb, 1034 | IMFAsyncCallback* pCallback, 1035 | IUnknown* punkState 1036 | ) 1037 | { 1038 | ULONG pcb = 0; 1039 | Read(pb, cb, &pcb); 1040 | NextCB = pcb; 1041 | CComPtr ar; 1042 | MFCreateAsyncResult(0, pCallback, punkState, &ar); 1043 | pCallback->Invoke(ar); 1044 | return S_OK; 1045 | } 1046 | 1047 | ULONG NextCB = 0; 1048 | HRESULT __stdcall BeginWrite( 1049 | const BYTE* pb, 1050 | ULONG cb, 1051 | IMFAsyncCallback* pCallback, 1052 | IUnknown* punkState 1053 | ) 1054 | { 1055 | ULONG pcb = 0; 1056 | Write(pb, cb, &pcb); 1057 | NextCB = pcb; 1058 | CComPtr ar; 1059 | MFCreateAsyncResult(0, pCallback, punkState, &ar); 1060 | pCallback->Invoke(ar); 1061 | return S_OK; 1062 | } 1063 | 1064 | HRESULT __stdcall Close( 1065 | 1066 | ) 1067 | { 1068 | return S_OK; 1069 | } 1070 | 1071 | HRESULT __stdcall EndRead( 1072 | IMFAsyncResult*, 1073 | ULONG* pcbRead 1074 | ) 1075 | { 1076 | if (!pcbRead) 1077 | return E_POINTER; 1078 | *pcbRead = NextCB; 1079 | return S_OK; 1080 | } 1081 | 1082 | HRESULT __stdcall EndWrite( 1083 | IMFAsyncResult* , 1084 | ULONG* pcbWritten 1085 | ) 1086 | { 1087 | if (!pcbWritten) 1088 | return E_POINTER; 1089 | *pcbWritten = NextCB; 1090 | return S_OK; 1091 | } 1092 | 1093 | HRESULT __stdcall Flush() 1094 | { 1095 | return S_OK; 1096 | } 1097 | 1098 | HRESULT __stdcall GetCapabilities( 1099 | DWORD* pdwCapabilities 1100 | ) 1101 | { 1102 | if (!pdwCapabilities) 1103 | return E_POINTER; 1104 | *pdwCapabilities = MFBYTESTREAM_IS_READABLE | MFBYTESTREAM_IS_WRITABLE | MFBYTESTREAM_IS_SEEKABLE; 1105 | return S_OK; 1106 | } 1107 | 1108 | HRESULT __stdcall GetCurrentPosition( 1109 | QWORD* pqwPosition 1110 | ) 1111 | { 1112 | if (!pqwPosition) 1113 | return E_POINTER; 1114 | *pqwPosition = p; 1115 | return S_OK; 1116 | } 1117 | 1118 | HRESULT __stdcall GetLength( 1119 | QWORD* q 1120 | ) 1121 | { 1122 | if (!q) 1123 | return E_POINTER; 1124 | *q = d.size(); 1125 | return S_OK; 1126 | } 1127 | 1128 | HRESULT __stdcall IsEndOfStream( 1129 | BOOL* q 1130 | ) 1131 | { 1132 | if (!q) 1133 | return E_POINTER; 1134 | *q = FALSE; 1135 | if (p == d.size()) 1136 | *q = TRUE; 1137 | return S_OK; 1138 | } 1139 | 1140 | HRESULT __stdcall Read( 1141 | BYTE* pv, 1142 | ULONG cb, 1143 | ULONG* pcbRead 1144 | ) 1145 | { 1146 | auto av = d.size() - p; 1147 | if (cb < av) 1148 | av = cb; 1149 | memcpy(pv, d.data() + p, av); 1150 | p += av; 1151 | if (pcbRead) 1152 | *pcbRead = (ULONG)av; 1153 | if (av < cb) 1154 | return S_FALSE; 1155 | return S_OK; 1156 | } 1157 | 1158 | std::function func; 1159 | void* cbx = 0; 1160 | HRESULT __stdcall Write( 1161 | const BYTE* pv, 1162 | ULONG cb, 1163 | ULONG* pcbWritten 1164 | ) 1165 | { 1166 | if (d.size() < (p + cb)) 1167 | { 1168 | auto exc = (p + cb) - d.size(); 1169 | d.resize(d.size() + exc); 1170 | } 1171 | memcpy(d.data() + p, pv, cb); 1172 | 1173 | if (func) 1174 | { 1175 | auto hr = func(pv, cb,cbx); 1176 | if (FAILED(hr)) 1177 | return hr; 1178 | } 1179 | 1180 | 1181 | p += cb; 1182 | if (pcbWritten) 1183 | *pcbWritten = cb; 1184 | return S_OK; 1185 | } 1186 | 1187 | HRESULT __stdcall SetLength( 1188 | QWORD qwLength 1189 | ) 1190 | 1191 | { 1192 | d.resize(qwLength); 1193 | if (p >= qwLength) 1194 | p = qwLength; 1195 | return S_OK; 1196 | } 1197 | 1198 | HRESULT __stdcall SetCurrentPosition( 1199 | QWORD q 1200 | ) 1201 | { 1202 | if (q > d.size()) 1203 | return E_FAIL; 1204 | p = q; 1205 | return S_OK; 1206 | 1207 | } 1208 | 1209 | 1210 | HRESULT __stdcall Seek( 1211 | MFBYTESTREAM_SEEK_ORIGIN dwOrigin, 1212 | LONGLONG llSeekOffset, 1213 | DWORD , 1214 | QWORD* pqwCurrentPosition 1215 | ) 1216 | { 1217 | LARGE_INTEGER lo = { 0 }; 1218 | if (dwOrigin == msoBegin) 1219 | { 1220 | p = llSeekOffset; 1221 | } 1222 | if (dwOrigin == msoCurrent) 1223 | { 1224 | p += llSeekOffset; 1225 | } 1226 | if (p >= d.size()) 1227 | p = d.size(); 1228 | if (pqwCurrentPosition) 1229 | *pqwCurrentPosition = p; 1230 | 1231 | return S_OK; 1232 | } 1233 | 1234 | 1235 | }; 1236 | 1237 | 1238 | 1239 | inline int DesktopCapture(DESKTOPCAPTUREPARAMS& dp) 1240 | { 1241 | HRESULT hr = S_OK; 1242 | struct AUDIOIN 1243 | { 1244 | private: 1245 | BYTE* pData = 0; 1246 | UINT32 framesAvailable = 0; 1247 | DWORD flags = 0; 1248 | 1249 | public: 1250 | std::wstring id; 1251 | CComPtr d; 1252 | CComPtr ac; 1253 | CComPtr ac2; 1254 | CComPtr cap; 1255 | CComPtr ren; 1256 | WAVEFORMATEXTENSIBLE wfx = {}; 1257 | 1258 | std::vector tempf; 1259 | std::vector chm; 1260 | std::vector> buffer; 1261 | HANDLE hEv = 0; 1262 | 1263 | 1264 | 1265 | bool Capturing = true; 1266 | bool CapturingFin1 = false; 1267 | bool CapturingFin2 = false; 1268 | std::shared_ptr AudioDataX; 1269 | std::vector PopBuffer; 1270 | 1271 | #define REFTIMES_PER_SEC 10000000 1272 | #define REFTIMES_PER_MILLISEC 10000 1273 | 1274 | size_t AvFrames() 1275 | { 1276 | auto avs = AudioDataX->Av(); 1277 | avs /= wfx.Format.nChannels; 1278 | avs /= (wfx.Format.wBitsPerSample / 8); 1279 | return avs; 1280 | } 1281 | 1282 | void PlaySilence(REFERENCE_TIME rt) 1283 | { 1284 | // ns 1285 | rt /= 10000; 1286 | // in SR , 1000 ms 1287 | // ? , rt ms 1288 | auto ns = (wfx.Format.nSamplesPerSec * rt); 1289 | ns /= 1000; 1290 | while (Capturing) 1291 | { 1292 | if (!ren) 1293 | break; 1294 | 1295 | Sleep((DWORD)(rt / 2)); 1296 | 1297 | if (!Capturing) 1298 | break; 1299 | 1300 | // See how much buffer space is available. 1301 | UINT32 numFramesPadding = 0; 1302 | auto hr = ac2->GetCurrentPadding(&numFramesPadding); 1303 | if (FAILED(hr)) 1304 | break; 1305 | 1306 | 1307 | auto numFramesAvailable = ns - numFramesPadding; 1308 | if (!numFramesAvailable) 1309 | continue; 1310 | 1311 | BYTE* db = 0; 1312 | hr = ren->GetBuffer((UINT32)numFramesAvailable, &db); 1313 | if (FAILED(hr)) 1314 | break; 1315 | auto bs = numFramesAvailable * wfx.Format.nChannels * wfx.Format.wBitsPerSample / 8; 1316 | memset(db, 0,(size_t) bs); 1317 | ren->ReleaseBuffer((UINT32)numFramesAvailable, 0); //AUDCLNT_BUFFERFLAGS_SILENT 1318 | } 1319 | CapturingFin2 = true; 1320 | } 1321 | 1322 | void ThreadLoopCapture() 1323 | { 1324 | UINT64 up, uq; 1325 | while (Capturing) 1326 | { 1327 | if (hEv) 1328 | WaitForSingleObject(hEv, INFINITE); 1329 | 1330 | if (!Capturing) 1331 | break; 1332 | auto hr = cap->GetBuffer(&pData, &framesAvailable, &flags, &up, &uq); 1333 | if (FAILED(hr)) 1334 | break; 1335 | if (framesAvailable == 0) 1336 | continue; 1337 | 1338 | auto ThisAudioBytes = framesAvailable * wfx.Format.nChannels * wfx.Format.wBitsPerSample/8 ; 1339 | 1340 | AudioDataX->PushX((const char*)pData, ThisAudioBytes); 1341 | cap->ReleaseBuffer(framesAvailable); 1342 | } 1343 | CapturingFin1 = true; 1344 | } 1345 | }; 1346 | #ifndef TURBO_PLAY_SC 1347 | std::vector vistamixers; 1348 | EnumVistaMixers(vistamixers); 1349 | #else 1350 | EnumVistaMixers(); 1351 | #endif 1352 | 1353 | 1354 | 1355 | /* if (dp.HasAudio) 1356 | { 1357 | VM::EnumVistaMixers(); 1358 | dp.AudioFrom 1359 | = { 1360 | // VM::vistamixers[0].id,VM::vistamixers[1].id, 1361 | // VM::vistamixers[1].id, 1362 | {VM::vistamixers[0].id, {0,1} } 1363 | }; 1364 | } 1365 | */ 1366 | // ---------------------- 1367 | std::vector> ains; 1368 | 1369 | class AINRELEASE 1370 | { 1371 | std::vector>* ains = 0; 1372 | public: 1373 | AINRELEASE(std::vector>& x) 1374 | { 1375 | ains = &x; 1376 | } 1377 | ~AINRELEASE() 1378 | { 1379 | if (!ains) 1380 | return; 1381 | for (auto& a : *ains) 1382 | { 1383 | a->Capturing = false; 1384 | if (a->hEv) 1385 | SetEvent(a->hEv); 1386 | for (int i = 0; i < 10; i++) 1387 | { 1388 | if (a->CapturingFin1 && a->CapturingFin2) 1389 | break; 1390 | Sleep(150); 1391 | } 1392 | if (a->hEv) 1393 | CloseHandle(a->hEv); 1394 | a->hEv = 0; 1395 | 1396 | 1397 | } 1398 | } 1399 | 1400 | }; 1401 | AINRELEASE arx(ains); 1402 | 1403 | REFERENCE_TIME rt; 1404 | unsigned long long a = dp.SR/ dp.fps; // Samples per frame 1405 | a *= 1000; 1406 | a *= 10000; 1407 | a /= dp.SR; 1408 | rt = a; 1409 | 1410 | if (dp.hWnd) 1411 | { 1412 | GetWindowRect(dp.hWnd, &dp.rx); 1413 | if (dp.rx.left < 0 || dp.rx.right < 0) 1414 | dp.rx = {}; 1415 | } 1416 | 1417 | CComPtr deviceEnumerator = 0; 1418 | if(dp.HasAudio) 1419 | hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&deviceEnumerator); 1420 | 1421 | CAPTURE cap; 1422 | int wi = 0, he = 0; 1423 | if (dp.HasVideo) 1424 | { 1425 | if (FAILED(cap.CreateDirect3DDevice(dp.ad))) 1426 | return -1; 1427 | if (!cap.Prepare(dp.nOutput)) 1428 | return -2; 1429 | if (cap.InHDR) 1430 | dp.VIDEO_ENCODING_FORMAT = MFVideoFormat_HEVC; 1431 | wi = cap.lOutputDuplDesc.ModeDesc.Width; 1432 | he = cap.lOutputDuplDesc.ModeDesc.Height; 1433 | if (dp.rx.right && dp.rx.bottom) 1434 | { 1435 | wi = dp.rx.right - dp.rx.left; 1436 | he = dp.rx.bottom - dp.rx.top; 1437 | } 1438 | } 1439 | 1440 | CComPtr attrs; 1441 | MFCreateAttributes(&attrs, 0); 1442 | attrs->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, true); 1443 | 1444 | VectorStreamX2 bs; 1445 | bs.func = dp.Streamer; 1446 | CComPtr pSinkWriter; 1447 | if (dp.f.empty()) 1448 | { 1449 | if (dp.Framer) 1450 | { 1451 | hr = S_OK; 1452 | } 1453 | else 1454 | { 1455 | if (dp.HasVideo) 1456 | attrs->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4); 1457 | else 1458 | attrs->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MP3); 1459 | if (dp.PrepareAttributes) 1460 | dp.PrepareAttributes(attrs); 1461 | hr = MFCreateSinkWriterFromURL(NULL, &bs, attrs, &pSinkWriter); 1462 | } 1463 | } 1464 | else 1465 | hr = MFCreateSinkWriterFromURL(dp.f.c_str(), NULL, attrs, &pSinkWriter); 1466 | if (FAILED(hr)) return -3; 1467 | 1468 | 1469 | CComPtr pMediaTypeOutVideo; 1470 | DWORD OutVideoStreamIndex = 0; 1471 | CComPtr pMediaTypeVideoIn; 1472 | if (dp.HasVideo && !dp.Framer) 1473 | { 1474 | hr = MFCreateMediaType(&pMediaTypeOutVideo); 1475 | if (FAILED(hr)) return -4; 1476 | 1477 | pMediaTypeOutVideo->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 1478 | 1479 | pMediaTypeOutVideo->SetGUID(MF_MT_SUBTYPE, dp.VIDEO_ENCODING_FORMAT); 1480 | 1481 | pMediaTypeOutVideo->SetUINT32(MF_MT_AVG_BITRATE, dp.BR * 1000); 1482 | MFSetAttributeSize(pMediaTypeOutVideo, MF_MT_FRAME_SIZE, wi, he); 1483 | MFSetAttributeRatio(pMediaTypeOutVideo, MF_MT_FRAME_RATE, dp.fps, 1); 1484 | MFSetAttributeRatio(pMediaTypeOutVideo, MF_MT_PIXEL_ASPECT_RATIO, 1, 1); 1485 | pMediaTypeOutVideo->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); 1486 | 1487 | 1488 | if (dp.VIDEO_ENCODING_FORMAT == MFVideoFormat_H265 || dp.VIDEO_ENCODING_FORMAT == MFVideoFormat_HEVC || dp.VIDEO_ENCODING_FORMAT == MFVideoFormat_HEVC_ES) 1489 | pMediaTypeOutVideo->SetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_Normal); 1490 | 1491 | if (dp.VIDEO_ENCODING_FORMAT == MFVideoFormat_VP80 || dp.VIDEO_ENCODING_FORMAT == MFVideoFormat_VP90) 1492 | pMediaTypeOutVideo->SetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_Normal); 1493 | 1494 | /* 1495 | MF_MT_FRAME_SIZE 1920 x 1080 1496 | MF_MT_AVG_BITRATE 185984000 1497 | MF_MT_MAJOR_TYPE MFMediaType_Video 1498 | MF_MT_VIDEO_NOMINAL_RANGE 1 1499 | MF_MT_FRAME_RATE 30 x 1 1500 | MF_MT_PIXEL_ASPECT_RATIO 1 x 1 1501 | MF_MT_INTERLACE_MODE 2 1502 | MF_MT_SUBTYPE MFVideoFormat_HEVC 1503 | 1504 | 1505 | */ 1506 | hr = pSinkWriter->AddStream(pMediaTypeOutVideo, &OutVideoStreamIndex); 1507 | if (FAILED(hr)) return -5; 1508 | } 1509 | 1510 | // Audio 1511 | std::vector AudioEvents; 1512 | if (deviceEnumerator && dp.HasAudio) 1513 | { 1514 | for (auto& vm : vistamixers) 1515 | { 1516 | for (auto& xy : dp.AudioFrom) 1517 | { 1518 | auto& x = std::get<0>(xy); 1519 | if (x == vm.id) 1520 | { 1521 | std::shared_ptr ain = std::make_shared(); 1522 | ain->AudioDataX = std::make_shared(); 1523 | ain->id = x; 1524 | 1525 | CComPtr d; 1526 | deviceEnumerator->GetDevice(x.c_str(), &d); 1527 | if (!d) 1528 | continue; 1529 | 1530 | DWORD flg = AUDCLNT_STREAMFLAGS_NOPERSIST; 1531 | flg |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM; 1532 | if (vm.CanCapture) 1533 | { 1534 | flg |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; 1535 | 1536 | } 1537 | if (vm.CanPlay) 1538 | { 1539 | flg |= AUDCLNT_STREAMFLAGS_LOOPBACK; 1540 | } 1541 | 1542 | ain->d = d; 1543 | 1544 | hr = d->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (LPVOID*)&ain->ac); 1545 | if (!ain->ac) 1546 | continue; 1547 | 1548 | 1549 | auto ww = vm.maps[AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED][dp.SR]; 1550 | if (ww.Format.nSamplesPerSec == 0 && dp.SR == 44100) 1551 | ww = vm.maps[AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED][48000]; 1552 | else 1553 | if (ww.Format.nSamplesPerSec == 0 && dp.SR == 48000) 1554 | ww = vm.maps[AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED][44100]; 1555 | 1556 | ww.Format.nSamplesPerSec = dp.SR; 1557 | ww.Format.nAvgBytesPerSec = dp.SR * ww.Format.nBlockAlign; 1558 | hr = ain->ac->Initialize(AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, flg, rt, 0, (WAVEFORMATEX*)&ww, 0); 1559 | if (FAILED(hr)) 1560 | continue; 1561 | 1562 | hr = ain->ac->GetService(__uuidof(IAudioCaptureClient), (void**)&ain->cap); 1563 | if (!ain->cap) 1564 | continue; 1565 | 1566 | 1567 | AudioEvents.push_back(ain->AudioDataX->Has); 1568 | if (vm.CanCapture) 1569 | { 1570 | ain->hEv = CreateEvent(0, 0, 0, 0); 1571 | ain->ac->SetEventHandle(ain->hEv); 1572 | } 1573 | 1574 | if (vm.CanPlay) 1575 | { 1576 | hr = d->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (LPVOID*)&ain->ac2); 1577 | if (!ain->ac2) 1578 | continue; 1579 | 1580 | 1581 | DWORD flg2 = AUDCLNT_STREAMFLAGS_NOPERSIST; 1582 | flg2 |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM; 1583 | // flg2 |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK; 1584 | 1585 | hr = ain->ac2->Initialize(AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, flg2, rt, 0, (WAVEFORMATEX*)&ww, 0); 1586 | if (FAILED(hr)) 1587 | continue; 1588 | 1589 | hr = ain->ac2->GetService(__uuidof(IAudioRenderClient), (void**)&ain->ren); 1590 | if (!ain->ren) 1591 | continue; 1592 | } 1593 | 1594 | hr = ain->ac->Start(); 1595 | if (FAILED(hr)) 1596 | continue; 1597 | 1598 | ain->wfx = ww; 1599 | ain->chm = std::get<1>(xy); 1600 | if (vm.CanPlay) 1601 | { 1602 | hr = ain->ac2->Start(); 1603 | if (FAILED(hr)) 1604 | continue; 1605 | std::thread tx2(&AUDIOIN::PlaySilence, ain.get(), rt); 1606 | tx2.detach(); 1607 | } 1608 | else 1609 | ain->CapturingFin2 = true; 1610 | std::thread tx(&AUDIOIN::ThreadLoopCapture, ain.get()); 1611 | tx.detach(); 1612 | ains.push_back(ain); 1613 | } 1614 | } 1615 | } 1616 | 1617 | } 1618 | 1619 | if (ains.empty()) 1620 | dp.HasAudio = 0; 1621 | 1622 | CComPtr pMediaTypeOutAudio; 1623 | DWORD OutAudioStreamIndex = 0; 1624 | 1625 | 1626 | 1627 | if (dp.HasAudio) 1628 | { 1629 | hr = MFCreateMediaType(&pMediaTypeOutAudio); 1630 | if (FAILED(hr)) return -6; 1631 | 1632 | hr = pMediaTypeOutAudio->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); 1633 | if (FAILED(hr)) return -7; 1634 | 1635 | 1636 | // pcm 1637 | /* if (AUDIO_ENCODING_FORMAT == MFAudioFormat_PCM) 1638 | { 1639 | pMediaTypeOutAudio->SetGUID(MF_MT_SUBTYPE, vp.AUDIO_TYPE); 1640 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, vp.AUDIO_CHANNELS); 1641 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, vp.AUDIO_SAMPLE_RATE); 1642 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, vp.AUDIO_BIT_RATE / 8); 1643 | int BA = (int)((vp.AUDIO_INPUT_BITS / 8) * vp.AUDIO_INPUT_CHANNELS); 1644 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, BA); 1645 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, vp.AUDIO_INPUT_BITS); 1646 | } 1647 | */ 1648 | // mp3 1649 | if (dp.AUDIO_ENCODING_FORMAT == MFAudioFormat_MP3) 1650 | { 1651 | pMediaTypeOutAudio->SetGUID(MF_MT_SUBTYPE, dp.AUDIO_ENCODING_FORMAT); 1652 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, dp.NCH); 1653 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, dp.SR); 1654 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, dp.ABR*1000 / 8); 1655 | } 1656 | 1657 | /* // wma 1658 | if (vp.AUDIO_TYPE == MFAudioFormat_WMAudioV9) 1659 | { 1660 | pMediaTypeOutAudio->SetGUID(MF_MT_SUBTYPE, vp.AUDIO_TYPE); 1661 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, vp.AUDIO_CHANNELS); 1662 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, vp.AUDIO_SAMPLE_RATE); 1663 | } 1664 | */ 1665 | // aac 1666 | if (dp.AUDIO_ENCODING_FORMAT == MFAudioFormat_AAC || dp.AUDIO_ENCODING_FORMAT == MFAudioFormat_FLAC) 1667 | { 1668 | pMediaTypeOutAudio->SetGUID(MF_MT_SUBTYPE, dp.AUDIO_ENCODING_FORMAT); 1669 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, dp.NCH); 1670 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16); 1671 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, dp.SR); 1672 | pMediaTypeOutAudio->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, dp.ABR*1000 / 8); 1673 | } 1674 | 1675 | hr = pSinkWriter->AddStream(pMediaTypeOutAudio, &OutAudioStreamIndex); 1676 | if (FAILED(hr)) return -8; 1677 | } 1678 | 1679 | 1680 | 1681 | hr = MFCreateMediaType(&pMediaTypeVideoIn); 1682 | if (FAILED(hr)) 1683 | return -9; 1684 | pMediaTypeVideoIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); 1685 | 1686 | MFSetAttributeSize(pMediaTypeVideoIn, MF_MT_FRAME_SIZE, wi,he); 1687 | MFSetAttributeRatio(pMediaTypeVideoIn, MF_MT_FRAME_RATE, dp.fps, 1); 1688 | MFSetAttributeRatio(pMediaTypeVideoIn, MF_MT_PIXEL_ASPECT_RATIO, 1, 1); 1689 | 1690 | 1691 | 1692 | 1693 | // NV12 converter 1694 | CComPtr VideoTransformMFT; 1695 | bool NV12 = 0; 1696 | if (false) 1697 | { 1698 | CComPtr trs; 1699 | trs.CoCreateInstance(CLSID_VideoProcessorMFT); 1700 | std::vector iids; 1701 | std::vector oods; 1702 | DWORD is = 0, os = 0; 1703 | hr = trs->GetStreamCount(&is, &os); 1704 | iids.resize(is); 1705 | oods.resize(os); 1706 | hr = trs->GetStreamIDs(is, iids.data(), os, oods.data()); 1707 | CComPtr ptype; 1708 | CComPtr ptype2; 1709 | MFCreateMediaType(&ptype); 1710 | MFCreateMediaType(&ptype2); 1711 | pMediaTypeVideoIn->CopyAllItems(ptype); 1712 | pMediaTypeVideoIn->CopyAllItems(ptype2); 1713 | 1714 | PROPVARIANT pv; 1715 | InitPropVariantFromCLSID(MFVideoFormat_RGB32, &pv); 1716 | ptype->SetItem(MF_MT_SUBTYPE, pv); 1717 | 1718 | PROPVARIANT pv2; 1719 | InitPropVariantFromCLSID(MFVideoFormat_NV12, &pv2); 1720 | ptype2->SetItem(MF_MT_SUBTYPE, pv2); 1721 | 1722 | hr = trs->SetInputType(iids[0], ptype, 0); 1723 | auto hr2 = trs->SetOutputType(oods[0], ptype2, 0); 1724 | if (SUCCEEDED(hr) && SUCCEEDED(hr2)) 1725 | { 1726 | VideoTransformMFT = trs; 1727 | pMediaTypeVideoIn->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); 1728 | NV12 = true; 1729 | } 1730 | } 1731 | 1732 | if (!NV12) 1733 | pMediaTypeVideoIn->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); 1734 | 1735 | if (cap.InHDR != DXGI_FORMAT_UNKNOWN) 1736 | { 1737 | // Force our Nvidia MFT encoder to load by calling first our fake guid 1738 | pMediaTypeVideoIn->SetGUID(MF_MT_SUBTYPE, MyFakeFmt); 1739 | } 1740 | 1741 | if (dp.HasVideo && !dp.Framer) 1742 | { 1743 | hr = pSinkWriter->SetInputMediaType(OutVideoStreamIndex, pMediaTypeVideoIn, NULL); 1744 | if (FAILED(hr)) return -10; 1745 | 1746 | 1747 | if (cap.InHDR != DXGI_FORMAT_UNKNOWN) 1748 | { 1749 | // Now put the real value 1750 | pMediaTypeVideoIn->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB10); 1751 | hr = pSinkWriter->SetInputMediaType(OutVideoStreamIndex, pMediaTypeVideoIn, NULL); 1752 | if (FAILED(hr)) return -10; 1753 | } 1754 | 1755 | CComPtr ca; 1756 | hr = pSinkWriter->GetServiceForStream(OutVideoStreamIndex, GUID_NULL, __uuidof(ICodecAPI), (void**)&ca); 1757 | if (ca) 1758 | { 1759 | int thr2 = dp.NumThreads; 1760 | if (thr2) 1761 | { 1762 | VARIANT v = {}; 1763 | v.vt = VT_UI4; 1764 | v.ulVal = thr2; 1765 | ca->SetValue(&CODECAPI_AVEncNumWorkerThreads, &v); 1766 | } 1767 | int qu = dp.Qu; 1768 | if (qu >= 0 && qu <= 100) 1769 | { 1770 | VARIANT v = {}; 1771 | v.vt = VT_UI4; 1772 | v.ulVal = qu; 1773 | ca->SetValue(&CODECAPI_AVEncCommonQualityVsSpeed, &v); 1774 | } 1775 | 1776 | if (dp.vbrm > 0) 1777 | { 1778 | VARIANT v = {}; 1779 | v.vt = VT_UI4; 1780 | if (dp.vbrm == 1) 1781 | v.ulVal = eAVEncCommonRateControlMode_UnconstrainedVBR; 1782 | if (dp.vbrm == 2) 1783 | v.ulVal = eAVEncCommonRateControlMode_Quality; 1784 | if (dp.vbrm == 3) 1785 | v.ulVal = eAVEncCommonRateControlMode_CBR; 1786 | ca->SetValue(&CODECAPI_AVEncCommonRateControlMode, &v); 1787 | if (dp.vbrm == 2) 1788 | { 1789 | VARIANT v7 = {}; 1790 | v7.vt = VT_UI4; 1791 | v7.ulVal = dp.vbrq; 1792 | ca->SetValue(&CODECAPI_AVEncCommonQuality, &v7); 1793 | } 1794 | } 1795 | } 1796 | } 1797 | 1798 | CComPtr pMediaTypeAudioIn; 1799 | std::vector TempAudioFiles; 1800 | if (dp.HasAudio) 1801 | { 1802 | hr = MFCreateMediaType(&pMediaTypeAudioIn); 1803 | if (FAILED(hr)) return -11; 1804 | 1805 | pMediaTypeAudioIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); 1806 | pMediaTypeAudioIn->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); 1807 | pMediaTypeAudioIn->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, dp.NCH); 1808 | pMediaTypeAudioIn->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, dp.SR); 1809 | pMediaTypeAudioIn->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16); 1810 | int BA = (int)((16 / 8) * dp.NCH); 1811 | pMediaTypeAudioIn->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, BA); 1812 | pMediaTypeAudioIn->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, (UINT32)(dp.SR * BA)); 1813 | 1814 | hr = pSinkWriter->SetInputMediaType(OutAudioStreamIndex, pMediaTypeAudioIn, NULL); 1815 | if (FAILED(hr)) return -12; 1816 | } 1817 | 1818 | 1819 | int multi = 4; 1820 | if (cap.InHDR != DXGI_FORMAT_UNKNOWN) 1821 | multi = 16; 1822 | 1823 | if (!dp.Framer) 1824 | { 1825 | if (pSinkWriter) 1826 | hr = pSinkWriter->BeginWriting(); 1827 | if (FAILED(hr)) return -13; 1828 | } 1829 | 1830 | const LONG cbWidth = multi * wi; 1831 | DWORD VideoBufferSize = cbWidth * he; 1832 | CComPtr pVideoBuffer = NULL; 1833 | CComPtr pVideoSampleS = NULL; 1834 | 1835 | unsigned long long rtA = 0; 1836 | unsigned long long rtV = 0; 1837 | std::chrono::system_clock::time_point last_frame_time_point; 1838 | bool isFirstFrame = true; 1839 | 1840 | [[maybe_unused]] unsigned long long msloop = 1000 / dp.fps; 1841 | 1842 | std::vector AudioData; 1843 | std::vector> v(dp.NCH); 1844 | std::vector> mixv(dp.NCH); 1845 | v.resize(dp.NCH); 1846 | mixv.resize(dp.NCH); 1847 | 1848 | #ifdef _DEBUG 1849 | wchar_t ydebug[300] = { 0}; 1850 | #endif 1851 | for (;;) 1852 | { 1853 | if (!dp.HasAudio && !dp.Framer) 1854 | Sleep((DWORD)msloop); 1855 | if (dp.MustEnd) 1856 | break; 1857 | 1858 | unsigned long long ThisDurA = 0; 1859 | unsigned long long ThisDurV = 0; 1860 | size_t FinalAudioBytes = 0; 1861 | 1862 | bool DirectAudioCopy = false; 1863 | /* if (ains.size() == 1 && ains[0]->wfx.Format.nChannels == dp.NCH && ains[0]->wfx.Format.wBitsPerSample == 16) 1864 | DirectAudioCopy = true; 1865 | */ 1866 | if (dp.HasAudio) 1867 | { 1868 | // Wait for all the events 1869 | if (!AudioEvents.empty()) 1870 | WaitForMultipleObjects((DWORD)AudioEvents.size(), AudioEvents.data(), TRUE, INFINITE); 1871 | if (DirectAudioCopy) 1872 | { 1873 | } 1874 | else 1875 | { 1876 | size_t AvailableSamples = 0; 1877 | for (auto& ain : ains) 1878 | { 1879 | auto frm = ain->AvFrames(); 1880 | if (frm < AvailableSamples || AvailableSamples == 0) 1881 | AvailableSamples = frm; 1882 | } 1883 | 1884 | // Pop All 1885 | int TotalBuffers = 0; 1886 | for (auto& ain : ains) 1887 | { 1888 | if (AvailableSamples == 0) 1889 | break; 1890 | size_t ThisAudioBytes = (size_t)(AvailableSamples * ain->wfx.Format.nChannels * ain->wfx.Format.wBitsPerSample / 8); 1891 | 1892 | ain->PopBuffer.resize(ThisAudioBytes); 1893 | ain->AudioDataX->PopX(ain->PopBuffer.data(), ThisAudioBytes); 1894 | 1895 | // Convert buffer to float 1896 | // shorts, interleaved 1897 | ain->buffer.resize(ain->chm.size()); 1898 | for (size_t i = 0; i < ain->buffer.size(); i++) 1899 | { 1900 | auto& aa = ain->buffer[i]; 1901 | auto& ch = ain->chm[i]; 1902 | if (!aa) 1903 | aa = std::make_shared(); 1904 | ain->tempf.resize(AvailableSamples); 1905 | 1906 | if (ain->wfx.Format.wBitsPerSample == 16) 1907 | { 1908 | short* s = (short*)ain->PopBuffer.data(); 1909 | for (UINT32 j = 0; j < AvailableSamples; j++) 1910 | { 1911 | short X = s[ch]; 1912 | float V = X / 32768.0f; 1913 | ain->tempf[j] = V; 1914 | 1915 | s += ain->wfx.Format.nChannels; 1916 | } 1917 | } 1918 | if (ain->wfx.Format.wBitsPerSample == 32) 1919 | { 1920 | float* s = (float*)ain->PopBuffer.data(); 1921 | for (UINT32 j = 0; j < AvailableSamples; j++) 1922 | { 1923 | float X = s[ch]; 1924 | float V = X; 1925 | ain->tempf[j] = V; 1926 | s += ain->wfx.Format.nChannels; 1927 | } 1928 | } 1929 | 1930 | aa->PushX((const char*)ain->tempf.data(), AvailableSamples * sizeof(float)); 1931 | TotalBuffers++; 1932 | } 1933 | } 1934 | 1935 | for (int i = 0; i < dp.NCH; i++) 1936 | v[i].resize(AvailableSamples); 1937 | 1938 | if (TotalBuffers == dp.NCH) 1939 | { 1940 | // Direct Copy to v 1941 | int ib = 0; 1942 | for (auto& ain : ains) 1943 | { 1944 | for (auto& b : ain->buffer) 1945 | { 1946 | if (!AvailableSamples) 1947 | continue; 1948 | v[ib].resize(AvailableSamples); 1949 | b->PopX((char*)v[ib].data(), AvailableSamples * sizeof(float)); 1950 | ib++; 1951 | } 1952 | } 1953 | } 1954 | else 1955 | { 1956 | // Mix to all 1957 | for (int ich = 0; ich < dp.NCH; ich++) 1958 | { 1959 | mixv[ich].Set(v[ich].data()); 1960 | mixv[ich].Reset(AvailableSamples); 1961 | } 1962 | 1963 | for (auto& ain : ains) 1964 | { 1965 | if (AvailableSamples == 0) 1966 | continue; 1967 | for (auto& b : ain->buffer) 1968 | { 1969 | if (!AvailableSamples) 1970 | continue; 1971 | ain->tempf.resize(AvailableSamples); 1972 | b->PopX((char*)ain->tempf.data(), AvailableSamples * sizeof(float)); 1973 | 1974 | for (int ich = 0; ich < dp.NCH; ich++) 1975 | mixv[ich].Put(ain->tempf.data(), AvailableSamples); 1976 | } 1977 | } 1978 | for (int ich = 0; ich < dp.NCH; ich++) 1979 | mixv[ich].Fin(AvailableSamples); 1980 | } 1981 | 1982 | 1983 | // v to shorts 1984 | FinalAudioBytes = AvailableSamples * dp.NCH * 2; 1985 | AudioData.resize(FinalAudioBytes); 1986 | short* ad = (short*)AudioData.data(); 1987 | for (size_t i = 0; i < AvailableSamples; i++) 1988 | { 1989 | for (int nch = 0; nch < dp.NCH; nch++) 1990 | { 1991 | float V = v[nch][i]; 1992 | short S = (short)(V * 32768.0f); 1993 | ad[i * dp.NCH + nch] = S; 1994 | } 1995 | } 1996 | 1997 | // In SR , 1000 ms 1998 | // In ThisAudioSamples, ? 1999 | unsigned long long ms = 1000 * AvailableSamples; 2000 | ms *= 10000; 2001 | ms /= dp.SR; 2002 | ThisDurA = ms; 2003 | } 2004 | 2005 | 2006 | 2007 | /* 2008 | if (DirectAudioCopy) 2009 | { 2010 | hr = ains[0].cap->GetBuffer(&ains[0].pData, &ains[0].framesAvailable, &ains[0].flags, NULL, NULL); 2011 | 2012 | ThisAudioSamples = ains[0].framesAvailable; 2013 | ThisAudioBytes = ains[0].framesAvailable * dp.NCH * 2; 2014 | AudioData.resize(ThisAudioBytes); 2015 | memcpy(AudioData.data(), ains[0].pData, ThisAudioBytes); 2016 | 2017 | ains[0].cap->ReleaseBuffer(ains[0].framesAvailable); 2018 | 2019 | // In SR , 1000 ms 2020 | // In ThisAudioSamples, ? 2021 | unsigned long long ms = 1000 * ThisAudioSamples; 2022 | ms *= 10000; 2023 | ms /= dp.SR; 2024 | ThisDurA = ms; 2025 | ThisDurV = ms; 2026 | } 2027 | 2028 | */ 2029 | } 2030 | 2031 | ThisDurV = 10 * 1000 * 1000 / dp.fps; 2032 | if (ThisDurA) 2033 | ThisDurV = ThisDurA; 2034 | 2035 | if (dp.HasAudio && ThisDurA == 0) 2036 | continue; 2037 | 2038 | if (dp.Pause) 2039 | continue; 2040 | 2041 | CComPtr lDesktopResource; 2042 | DXGI_OUTDUPL_FRAME_INFO lFrameInfo; 2043 | 2044 | // Get new frame 2045 | BYTE* pData = NULL; 2046 | HRESULT hrf = S_FALSE; 2047 | if (dp.HasVideo) 2048 | { 2049 | hr = cap.lDeskDupl->AcquireNextFrame( 2050 | 0, 2051 | &lFrameInfo, 2052 | &lDesktopResource); 2053 | if (hr == DXGI_ERROR_WAIT_TIMEOUT) 2054 | hr = S_OK; 2055 | if (hr == DXGI_ERROR_ACCESS_LOST) 2056 | { 2057 | cap.lDeskDupl = 0; 2058 | bool C = false; 2059 | for (int i = 0; i < 10; i++) 2060 | { 2061 | if (cap.Prepare(dp.nOutput)) 2062 | { 2063 | C = true; 2064 | break; 2065 | } 2066 | Sleep(250); 2067 | } 2068 | if (!C) 2069 | break; 2070 | hr = S_OK; 2071 | } 2072 | if (FAILED(hr)) 2073 | break; 2074 | 2075 | 2076 | 2077 | // take a time stamp here 2078 | // Get the current time point 2079 | if (lDesktopResource && !cap.Get(lDesktopResource, dp.Cursor, dp.rx.right && dp.rx.bottom ? &dp.rx : 0)) 2080 | break; 2081 | 2082 | if (isFirstFrame) 2083 | { 2084 | isFirstFrame = false; 2085 | rtV = 0; 2086 | last_frame_time_point = std::chrono::system_clock::now(); 2087 | } 2088 | else 2089 | { 2090 | // Time stamp of current frame. 2091 | // Calculate the dt with last frame. and set to the frame property. 2092 | auto current_frameTimePoint = std::chrono::system_clock::now(); 2093 | auto frameDuration = current_frameTimePoint - last_frame_time_point; 2094 | last_frame_time_point = current_frameTimePoint; 2095 | ThisDurV = frameDuration.count(); 2096 | rtV += ThisDurV; 2097 | } 2098 | 2099 | 2100 | std::vector hdrout; 2101 | 2102 | 2103 | if (cap.InHDR == DXGI_FORMAT_R32G32B32A32_FLOAT) 2104 | { 2105 | // This is a 16-bit 4xfloat sample 2106 | void* fld = cap.buf.data(); 2107 | auto hr2 = cap.Convert(fld, wi, he, hdrout, GUID_WICPixelFormat128bppPRGBAFloat); 2108 | if (FAILED(hr2)) 2109 | break; 2110 | VideoBufferSize = (DWORD)(hdrout.size() * 4); 2111 | } 2112 | if (cap.InHDR == DXGI_FORMAT_R16G16B16A16_FLOAT) 2113 | { 2114 | // 64-bit float 16 bit each float, convert 2115 | void* fld = cap.buf.data(); 2116 | auto hr2 = cap.Convert(fld, wi, he, hdrout, GUID_WICPixelFormat64bppRGBAHalf); 2117 | if (FAILED(hr2)) 2118 | break; 2119 | VideoBufferSize = (DWORD)(hdrout.size() * 4); 2120 | // Up down 2121 | 2122 | std::vector raw3; 2123 | raw3.resize(wi); 2124 | for (int y = 0; y < he / 2; y++) 2125 | { 2126 | DWORD* d = hdrout.data() + y * wi; 2127 | DWORD* d2 = hdrout.data() + (he - y - 1) * wi; 2128 | memcpy(raw3.data(), d, wi * sizeof(DWORD)); 2129 | memcpy(d, d2, wi * sizeof(DWORD)); 2130 | memcpy(d2, raw3.data(), wi * sizeof(DWORD)); 2131 | } 2132 | } 2133 | 2134 | 2135 | 2136 | if (!pVideoBuffer) 2137 | { 2138 | hr = MFCreateMemoryBuffer(VideoBufferSize, &pVideoBuffer); 2139 | if (FAILED(hr)) break; 2140 | } 2141 | 2142 | hr = pVideoBuffer->Lock(&pData, NULL, NULL); 2143 | if (FAILED(hr)) 2144 | break; 2145 | 2146 | if (cap.InHDR != DXGI_FORMAT_UNKNOWN) 2147 | { 2148 | memcpy(pData, hdrout.data(), VideoBufferSize); 2149 | } 2150 | else 2151 | { 2152 | memcpy(pData, cap.buf.data(), std::min(cap.buf.size(), (size_t)VideoBufferSize)); 2153 | } 2154 | if (dp.Framer) 2155 | hrf = dp.Framer(pData, std::min(cap.buf.size(), (size_t)VideoBufferSize), dp.cb); 2156 | 2157 | hr = pVideoBuffer->Unlock(); 2158 | if (FAILED(hr)) break; 2159 | 2160 | hr = pVideoBuffer->SetCurrentLength(VideoBufferSize); 2161 | if (FAILED(hr)) break; 2162 | 2163 | CComPtr pVideoSample = NULL; 2164 | 2165 | if (!pVideoSampleS) 2166 | { 2167 | hr = MFCreateSample(&pVideoSampleS); 2168 | if (FAILED(hr)) break; 2169 | } 2170 | pVideoSample = pVideoSampleS; 2171 | 2172 | 2173 | pVideoSample->RemoveAllBuffers(); 2174 | hr = pVideoSample->AddBuffer(pVideoBuffer); 2175 | if (FAILED(hr)) break; 2176 | 2177 | cap.lDeskDupl->ReleaseFrame(); 2178 | 2179 | 2180 | 2181 | 2182 | hr = pVideoSample->SetSampleTime(rtV); 2183 | if (FAILED(hr)) break; 2184 | 2185 | hr = pVideoSample->SetSampleDuration(ThisDurV); 2186 | if (FAILED(hr)) break; 2187 | 2188 | 2189 | // Video Transform MFT 2190 | if (VideoTransformMFT) 2191 | { 2192 | CComPtr s2; 2193 | ::MFTrs(0, 0, 0, VideoTransformMFT, pVideoSample, &s2); 2194 | if (s2) 2195 | pVideoSample = s2; 2196 | } 2197 | 2198 | if (pVideoSample && dp.HasVideo && !dp.Framer) 2199 | { 2200 | hr = pVideoSample->SetSampleTime(rtV); 2201 | if (FAILED(hr)) break; 2202 | 2203 | hr = pVideoSample->SetSampleDuration(ThisDurV); 2204 | if (FAILED(hr)) break; 2205 | 2206 | 2207 | #ifdef _DEBUG 2208 | swprintf_s(ydebug, 300, L"Writing video at %llu - %llu...", rtV, ThisDurV); 2209 | OutputDebugString(ydebug); 2210 | #endif 2211 | hr = pSinkWriter->WriteSample(OutVideoStreamIndex, pVideoSample); 2212 | #ifdef _DEBUG 2213 | OutputDebugString(L"OK\r\n"); 2214 | #endif 2215 | if (FAILED(hr)) break; 2216 | } 2217 | } 2218 | 2219 | // Audio 2220 | CComPtr pAudioBuffer = NULL; 2221 | if (dp.HasAudio && FinalAudioBytes) 2222 | { 2223 | hr = MFCreateMemoryBuffer((DWORD)FinalAudioBytes, &pAudioBuffer); 2224 | if (FAILED(hr)) 2225 | break; 2226 | 2227 | hr = pAudioBuffer->Lock(&pData, NULL, NULL); 2228 | if (FAILED(hr)) 2229 | break; 2230 | 2231 | memset(pData, 0, FinalAudioBytes); 2232 | memcpy(pData, AudioData.data(), FinalAudioBytes); 2233 | 2234 | hr = pAudioBuffer->Unlock(); 2235 | if (FAILED(hr)) 2236 | break; 2237 | 2238 | hr = pAudioBuffer->SetCurrentLength((DWORD)FinalAudioBytes); 2239 | if (FAILED(hr)) 2240 | break; 2241 | 2242 | CComPtr pAudioSample = NULL; 2243 | hr = MFCreateSample(&pAudioSample); 2244 | if (FAILED(hr)) 2245 | break; 2246 | 2247 | hr = pAudioSample->AddBuffer(pAudioBuffer); 2248 | if (FAILED(hr)) 2249 | break; 2250 | 2251 | hr = pAudioSample->SetSampleTime(rtA); 2252 | if (FAILED(hr)) 2253 | break; 2254 | 2255 | hr = pAudioSample->SetSampleDuration(ThisDurA); 2256 | if (FAILED(hr)) 2257 | break; 2258 | 2259 | #ifdef _DEBUG 2260 | swprintf_s(ydebug, 300, L"Writing audio at %llu - %llu...", rtA, ThisDurA); 2261 | OutputDebugString(ydebug); 2262 | #endif 2263 | hr = pSinkWriter->WriteSample(OutAudioStreamIndex, pAudioSample); 2264 | #ifdef _DEBUG 2265 | OutputDebugString(L"OK\r\n"); 2266 | #endif 2267 | if (FAILED(hr)) 2268 | break; 2269 | 2270 | rtA += ThisDurA; 2271 | } 2272 | 2273 | // rtv to ms 2274 | auto rvx = rtV / 10000; 2275 | if (dp.EndMS > 0 && rvx >= dp.EndMS) 2276 | break; 2277 | 2278 | if (dp.Framer && hrf == S_OK) 2279 | break; 2280 | } 2281 | 2282 | // Audio off 2283 | for (auto& a6 : ains) 2284 | { 2285 | if (a6->ac) 2286 | a6->ac->Stop(); 2287 | } 2288 | 2289 | if (pSinkWriter) 2290 | hr = pSinkWriter->Finalize(); 2291 | if (FAILED(hr)) 2292 | return -14; 2293 | return 0; 2294 | } 2295 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "capture.hpp" 3 | #include 4 | 5 | int wmain() 6 | { 7 | CoInitializeEx(0, COINIT_APARTMENTTHREADED); 8 | MFStartup(MF_VERSION); 9 | std::cout << "Capturing screen for 10 seconds..."; 10 | DESKTOPCAPTUREPARAMS dp; 11 | dp.Cursor = 0; 12 | // To Video 13 | dp.VIDEO_ENCODING_FORMAT = MFVideoFormat_HEVC; 14 | dp.f = L"capture.mp4"; 15 | 16 | // To Stream 17 | /* dp.f = L""; 18 | dp.PrepareAttributes = [](IMFAttributes* attrs) 19 | { 20 | attrs->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_ASF); 21 | }; 22 | dp.Streamer = [](const BYTE* b,size_t sz,void* cb) 23 | { 24 | FILE* fp = 0; 25 | _wfopen_s(&fp, L"capture.asf", L"a+b"); 26 | fwrite(b, 1, sz, fp); 27 | fclose(fp); 28 | return S_OK; 29 | }; 30 | */ 31 | 32 | // To callback frame 33 | /* 34 | dp.f = L""; 35 | dp.HasAudio = 0; 36 | dp.HasVideo = 1; 37 | dp.Framer = [](const BYTE* b, size_t sz,void* cb) 38 | { 39 | if (sz) 40 | return S_OK; 41 | return S_FALSE; 42 | }; 43 | */ 44 | 45 | dp.EndMS = 10000; 46 | DesktopCapture(dp); 47 | ShellExecute(0, L"open", L"capture.mp4", 0, 0, SW_SHOWNORMAL); 48 | std::cout << "Done.\r\n"; 49 | return 0; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /stdafx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | #pragma comment(lib,"ws2_32.lib") 4 | #pragma comment(lib,"wininet.lib") 5 | #pragma comment(lib,"d3d11.lib") 6 | #pragma comment(lib,"bcrypt.lib") 7 | #pragma comment(lib,"crypt32.lib") 8 | #pragma comment(lib,"Comctl32.lib") 9 | #pragma comment(lib,"Msacm32.lib") 10 | #pragma comment(lib,"Mfplat.lib") 11 | #pragma comment(lib,"mfreadwrite.lib") 12 | #pragma comment(lib,"mfuuid.lib") 13 | #pragma comment(lib, "strmiids.lib") 14 | #pragma comment(lib, "windowscodecs.lib") 15 | #pragma comment(lib, "propsys.lib") 16 | #pragma comment(lib, "wmcodecdspuuid.lib") 17 | #pragma comment(lib, "dxguid.lib") 18 | #pragma comment(lib, "d2d1.lib") 19 | #pragma comment(lib, "gdiplus.lib") 20 | #pragma comment(lib, "Mf.lib") 21 | #pragma comment(lib, "dxgi.lib") 22 | 23 | 24 | #pragma comment(linker,"\"/manifestdependency:type='win32' \ 25 | name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ 26 | processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 27 | -------------------------------------------------------------------------------- /stdafx.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 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 36 | #include 37 | #include 38 | #include 39 | #include 40 | #undef min 41 | #undef max 42 | 43 | 44 | --------------------------------------------------------------------------------