├── .gitattributes ├── .github └── workflows │ ├── msbuild.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── Nuget ├── ScreenRecorderLib.nuspec ├── ScreenRecorderLib.targets ├── nuget.exe └── pack.cmd ├── README.md ├── ScreenRecorderLib.sln ├── ScreenRecorderLib ├── .editorconfig ├── AssemblyInfo.cpp ├── AudioDevice.h ├── Callback.h ├── Coordinates.h ├── ManagedIStream.h ├── ManagedStreamWrapper.h ├── Options.h ├── Recorder.cpp ├── Recorder.h ├── RecordingOverlays.h ├── RecordingSources.h ├── ScreenRecorderLib.vcxproj ├── ScreenRecorderLib.vcxproj.filters ├── VideoCaptureFormat.h ├── VideoEncoders.h ├── Win32WindowEnumeration.h ├── app.ico ├── app.rc └── resource.h ├── ScreenRecorderLibNative ├── .editorconfig ├── AudioManager.cpp ├── AudioManager.h ├── CMFSinkWriterCallback.h ├── CameraCapture.cpp ├── CameraCapture.h ├── CaptureBase.cpp ├── CaptureBase.h ├── CommonTypes.h ├── CoreAudio.util.cpp ├── CoreAudio.util.h ├── DX.util.cpp ├── DX.util.h ├── DesktopDuplicationCapture.cpp ├── DesktopDuplicationCapture.h ├── DynamicWait.cpp ├── DynamicWait.h ├── Exception.h ├── GifReader.cpp ├── GifReader.h ├── HighresTimer.cpp ├── HighresTimer.h ├── ImageReader.cpp ├── ImageReader.h ├── Log.cpp ├── LogMediaType.cpp ├── LogMediaType.h ├── MF.util.cpp ├── MF.util.h ├── MouseManager.cpp ├── MouseManager.h ├── OutputManager.cpp ├── OutputManager.h ├── PixelShader.hlsl ├── RecordingManager.cpp ├── RecordingManager.h ├── Resizer.cpp ├── Resizer.h ├── ScreenCaptureBase.h ├── ScreenCaptureManager.cpp ├── ScreenCaptureManager.h ├── ScreenRecorderLibNative.vcxproj ├── ScreenRecorderLibNative.vcxproj.filters ├── SourceReaderBase.cpp ├── SourceReaderBase.h ├── TextureManager.cpp ├── TextureManager.h ├── Util.cpp ├── VertexShader.hlsl ├── VideoReader.cpp ├── VideoReader.h ├── WASAPICapture.cpp ├── WASAPICapture.h ├── WASAPINotify.cpp ├── WASAPINotify.h ├── WWMFResampler.cpp ├── WWMFResampler.h ├── WindowsGraphicsCapture.cpp ├── WindowsGraphicsCapture.h ├── WindowsGraphicsCapture.util.cpp ├── WindowsGraphicsCapture.util.h ├── cleanup.h ├── fifo_map.h ├── log.h ├── native.h ├── screengrab.cpp ├── screengrab.h └── util.h ├── TestApp ├── App.config ├── App.xaml ├── App.xaml.cs ├── BytesToKilobytesConverter.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── OverlayModel.cs ├── OverlayTemplateSelector.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── RecordingSourceTemplateSelector.cs ├── Sources │ ├── CheckableRecordableCamera.cs │ ├── CheckableRecordableDisplay.cs │ ├── CheckableRecordableImage.cs │ ├── CheckableRecordableVideo.cs │ ├── CheckableRecordableWindow.cs │ └── ICheckableRecordingSource.cs ├── TestApp.csproj └── packages.config ├── TestAppWinforms ├── App.config ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings └── TestAppWinforms.csproj ├── TestConsoleApp ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── TestConsoleApp.csproj ├── TestConsoleAppCpp ├── Demo.cpp ├── TestConsoleAppCpp.vcxproj └── TestConsoleAppCpp.vcxproj.filters ├── TestConsoleAppDotNetCore ├── Program.cs ├── Properties │ └── launchSettings.json └── TestConsoleAppDotNetCore.csproj ├── Testmedia ├── alphatest.png ├── cat.mp4 ├── cat2.mp4 ├── earth.gif ├── giftest.gif └── renault.png └── Tests ├── Properties └── AssemblyInfo.cs ├── ScreenRecorderTests.cs ├── Tests.csproj └── packages.config /.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 | -------------------------------------------------------------------------------- /.github/workflows/msbuild.yml: -------------------------------------------------------------------------------- 1 | name: MSBuild 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | SOLUTION_NAME: ScreenRecorderLib 7 | PROJECT_NAME: TestApp 8 | 9 | jobs: 10 | build: 11 | name: Build ${{ matrix.platform }} 12 | runs-on: windows-2022 13 | strategy: 14 | matrix: 15 | platform: [x64, x86] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: microsoft/setup-msbuild@v1.1 20 | - run: nuget restore ${{ env.SOLUTION_NAME }}.sln 21 | - run: msbuild /m /p:Configuration=Release /p:Platform="${{ matrix.platform }}" ${{ env.SOLUTION_NAME }}.sln 22 | - name: Upload artifact 23 | uses: actions/upload-artifact@v3 24 | with: 25 | name: ${{ env.SOLUTION_NAME }}-${{ matrix.platform }}-${{ github.sha }} 26 | path: ${{ env.PROJECT_NAME }}\bin\${{ matrix.platform }}\Release 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tags: 7 | description: 'tag' 8 | push: 9 | tags: 10 | - "v*" 11 | 12 | env: 13 | SOLUTION_NAME: ScreenRecorderLib 14 | PROJECT_NAME: ScreenRecorderLib 15 | 16 | jobs: 17 | build: 18 | name: Build ${{ matrix.platform }} 19 | runs-on: windows-2022 20 | strategy: 21 | matrix: 22 | platform: [x64, x86] 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: microsoft/setup-msbuild@v1.1 27 | - run: nuget restore ${{ env.SOLUTION_NAME }}.sln 28 | - run: msbuild /m /p:Configuration=Release /p:Platform="${{ matrix.platform }}" ${{ env.SOLUTION_NAME }}.sln 29 | 30 | - uses: actions/upload-artifact@v4 31 | with: 32 | name: ScreenRecorderLib.${{matrix.platform}} 33 | path: ${{ env.PROJECT_NAME }}\bin\${{ matrix.platform }}\Release\ScreenRecorderLib.dll 34 | 35 | release: 36 | name: Upload Release 37 | runs-on: windows-2022 38 | needs : build 39 | steps: 40 | - uses: actions/download-artifact@v4 41 | with: 42 | name: ScreenRecorderLib.x86 43 | path: ScreenRecorderLib.x86.zip 44 | - uses: actions/download-artifact@v4 45 | with: 46 | name: ScreenRecorderLib.x64 47 | path: ScreenRecorderLib.x64.zip 48 | - name: Display structure of downloaded files 49 | run: ls -R 50 | - uses: marvinpinto/action-automatic-releases@latest 51 | with: 52 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 53 | prerelease: false 54 | files: | 55 | ScreenRecorderLib.x86.zip 56 | ScreenRecorderLib.x64.zip 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sverre Skodje 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 | -------------------------------------------------------------------------------- /Nuget/ScreenRecorderLib.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ScreenRecorderLib 5 | 6.5.1 6 | Sverre Kristoffer Skodje 7 | MIT 8 | https://github.com/sskodje/ScreenRecorderLib 9 | false 10 | A .NET library for screen recording in Windows, using Microsoft Media Foundation for realtime encoding to video or image files. 11 | 12 | README.md 13 | 14 | • Fixed bug where monitors with negative coordinates (to the left of main monitor) did not record properly. 15 | 16 | Copyright © Sverre Kristoffer Skodje 2025 17 | en-GB 18 | Screen Recording Windows .NET 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Nuget/ScreenRecorderLib.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | $(MSBuildThisFileDirectory)x86\ScreenRecorderLib.dll 13 | 14 | 15 | $(MSBuildThisFileDirectory)x86\ScreenRecorderLib.dll 16 | 17 | 18 | $(MSBuildThisFileDirectory)x64\ScreenRecorderLib.dll 19 | 20 | 21 | $(MSBuildThisFileDirectory)ARM64\ScreenRecorderLib.dll 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Nuget/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Nuget/nuget.exe -------------------------------------------------------------------------------- /Nuget/pack.cmd: -------------------------------------------------------------------------------- 1 | nuget.exe pack ScreenRecorderLib.nuspec -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScreenRecorderLib 2 | A .NET library for screen recording in Windows, using native Microsoft Media Foundation for realtime encoding to h264 video or PNG images. This library requires Windows 8 or higher to function, as well as Visual C++ Redistributable [x64](https://aka.ms/vs/17/release/vc_redist.x64.exe) or [x86](https://aka.ms/vs/17/release/vc_redist.x86.exe) installed, depending on platform compiled for. This library also requires Media Foundation to work, which have to be installed from Server Manager if run on Windows Server, or from the respective "Media Feature Pack" if run on a Windows N or KN version. 3 | 4 | Available on [NuGet](https://www.nuget.org/packages/ScreenRecorderLib/). 5 | 6 | **Basic usage:** 7 | 8 | This will start a video recording to a file, using the default settings: 9 | 10 | ```csharp 11 | using ScreenRecorderLib; 12 | 13 | Recorder _rec; 14 | void CreateRecording() 15 | { 16 | string videoPath = Path.Combine(Path.GetTempPath(), "test.mp4"); 17 | _rec = Recorder.CreateRecorder(); 18 | _rec.OnRecordingComplete += Rec_OnRecordingComplete; 19 | _rec.OnRecordingFailed += Rec_OnRecordingFailed; 20 | _rec.OnStatusChanged += Rec_OnStatusChanged; 21 | //Record to a file 22 | string videoPath = Path.Combine(Path.GetTempPath(), "test.mp4"); 23 | _rec.Record(videoPath); 24 | } 25 | void EndRecording() 26 | { 27 | _rec.Stop(); 28 | } 29 | private void Rec_OnRecordingComplete(object sender, RecordingCompleteEventArgs e) 30 | { 31 | //Get the file path if recorded to a file 32 | string path = e.FilePath; 33 | } 34 | private void Rec_OnRecordingFailed(object sender, RecordingFailedEventArgs e) 35 | { 36 | string error = e.Error; 37 | } 38 | private void Rec_OnStatusChanged(object sender, RecordingStatusEventArgs e) 39 | { 40 | RecorderStatus status = e.Status; 41 | } 42 | ``` 43 | 44 | For more info and examples, see the [quickstart guide](https://github.com/sskodje/ScreenRecorderLib/wiki/Quickstart-guide-v5.x.x-and-newer), or check out the sample projects. 45 | 46 | ## Donation 47 | If this project is useful to you, please consider supporting the development with a donation :) 48 | 49 | [![paypal](https://www.paypalobjects.com/en_US/NO/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate/?business=RBE7933GCPLZU&no_recurring=0&item_name=Thanks+for+supporting+my+open+source+work%21¤cy_code=USD) 50 | -------------------------------------------------------------------------------- /ScreenRecorderLib/.editorconfig: -------------------------------------------------------------------------------- 1 | # Visual Studio generated .editorconfig file with C++ settings. 2 | root = true 3 | 4 | [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] 5 | 6 | # Visual C++ Code Style settings 7 | 8 | cpp_generate_documentation_comments = xml 9 | 10 | # Visual C++ Formatting settings 11 | 12 | cpp_indent_braces = false 13 | cpp_indent_multi_line_relative_to = innermost_parenthesis 14 | cpp_indent_within_parentheses = indent 15 | cpp_indent_preserve_within_parentheses = true 16 | cpp_indent_case_contents = true 17 | cpp_indent_case_labels = true 18 | cpp_indent_case_contents_when_block = false 19 | cpp_indent_lambda_braces_when_parameter = true 20 | cpp_indent_goto_labels = one_left 21 | cpp_indent_preprocessor = leftmost_column 22 | cpp_indent_access_specifiers = false 23 | cpp_indent_namespace_contents = true 24 | cpp_indent_preserve_comments = false 25 | cpp_new_line_before_open_brace_namespace = ignore 26 | cpp_new_line_before_open_brace_type = ignore 27 | cpp_new_line_before_open_brace_function = ignore 28 | cpp_new_line_before_open_brace_block = ignore 29 | cpp_new_line_before_open_brace_lambda = ignore 30 | cpp_new_line_scope_braces_on_separate_lines = false 31 | cpp_new_line_close_brace_same_line_empty_type = false 32 | cpp_new_line_close_brace_same_line_empty_function = false 33 | cpp_new_line_before_catch = true 34 | cpp_new_line_before_else = true 35 | cpp_new_line_before_while_in_do_while = false 36 | cpp_space_before_function_open_parenthesis = remove 37 | cpp_space_within_parameter_list_parentheses = false 38 | cpp_space_between_empty_parameter_list_parentheses = false 39 | cpp_space_after_keywords_in_control_flow_statements = true 40 | cpp_space_within_control_flow_statement_parentheses = false 41 | cpp_space_before_lambda_open_parenthesis = false 42 | cpp_space_within_cast_parentheses = false 43 | cpp_space_after_cast_close_parenthesis = false 44 | cpp_space_within_expression_parentheses = false 45 | cpp_space_before_block_open_brace = true 46 | cpp_space_between_empty_braces = false 47 | cpp_space_before_initializer_list_open_brace = false 48 | cpp_space_within_initializer_list_braces = true 49 | cpp_space_preserve_in_initializer_list = true 50 | cpp_space_before_open_square_bracket = false 51 | cpp_space_within_square_brackets = false 52 | cpp_space_before_empty_square_brackets = false 53 | cpp_space_between_empty_square_brackets = false 54 | cpp_space_group_square_brackets = true 55 | cpp_space_within_lambda_brackets = false 56 | cpp_space_between_empty_lambda_brackets = false 57 | cpp_space_before_comma = false 58 | cpp_space_after_comma = true 59 | cpp_space_remove_around_member_operators = true 60 | cpp_space_before_inheritance_colon = true 61 | cpp_space_before_constructor_colon = true 62 | cpp_space_remove_before_semicolon = true 63 | cpp_space_after_semicolon = true 64 | cpp_space_remove_around_unary_operator = true 65 | cpp_space_around_binary_operator = insert 66 | cpp_space_around_assignment_operator = insert 67 | cpp_space_pointer_reference_alignment = left 68 | cpp_space_around_ternary_operator = insert 69 | cpp_wrap_preserve_blocks = one_liners 70 | -------------------------------------------------------------------------------- /ScreenRecorderLib/AssemblyInfo.cpp: -------------------------------------------------------------------------------- 1 | using namespace System; 2 | using namespace System::Reflection; 3 | using namespace System::Runtime::CompilerServices; 4 | using namespace System::Runtime::InteropServices; 5 | using namespace System::Security::Permissions; 6 | 7 | // 8 | // General Information about an assembly is controlled through the following 9 | // set of attributes. Change these attribute values to modify the information 10 | // associated with an assembly. 11 | // 12 | [assembly:AssemblyTitleAttribute(L"ScreenRecorderLib")]; 13 | [assembly:AssemblyDescriptionAttribute(L"")]; 14 | [assembly:AssemblyConfigurationAttribute(L"")]; 15 | [assembly:AssemblyCompanyAttribute(L"Sverre Kristoffer Skodje")]; 16 | [assembly:AssemblyProductAttribute(L"ScreenRecorder")]; 17 | [assembly:AssemblyCopyrightAttribute(L"Copyright (c) 2017")]; 18 | [assembly:AssemblyTrademarkAttribute(L"")]; 19 | [assembly:AssemblyCultureAttribute(L"")]; 20 | 21 | // 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the value or you can default the Revision and Build Numbers 30 | // by using the '*' as shown below: 31 | 32 | [assembly:AssemblyVersionAttribute("1.0.*")]; 33 | 34 | [assembly:ComVisible(false)]; 35 | 36 | [assembly:CLSCompliantAttribute(true)]; -------------------------------------------------------------------------------- /ScreenRecorderLib/AudioDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | using namespace System; 3 | namespace ScreenRecorderLib { 4 | public ref class AudioDevice { 5 | public: 6 | AudioDevice() {}; 7 | AudioDevice(String^ deviceName, String^ friendlyName) { 8 | DeviceName = deviceName; 9 | FriendlyName = friendlyName; 10 | } 11 | virtual property String^ DeviceName; 12 | virtual property String^ FriendlyName; 13 | }; 14 | } -------------------------------------------------------------------------------- /ScreenRecorderLib/Callback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using namespace System; 4 | using namespace System::Collections::Generic; 5 | 6 | namespace ScreenRecorderLib { 7 | public enum class RecorderStatus { 8 | Idle, 9 | Recording, 10 | Paused, 11 | Finishing 12 | }; 13 | public ref class FrameData { 14 | public: 15 | property String^ Path; 16 | property int Delay; 17 | FrameData() {} 18 | FrameData(String^ path, int delay) { 19 | Path = path; 20 | Delay = delay; 21 | } 22 | }; 23 | 24 | public ref class FrameBitmapData { 25 | public: 26 | property int Stride; 27 | property int Width; 28 | property int Height; 29 | property IntPtr Data; 30 | property int Length; 31 | FrameBitmapData() {} 32 | FrameBitmapData(int stride, byte* data, int length, int width, int height) { 33 | Stride = stride; 34 | Data = IntPtr(data); 35 | Length = length; 36 | Width = width; 37 | Height = height; 38 | } 39 | }; 40 | 41 | public ref class RecordingStatusEventArgs :System::EventArgs { 42 | public: 43 | property RecorderStatus Status; 44 | RecordingStatusEventArgs(RecorderStatus status) { 45 | Status = status; 46 | } 47 | }; 48 | public ref class RecordingCompleteEventArgs :System::EventArgs { 49 | public: 50 | property String^ FilePath; 51 | property List^ FrameInfos; 52 | RecordingCompleteEventArgs(String^ path, List^ frameInfos) { 53 | FilePath = path; 54 | FrameInfos = frameInfos; 55 | } 56 | }; 57 | public ref class RecordingFailedEventArgs :System::EventArgs { 58 | public: 59 | property String^ Error; 60 | property String^ FilePath; 61 | RecordingFailedEventArgs(String^ error, String^ path) { 62 | Error = error; 63 | FilePath = path; 64 | } 65 | }; 66 | 67 | public ref class SnapshotSavedEventArgs :System::EventArgs { 68 | public: 69 | property String^ SnapshotPath; 70 | SnapshotSavedEventArgs(String^ path) { 71 | SnapshotPath = path; 72 | } 73 | }; 74 | public ref class FrameRecordedEventArgs :System::EventArgs { 75 | public: 76 | property int FrameNumber; 77 | property INT64 Timestamp; 78 | FrameBitmapData^ BitmapData; 79 | FrameRecordedEventArgs() {} 80 | FrameRecordedEventArgs(int frameNumber, INT64 timestamp) { 81 | FrameNumber = frameNumber; 82 | Timestamp = timestamp; 83 | } 84 | FrameRecordedEventArgs(int frameNumber, INT64 timestamp, FrameBitmapData^ bitmapData) { 85 | FrameNumber = frameNumber; 86 | Timestamp = timestamp; 87 | BitmapData = bitmapData; 88 | } 89 | }; 90 | 91 | public ref class FrameDataRecordedEventArgs :System::EventArgs { 92 | public: 93 | property FrameBitmapData^ BitmapData; 94 | FrameDataRecordedEventArgs() {} 95 | FrameDataRecordedEventArgs(FrameBitmapData^ bitmapData) 96 | { 97 | this->BitmapData = bitmapData; 98 | } 99 | FrameDataRecordedEventArgs(int stride, byte* data, int length, int width, int height) { 100 | this->BitmapData = gcnew FrameBitmapData(stride, data, length, width, height); 101 | } 102 | }; 103 | } -------------------------------------------------------------------------------- /ScreenRecorderLib/ManagedIStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "ManagedStreamWrapper.h" 6 | 7 | namespace ScreenRecorderLib { 8 | private class ManagedIStream : public IStream 9 | { 10 | public: 11 | ManagedIStream(System::IO::Stream^ stream) 12 | : refCount(1) 13 | { 14 | this->baseStream = gcnew ManagedStreamWrapper(stream); 15 | 16 | /* https://stackoverflow.com/a/39712297/2129964 17 | * We need to call all functions on the managed stream through delegates, 18 | * or we get errors if the code is running in a non-default AppDomain.*/ 19 | m_pSeekFnc = baseStream->GetSeekFunctionPointer(); 20 | m_pWriteFnc = baseStream->GetWriteFunctionPointer(); 21 | m_pReadFnc = baseStream->GetReadFunctionPointer(); 22 | m_pCanWriteFnc = baseStream->GetCanWriteFunctionPointer(); 23 | m_pCanReadFnc = baseStream->GetCanReadFunctionPointer(); 24 | m_pCanSeekFnc = baseStream->GetCanSeekFunctionPointer(); 25 | m_pSetLengthFnc = baseStream->GetSetLengthFunctionPointer(); 26 | m_pGetLengthFnc = baseStream->GetLengthFunctionPointer(); 27 | } 28 | 29 | public: 30 | virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) 31 | { 32 | if (riid == __uuidof (IStream)) { *ppvObject = this; AddRef(); return S_OK; } 33 | else { *ppvObject = NULL; return E_NOINTERFACE; } 34 | } 35 | virtual ULONG STDMETHODCALLTYPE AddRef(void) { return InterlockedIncrement(&refCount); } 36 | virtual ULONG STDMETHODCALLTYPE Release(void) 37 | { 38 | ULONG temp = InterlockedDecrement(&refCount); 39 | if (temp == 0) delete this; 40 | return temp; 41 | } 42 | 43 | public: 44 | // IStream 45 | virtual HRESULT STDMETHODCALLTYPE Read(void* pv, _In_ ULONG cb, _Out_opt_ ULONG* pcbRead)override 46 | { 47 | if (!m_pCanReadFnc()) 48 | return E_ACCESSDENIED; 49 | 50 | int bytesRead = m_pReadFnc(System::IntPtr(pv), 0, cb); 51 | if (pcbRead != nullptr) *pcbRead = bytesRead; 52 | return S_OK; 53 | } 54 | virtual HRESULT STDMETHODCALLTYPE Write(const void* pv, ULONG cb, ULONG* pcbWritten) override 55 | { 56 | if (!m_pCanWriteFnc()) 57 | return E_ACCESSDENIED; 58 | 59 | m_pWriteFnc(System::IntPtr((void*)pv), 0, cb); 60 | if (pcbWritten != nullptr) *pcbWritten = cb; 61 | return S_OK; 62 | } 63 | virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, _Out_opt_ ULARGE_INTEGER* plibNewPosition) override 64 | { 65 | if (!m_pCanSeekFnc()) 66 | return E_ACCESSDENIED; 67 | System::IO::SeekOrigin seekOrigin; 68 | 69 | switch (dwOrigin) 70 | { 71 | case 0: seekOrigin = System::IO::SeekOrigin::Begin; break; 72 | case 1: seekOrigin = System::IO::SeekOrigin::Current; break; 73 | case 2: seekOrigin = System::IO::SeekOrigin::End; break; 74 | default: throw gcnew System::ArgumentOutOfRangeException("dwOrigin"); 75 | } 76 | 77 | long long position = m_pSeekFnc(dlibMove.QuadPart, seekOrigin); 78 | if (plibNewPosition != nullptr) 79 | plibNewPosition->QuadPart = position; 80 | return S_OK; 81 | } 82 | virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize) 83 | { 84 | m_pSetLengthFnc(libNewSize.QuadPart); 85 | return S_OK; 86 | } 87 | virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, ULARGE_INTEGER* pcbWritten) override 88 | { 89 | return E_NOTIMPL; 90 | } 91 | virtual HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags) override { return E_NOTIMPL; } 92 | virtual HRESULT STDMETHODCALLTYPE Revert(void) override { return E_NOTIMPL; } 93 | virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) override { return E_NOTIMPL; } 94 | virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) override { return E_NOTIMPL; } 95 | 96 | virtual HRESULT STDMETHODCALLTYPE Stat(::STATSTG* pstatstg, DWORD grfStatFlag) override 97 | { 98 | memset(pstatstg, 0, sizeof(::STATSTG)); 99 | pstatstg->type = STGTY_STREAM; 100 | pstatstg->cbSize.QuadPart = m_pGetLengthFnc(); 101 | 102 | if (m_pCanReadFnc() && m_pCanWriteFnc()) 103 | pstatstg->grfMode |= STGM_READWRITE; 104 | else if (m_pCanReadFnc()) 105 | pstatstg->grfMode |= STGM_READ; 106 | else if (m_pCanWriteFnc()) 107 | pstatstg->grfMode |= STGM_WRITE; 108 | else throw gcnew System::IO::IOException(); 109 | return S_OK; 110 | } 111 | virtual HRESULT STDMETHODCALLTYPE Clone(IStream** ppstm) override { return E_NOTIMPL; } 112 | 113 | private: 114 | ULONG refCount; 115 | gcroot baseStream; 116 | 117 | SeekFnc* m_pSeekFnc; 118 | ReadFnc* m_pReadFnc; 119 | WriteFnc* m_pWriteFnc; 120 | CanReadFnc* m_pCanReadFnc; 121 | CanWriteFnc* m_pCanWriteFnc; 122 | CanSeekFnc* m_pCanSeekFnc; 123 | SetLengthFnc* m_pSetLengthFnc; 124 | GetLengthFnc* m_pGetLengthFnc; 125 | }; 126 | } 127 | -------------------------------------------------------------------------------- /ScreenRecorderLib/ManagedStreamWrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | typedef bool(__stdcall CanReadFnc)(); 3 | typedef bool(__stdcall CanWriteFnc)(); 4 | typedef bool(__stdcall CanSeekFnc)(); 5 | typedef long long(__stdcall SeekFnc)(long long offset, System::IO::SeekOrigin origin); 6 | typedef int(__stdcall ReadFnc)(System::IntPtr dest, int offset, int count); 7 | typedef void(__stdcall WriteFnc)(System::IntPtr, int offset, int count); 8 | typedef void(__stdcall SetLengthFnc)(long long value); 9 | typedef long long(__stdcall GetLengthFnc)(); 10 | 11 | namespace ScreenRecorderLib { 12 | public ref class ManagedStreamWrapper 13 | { 14 | public: 15 | ManagedStreamWrapper(System::IO::Stream^ stream); 16 | 17 | 18 | SeekFnc* GetSeekFunctionPointer() 19 | { 20 | return (SeekFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_SeekDelegate).ToPointer()); 21 | } 22 | 23 | WriteFnc* GetWriteFunctionPointer() 24 | { 25 | return (WriteFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_WriteDelegate).ToPointer()); 26 | } 27 | 28 | ReadFnc* GetReadFunctionPointer() 29 | { 30 | return (ReadFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_ReadDelegate).ToPointer()); 31 | } 32 | 33 | SetLengthFnc* GetSetLengthFunctionPointer() 34 | { 35 | return (SetLengthFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_SetLengthDelegate).ToPointer()); 36 | } 37 | 38 | GetLengthFnc* GetLengthFunctionPointer() 39 | { 40 | return (GetLengthFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_GetLengthDelegate).ToPointer()); 41 | } 42 | CanReadFnc* GetCanReadFunctionPointer() 43 | { 44 | return (CanReadFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_CanReadDelegate).ToPointer()); 45 | } 46 | CanWriteFnc* GetCanWriteFunctionPointer() 47 | { 48 | return (CanWriteFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_CanWriteDelegate).ToPointer()); 49 | } 50 | CanSeekFnc* GetCanSeekFunctionPointer() 51 | { 52 | return (CanSeekFnc*)(System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(m_CanSeekDelegate).ToPointer()); 53 | } 54 | 55 | 56 | private: 57 | System::IO::Stream^ m_Stream; 58 | delegate long long LongDelegate(); 59 | delegate long long SeekDelegate(long long offset, System::IO::SeekOrigin origin); 60 | delegate int ReadDelegate(System::IntPtr dest, int offset, int count); 61 | delegate void WriteDelegate(System::IntPtr source, int offset, int count); 62 | delegate void SetLengthDelegate(long long value); 63 | delegate int IntDelegate(); 64 | delegate bool BoolDelegate(); 65 | delegate cli::array^ BufferDelegate(); 66 | 67 | LongDelegate^ m_GetLengthDelegate; 68 | BoolDelegate^ m_CanWriteDelegate; 69 | BoolDelegate^ m_CanReadDelegate; 70 | BoolDelegate^ m_CanSeekDelegate; 71 | SeekDelegate^ m_SeekDelegate; 72 | ReadDelegate^ m_ReadDelegate; 73 | WriteDelegate^ m_WriteDelegate; 74 | SetLengthDelegate^ m_SetLengthDelegate; 75 | 76 | long long GetLength() { 77 | return m_Stream->Length; 78 | } 79 | bool GetCanWrite() { 80 | return m_Stream->CanWrite; 81 | } 82 | bool GetCanRead() { 83 | return m_Stream->CanRead; 84 | } 85 | bool GetCanSeek() { 86 | return m_Stream->CanSeek; 87 | } 88 | long long Seek(long long offset, System::IO::SeekOrigin origin) { 89 | return m_Stream->Seek(offset, origin); 90 | }; 91 | int Read(System::IntPtr dest, int offset, int count) { 92 | auto tempBytes = gcnew cli::array(count); 93 | int bytesRead = m_Stream->Read(tempBytes, offset, count); 94 | System::Runtime::InteropServices::Marshal::Copy(tempBytes, 0, dest, count); 95 | return bytesRead; 96 | }; 97 | void Write(System::IntPtr source, int offset, int count) { 98 | auto tempBytes = gcnew cli::array(count); 99 | System::Runtime::InteropServices::Marshal::Copy(source, tempBytes, 0, count); 100 | return m_Stream->Write(tempBytes, offset, count); 101 | }; 102 | void SetLength(long long value) { 103 | return m_Stream->SetLength(value); 104 | }; 105 | }; 106 | 107 | 108 | ManagedStreamWrapper::ManagedStreamWrapper(System::IO::Stream^ stream) 109 | { 110 | m_Stream = stream; 111 | m_GetLengthDelegate = gcnew LongDelegate(this, &ManagedStreamWrapper::GetLength); 112 | m_CanWriteDelegate = gcnew BoolDelegate(this, &ManagedStreamWrapper::GetCanWrite); 113 | m_CanReadDelegate = gcnew BoolDelegate(this, &ManagedStreamWrapper::GetCanRead); 114 | m_CanSeekDelegate = gcnew BoolDelegate(this, &ManagedStreamWrapper::GetCanSeek); 115 | m_SeekDelegate = gcnew SeekDelegate(this, &ManagedStreamWrapper::Seek); 116 | m_ReadDelegate = gcnew ReadDelegate(this, &ManagedStreamWrapper::Read); 117 | m_WriteDelegate = gcnew WriteDelegate(this, &ManagedStreamWrapper::Write); 118 | m_SetLengthDelegate = gcnew SetLengthDelegate(this, &ManagedStreamWrapper::SetLength); 119 | } 120 | } -------------------------------------------------------------------------------- /ScreenRecorderLib/ScreenRecorderLib.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;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 | Header Files 56 | 57 | 58 | 59 | 60 | Source Files 61 | 62 | 63 | Source Files 64 | 65 | 66 | 67 | 68 | Resource Files 69 | 70 | 71 | 72 | 73 | Resource Files 74 | 75 | 76 | -------------------------------------------------------------------------------- /ScreenRecorderLib/VideoCaptureFormat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace ScreenRecorderLib { 3 | public enum class VideoNominalRange { 4 | MFNominalRange_Unknown = 0, 5 | MFNominalRange_Normal = 1, 6 | MFNominalRange_Wide = 2, 7 | MFNominalRange_0_255 = 1, 8 | MFNominalRange_16_235 = 2, 9 | MFNominalRange_48_208 = 3, 10 | MFNominalRange_64_127 = 4 11 | }; 12 | 13 | public enum class VideoInterlaceMode { 14 | MFVideoInterlace_Unknown = 0, 15 | MFVideoInterlace_Progressive = 2, 16 | MFVideoInterlace_FieldInterleavedUpperFirst = 3, 17 | MFVideoInterlace_FieldInterleavedLowerFirst = 4, 18 | MFVideoInterlace_FieldSingleUpper = 5, 19 | MFVideoInterlace_FieldSingleLower = 6, 20 | MFVideoInterlace_MixedInterlaceOrProgressive = 7 21 | }; 22 | 23 | public enum VideoTransferMatrix { 24 | MFVideoTransferMatrix_Unknown = 0, 25 | MFVideoTransferMatrix_BT709 = 1, 26 | MFVideoTransferMatrix_BT601 = 2, 27 | MFVideoTransferMatrix_SMPTE240M = 3, 28 | MFVideoTransferMatrix_BT2020_10 = 4, 29 | MFVideoTransferMatrix_BT2020_12 = 5 30 | }; 31 | 32 | public ref class VideoCaptureFormat { 33 | public: 34 | VideoCaptureFormat() { 35 | 36 | }; 37 | property int Index; 38 | property Guid VideoFormat; 39 | property String^ VideoFormatName; 40 | property double Framerate; 41 | property ScreenSize^ FrameSize; 42 | property int AverageBitrate; 43 | property VideoTransferMatrix YUVMatrix; 44 | property VideoInterlaceMode InterlaceMode; 45 | property VideoNominalRange NominalRange; 46 | 47 | virtual String^ ToString() override { 48 | return String::Format("{0} - [{1},{2}] {3}fps", VideoFormatName, FrameSize->Width, FrameSize->Height, Framerate); 49 | } 50 | private: 51 | 52 | }; 53 | } -------------------------------------------------------------------------------- /ScreenRecorderLib/VideoEncoders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | using namespace System; 3 | using namespace System::Runtime::InteropServices; 4 | 5 | namespace ScreenRecorderLib { 6 | public enum class H264BitrateControlMode { 7 | ///Constant bitrate. Faster encoding than VBR, but produces larger files with consistent size. This setting might not work on software encoding. 8 | CBR = 0, 9 | ///Default is unconstrained variable bitrate. Overall bitrate will average towards the Bitrate property, but can fluctuate greatly over and under it. 10 | UnconstrainedVBR = 2, 11 | ///Quality-based VBR encoding. The encoder selects the bit rate to match a specified quality level. Set Quality level in VideoEncoderOptions from 1-100. Default is 70. 12 | Quality = 3 13 | }; 14 | public enum class H265BitrateControlMode { 15 | ///Constant bitrate. Faster encoding than VBR, but produces larger files with consistent size. This setting might not work on software encoding. 16 | CBR = 0, 17 | ///Quality-based VBR encoding. The encoder selects the bit rate to match a specified quality level. Set Quality level in VideoEncoderOptions from 1-100. Default is 70. 18 | Quality = 3 19 | }; 20 | 21 | public enum class H264Profile { 22 | Baseline = 66, 23 | Main = 77, 24 | High = 100 25 | }; 26 | public enum class H265Profile { 27 | Main = 1, 28 | }; 29 | 30 | public enum class VideoEncoderFormat { 31 | ///H.264/AVC encoder. 32 | H264, 33 | ///H.265/HEVC encoder. 34 | H265 35 | }; 36 | 37 | public interface class IVideoEncoder { 38 | public: 39 | property VideoEncoderFormat EncodingFormat { 40 | VideoEncoderFormat get(); 41 | } 42 | UInt32 GetEncoderProfile(); 43 | UInt32 GetBitrateMode(); 44 | }; 45 | 46 | /// 47 | /// Encode video with H264 encoder. 48 | /// 49 | public ref class H264VideoEncoder : public IVideoEncoder, INotifyPropertyChanged { 50 | private: 51 | H264Profile _encoderProfile; 52 | H264BitrateControlMode _bitrateMode; 53 | public: 54 | H264VideoEncoder() { 55 | EncoderProfile = H264Profile::High; 56 | BitrateMode = H264BitrateControlMode::Quality; 57 | } 58 | virtual event PropertyChangedEventHandler^ PropertyChanged; 59 | void OnPropertyChanged(String^ info) 60 | { 61 | PropertyChanged(this, gcnew PropertyChangedEventArgs(info)); 62 | } 63 | virtual property VideoEncoderFormat EncodingFormat { 64 | VideoEncoderFormat get() { 65 | return VideoEncoderFormat::H264; 66 | } 67 | } 68 | /// 69 | ///The capabilities the h264 video encoder. 70 | ///Lesser profiles may increase playback compatibility and use less resources with older decoders and hardware at the cost of quality. 71 | /// 72 | property H264Profile EncoderProfile { 73 | H264Profile get() { 74 | return _encoderProfile; 75 | } 76 | void set(H264Profile value) { 77 | _encoderProfile = value; 78 | OnPropertyChanged("EncoderProfile"); 79 | } 80 | } 81 | /// 82 | ///The bitrate control mode of the video encoder. Default is Quality. 83 | /// 84 | property H264BitrateControlMode BitrateMode { 85 | H264BitrateControlMode get() { 86 | return _bitrateMode; 87 | } 88 | void set(H264BitrateControlMode value) { 89 | _bitrateMode = value; 90 | OnPropertyChanged("BitrateMode"); 91 | } 92 | } 93 | 94 | virtual UInt32 GetEncoderProfile() { return (UInt32)EncoderProfile; } 95 | virtual UInt32 GetBitrateMode() { return (UInt32)BitrateMode; } 96 | }; 97 | 98 | /// 99 | /// Encode video with H265/HEVC encoder. 100 | /// 101 | public ref class H265VideoEncoder : public IVideoEncoder, INotifyPropertyChanged{ 102 | private: 103 | H265Profile _encoderProfile; 104 | H265BitrateControlMode _bitrateMode; 105 | public: 106 | H265VideoEncoder() { 107 | EncoderProfile = H265Profile::Main; 108 | BitrateMode = H265BitrateControlMode::Quality; 109 | } 110 | virtual event PropertyChangedEventHandler^ PropertyChanged; 111 | void OnPropertyChanged(String^ info) 112 | { 113 | PropertyChanged(this, gcnew PropertyChangedEventArgs(info)); 114 | } 115 | virtual property VideoEncoderFormat EncodingFormat { 116 | VideoEncoderFormat get() { 117 | return VideoEncoderFormat::H265; 118 | } 119 | } 120 | /// 121 | ///The capabilities the h265 video encoder. At the moment only Main is supported 122 | /// 123 | property H265Profile EncoderProfile { 124 | H265Profile get() { 125 | return _encoderProfile; 126 | } 127 | void set(H265Profile value) { 128 | _encoderProfile = value; 129 | OnPropertyChanged("EncoderProfile"); 130 | } 131 | } 132 | /// 133 | ///The bitrate control mode of the video encoder. Default is Quality. 134 | /// 135 | property H265BitrateControlMode BitrateMode { 136 | H265BitrateControlMode get() { 137 | return _bitrateMode; 138 | } 139 | void set(H265BitrateControlMode value) { 140 | _bitrateMode = value; 141 | OnPropertyChanged("BitrateMode"); 142 | } 143 | } 144 | 145 | virtual UInt32 GetEncoderProfile() { return (UInt32)EncoderProfile; } 146 | virtual UInt32 GetBitrateMode() { return (UInt32)BitrateMode; } 147 | }; 148 | } -------------------------------------------------------------------------------- /ScreenRecorderLib/Win32WindowEnumeration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | struct Window 7 | { 8 | public: 9 | Window(nullptr_t) {} 10 | Window(HWND hwnd, std::wstring const& title, std::wstring& className) 11 | { 12 | m_hwnd = hwnd; 13 | m_title = title; 14 | m_className = className; 15 | } 16 | 17 | HWND Hwnd() const noexcept { return m_hwnd; } 18 | std::wstring Title() const noexcept { return m_title; } 19 | std::wstring ClassName() const noexcept { return m_className; } 20 | 21 | private: 22 | HWND m_hwnd; 23 | std::wstring m_title; 24 | std::wstring m_className; 25 | }; 26 | 27 | std::wstring GetClassName(HWND hwnd) 28 | { 29 | std::array className; 30 | 31 | ::GetClassName(hwnd, className.data(), (int)className.size()); 32 | 33 | std::wstring title(className.data()); 34 | return title; 35 | } 36 | 37 | std::wstring GetWindowText(HWND hwnd) 38 | { 39 | std::array windowText; 40 | 41 | ::GetWindowText(hwnd, windowText.data(), (int)windowText.size()); 42 | 43 | std::wstring title(windowText.data()); 44 | return title; 45 | } 46 | 47 | bool IsRecordableWindow(Window const& window) 48 | { 49 | HWND hwnd = window.Hwnd(); 50 | HWND shellWindow = GetShellWindow(); 51 | 52 | auto title = window.Title(); 53 | auto className = window.ClassName(); 54 | 55 | if (hwnd == shellWindow) 56 | { 57 | return false; 58 | } 59 | 60 | if (title.length() == 0) 61 | { 62 | return false; 63 | } 64 | 65 | if (!IsWindowVisible(hwnd)) 66 | { 67 | return false; 68 | } 69 | 70 | if (GetAncestor(hwnd, GA_ROOT) != hwnd) 71 | { 72 | return false; 73 | } 74 | 75 | LONG style = GetWindowLong(hwnd, GWL_STYLE); 76 | if (!((style & WS_DISABLED) != WS_DISABLED)) 77 | { 78 | return false; 79 | } 80 | 81 | DWORD cloaked = FALSE; 82 | HRESULT hrTemp = DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked)); 83 | if (SUCCEEDED(hrTemp) && 84 | cloaked == DWM_CLOAKED_SHELL) 85 | { 86 | return false; 87 | } 88 | 89 | return true; 90 | } 91 | 92 | BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) 93 | { 94 | auto class_name = GetClassName(hwnd); 95 | auto title = GetWindowText(hwnd); 96 | 97 | auto window = Window(hwnd, title, class_name); 98 | 99 | if (IsRecordableWindow(window)) 100 | { 101 | std::vector& windows = *reinterpret_cast*>(lParam); 102 | windows.push_back(window); 103 | } 104 | return TRUE; 105 | } 106 | 107 | const std::vector EnumerateWindows() 108 | { 109 | std::vector windows; 110 | EnumWindows(EnumWindowsProc, reinterpret_cast(&windows)); 111 | 112 | return windows; 113 | } -------------------------------------------------------------------------------- /ScreenRecorderLib/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/ScreenRecorderLib/app.ico -------------------------------------------------------------------------------- /ScreenRecorderLib/app.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/ScreenRecorderLib/app.rc -------------------------------------------------------------------------------- /ScreenRecorderLib/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by app.rc 4 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/.editorconfig: -------------------------------------------------------------------------------- 1 | # Visual Studio generated .editorconfig file with C++ settings. 2 | root = true 3 | 4 | [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] 5 | 6 | # Visual C++ Code Style settings 7 | 8 | cpp_generate_documentation_comments = xml 9 | 10 | # Visual C++ Formatting settings 11 | 12 | cpp_indent_braces = false 13 | cpp_indent_multi_line_relative_to = innermost_parenthesis 14 | cpp_indent_within_parentheses = indent 15 | cpp_indent_preserve_within_parentheses = true 16 | cpp_indent_case_contents = true 17 | cpp_indent_case_labels = true 18 | cpp_indent_case_contents_when_block = false 19 | cpp_indent_lambda_braces_when_parameter = true 20 | cpp_indent_goto_labels = one_left 21 | cpp_indent_preprocessor = leftmost_column 22 | cpp_indent_access_specifiers = false 23 | cpp_indent_namespace_contents = true 24 | cpp_indent_preserve_comments = false 25 | cpp_new_line_before_open_brace_namespace = ignore 26 | cpp_new_line_before_open_brace_type = ignore 27 | cpp_new_line_before_open_brace_function = ignore 28 | cpp_new_line_before_open_brace_block = ignore 29 | cpp_new_line_before_open_brace_lambda = ignore 30 | cpp_new_line_scope_braces_on_separate_lines = false 31 | cpp_new_line_close_brace_same_line_empty_type = false 32 | cpp_new_line_close_brace_same_line_empty_function = false 33 | cpp_new_line_before_catch = true 34 | cpp_new_line_before_else = true 35 | cpp_new_line_before_while_in_do_while = false 36 | cpp_space_before_function_open_parenthesis = remove 37 | cpp_space_within_parameter_list_parentheses = false 38 | cpp_space_between_empty_parameter_list_parentheses = false 39 | cpp_space_after_keywords_in_control_flow_statements = true 40 | cpp_space_within_control_flow_statement_parentheses = false 41 | cpp_space_before_lambda_open_parenthesis = false 42 | cpp_space_within_cast_parentheses = false 43 | cpp_space_after_cast_close_parenthesis = false 44 | cpp_space_within_expression_parentheses = false 45 | cpp_space_before_block_open_brace = true 46 | cpp_space_between_empty_braces = false 47 | cpp_space_before_initializer_list_open_brace = false 48 | cpp_space_within_initializer_list_braces = true 49 | cpp_space_preserve_in_initializer_list = true 50 | cpp_space_before_open_square_bracket = false 51 | cpp_space_within_square_brackets = false 52 | cpp_space_before_empty_square_brackets = false 53 | cpp_space_between_empty_square_brackets = false 54 | cpp_space_group_square_brackets = true 55 | cpp_space_within_lambda_brackets = false 56 | cpp_space_between_empty_lambda_brackets = false 57 | cpp_space_before_comma = false 58 | cpp_space_after_comma = true 59 | cpp_space_remove_around_member_operators = true 60 | cpp_space_before_inheritance_colon = true 61 | cpp_space_before_constructor_colon = true 62 | cpp_space_remove_before_semicolon = true 63 | cpp_space_after_semicolon = true 64 | cpp_space_remove_around_unary_operator = true 65 | cpp_space_around_binary_operator = insert 66 | cpp_space_around_assignment_operator = insert 67 | cpp_space_pointer_reference_alignment = right 68 | cpp_space_around_ternary_operator = insert 69 | cpp_wrap_preserve_blocks = one_liners 70 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/AudioManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "WASAPICapture.h" 4 | #include "CommonTypes.h" 5 | class AudioManager 6 | { 7 | public: 8 | AudioManager(); 9 | ~AudioManager(); 10 | HRESULT Initialize(_In_ std::shared_ptr &audioOptions); 11 | void ClearRecordedBytes(); 12 | HRESULT StartCapture(); 13 | HRESULT StopCapture(); 14 | std::vector GrabAudioFrame(_In_ UINT64 durationHundredNanos); 15 | private: 16 | CRITICAL_SECTION m_CriticalSection; 17 | std::shared_ptr m_AudioOptions; 18 | //Output loopback capture, e.g. system audio. 19 | std::unique_ptr m_AudioOutputCapture; 20 | //Audio input, i.e. microphone 21 | std::unique_ptr m_AudioInputCapture; 22 | 23 | bool m_IsCaptureEnabled; 24 | 25 | AUDIO_OPTIONS *GetAudioOptions() { return m_AudioOptions.get(); } 26 | 27 | HRESULT StartDeviceCapture(WASAPICapture *pCapture, std::wstring deviceId, EDataFlow flow); 28 | HRESULT StopDeviceCapture(WASAPICapture *pCapture); 29 | HRESULT ConfigureAudioCapture(); 30 | 31 | std::thread m_OptionsListenerThread; 32 | HANDLE m_OptionsListenerStopEvent = nullptr; 33 | void OnOptionsChanged(); 34 | HRESULT StopOptionsChangeListenerThread(); 35 | 36 | std::vector MixAudio(_In_ std::vector const &first, _In_ std::vector const &second, _In_ float firstVolume, _In_ float secondVolume); 37 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/CMFSinkWriterCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | class CMFSinkWriterCallback : public IMFSinkWriterCallback { 7 | 8 | public: 9 | CMFSinkWriterCallback(_In_ HANDLE hFinalizeEvent, _In_opt_ HANDLE hMarkerEvent) : 10 | m_nRefCount(1), 11 | m_hFinalizeEvent(hFinalizeEvent), 12 | m_hMarkerEvent(hMarkerEvent) {} 13 | virtual ~CMFSinkWriterCallback() 14 | { 15 | } 16 | // IMFSinkWriterCallback methods 17 | STDMETHODIMP OnFinalize(HRESULT hrStatus) { 18 | LOG_DEBUG(L"CMFSinkWriterCallback::OnFinalize"); 19 | if (m_hFinalizeEvent != NULL) { 20 | SetEvent(m_hFinalizeEvent); 21 | } 22 | return hrStatus; 23 | } 24 | 25 | STDMETHODIMP OnMarker(DWORD dwStreamIndex, LPVOID pvContext) { 26 | LOG_DEBUG(L"CMFSinkWriterCallback::OnMarker"); 27 | if (m_hMarkerEvent != NULL) { 28 | SetEvent(m_hMarkerEvent); 29 | } 30 | return S_OK; 31 | } 32 | 33 | // IUnknown methods 34 | STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { 35 | static const QITAB qit[] = { 36 | QITABENT(CMFSinkWriterCallback, IMFSinkWriterCallback), 37 | {0} 38 | }; 39 | return QISearch(this, qit, riid, ppv); 40 | } 41 | 42 | STDMETHODIMP_(ULONG) AddRef() { 43 | return InterlockedIncrement(&m_nRefCount); 44 | } 45 | 46 | STDMETHODIMP_(ULONG) Release() { 47 | ULONG refCount = InterlockedDecrement(&m_nRefCount); 48 | if (refCount == 0) { 49 | delete this; 50 | } 51 | return refCount; 52 | } 53 | 54 | private: 55 | volatile long m_nRefCount; 56 | HANDLE m_hFinalizeEvent; 57 | HANDLE m_hMarkerEvent; 58 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/CameraCapture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SourceReaderBase.h" 3 | #include "MF.util.h" 4 | 5 | class CameraCapture : public SourceReaderBase 6 | { 7 | public: 8 | CameraCapture(); 9 | virtual ~CameraCapture(); 10 | virtual inline std::wstring Name() override { return L"CameraCapture"; }; 11 | protected: 12 | virtual HRESULT InitializeSourceReader( 13 | _In_ std::wstring source, 14 | _In_ std::optional sourceFormatIndex, 15 | _Out_ long *pStreamIndex, 16 | _Outptr_ IMFSourceReader **ppSourceReader, 17 | _Outptr_ IMFMediaType **ppInputMediaType, 18 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 19 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform) override; 20 | 21 | virtual HRESULT InitializeSourceReader( 22 | _In_ IStream *pSourceStream, 23 | _In_ std::optional sourceFormatIndex, 24 | _Out_ long *pStreamIndex, 25 | _Outptr_ IMFSourceReader **ppSourceReader, 26 | _Outptr_ IMFMediaType **ppInputMediaType, 27 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 28 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform) override; 29 | 30 | private: 31 | HRESULT InitializeMediaSource( 32 | _In_ CComPtr pDevice, 33 | _In_ std::optional sourceFormatIndex, 34 | _Out_ long *pStreamIndex, 35 | _Outptr_ IMFSourceReader **ppSourceReader, 36 | _Outptr_ IMFMediaType **ppInputMediaType, 37 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 38 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform 39 | ); 40 | HRESULT SetDeviceFormat(_In_ CComPtr pDevice, _In_ DWORD dwFormatIndex); 41 | std::wstring m_DeviceName; 42 | std::wstring m_DeviceSymbolicLink; 43 | }; 44 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/CaptureBase.cpp: -------------------------------------------------------------------------------- 1 | #include "CaptureBase.h" 2 | #include "cleanup.h" 3 | 4 | 5 | CaptureBase::CaptureBase() : 6 | m_LastGrabTimeStamp{}, 7 | m_Device(nullptr), 8 | m_DeviceContext(nullptr), 9 | m_RecordingSource(nullptr), 10 | m_TextureManager(nullptr), 11 | m_FrameDataCallbackTexture(nullptr) 12 | { 13 | RtlZeroMemory(&m_FrameDataCallbackTextureDesc, sizeof(m_FrameDataCallbackTextureDesc)); 14 | } 15 | CaptureBase::~CaptureBase() 16 | { 17 | SafeRelease(&m_Device); 18 | SafeRelease(&m_DeviceContext); 19 | SafeRelease(&m_FrameDataCallbackTexture); 20 | } 21 | SIZE CaptureBase::GetContentOffset(_In_ ContentAnchor anchor, _In_ RECT parentRect, _In_ RECT contentRect) 22 | { 23 | int leftMargin; 24 | int topMargin; 25 | switch (anchor) 26 | { 27 | case ContentAnchor::TopLeft: 28 | default: { 29 | leftMargin = 0; 30 | topMargin = 0; 31 | break; 32 | } 33 | case ContentAnchor::TopRight: { 34 | leftMargin = (int)max(0, round(((double)RectWidth(parentRect) - (double)RectWidth(contentRect)))); 35 | topMargin = 0; 36 | break; 37 | } 38 | case ContentAnchor::Center: { 39 | leftMargin = (int)max(0, round(((double)RectWidth(parentRect) - (double)RectWidth(contentRect))) / 2); 40 | topMargin = (int)max(0, round(((double)RectHeight(parentRect) - (double)RectHeight(contentRect))) / 2); 41 | break; 42 | } 43 | case ContentAnchor::BottomLeft: { 44 | leftMargin = 0; 45 | topMargin = (int)max(0, round(((double)RectHeight(parentRect) - (double)RectHeight(contentRect)))); 46 | break; 47 | } 48 | case ContentAnchor::BottomRight: { 49 | leftMargin = (int)max(0, round(((double)RectWidth(parentRect) - (double)RectWidth(contentRect)))); 50 | topMargin = (int)max(0, round(((double)RectHeight(parentRect) - (double)RectHeight(contentRect)))); 51 | break; 52 | } 53 | } 54 | return SIZE{ leftMargin,topMargin }; 55 | } 56 | 57 | HRESULT CaptureBase::SendBitmapCallback(_In_ ID3D11Texture2D *pTexture) { 58 | HRESULT hr = S_FALSE; 59 | CComPtr< ID3D11Texture2D> pProcessedTexture = nullptr; 60 | if (m_RecordingSource->IsVideoFramePreviewEnabled.value_or(false) && m_RecordingSource->HasRegisteredCallbacks()) { 61 | D3D11_TEXTURE2D_DESC textureDesc; 62 | pTexture->GetDesc(&textureDesc); 63 | if (m_RecordingSource->VideoFramePreviewSize.has_value()) { 64 | long cx = m_RecordingSource->VideoFramePreviewSize.value().cx; 65 | long cy = m_RecordingSource->VideoFramePreviewSize.value().cy; 66 | if (cx > 0 && cy == 0) { 67 | cy = static_cast(round((static_cast(textureDesc.Height) / static_cast(textureDesc.Width)) * cx)); 68 | } 69 | else if (cx == 0 && cy > 0) { 70 | cx = static_cast(round((static_cast(textureDesc.Width) / static_cast(textureDesc.Height)) * cy)); 71 | } 72 | ID3D11Texture2D *pResizedTexture; 73 | RETURN_ON_BAD_HR(hr = m_TextureManager->ResizeTexture(pTexture, SIZE{ cx,cy }, TextureStretchMode::Uniform, &pResizedTexture)); 74 | pProcessedTexture.Attach(pResizedTexture); 75 | pResizedTexture->GetDesc(&textureDesc); 76 | } 77 | else { 78 | pProcessedTexture.Attach(pTexture); 79 | pTexture->AddRef(); 80 | } 81 | int width = textureDesc.Width; 82 | int height = textureDesc.Height; 83 | 84 | if (m_FrameDataCallbackTextureDesc.Width != width || m_FrameDataCallbackTextureDesc.Height != height) { 85 | SafeRelease(&m_FrameDataCallbackTexture); 86 | textureDesc.Usage = D3D11_USAGE_STAGING; 87 | textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; 88 | textureDesc.MiscFlags = 0; 89 | textureDesc.BindFlags = 0; 90 | RETURN_ON_BAD_HR(m_Device->CreateTexture2D(&textureDesc, nullptr, &m_FrameDataCallbackTexture)); 91 | m_FrameDataCallbackTextureDesc = textureDesc; 92 | } 93 | 94 | m_DeviceContext->CopyResource(m_FrameDataCallbackTexture, pProcessedTexture); 95 | D3D11_MAPPED_SUBRESOURCE map; 96 | m_DeviceContext->Map(m_FrameDataCallbackTexture, 0, D3D11_MAP_READ, 0, &map); 97 | 98 | int bytesPerPixel = map.RowPitch / width; 99 | int len = map.DepthPitch; 100 | int stride = map.RowPitch; 101 | BYTE *data = static_cast(map.pData); 102 | 103 | m_RecordingSource->NotifyNewFrameDataCallbacks(abs(stride), data, len, width, height); 104 | m_DeviceContext->Unmap(m_FrameDataCallbackTexture, 0); 105 | } 106 | return hr; 107 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/CaptureBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | #include "TextureManager.h" 4 | class CaptureBase abstract 5 | { 6 | public: 7 | CaptureBase(); 8 | virtual ~CaptureBase(); 9 | virtual HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice) abstract; 10 | virtual HRESULT StartCapture(_In_ RECORDING_SOURCE_BASE &source) abstract; 11 | virtual HRESULT AcquireNextFrame(_In_ DWORD timeoutMillis, _Outptr_opt_ ID3D11Texture2D **ppFrame) abstract; 12 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ ID3D11Texture2D *pTexture = nullptr) abstract; 13 | virtual HRESULT GetNativeSize(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ SIZE *nativeMediaSize) abstract; 14 | virtual HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ RECT frameCoordinates, _In_ int offsetX, _In_ int offsetY) abstract; 15 | virtual std::wstring Name() abstract; 16 | virtual HRESULT SendBitmapCallback(_In_ ID3D11Texture2D *pTexture); 17 | /// 18 | /// Calculate the offset used to position the content withing the parent frame based on the given anchor. 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | virtual SIZE GetContentOffset(_In_ ContentAnchor anchor, _In_ RECT parentRect, _In_ RECT contentRect); 25 | protected: 26 | 27 | ID3D11Device *m_Device; 28 | ID3D11DeviceContext *m_DeviceContext; 29 | std::unique_ptr m_TextureManager; 30 | RECORDING_SOURCE_BASE *m_RecordingSource; 31 | LARGE_INTEGER m_LastGrabTimeStamp; 32 | 33 | private: 34 | ID3D11Texture2D *m_FrameDataCallbackTexture; 35 | D3D11_TEXTURE2D_DESC m_FrameDataCallbackTextureDesc; 36 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/CoreAudio.util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | #include 4 | #include 5 | 6 | HRESULT GetDefaultAudioDevice(_In_ EDataFlow flow, _Outptr_ IMMDevice **ppMMDevice); 7 | HRESULT ListAudioDevices(_In_ EDataFlow flow, _Out_ std::map *devices); 8 | HRESULT GetActiveAudioDevice(_In_ LPCWSTR szDeviceId, _In_ EDataFlow flow, _Outptr_ IMMDevice **ppMMDevice); 9 | HRESULT GetAudioDevice(_In_ LPCWSTR szDeviceId, _Outptr_ IMMDevice **ppMMDevice); 10 | 11 | HRESULT GetAudioDeviceFlow(_In_ IMMDevice *pMMDevice, _Out_ EDataFlow *pFlow); 12 | HRESULT GetAudioDeviceFriendlyName(_In_ IMMDevice *pDevice, _Out_ std::wstring *deviceName); 13 | HRESULT GetAudioDeviceFriendlyName(_In_ LPCWSTR pwstrId, _Out_ std::wstring *deviceName); -------------------------------------------------------------------------------- /ScreenRecorderLibNative/DX.util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | 4 | HRESULT InitializeDx(_In_opt_ IDXGIAdapter *adapter, _Out_ DX_RESOURCES *Data); 5 | HRESULT GetAdapterForDevice(_In_ ID3D11Device *pDevice, _Outptr_ IDXGIAdapter **ppAdapter); 6 | HRESULT GetAdapterForDeviceName(_In_ std::wstring deviceName, _Outptr_opt_result_maybenull_ IDXGIAdapter **ppAdapter); 7 | HRESULT GetOutputForDeviceName(_In_ std::wstring deviceName, _Outptr_opt_result_maybenull_ IDXGIOutput **ppOutput); 8 | 9 | /// 10 | /// Returns the main display output of the system. 11 | /// 12 | /// The IDXGIOutput corresponding to the main output 13 | HRESULT GetMainOutput(_Outptr_result_maybenull_ IDXGIOutput **ppOutput); 14 | 15 | /// 16 | /// Create the recording output rectangles for each source in the input, based on their coordinates and any custom size, crop or position rules. 17 | /// 18 | /// The recording sources to process 19 | /// A vector of pairs, containing the source and corresponding rectangle. 20 | HRESULT GetOutputRectsForRecordingSources(_In_ const std::vector &sources, _Out_ std::vector> *outputs); 21 | 22 | /// 23 | /// Initialize shaders for drawing to screen 24 | /// 25 | /// 26 | /// 27 | HRESULT InitShaders(_In_ ID3D11Device *pDevice, _Outptr_ ID3D11PixelShader **ppPixelShader, _Outptr_ ID3D11VertexShader **ppVertexShader, _Outptr_ ID3D11InputLayout **ppInputLayout); 28 | 29 | /// 30 | /// Creates a list of all display adapters on the system. 31 | /// 32 | /// A vector containing IDXGIOutput interfaces 33 | void EnumOutputs(_Out_ std::vector *pOutputs); 34 | 35 | /// 36 | /// Creates a union of the input rectangles. The union is the smallest rectangle that contains all source rectangles. 37 | /// 38 | /// A vector of RECT coordinates to combine. 39 | /// The combined union of input rectangles. 40 | /// Optional vector containing offsets corresponding to any gaps between input rects. 41 | void GetCombinedRects(_In_ std::vector inputs, _Out_ RECT *pOutRect, _Out_opt_ std::vector *pOffsets); 42 | 43 | /// 44 | /// Gets the display name of the monitor corresponding to the monitor handle. 45 | /// 46 | /// A handle to a monitor 47 | /// The display name of the monitor 48 | std::wstring GetMonitorName(HMONITOR monitor); 49 | 50 | std::vector EnumDisplayAdapters(); 51 | void CleanDx(_Inout_ DX_RESOURCES *Data); 52 | void SetViewPort(_In_ ID3D11DeviceContext *deviceContext, _In_ float Width, _In_ float Height, _In_ float left = 0, _In_ float top = 0); 53 | void SetDebugName(_In_ ID3D11DeviceChild *child, _In_ const std::string &name); 54 | _Ret_maybenull_ HANDLE GetSharedHandle(_In_ ID3D11Texture2D *pSurface); -------------------------------------------------------------------------------- /ScreenRecorderLibNative/DesktopDuplicationCapture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | #include "ScreenCaptureBase.h" 4 | #include 5 | #include "MouseManager.h" 6 | #include "TextureManager.h" 7 | 8 | class DesktopDuplicationCapture : public CaptureBase 9 | { 10 | public: 11 | DesktopDuplicationCapture(); 12 | virtual ~DesktopDuplicationCapture(); 13 | virtual HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice) override; 14 | virtual HRESULT AcquireNextFrame(_In_ DWORD timeoutMillis, _Outptr_opt_ ID3D11Texture2D **ppFrame) override; 15 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ ID3D11Texture2D *pTexture = nullptr) override; 16 | virtual HRESULT StartCapture(_In_ RECORDING_SOURCE_BASE &recordingSource) override; 17 | virtual HRESULT GetNativeSize(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ SIZE *nativeMediaSize) override; 18 | virtual HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ RECT frameCoordinates, _In_ int offsetX, _In_ int offsetY) override; 19 | virtual inline std::wstring Name() override { return L"DesktopDuplicationCapture"; }; 20 | private: 21 | static const int NUMVERTICES = 6; 22 | // methods 23 | HRESULT InitializeDesktopDuplication(std::wstring deviceName); 24 | HRESULT GetNextFrame(_In_ DWORD timeoutMillis, _Inout_ DUPL_FRAME_DATA *pData); 25 | HRESULT CopyDirty(_In_ ID3D11Texture2D *pSrcSurface, _Inout_ ID3D11Texture2D *pSharedSurf, _In_reads_(dirtyCount) RECT *pDirtyBuffer, UINT dirtyCount, INT offsetX, INT offsetY, _In_ RECT desktopCoordinates, _In_ DXGI_MODE_ROTATION rotation); 26 | HRESULT CopyMove(_Inout_ ID3D11Texture2D *pSharedSurf, _In_reads_(moveCount) DXGI_OUTDUPL_MOVE_RECT *pMoveBuffer, UINT moveCount, INT offsetX, INT offsetY, _In_ RECT desktopCoordinates, _In_ DXGI_MODE_ROTATION rotation); 27 | void SetDirtyVert(_Out_writes_(NUMVERTICES) VERTEX *pVertices, _In_ RECT *pDirty, INT offsetX, INT offsetY, _In_ RECT desktopCoordinates, _In_ DXGI_MODE_ROTATION rotation, _In_ D3D11_TEXTURE2D_DESC *pFullDesc, _In_ D3D11_TEXTURE2D_DESC *pThisDesc); 28 | void SetMoveRect(_Out_ RECT *SrcRect, _Out_ RECT *pDestRect, _In_ DXGI_MODE_ROTATION rotation, _In_ DXGI_OUTDUPL_MOVE_RECT *pMoveRect, INT texWidth, INT texHeight); 29 | HRESULT SendBitmapCallback(_In_ ID3D11Texture2D *pSharedSurf, _In_ SIZE frameOffset, _In_ SIZE contentOffset, _In_ RECT destinationRect); 30 | 31 | std::unique_ptr m_MouseManager; 32 | DUPL_FRAME_DATA m_CurrentData; 33 | 34 | ID3D11Texture2D *m_BitmapDataCallbackTexture; 35 | D3D11_TEXTURE2D_DESC m_BitmapDataCallbackTextureDesc; 36 | PTR_INFO m_BitmapDataCallbackPtrInfo; 37 | 38 | int m_CursorOffsetX; 39 | int m_CursorOffsetY; 40 | float m_CursorScaleX; 41 | float m_CursorScaleY; 42 | 43 | bool m_IsInitialized; 44 | LARGE_INTEGER m_LastSampleUpdatedTimeStamp; 45 | 46 | bool m_OutputIsOnSeparateGraphicsAdapter; 47 | IDXGIOutputDuplication *m_DeskDupl; 48 | ID3D11Texture2D *m_MoveSurf; 49 | _Field_size_bytes_(m_MetaDataSize) BYTE *m_MetaDataBuffer; 50 | UINT m_MetaDataSize; 51 | DXGI_OUTPUT_DESC m_OutputDesc; 52 | ID3D11VertexShader *m_VertexShader; 53 | ID3D11PixelShader *m_PixelShader; 54 | ID3D11InputLayout *m_InputLayout; 55 | ID3D11RenderTargetView *m_RTV; 56 | ID3D11SamplerState *m_SamplerLinear; 57 | BYTE *m_DirtyVertexBufferAlloc; 58 | UINT m_DirtyVertexBufferAllocSize; 59 | }; 60 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/DynamicWait.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicWait.h" 2 | #include 3 | #include 4 | 5 | DynamicWait::DynamicWait() : m_CurrentWaitBandIdx(0), m_WaitCountInCurrentBand(0) 6 | { 7 | m_QPCValid = QueryPerformanceFrequency(&m_QPCFrequency); 8 | m_LastWakeUpTime.QuadPart = 0L; 9 | m_WaitBands = { 10 | {25, 10}, 11 | {250, 20}, 12 | {1000, WAIT_BAND_STOP} 13 | }; // Never move past this band 14 | m_CancelEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); 15 | } 16 | 17 | DynamicWait::~DynamicWait() 18 | { 19 | SetEvent(m_CancelEvent); 20 | CloseHandle(m_CancelEvent); 21 | } 22 | 23 | void DynamicWait::Wait() 24 | { 25 | LARGE_INTEGER CurrentQPC = { 0 }; 26 | 27 | // Is this wait being called with the period that we consider it to be part of the same wait sequence 28 | QueryPerformanceCounter(&CurrentQPC); 29 | if (m_QPCValid && (CurrentQPC.QuadPart <= (m_LastWakeUpTime.QuadPart + (m_QPCFrequency.QuadPart * m_WaitSequenceTimeInSeconds)))) 30 | { 31 | // We are still in the same wait sequence, lets check if we should move to the next band 32 | if ((m_WaitBands[m_CurrentWaitBandIdx].WaitCount != WAIT_BAND_STOP) && (m_WaitCountInCurrentBand > m_WaitBands[m_CurrentWaitBandIdx].WaitCount)) 33 | { 34 | m_CurrentWaitBandIdx++; 35 | m_WaitCountInCurrentBand = 0; 36 | } 37 | } 38 | else 39 | { 40 | // Either we could not get the current time or we are starting a new wait sequence 41 | m_WaitCountInCurrentBand = 0; 42 | m_CurrentWaitBandIdx = 0; 43 | } 44 | 45 | // Sleep for the required period of time 46 | WaitForSingleObject(m_CancelEvent, m_WaitBands[m_CurrentWaitBandIdx].WaitTime); 47 | 48 | // Record the time we woke up so we can detect wait sequences 49 | QueryPerformanceCounter(&m_LastWakeUpTime); 50 | m_WaitCountInCurrentBand++; 51 | } 52 | 53 | void DynamicWait::Cancel() { 54 | SetEvent(m_CancelEvent); 55 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/DynamicWait.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #define WAIT_BAND_STOP 0 6 | 7 | struct WAIT_BAND 8 | { 9 | UINT WaitTime; 10 | UINT WaitCount; 11 | }; 12 | 13 | class DynamicWait 14 | { 15 | public: 16 | DynamicWait(); 17 | ~DynamicWait(); 18 | 19 | inline void SetWaitBands(std::vector bands) { 20 | m_WaitBands = bands; 21 | } 22 | void Wait(); 23 | void Cancel(); 24 | 25 | private: 26 | 27 | std::vector m_WaitBands; 28 | HANDLE m_CancelEvent; 29 | // Period in seconds that a new wait call is considered part of the same wait sequence 30 | static const UINT m_WaitSequenceTimeInSeconds = 2; 31 | 32 | UINT m_CurrentWaitBandIdx; 33 | UINT m_WaitCountInCurrentBand; 34 | LARGE_INTEGER m_QPCFrequency; 35 | LARGE_INTEGER m_LastWakeUpTime; 36 | BOOL m_QPCValid; 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/Exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | class SE_Exception : public std::exception{ 5 | private: 6 | const unsigned int nSE; 7 | public: 8 | SE_Exception() noexcept : SE_Exception{ 0 } {} 9 | SE_Exception(unsigned int n) noexcept : nSE{ n } {} 10 | unsigned int getSeNumber() const noexcept { return nSE; } 11 | }; 12 | 13 | class AccessViolationException : public std::exception { 14 | 15 | }; 16 | 17 | inline void ExceptionTranslator(unsigned int u, EXCEPTION_POINTERS *pExp) { 18 | switch (u) { 19 | case EXCEPTION_ACCESS_VIOLATION: 20 | throw AccessViolationException(); 21 | break; 22 | default: 23 | throw SE_Exception(u); 24 | break; 25 | 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/GifReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "CommonTypes.h" 7 | #include "DX.util.h" 8 | #include "HighresTimer.h" 9 | #include "CaptureBase.h" 10 | #include "TextureManager.h" 11 | 12 | class GifReader : public CaptureBase 13 | { 14 | public: 15 | 16 | GifReader(); 17 | ~GifReader(); 18 | virtual HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice) override; 19 | virtual HRESULT StartCapture(_In_ RECORDING_SOURCE_BASE &source) override; 20 | virtual HRESULT GetNativeSize(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ SIZE *nativeMediaSize) override; 21 | virtual HRESULT StopCapture(); 22 | virtual HRESULT AcquireNextFrame(_In_ DWORD timeoutMillis, _Outptr_opt_ ID3D11Texture2D **ppFrame) override; 23 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ ID3D11Texture2D *pTexture = nullptr) override; 24 | inline virtual HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ RECT frameCoordinates, _In_ int offsetX, _In_ int offsetY) override { 25 | return S_FALSE; 26 | } 27 | virtual inline std::wstring Name() override { return L"GifReader"; }; 28 | private: 29 | enum DISPOSAL_METHODS 30 | { 31 | DM_UNDEFINED = 0, 32 | DM_NONE = 1, 33 | DM_BACKGROUND = 2, 34 | DM_PREVIOUS = 3 35 | }; 36 | HRESULT InitializeDecoder(_In_ std::wstring source); 37 | HRESULT InitializeDecoder(_In_ IStream *pSourceStream); 38 | HRESULT CreateDeviceResources(); 39 | 40 | 41 | HRESULT GetRawFrame(UINT uFrameIndex); 42 | HRESULT GetGlobalMetadata(); 43 | HRESULT GetBackgroundColor(IWICMetadataQueryReader *pMetadataQueryReader); 44 | 45 | HRESULT StartCaptureLoop(); 46 | HRESULT ComposeNextFrame(); 47 | HRESULT DisposeCurrentFrame(); 48 | HRESULT OverlayNextFrame(); 49 | 50 | HRESULT SaveComposedFrame(); 51 | HRESULT RestoreSavedFrame(); 52 | HRESULT ClearCurrentFrameArea(); 53 | 54 | void ResetGifState(); 55 | 56 | BOOL IsLastFrame() 57 | { 58 | return (m_uNextFrameIndex == 0); 59 | } 60 | 61 | BOOL EndOfAnimation() 62 | { 63 | return m_fHasLoop && IsLastFrame() && m_uLoopNumber == m_uTotalLoopCount + 1; 64 | } 65 | 66 | private: 67 | HANDLE m_NewFrameEvent; 68 | CRITICAL_SECTION m_CriticalSection; 69 | Concurrency::task m_CaptureTask = concurrency::task_from_result(); 70 | LARGE_INTEGER m_LastSampleReceivedTimeStamp; 71 | std::unique_ptr m_FramerateTimer; 72 | 73 | ID3D11Texture2D *m_RenderTexture; 74 | ID2D1Factory *m_pD2DFactory; 75 | ID2D1BitmapRenderTarget *m_pFrameComposeRT; 76 | ID2D1RenderTarget *m_RenderTarget; 77 | ID2D1Bitmap *m_pRawFrame; 78 | ID2D1Bitmap *m_pSavedFrame; // The temporary bitmap used for disposal 3 method 79 | D2D1_COLOR_F m_backgroundColor; 80 | 81 | IWICImagingFactory *m_pIWICFactory; 82 | IWICBitmapDecoder *m_pDecoder; 83 | 84 | UINT m_uNextFrameIndex; 85 | UINT m_uTotalLoopCount; // The number of loops for which the animation will be played 86 | UINT m_uLoopNumber; // The current animation loop number (e.g. 1 when the animation is first played) 87 | BOOL m_fHasLoop; // Whether the gif has a loop 88 | UINT m_cFrames; 89 | UINT m_uFrameDisposal; 90 | UINT m_uFrameDelay; 91 | UINT m_cxGifImage; 92 | UINT m_cyGifImage; 93 | UINT m_cxGifImagePixel; // Width of the displayed image in pixel calculated using pixel aspect ratio 94 | UINT m_cyGifImagePixel; // Height of the displayed image in pixel calculated using pixel aspect ratio 95 | D2D1_RECT_F m_framePosition; 96 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/HighresTimer.cpp: -------------------------------------------------------------------------------- 1 | #include "HighresTimer.h" 2 | #include "Log.h" 3 | 4 | using namespace std::chrono; 5 | HighresTimer::HighresTimer() : 6 | m_TimerResolution(0), 7 | m_TickEvent(nullptr), 8 | m_StopEvent(nullptr), 9 | m_EventArray{}, 10 | m_LastTick{}, 11 | m_Interval(0), 12 | m_TickCount(0), 13 | m_IsActive(false) 14 | { 15 | TIMECAPS tc; 16 | UINT targetResolutionMs = 1; 17 | if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) == TIMERR_NOERROR) 18 | { 19 | m_TimerResolution = min(max(tc.wPeriodMin, targetResolutionMs), tc.wPeriodMax); 20 | timeBeginPeriod(m_TimerResolution); 21 | } 22 | //CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is an undocumented flag introduced in Windows 10 1803. 23 | //CreateWaitableTimerEx returns NULL if not available. 24 | m_TickEvent = CreateWaitableTimerEx(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); 25 | if (m_TickEvent == NULL) { 26 | m_TickEvent = CreateWaitableTimer(NULL, false, NULL); 27 | } 28 | m_StopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); 29 | HANDLE eventArray[2]{ m_StopEvent ,m_TickEvent }; 30 | 31 | m_EventArray[0] = m_StopEvent; 32 | m_EventArray[1] = m_TickEvent; 33 | } 34 | 35 | HighresTimer::~HighresTimer() 36 | { 37 | if (m_TimerResolution > 0) { 38 | timeEndPeriod(m_TimerResolution); 39 | } 40 | CloseHandle(m_TickEvent); 41 | CloseHandle(m_StopEvent); 42 | } 43 | 44 | HRESULT HighresTimer::StartRecurringTimer(INT64 msInterval) 45 | { 46 | if (NULL == m_TickEvent) { 47 | DWORD dwErr = GetLastError(); 48 | LOG_ERROR(L"CreateWaitableTimer failed: last error = %u", dwErr); 49 | return HRESULT_FROM_WIN32(dwErr); 50 | } 51 | 52 | m_Interval = msInterval; 53 | ResetEvent(m_TickEvent); 54 | ResetEvent(m_StopEvent); 55 | 56 | m_LastTick = std::chrono::steady_clock::now(); 57 | 58 | LARGE_INTEGER liFirstFire; 59 | liFirstFire.QuadPart = -0; // negative means relative time 60 | BOOL bOK = SetWaitableTimer( 61 | m_TickEvent, 62 | &liFirstFire, 63 | static_cast(msInterval), 64 | NULL, NULL, FALSE 65 | ); 66 | if (!bOK) { 67 | DWORD dwErr = GetLastError(); 68 | LOG_ERROR(L"SetWaitableTimer failed on OnReadSample: last error = %u", dwErr); 69 | return HRESULT_FROM_WIN32(dwErr); 70 | } 71 | m_IsActive = true; 72 | return S_OK; 73 | } 74 | 75 | HRESULT HighresTimer::StopTimer(bool waitForCompletion) 76 | { 77 | SetEvent(m_StopEvent); 78 | if (waitForCompletion && m_IsActive) { 79 | if (WaitForSingleObject(m_TickEvent, INFINITE) != WAIT_OBJECT_0) { 80 | LOG_ERROR("Failed to wait for timer tick"); 81 | return E_FAIL; 82 | } 83 | } 84 | m_IsActive = false; 85 | CancelWaitableTimer(m_TickEvent); 86 | LOG_DEBUG("Stopped HighresTimer"); 87 | return S_OK; 88 | } 89 | 90 | HRESULT HighresTimer::WaitForNextTick() 91 | { 92 | //WAIT_OBJECT_0 means the first handle in the array, the stop event, signaled the stop, so exit. 93 | if (WaitForMultipleObjects(ARRAYSIZE(m_EventArray), m_EventArray, FALSE, INFINITE) == WAIT_OBJECT_0) { 94 | LOG_TRACE("HighresTimer was canceled"); 95 | return E_FAIL; 96 | } 97 | 98 | m_LastTick = std::chrono::steady_clock::now(); 99 | m_TickCount++; 100 | return S_OK; 101 | } 102 | 103 | HRESULT HighresTimer::WaitFor(INT64 interval100Nanos) 104 | { 105 | LARGE_INTEGER liFirstFire; 106 | liFirstFire.QuadPart = -interval100Nanos; // negative means relative time 107 | BOOL bOK = SetWaitableTimer( 108 | m_TickEvent, 109 | &liFirstFire, 110 | 0, 111 | NULL, NULL, FALSE 112 | ); 113 | if (!bOK) { 114 | LOG_ERROR(L"HighresTimer::WaitFor failed setting timer"); 115 | return E_FAIL; 116 | } 117 | m_IsActive = true; 118 | //WAIT_OBJECT_0 means the first handle in the array, the stop event, signaled the stop, so exit. 119 | if (WaitForMultipleObjects(ARRAYSIZE(m_EventArray), m_EventArray, FALSE, INFINITE) == WAIT_OBJECT_0) { 120 | LOG_TRACE("HighresTimer was canceled"); 121 | m_IsActive = false; 122 | return E_FAIL; 123 | } 124 | m_IsActive = false; 125 | m_LastTick = std::chrono::steady_clock::now(); 126 | m_TickCount++; 127 | return S_OK; 128 | } 129 | 130 | double HighresTimer::GetMillisUntilNextTick() 131 | { 132 | if (m_TickCount == 0) 133 | return 0; 134 | return max(0, (m_Interval - std::chrono::duration(std::chrono::steady_clock::now() - m_LastTick).count())); 135 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/HighresTimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | #include "Util.h" 4 | class HighresTimer 5 | { 6 | public: 7 | HighresTimer(); 8 | ~HighresTimer(); 9 | inline HANDLE GetTickEvent() { return m_TickEvent; } 10 | HRESULT StartRecurringTimer(INT64 msInterval); 11 | HRESULT StopTimer(bool waitForCompletion); 12 | HRESULT WaitForNextTick(); 13 | HRESULT WaitFor(INT64 interval100Nanos); 14 | double GetMillisUntilNextTick(); 15 | inline INT64 GetTickCount() { return m_TickCount; } 16 | private: 17 | bool m_IsActive; 18 | INT64 m_TickCount; 19 | std::chrono::steady_clock::time_point m_LastTick; 20 | INT64 m_Interval; 21 | UINT m_TimerResolution; 22 | HANDLE m_TickEvent; 23 | HANDLE m_StopEvent; 24 | HANDLE m_EventArray[2]; 25 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/ImageReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CaptureBase.h" 3 | #include "CommonTypes.h" 4 | #include 5 | #include "TextureManager.h" 6 | #include 7 | 8 | class ImageReader :public CaptureBase 9 | { 10 | public: 11 | ImageReader(); 12 | ~ImageReader(); 13 | virtual HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice) override; 14 | virtual HRESULT StartCapture(_In_ RECORDING_SOURCE_BASE &source) override; 15 | virtual HRESULT GetNativeSize(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ SIZE *nativeMediaSize) override; 16 | virtual inline HRESULT StopCapture() { return S_OK; }; 17 | virtual HRESULT AcquireNextFrame(_In_ DWORD timeoutMillis, _Outptr_opt_result_maybenull_ ID3D11Texture2D **ppFrame) override; 18 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ ID3D11Texture2D *pTexture = nullptr) override; 19 | inline virtual HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ RECT frameCoordinates, _In_ int offsetX, _In_ int offsetY) override { 20 | return S_FALSE; 21 | } 22 | virtual inline std::wstring Name() override { return L"ImageReader"; }; 23 | 24 | private: 25 | HRESULT InitializeDecoder(_In_ std::wstring source); 26 | HRESULT InitializeDecoder(_In_ IStream *pSourceStream); 27 | HRESULT InitializeDecoder(_In_ IWICBitmapSource *pBitmap); 28 | 29 | CComPtr m_Texture; 30 | SIZE m_NativeSize; 31 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/Log.cpp: -------------------------------------------------------------------------------- 1 | #include "Log.h" 2 | #include 3 | 4 | static std::mutex g_Mtx{}; 5 | 6 | void _log(PCWSTR format, ...) 7 | { 8 | const std::lock_guard lock(g_Mtx); 9 | wchar_t buffer[LOG_BUFFER_SIZE]; 10 | va_list args; 11 | va_start(args, format); 12 | 13 | vswprintf_s(buffer, LOG_BUFFER_SIZE, format, args); 14 | if (!logFilePath.empty()) { 15 | 16 | std::wofstream logFile(logFilePath, std::ios_base::app | std::ios_base::out); 17 | if (logFile.is_open()) 18 | { 19 | logFile << buffer; 20 | logFile.close(); 21 | } 22 | else { 23 | OutputDebugStringW(L"Error opening log file for write"); 24 | } 25 | } 26 | else { 27 | OutputDebugStringW(buffer); 28 | } 29 | va_end(args); 30 | } 31 | 32 | std::wstring GetTimestamp() { 33 | // get a precise timestamp as a string 34 | const auto now = std::chrono::system_clock::now(); 35 | const auto nowAsTimeT = std::chrono::system_clock::to_time_t(now); 36 | tm localTime; 37 | localtime_s(&localTime, &nowAsTimeT); 38 | const auto nowMs = std::chrono::duration_cast(now.time_since_epoch()) % 1000; 39 | std::wstringstream nowSs; 40 | nowSs 41 | << std::put_time(&localTime, L"%Y-%m-%d %H:%M:%S") 42 | << '.' << std::setfill(L'0') << std::setw(3) << nowMs.count(); 43 | return nowSs.str(); 44 | } 45 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/LogMediaType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef IF_EQUAL_RETURN 10 | #define IF_EQUAL_RETURN(param, val) if(val == param) return L#val 11 | #endif 12 | 13 | LPCWSTR GetGUIDNameConst(const GUID &guid); 14 | HRESULT GetGUIDName(const GUID &guid, WCHAR **ppwsz); 15 | 16 | HRESULT LogAttributeValueByIndex(IMFAttributes *pAttr, DWORD index); 17 | HRESULT SpecialCaseAttributeValue(GUID guid, const PROPVARIANT &var); 18 | 19 | HRESULT LogMediaType(IMFMediaType *pType); 20 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/MF.util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | HRESULT FindDecoderEx(const GUID &subtype, BOOL bAudio, IMFActivate **ppDecoder); 12 | HRESULT FindVideoDecoder(GUID *inputSubtype, GUID *outputSubtype, BOOL bAllowAsync, BOOL bAllowHardware, BOOL bAllowTranscode, IMFActivate **ppDecoder); 13 | HRESULT CreateIMFTransform(_In_ DWORD streamIndex, _In_ IMFMediaType *pInputMediaType, _In_ IMFMediaType *pOutputMediaType, _Outptr_ IMFTransform **ppVideoConverter); 14 | HRESULT EnumVideoCaptureDevices(_Out_ std::vector *pDevices); 15 | HRESULT CopyMediaType(_In_ IMFMediaType *pType, _Outptr_ IMFMediaType **ppType); 16 | HRESULT EnumerateCaptureFormats(_In_ IMFMediaSource *pSource, _Out_ std::vector *pMediaTypes); 17 | HRESULT GetFrameRate(_In_ IMFMediaType *pMediaType, _Out_ double *pFramerate); 18 | HRESULT GetFrameSize(_In_ IMFAttributes *pMediaType, _Out_ SIZE *pFrameSize); 19 | HRESULT GetDefaultStride(_In_ IMFMediaType *pType, _Out_ LONG *plStride); 20 | bool IsVideoInfo2(_In_ IMFMediaType *pType); -------------------------------------------------------------------------------- /ScreenRecorderLibNative/MouseManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "CommonTypes.h" 7 | #include "TextureManager.h" 8 | #include 9 | LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam); 10 | 11 | class MouseManager 12 | { 13 | public: 14 | MouseManager(); 15 | ~MouseManager(); 16 | 17 | HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice, _In_ std::shared_ptr &pOptions); 18 | void InitializeMouseClickDetection(); 19 | void StopMouseClickDetection(); 20 | HRESULT ProcessMousePointer(_In_ ID3D11Texture2D *pFrame, _In_ PTR_INFO *pPtrInfo); 21 | HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ bool getShapeBuffer, _In_ DXGI_OUTDUPL_FRAME_INFO *pFrameInfo, _In_ RECT screenRect, _In_ IDXGIOutputDuplication *pDeskDupl, _In_ int offsetX, _In_ int offsetY); 22 | HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ bool getShapeBuffer, _In_ int offsetX, _In_ int offsetY); 23 | void CleanDX(); 24 | protected: 25 | HRESULT DrawMousePointer(_In_ PTR_INFO *pPtrInfo, _Inout_ ID3D11Texture2D *pBbgTexture, DXGI_MODE_ROTATION rotation); 26 | HRESULT DrawMouseClick(_In_ PTR_INFO *pPtrInfo, _In_ ID3D11Texture2D *pBgTexture, std::string colorStr, float radius, DXGI_MODE_ROTATION rotation); 27 | private: 28 | static const UINT TRANSPARENT_WHITE = 0x00FFFFFF; 29 | static const UINT TRANSPARENT_BLACK = 0x00000000; 30 | static const UINT OPAQUE_WHITE = 0xFFFFFFFF; 31 | static const UINT OPAQUE_BLACK = 0xFF000000; 32 | static const int NUMVERTICES = 6; 33 | static const int BPP = 4; 34 | 35 | ATL::CComPtr m_SamplerLinear; 36 | ATL::CComPtr m_BlendState; 37 | ATL::CComPtr m_VertexShader; 38 | ATL::CComPtr m_PixelShader; 39 | ATL::CComPtr m_InputLayout; 40 | ATL::CComPtr m_D2DFactory; 41 | 42 | std::unique_ptr m_TextureManager; 43 | std::shared_ptr m_MouseOptions; 44 | ID3D11DeviceContext *m_DeviceContext; 45 | ID3D11Device *m_Device; 46 | 47 | CRITICAL_SECTION m_CriticalSection; 48 | bool m_IsCapturingMouseClicks; 49 | std::chrono::steady_clock::time_point m_LastMouseDrawTimeStamp; 50 | HANDLE m_MouseHookThread; 51 | DWORD m_MouseHookThreadId; 52 | std::vector _InitBuffer; 53 | std::vector _DesktopBuffer; 54 | HANDLE m_StopPollingTaskEvent; 55 | std::thread m_MousePollingThread; 56 | long ParseColorString(std::string color); 57 | void GetPointerPosition(_In_ PTR_INFO *pPtrInfo, DXGI_MODE_ROTATION rotation, int desktopWidth, int desktopHeight, _Out_ INT *PtrLeft, _Out_ INT *PtrTop); 58 | HRESULT ProcessMonoMask(_In_ ID3D11Texture2D *pBgTexture, _In_ DXGI_MODE_ROTATION rotation, _In_ bool IsMono, _Inout_ PTR_INFO *PtrInfo, _Out_ INT *PtrWidth, _Out_ INT *PtrHeight, _Out_ INT *PtrLeft, _Out_ INT *PtrTop, _Outptr_result_bytebuffer_(*PtrHeight **PtrWidth *BPP) BYTE **pInitBuffer); 59 | 60 | HRESULT InitMouseClickTexture(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice); 61 | HRESULT ResizeShapeBuffer(_Inout_ PTR_INFO *pPtrInfo, _In_ int bufferSize); 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/OutputManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | #include "Log.h" 4 | #include "Util.h" 5 | #include "MF.util.h" 6 | #include "CMFSinkWriterCallback.h" 7 | #include "cleanup.h" 8 | #include "fifo_map.h" 9 | #include 10 | 11 | struct FrameWriteModel 12 | { 13 | //Timestamp of the start of the frame, in 100 nanosecond units. 14 | INT64 StartPos; 15 | //Duration of the frame, in 100 nanosecond units. 16 | INT64 Duration; 17 | //The audio sample bytes for this frame. 18 | std::vector Audio; 19 | //The frame texture. 20 | CComPtr Frame; 21 | }; 22 | 23 | class OutputManager 24 | { 25 | public: 26 | OutputManager(); 27 | ~OutputManager(); 28 | HRESULT Initialize( 29 | _In_ ID3D11DeviceContext *pDeviceContext, 30 | _In_ ID3D11Device *pDevice, 31 | _In_ std::shared_ptr &pEncoderOptions, 32 | _In_ std::shared_ptr pAudioOptions, 33 | _In_ std::shared_ptr pSnapshotOptions, 34 | _In_ std::shared_ptr pOutputOptions); 35 | 36 | HRESULT BeginRecording(_In_ std::wstring outputPath, _In_ SIZE videoOutputFrameSizer); 37 | HRESULT BeginRecording(_In_ IStream *pStream, _In_ SIZE videoOutputFrameSize); 38 | HRESULT FinalizeRecording(); 39 | HRESULT RenderFrame(_In_ FrameWriteModel &model); 40 | HRESULT WriteFrameToImage(_In_ ID3D11Texture2D *pAcquiredDesktopImage, _In_ std::wstring filePath); 41 | HRESULT WriteFrameToImage(_In_ ID3D11Texture2D *pAcquiredDesktopImage, _In_ IStream *pStream); 42 | inline nlohmann::fifo_map GetFrameDelays() { return m_FrameDelays; } 43 | inline UINT64 GetRenderedFrameCount() { return m_RenderedFrameCount; } 44 | HRESULT StartMediaClock(); 45 | HRESULT ResumeMediaClock(); 46 | HRESULT PauseMediaClock(); 47 | HRESULT StopMediaClock(); 48 | HRESULT GetMediaTimeStamp(_Out_ INT64 *pTime); 49 | bool isMediaClockRunning(); 50 | bool isMediaClockPaused(); 51 | private: 52 | ID3D11DeviceContext *m_DeviceContext = nullptr; 53 | ID3D11Device *m_Device = nullptr; 54 | 55 | CComPtr m_TimeSrc; 56 | CComPtr m_PresentationClock; 57 | 58 | std::shared_ptr m_EncoderOptions; 59 | std::shared_ptr m_AudioOptions; 60 | std::shared_ptr m_SnapshotOptions; 61 | std::shared_ptr m_OutputOptions; 62 | 63 | nlohmann::fifo_map m_FrameDelays; 64 | 65 | CComPtr m_SinkWriter; 66 | CComPtr m_CallBack; 67 | CComPtr m_MediaTransform; 68 | CComPtr m_DeviceManager; 69 | UINT m_ResetToken; 70 | IStream *m_OutStream; 71 | DWORD m_VideoStreamIndex; 72 | DWORD m_AudioStreamIndex; 73 | HANDLE m_FinalizeEvent; 74 | std::wstring m_OutputFolder; 75 | std::wstring m_OutputFullPath; 76 | bool m_LastFrameHadAudio; 77 | UINT64 m_RenderedFrameCount; 78 | std::chrono::steady_clock::time_point m_PreviousSnapshotTaken; 79 | CRITICAL_SECTION m_CriticalSection; 80 | bool m_UseManualNV12Converter; 81 | 82 | std::shared_ptr GetAudioOptions() { return m_AudioOptions; } 83 | std::shared_ptr GetEncoderOptions() { return m_EncoderOptions; } 84 | std::shared_ptr GetSnapshotOptions() { return m_SnapshotOptions; } 85 | std::shared_ptr GetOutputOptions() { return m_OutputOptions; } 86 | 87 | HRESULT ConfigureOutputMediaTypes(_In_ UINT destWidth, _In_ UINT destHeight, _Outptr_ IMFMediaType **pVideoMediaTypeOut, _Outptr_result_maybenull_ IMFMediaType **pAudioMediaTypeOut); 88 | HRESULT ConfigureInputMediaTypes(_In_ UINT sourceWidth, _In_ UINT sourceHeight, _In_ MFVideoRotationFormat rotationFormat, _In_ IMFMediaType *pVideoMediaTypeOut, _Outptr_ IMFMediaType **pVideoMediaTypeIn, _Outptr_result_maybenull_ IMFMediaType **pAudioMediaTypeIn); 89 | HRESULT InitializeVideoSinkWriter(_In_ IMFByteStream *pOutStream, _In_ RECT sourceRect, _In_ SIZE outputFrameSize, _In_ DXGI_MODE_ROTATION rotation, _In_ IMFSinkWriterCallback *pCallback, _Outptr_ IMFSinkWriter **ppWriter, _Out_ DWORD *pVideoStreamIndex, _Out_ DWORD *pAudioStreamIndex); 90 | HRESULT WriteFrameToVideo(_In_ INT64 frameStartPos, _In_ INT64 frameDuration, _In_ DWORD streamIndex, _In_ ID3D11Texture2D *pAcquiredDesktopImage); 91 | 92 | HRESULT WriteAudioSamplesToVideo(_In_ INT64 frameStartPos, _In_ INT64 frameDuration, _In_ DWORD streamIndex, _In_ BYTE *pSrc, _In_ DWORD cbData); 93 | }; 94 | 95 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/PixelShader.hlsl: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | //---------------------------------------------------------------------- 8 | 9 | Texture2D tx : register(t0); 10 | SamplerState samLinear : register(s0); 11 | 12 | struct PS_INPUT 13 | { 14 | float4 Pos : SV_POSITION; 15 | float2 Tex : TEXCOORD; 16 | }; 17 | 18 | //-------------------------------------------------------------------------------------- 19 | // Pixel Shader 20 | //-------------------------------------------------------------------------------------- 21 | float4 PS(PS_INPUT input) : SV_Target 22 | { 23 | return tx.Sample(samLinear, input.Tex); 24 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/Resizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "PixelShader.h" 8 | #include "VertexShader.h" 9 | 10 | class Resizer 11 | { 12 | public: 13 | // 14 | // A vertex with a position and texture coordinate 15 | // 16 | typedef struct _VERTEX 17 | { 18 | DirectX::XMFLOAT3 Pos; 19 | DirectX::XMFLOAT2 TexCoord; 20 | } VERTEX; 21 | 22 | Resizer(); 23 | ~Resizer(); 24 | HRESULT Initialize(ID3D11DeviceContext* ImmediateContext, ID3D11Device* Device); 25 | HRESULT Resize(ID3D11Texture2D* orgTexture, ID3D11Texture2D** pResizedTexture, UINT targetWidth, UINT targetHeight, double viewPortRatio_width = 1.0, double viewPortRatio_height = 1.0); 26 | 27 | private: 28 | void SetViewPort(FLOAT width, FLOAT height); 29 | HRESULT InitShaders(); 30 | HRESULT InitializeDesc(_In_ UINT width, _In_ UINT height, _Out_ D3D11_TEXTURE2D_DESC* pTargetDesc); 31 | void CleanRefs(); 32 | 33 | ID3D11Device* m_Device; 34 | ID3D11DeviceContext* m_DeviceContext; 35 | ID3D11SamplerState* m_SamplerLinear; 36 | ID3D11BlendState* m_BlendState; 37 | ID3D11VertexShader* m_VertexShader; 38 | ID3D11PixelShader* m_PixelShader; 39 | ID3D11InputLayout* m_InputLayout; 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/ScreenCaptureBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CaptureBase.h" 3 | #include "CommonTypes.h" 4 | class ScreenCaptureBase abstract: public CaptureBase 5 | { 6 | public: 7 | ScreenCaptureBase() {}; 8 | virtual ~ScreenCaptureBase() {}; 9 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ const std::optional &sourceRect = std::nullopt) abstract; 10 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/ScreenCaptureManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CommonTypes.h" 3 | #include "Log.h" 4 | #include "DX.util.h" 5 | #include "Screengrab.h" 6 | #include "TextureManager.h" 7 | #include "Util.h" 8 | #include 9 | 10 | void ProcessCaptureHRESULT(_In_ HRESULT hr, _Inout_ CAPTURE_RESULT *pResult, _In_opt_ ID3D11Device *pDevice); 11 | 12 | class ScreenCaptureManager 13 | { 14 | public: 15 | ScreenCaptureManager(); 16 | virtual ~ScreenCaptureManager(); 17 | virtual HRESULT Initialize( 18 | _In_ ID3D11DeviceContext *pDeviceContext, 19 | _In_ ID3D11Device *pDevice, 20 | _In_ std::shared_ptr pOutputOptions, 21 | _In_ std::shared_ptr pEncoderOptions, 22 | _In_ std::shared_ptr pMouseOptions); 23 | virtual inline PTR_INFO *GetPointerInfo() { 24 | return &m_PtrInfo; 25 | } 26 | virtual RECT GetOutputRect() { return m_OutputRect; } 27 | virtual SIZE GetOutputSize() { return SIZE{ RectWidth(m_OutputRect),RectHeight(m_OutputRect) }; } 28 | virtual HRESULT CopyCurrentFrame(_Out_ CAPTURED_FRAME *pFrame); 29 | virtual HRESULT AcquireNextFrame(_In_ double timeUntilNextFrame, _In_ double maxFrameLength, _Out_ CAPTURED_FRAME *pFrame); 30 | virtual HRESULT StartCapture(_In_ const std::vector &sources, _In_ const std::vector &overlays, _In_ HANDLE hErrorEvent); 31 | virtual HRESULT StopCapture(); 32 | virtual bool IsUpdatedFramesAvailable(); 33 | virtual bool IsInitialFrameWriteComplete(); 34 | virtual bool IsInitialOverlayWriteComplete(); 35 | virtual bool IsCapturing() { return m_IsCapturing; } 36 | virtual UINT GetUpdatedSourceCount(); 37 | virtual UINT GetUpdatedOverlayCount(); 38 | virtual void InvalidateCaptureSources(); 39 | std::vector GetCaptureResults(); 40 | std::vector GetCaptureThreadData(); 41 | std::vector GetOverlayThreadData(); 42 | virtual HRESULT ProcessOverlays(_Inout_ ID3D11Texture2D *pBackgroundFrame, _Out_ int *updateCount); 43 | HRESULT InitializeOverlays(_In_ const std::vector &overlays, _In_opt_ HANDLE hErrorEvent); 44 | protected: 45 | LARGE_INTEGER m_LastAcquiredFrameTimeStamp; 46 | ID3D11Texture2D *m_SharedSurf; 47 | IDXGIKeyedMutex *m_KeyMutex; 48 | ID3D11Device *m_Device; 49 | ID3D11DeviceContext *m_DeviceContext; 50 | RECT m_OutputRect; 51 | PTR_INFO m_PtrInfo; 52 | 53 | virtual HRESULT CreateSharedSurf(_In_ RECT desktopRect, _Outptr_ ID3D11Texture2D **ppSharedTexture, _Outptr_ IDXGIKeyedMutex **ppKeyedMutex); 54 | virtual HRESULT CreateSharedSurf(_In_ const std::vector &sources, _Out_ std::vector *pCreatedOutputs, _Out_ RECT *pDeskBounds, _Outptr_ ID3D11Texture2D **ppSharedTexture, _Outptr_ IDXGIKeyedMutex **ppKeyedMutex); 55 | private: 56 | bool m_IsInitialFrameWriteComplete; 57 | bool m_IsInitialOverlayWriteComplete; 58 | bool m_IsCapturing; 59 | HANDLE m_TerminateThreadsEvent; 60 | CRITICAL_SECTION m_CriticalSection; 61 | std::shared_ptr m_EncoderOptions; 62 | std::shared_ptr m_OutputOptions; 63 | std::shared_ptr m_MouseOptions; 64 | std::unique_ptr m_TextureManager; 65 | CComPtr m_FrameCopy; 66 | 67 | std::vector m_CaptureThreads; 68 | std::vector m_OverlayThreads; 69 | 70 | void Clean(); 71 | HRESULT WaitForThreadTermination(); 72 | _Ret_maybenull_ CAPTURE_THREAD_DATA *GetCaptureDataForRect(RECT rect); 73 | RECT GetSourceRect(_In_ SIZE canvasSize, _In_ RECORDING_SOURCE_DATA *pSource); 74 | RECT GetOverlayRect(_In_ SIZE canvasSize, _In_ SIZE overlayTextureSize, _In_ RECORDING_OVERLAY *pOverlay); 75 | HRESULT ScreenCaptureManager::InitializeRecordingSources(_In_ const std::vector &recordingSources, _In_opt_ HANDLE hErrorEvent); 76 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/SourceReaderBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "CommonTypes.h" 11 | #include "HighresTimer.h" 12 | #include "LogMediaType.h" 13 | #include "CaptureBase.h" 14 | #include "TextureManager.h" 15 | #include "MF.util.h" 16 | 17 | class SourceReaderBase abstract : public CaptureBase, public IMFSourceReaderCallback //this class inherits from IMFSourceReaderCallback 18 | { 19 | public: 20 | virtual void Close(); 21 | SourceReaderBase(); 22 | virtual ~SourceReaderBase(); 23 | virtual HRESULT StartCapture(_In_ RECORDING_SOURCE_BASE &recordingSource) override; 24 | virtual HRESULT GetNativeSize(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ SIZE *nativeMediaSize) override; 25 | virtual HRESULT AcquireNextFrame(_In_ DWORD timeoutMillis, _Outptr_opt_ ID3D11Texture2D **ppFrame) override; 26 | virtual HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice) override; 27 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ ID3D11Texture2D *pTexture = nullptr) override; 28 | inline virtual HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ RECT frameCoordinates, _In_ int offsetX, _In_ int offsetY) override { 29 | return S_FALSE; 30 | } 31 | 32 | // the class must implement the methods from IMFSourceReaderCallback 33 | STDMETHODIMP OnReadSample(HRESULT status, DWORD streamIndex, DWORD streamFlags, LONGLONG timeStamp, IMFSample *sample); 34 | STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *); 35 | STDMETHODIMP OnFlush(DWORD); 36 | // the class must implement the methods from IUnknown 37 | STDMETHODIMP QueryInterface(REFIID iid, void **ppv); 38 | STDMETHODIMP_(ULONG) AddRef(); 39 | STDMETHODIMP_(ULONG) Release(); 40 | 41 | protected: 42 | virtual HRESULT InitializeSourceReader( 43 | _In_ std::wstring source, 44 | _In_ std::optional sourceFormatIndex, 45 | _Out_ long *pStreamIndex, 46 | _Outptr_ IMFSourceReader **ppSourceReader, 47 | _Outptr_ IMFMediaType **ppInputMediaType, 48 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 49 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform) abstract; 50 | 51 | virtual HRESULT InitializeSourceReader( 52 | _In_ IStream *pSourceStream, 53 | _In_ std::optional sourceFormatIndex, 54 | _Out_ long *pStreamIndex, 55 | _Outptr_ IMFSourceReader **ppSourceReader, 56 | _Outptr_ IMFMediaType **ppInputMediaType, 57 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 58 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform) abstract; 59 | 60 | 61 | virtual HRESULT CreateOutputMediaType(_In_ SIZE frameSize, _Outptr_ IMFMediaType **pType, _Out_ LONG *stride); 62 | virtual HRESULT CreateIMFTransform(_In_ DWORD streamIndex, _In_ IMFMediaType *pInputMediaType, _Outptr_ IMFTransform **pColorConverter, _Outptr_ IMFMediaType **ppOutputMediaType); 63 | virtual HRESULT SourceReaderBase::ResizeFrameBuffer(UINT bufferSize); 64 | CRITICAL_SECTION m_CriticalSection; 65 | inline IMFDXGIDeviceManager *GetDeviceManager() { return m_DeviceManager; } 66 | private: 67 | long m_ReferenceCount; 68 | HANDLE m_NewFrameEvent; 69 | HANDLE m_StopCaptureEvent; 70 | LARGE_INTEGER m_LastSampleReceivedTimeStamp; 71 | IMFMediaBuffer *m_Sample; 72 | HighresTimer *m_FramerateTimer; 73 | IMFMediaType *m_OutputMediaType; 74 | IMFMediaType *m_InputMediaType; 75 | IMFSourceReader *m_SourceReader; 76 | IMFTransform *m_MediaTransform; 77 | CComPtr m_DeviceManager; 78 | UINT m_ResetToken; 79 | UINT m_BufferSize; 80 | _Field_size_bytes_(m_BufferSize) BYTE *m_PtrFrameBuffer; 81 | LONG m_Stride; 82 | SIZE m_FrameSize; 83 | double m_FrameRate; 84 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/TextureManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "CommonTypes.h" 4 | #include "DX.util.h" 5 | #include 6 | 7 | using namespace std; 8 | 9 | class TextureManager 10 | { 11 | public: 12 | TextureManager(); 13 | ~TextureManager(); 14 | HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *Device); 15 | HRESULT ResizeTexture(_In_ ID3D11Texture2D *pOrgTexture, _In_ SIZE targetSize, _In_ TextureStretchMode stretch, _Outptr_ ID3D11Texture2D **ppResizedTexture, _Out_opt_ RECT *pContentRect = nullptr); 16 | HRESULT RotateTexture(_In_ ID3D11Texture2D *pOrgTexture, _In_ DXGI_MODE_ROTATION rotation, _Outptr_ ID3D11Texture2D **ppRotatedTexture); 17 | HRESULT DrawTexture(_Inout_ ID3D11Texture2D *pCanvasTexture, _In_ ID3D11Texture2D *pTexture, _In_ RECT rect); 18 | /// 19 | /// Crops a texture to the given rectangle. 20 | /// 21 | /// The texture to crop 22 | /// A rectangle to crop the texture to 23 | /// The cropped texture 24 | /// S_OK if successful, S_FALSE is crop rect is larger than texture, error code on failure 25 | HRESULT CropTexture(_In_ ID3D11Texture2D *pTexture, _In_ RECT cropRect, _Outptr_ ID3D11Texture2D **pCroppedFrame); 26 | /// 27 | /// Copy a texture via the CPU. This can be used to copy a texture created on one physical device to be rendered on another. 28 | /// 29 | /// The device with which to create the texture copy 30 | /// The texture to copy 31 | /// The copied texture 32 | HRESULT CopyTextureWithCPU(_In_ ID3D11Device *pDevice, _In_ ID3D11Texture2D *pTexture, _Outptr_ ID3D11Texture2D **ppTextureCopy); 33 | HRESULT CreateTexture(_In_ UINT width, _In_ UINT height, _Outptr_ ID3D11Texture2D **ppTexture, UINT miscFlag = 0, UINT bindFlag = 0); 34 | HRESULT CreateTextureFromBuffer(_In_ BYTE *pFrameBuffer, _In_ LONG stride, _In_ UINT width, _In_ UINT height, _Outptr_ ID3D11Texture2D **ppTexture, UINT miscFlag = 0, UINT bindFlag = 0); 35 | HRESULT BlankTexture(_Inout_ ID3D11Texture2D *pTexture, _In_ RECT rect, _In_ INT OffsetX = 0, _In_ INT OffsetY = 0); 36 | private: 37 | HRESULT InitializeDesc(_In_ UINT width, _In_ UINT height, _Out_ D3D11_TEXTURE2D_DESC *pTargetDesc); 38 | HRESULT GetOrCreateTexture(_In_ D3D11_TEXTURE2D_DESC desc, _Outptr_ ID3D11Texture2D **ppTexture); 39 | void ConfigureRotationVertices(_Inout_ VERTEX(&vertices)[6], _In_ RECT textureRect, _In_opt_ DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED); 40 | void CleanRefs(); 41 | 42 | ID3D11Device *m_Device; 43 | ID3D11DeviceContext *m_DeviceContext; 44 | ID3D11SamplerState *m_SamplerLinear; 45 | ID3D11BlendState *m_BlendState; 46 | ID3D11VertexShader *m_VertexShader; 47 | ID3D11PixelShader *m_PixelShader; 48 | ID3D11InputLayout *m_InputLayout; 49 | 50 | 51 | struct TextureDescHasher { 52 | std::size_t operator()(const D3D11_TEXTURE2D_DESC &desc) const noexcept { 53 | std::string temp = 54 | to_string(desc.Format) 55 | .append(to_string(desc.ArraySize)) 56 | .append(to_string(desc.BindFlags)) 57 | .append(to_string(desc.CPUAccessFlags)) 58 | .append(to_string(desc.Format)) 59 | .append(to_string(desc.Height)) 60 | .append(to_string(desc.MipLevels)) 61 | .append(to_string(desc.MiscFlags)) 62 | .append(to_string(desc.SampleDesc.Count)) 63 | .append(to_string(desc.SampleDesc.Quality)) 64 | .append(to_string(desc.Width)) 65 | .append(to_string(desc.Usage)); 66 | return hash{}(temp); 67 | }; 68 | }; 69 | 70 | struct TextureDescComparator { 71 | bool operator()(const D3D11_TEXTURE2D_DESC &A, 72 | const D3D11_TEXTURE2D_DESC &B) const noexcept { 73 | return A.Format == B.Format 74 | && A.ArraySize == B.ArraySize 75 | && A.BindFlags == B.BindFlags 76 | && A.CPUAccessFlags == B.CPUAccessFlags 77 | && A.Format == B.Format 78 | && A.Height == B.Height 79 | && A.MipLevels == B.MipLevels 80 | && A.MiscFlags == B.MiscFlags 81 | && A.SampleDesc.Count == B.SampleDesc.Count 82 | && A.SampleDesc.Quality == B.SampleDesc.Quality 83 | && A.Usage == B.Usage 84 | && A.Width == B.Width; 85 | }; 86 | }; 87 | std::unordered_map m_TextureCache; 88 | }; 89 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/Util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | using _GetDpiForSystem = UINT __stdcall(); 4 | 5 | /// 6 | /// Returns the system DPI from GetDpiForSystem() on Windows 10 v1607 or above, 7 | /// or falls back to using GetDeviceCaps for earlier windows versions. 8 | /// 9 | /// 10 | UINT GetSystemDpi() 11 | { 12 | auto libraryModule = LoadLibraryA("User32.dll"); 13 | HRESULT hr = E_FAIL; 14 | int dpi = 96; 15 | if (libraryModule != nullptr) 16 | { 17 | auto addr = GetProcAddress(libraryModule, "GetDpiForSystem"); 18 | if (addr != nullptr) 19 | { 20 | auto GetDpi = reinterpret_cast<_GetDpiForSystem *>(addr); 21 | dpi = GetDpi(); 22 | } 23 | else { 24 | auto dc = GetDC(nullptr); 25 | dpi = GetDeviceCaps(dc, LOGPIXELSX); 26 | ReleaseDC(nullptr, dc); 27 | } 28 | FreeLibrary(libraryModule); 29 | } 30 | 31 | return dpi; 32 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/VertexShader.hlsl: -------------------------------------------------------------------------------- 1 | // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 2 | // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 3 | // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 4 | // PARTICULAR PURPOSE. 5 | // 6 | // Copyright (c) Microsoft Corporation. All rights reserved 7 | //---------------------------------------------------------------------- 8 | 9 | struct VS_INPUT 10 | { 11 | float4 Pos : SV_POSITION; 12 | float2 Tex : TEXCOORD; 13 | }; 14 | 15 | struct VS_OUTPUT 16 | { 17 | float4 Pos : SV_POSITION; 18 | float2 Tex : TEXCOORD; 19 | }; 20 | 21 | 22 | //-------------------------------------------------------------------------------------- 23 | // Vertex Shader 24 | //-------------------------------------------------------------------------------------- 25 | VS_OUTPUT VS(VS_INPUT input) 26 | { 27 | return input; 28 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/VideoReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SourceReaderBase.h" 3 | #include "MF.util.h" 4 | 5 | class VideoReader :public SourceReaderBase 6 | { 7 | public: 8 | VideoReader(); 9 | virtual ~VideoReader(); 10 | virtual inline std::wstring Name() override { return L"VideoReader"; }; 11 | protected: 12 | virtual HRESULT InitializeSourceReader( 13 | _In_ std::wstring filePath, 14 | _In_ std::optional sourceFormatIndex, 15 | _Out_ long *pStreamIndex, 16 | _Outptr_ IMFSourceReader **ppSourceReader, 17 | _Outptr_ IMFMediaType **ppInputMediaType, 18 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 19 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform) override; 20 | 21 | virtual HRESULT InitializeSourceReader( 22 | _In_ IStream *pSourceStream, 23 | _In_ std::optional sourceFormatIndex, 24 | _Out_ long *pStreamIndex, 25 | _Outptr_ IMFSourceReader **ppSourceReader, 26 | _Outptr_ IMFMediaType **ppInputMediaType, 27 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 28 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform) override; 29 | 30 | private: 31 | HRESULT InitializeSourceReader( 32 | _In_ IMFSourceReader *ppSourceReader, 33 | _In_ std::optional sourceFormatIndex, 34 | _Out_ long *pStreamIndex, 35 | _Outptr_ IMFMediaType **ppInputMediaType, 36 | _Outptr_opt_ IMFMediaType **ppOutputMediaType, 37 | _Outptr_opt_result_maybenull_ IMFTransform **ppMediaTransform); 38 | 39 | HRESULT CreateAttributes(_Outptr_ IMFAttributes **ppAttributes); 40 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WASAPICapture.h: -------------------------------------------------------------------------------- 1 | //https://github.com/mvaneerde/blog/tree/master/loopback-capture 2 | #pragma once 3 | #include "WWMFResampler.h" 4 | #include "Log.h" 5 | #include "CommonTypes.h" 6 | #include "DynamicWait.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #pragma comment(lib, "avrt.lib") 18 | #pragma comment(lib, "ole32.lib") 19 | #pragma comment(lib, "winmm.lib") 20 | 21 | class WASAPICapture 22 | { 23 | public: 24 | WASAPICapture(_In_ std::shared_ptr &audioOptions, _In_opt_ std::wstring tag = L""); 25 | ~WASAPICapture(); 26 | void ClearRecordedBytes(); 27 | bool IsCapturing(); 28 | std::vector PeakRecordedBytes(); 29 | std::vector GetRecordedBytes(UINT64 duration100Nanos); 30 | HRESULT Initialize(_In_ std::wstring deviceId, _In_ EDataFlow flow); 31 | HRESULT StartCapture(); 32 | HRESULT StopCapture(); 33 | void ReturnAudioBytesToBuffer(std::vector bytes); 34 | void SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id); 35 | void SetOffline(bool isOffline); 36 | inline EDataFlow GetFlow() { return m_Flow; } 37 | inline std::wstring GetTag() { return m_Tag; } 38 | inline std::wstring GetDeviceName() { return m_DeviceName; } 39 | inline std::wstring GetDeviceId() { return m_DeviceId; } 40 | 41 | private: 42 | const long AUDIO_CLIENT_BUFFER_100_NS = 200 * 10000; 43 | HRESULT GetWaveFormat( 44 | _In_ IAudioClient *pAudioClient, 45 | _In_ bool bInt16, 46 | _Out_ WAVEFORMATEX **ppWaveFormat); 47 | HRESULT InitializeAudioClient( 48 | _In_ IMMDevice *pMMDevice, 49 | _Outptr_ IAudioClient **ppAudioClient); 50 | 51 | HRESULT InitializeResampler( 52 | _In_ UINT32 samplerate, 53 | _In_ UINT32 nChannels, 54 | _In_ IAudioClient *pAudioClient, 55 | _Out_ WWMFPcmFormat *pInputFormat, 56 | _Out_ WWMFPcmFormat *pOutputFormat, 57 | _Outptr_result_maybenull_ WWMFResampler **ppResampler); 58 | 59 | HRESULT StartCaptureLoop( 60 | _In_ IAudioClient *pAudioClient, 61 | _In_ HANDLE hStartedEvent, 62 | _In_ HANDLE hStopEvent, 63 | _In_ HANDLE hRestartEvent 64 | ); 65 | 66 | bool StartListeners(); 67 | bool StopListeners(); 68 | 69 | HRESULT StopReconnectThread(); 70 | 71 | struct TaskWrapper; 72 | std::unique_ptr m_TaskWrapperImpl; 73 | std::wstring m_DefaultDeviceId; 74 | std::wstring m_DeviceId; 75 | std::wstring m_DeviceName; 76 | std::wstring m_Tag; 77 | EDataFlow m_Flow; 78 | DynamicWait m_RetryWait; 79 | 80 | bool m_IsRegisteredForEndpointNotifications = false; 81 | bool m_IsDefaultDevice = false; 82 | std::atomic m_IsCapturing = false; 83 | std::atomic m_IsOffline = false; 84 | std::vector m_OverflowBytes = {}; 85 | std::vector m_RecordedBytes = {}; 86 | HANDLE m_CaptureStartedEvent = nullptr; 87 | HANDLE m_CaptureStopEvent = nullptr; 88 | HANDLE m_CaptureRestartEvent = nullptr; 89 | HANDLE m_CaptureReconnectEvent = nullptr; 90 | HANDLE m_ReconnectThreadStopEvent = nullptr; 91 | 92 | HRESULT ReconnectThreadLoop(); 93 | 94 | CComPtr m_pEnumerator; 95 | CComPtr m_AudioClient; 96 | std::unique_ptr m_Resampler; 97 | WWMFPcmFormat m_InputFormat; 98 | WWMFPcmFormat m_OutputFormat; 99 | 100 | std::shared_ptr m_AudioOptions; 101 | }; 102 | 103 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WASAPINotify.cpp: -------------------------------------------------------------------------------- 1 | #include "WASAPINotify.h" 2 | #include "CoreAudio.util.h" 3 | 4 | 5 | // IUnknown methods -- AddRef, Release, and QueryInterface 6 | ULONG STDMETHODCALLTYPE WASAPINotify::AddRef() 7 | { 8 | return InterlockedIncrement(&m_cRef); 9 | } 10 | 11 | ULONG STDMETHODCALLTYPE WASAPINotify::Release() 12 | { 13 | ULONG ulRef = InterlockedDecrement(&m_cRef); 14 | if (0 == ulRef) 15 | { 16 | delete this; 17 | } 18 | return ulRef; 19 | } 20 | 21 | HRESULT STDMETHODCALLTYPE WASAPINotify::QueryInterface(REFIID riid, VOID **ppvInterface) 22 | { 23 | if (IID_IUnknown == riid) 24 | { 25 | AddRef(); 26 | *ppvInterface = (IUnknown *)this; 27 | } 28 | else if (__uuidof(IMMNotificationClient) == riid) 29 | { 30 | AddRef(); 31 | *ppvInterface = (IMMNotificationClient *)this; 32 | } 33 | else 34 | { 35 | *ppvInterface = NULL; 36 | return E_NOINTERFACE; 37 | } 38 | return S_OK; 39 | } 40 | 41 | 42 | // Callback methods for device-event notifications. 43 | HRESULT STDMETHODCALLTYPE WASAPINotify::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) 44 | { 45 | wchar_t *pszFlow = L"?????"; 46 | wchar_t *pszRole = L"?????"; 47 | 48 | switch (role) 49 | { 50 | case eConsole: 51 | pszRole = L"eConsole"; 52 | break; 53 | case eMultimedia: 54 | pszRole = L"eMultimedia"; 55 | break; 56 | case eCommunications: 57 | pszRole = L"eCommunications"; 58 | break; 59 | } 60 | switch (flow) 61 | { 62 | case eRender: 63 | pszFlow = L"eRender"; 64 | break; 65 | case eCapture: 66 | pszFlow = L"eCapture"; 67 | break; 68 | } 69 | if (flow == m_CaptureClient->GetFlow()) { 70 | m_CaptureClient->SetDefaultDevice(flow, role, pwstrDeviceId); 71 | std::wstring deviceName; 72 | HRESULT hr = GetAudioDeviceFriendlyName(pwstrDeviceId, &deviceName); 73 | if (FAILED(hr)) { 74 | deviceName = L"Unknown Device"; 75 | } 76 | 77 | LOG_DEBUG("New default device: name=%s, flow = %s, role = %s\n", 78 | deviceName.c_str(), pszFlow, pszRole); 79 | } 80 | return S_OK; 81 | } 82 | 83 | HRESULT STDMETHODCALLTYPE WASAPINotify::OnDeviceAdded(LPCWSTR pwstrDeviceId) 84 | { 85 | std::wstring deviceName = L"?????"; 86 | EDataFlow flow; 87 | IMMDevice *pMMDevice; 88 | GetAudioDevice(pwstrDeviceId, &pMMDevice); 89 | GetAudioDeviceFriendlyName(pMMDevice, &deviceName); 90 | GetAudioDeviceFlow(pMMDevice, &flow); 91 | if (flow == m_CaptureClient->GetFlow()) { 92 | m_CaptureClient->SetOffline(false); 93 | m_CaptureClient->StartCapture(); 94 | LOG_DEBUG(L"Added audio device %s", deviceName); 95 | } 96 | return S_OK; 97 | }; 98 | 99 | HRESULT STDMETHODCALLTYPE WASAPINotify::OnDeviceRemoved(LPCWSTR pwstrDeviceId) 100 | { 101 | std::wstring deviceName = L"?????"; 102 | EDataFlow flow; 103 | IMMDevice *pMMDevice; 104 | GetAudioDevice(pwstrDeviceId, &pMMDevice); 105 | GetAudioDeviceFriendlyName(pMMDevice, &deviceName); 106 | GetAudioDeviceFlow(pMMDevice, &flow); 107 | if (flow == m_CaptureClient->GetFlow()) { 108 | if (pwstrDeviceId == m_CaptureClient->GetDeviceId()) { 109 | m_CaptureClient->SetOffline(true); 110 | } 111 | 112 | LOG_DEBUG(L"Removed audio device %s", deviceName); 113 | } 114 | return S_OK; 115 | } 116 | 117 | HRESULT STDMETHODCALLTYPE WASAPINotify::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) 118 | { 119 | std::wstring state = L"?????"; 120 | std::wstring deviceName = L"?????"; 121 | EDataFlow flow; 122 | IMMDevice *pMMDevice; 123 | GetAudioDevice(pwstrDeviceId, &pMMDevice); 124 | GetAudioDeviceFriendlyName(pMMDevice, &deviceName); 125 | GetAudioDeviceFlow(pMMDevice, &flow); 126 | if (flow == m_CaptureClient->GetFlow()) { 127 | switch (dwNewState) 128 | { 129 | case DEVICE_STATE_ACTIVE: 130 | state = L"ACTIVE"; 131 | if (pwstrDeviceId == m_CaptureClient->GetDeviceId()) { 132 | m_CaptureClient->SetOffline(false); 133 | m_CaptureClient->StartCapture(); 134 | } 135 | break; 136 | case DEVICE_STATE_DISABLED: 137 | state = L"DISABLED"; 138 | if (pwstrDeviceId == m_CaptureClient->GetDeviceId()) { 139 | m_CaptureClient->SetOffline(true); 140 | } 141 | break; 142 | case DEVICE_STATE_NOTPRESENT: 143 | state = L"NOTPRESENT"; 144 | if (pwstrDeviceId == m_CaptureClient->GetDeviceId()) { 145 | m_CaptureClient->SetOffline(true); 146 | } 147 | break; 148 | case DEVICE_STATE_UNPLUGGED: 149 | state = L"UNPLUGGED"; 150 | if (pwstrDeviceId == m_CaptureClient->GetDeviceId()) { 151 | m_CaptureClient->SetOffline(true); 152 | } 153 | break; 154 | } 155 | 156 | LOG_DEBUG("New device state for audio device %s is DEVICE_STATE_%s (0x%8.8x)\n", 157 | deviceName.c_str(), state.c_str(), dwNewState); 158 | } 159 | return S_OK; 160 | } 161 | 162 | HRESULT STDMETHODCALLTYPE WASAPINotify::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { 163 | return S_OK; 164 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WASAPINotify.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "WASAPICapture.h" 4 | 5 | class WASAPINotify : public IMMNotificationClient { 6 | public: 7 | //Will get increased to 1 by CComPtr 8 | LONG m_cRef = 0; 9 | WASAPICapture *m_CaptureClient; 10 | 11 | WASAPINotify(WASAPICapture *client) : m_CaptureClient(client) {} 12 | 13 | // IUnknown methods -- AddRef, Release, and QueryInterface 14 | ULONG STDMETHODCALLTYPE AddRef(); 15 | ULONG STDMETHODCALLTYPE Release(); 16 | HRESULT STDMETHODCALLTYPE QueryInterface( 17 | REFIID riid, VOID **ppvInterface); 18 | private: 19 | // IMMNotificationClient methods 20 | virtual HRESULT STDMETHODCALLTYPE IMMNotificationClient::OnDeviceStateChanged(LPCWSTR, DWORD); 21 | virtual HRESULT STDMETHODCALLTYPE IMMNotificationClient::OnDeviceAdded(LPCWSTR); 22 | virtual HRESULT STDMETHODCALLTYPE IMMNotificationClient::OnDeviceRemoved(LPCWSTR); 23 | virtual HRESULT STDMETHODCALLTYPE IMMNotificationClient::OnDefaultDeviceChanged(EDataFlow, ERole, LPCWSTR); 24 | virtual HRESULT STDMETHODCALLTYPE IMMNotificationClient::OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY); 25 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WWMFResampler.h: -------------------------------------------------------------------------------- 1 | #pragma warning (disable : 26451) 2 | #pragma once 3 | 4 | //#define WINVER _WIN32_WINNT_WIN7 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /// sample data type. int or float 12 | /// it is compatible to WWBitFormatType on WasapiUser.h 13 | enum class WWMFBitFormatType { 14 | WWMFBitFormatUnknown = -1, 15 | WWMFBitFormatInt, 16 | WWMFBitFormatFloat, 17 | WWMFBitFormatNUM 18 | }; 19 | 20 | struct WWMFPcmFormat { 21 | WWMFBitFormatType sampleFormat; 22 | WORD nChannels; 23 | WORD bits; 24 | DWORD sampleRate; 25 | DWORD dwChannelMask; 26 | WORD validBitsPerSample; 27 | 28 | WWMFPcmFormat(void) { 29 | sampleFormat = WWMFBitFormatType::WWMFBitFormatUnknown; 30 | nChannels = 0; 31 | bits = 0; 32 | sampleRate = 0; 33 | dwChannelMask = 0; 34 | validBitsPerSample = 0; 35 | } 36 | 37 | WWMFPcmFormat(WWMFBitFormatType aSampleFormat, WORD aNChannels, WORD aBits, 38 | DWORD aSampleRate, DWORD aDwChannelMask, WORD aValidBitsPerSample) { 39 | sampleFormat = aSampleFormat; 40 | nChannels = aNChannels; 41 | bits = aBits; 42 | sampleRate = aSampleRate; 43 | dwChannelMask = aDwChannelMask; 44 | validBitsPerSample = aValidBitsPerSample; 45 | } 46 | 47 | WORD FrameBytes(void) const { 48 | return (WORD)(nChannels * bits / 8U); 49 | } 50 | 51 | DWORD BytesPerSec(void) const { 52 | return sampleRate * FrameBytes(); 53 | } 54 | }; 55 | 56 | /// WWMFSampleData contains new[] ed byte buffer pointer(data) and buffer size(bytes). 57 | struct WWMFSampleData { 58 | DWORD bytes; 59 | BYTE *data; 60 | 61 | WWMFSampleData(void) : bytes(0), data(NULL) { } 62 | 63 | /// @param aData must point new[] ed memory address 64 | WWMFSampleData(BYTE *aData, int aBytes) { 65 | data = aData; 66 | bytes = aBytes; 67 | } 68 | 69 | ~WWMFSampleData(void) { 70 | assert(NULL == data); 71 | } 72 | 73 | void Release(void) { 74 | delete[] data; 75 | data = NULL; 76 | bytes = 0; 77 | } 78 | 79 | void Forget(void) { 80 | data = NULL; 81 | bytes = 0; 82 | } 83 | 84 | HRESULT Add(WWMFSampleData &rhs) { 85 | BYTE *buff = new BYTE[bytes + rhs.bytes]; 86 | if (NULL == buff) { 87 | return E_FAIL; 88 | } 89 | 90 | memcpy(buff, data, bytes); 91 | memcpy(&buff[bytes], rhs.data, rhs.bytes); 92 | 93 | delete[] data; 94 | data = buff; 95 | bytes += rhs.bytes; 96 | return S_OK; 97 | } 98 | 99 | /** 100 | * If this instance is not empty, rhs content is concatenated to this instance. rhs remains untouched. 101 | * If this instance is empty, rhs content moves to this instance. rhs becomes empty. 102 | * rhs.Release() must be called to release memory either way! 103 | */ 104 | HRESULT MoveAdd(WWMFSampleData &rhs) { 105 | if (bytes != 0) { 106 | return Add(rhs); 107 | } 108 | 109 | assert(NULL == data); 110 | *this = rhs; //< Just copy 8 bytes. It's way faster than Add() 111 | rhs.Forget(); 112 | 113 | return S_OK; 114 | } 115 | }; 116 | 117 | class WWMFResampler { 118 | public: 119 | WWMFResampler(void) : 120 | m_pTransform(NULL), 121 | m_inputFrameTotal(0), 122 | m_outputFrameTotal(0) 123 | { 124 | 125 | } 126 | ~WWMFResampler(void); 127 | 128 | /// @param halfFilterLength conversion quality. 1(min) to 60 (max) 129 | HRESULT Initialize(const WWMFPcmFormat &inputFormat, const WWMFPcmFormat &outputFormat, int halfFilterLength); 130 | 131 | /// @bytes buffer bytes. must be smaller than approx. 512KB to convert 44100Hz to 192000Hz 132 | HRESULT Resample(const BYTE *buff, DWORD bytes, WWMFSampleData *sampleData_return); 133 | 134 | /// @param resampleInputBytes input buffer bytes of Resample(). this arg is used to calculate expected output buffer size 135 | /// @param sampleData_return [out] set fresh (its data shold not be allocated yet) WWMFSampleData instance as this arg 136 | HRESULT Drain(DWORD resampleInputBytes, WWMFSampleData *sampleData_return); 137 | 138 | /// Finalize must be called even when Initialize() is failed 139 | void Finalize(void); 140 | 141 | INT64 GetOutputFrameTotal(void) const { 142 | return m_outputFrameTotal; 143 | } 144 | 145 | INT64 GetInputFrameTotal(void) const { 146 | return m_inputFrameTotal; 147 | } 148 | 149 | private: 150 | IMFTransform *m_pTransform; 151 | WWMFPcmFormat m_inputFormat; 152 | WWMFPcmFormat m_outputFormat; 153 | INT64 m_inputFrameTotal; 154 | INT64 m_outputFrameTotal; 155 | 156 | 157 | HRESULT ConvertWWSampleDataToMFSample(WWMFSampleData &sampleData, IMFSample **ppSample); 158 | HRESULT ConvertMFSampleToWWSampleData(IMFSample *pSample, WWMFSampleData *sampleData_return); 159 | HRESULT GetSampleDataFromMFTransform(WWMFSampleData *sampleData_return); 160 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WindowsGraphicsCapture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ScreenCaptureBase.h" 3 | #include "CommonTypes.h" 4 | #include "TextureManager.h" 5 | #include 6 | #include "WindowsGraphicsCapture.util.h" 7 | #include "MouseManager.h" 8 | class WindowsGraphicsCapture : public CaptureBase 9 | { 10 | public: 11 | WindowsGraphicsCapture(); 12 | virtual ~WindowsGraphicsCapture(); 13 | virtual HRESULT Initialize(_In_ ID3D11DeviceContext *pDeviceContext, _In_ ID3D11Device *pDevice) override; 14 | virtual HRESULT AcquireNextFrame(_In_ DWORD timeoutMillis, _Outptr_opt_ ID3D11Texture2D **ppFrame) override; 15 | virtual HRESULT WriteNextFrameToSharedSurface(_In_ DWORD timeoutMillis, _Inout_ ID3D11Texture2D *pSharedSurf, INT offsetX, INT offsetY, _In_ RECT destinationRect, _In_opt_ ID3D11Texture2D *pTexture = nullptr) override; 16 | virtual HRESULT StartCapture(_In_ RECORDING_SOURCE_BASE &recordingSource) override; 17 | virtual HRESULT StopCapture(); 18 | virtual HRESULT GetNativeSize(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ SIZE *nativeMediaSize) override; 19 | virtual HRESULT GetMouse(_Inout_ PTR_INFO *pPtrInfo, _In_ RECT frameCoordinates, _In_ int offsetX, _In_ int offsetY) override; 20 | virtual inline std::wstring Name() override { return L"WindowsGraphicsCapture"; }; 21 | 22 | private: 23 | void OnFrameArrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender, winrt::Windows::Foundation::IInspectable const &args); 24 | HRESULT GetNextFrame(_In_ DWORD timeoutMillis, _Inout_ GRAPHICS_FRAME_DATA *pData); 25 | HRESULT GetCaptureItem(_In_ RECORDING_SOURCE_BASE &recordingSource, _Out_ winrt::Windows::Graphics::Capture::GraphicsCaptureItem *item); 26 | HRESULT RecreateFramePool(_Inout_ GRAPHICS_FRAME_DATA *pData, _In_ winrt::Windows::Graphics::SizeInt32 newSize); 27 | HRESULT ProcessRecordingTimeout(_Inout_ GRAPHICS_FRAME_DATA *pData); 28 | bool IsRecordingSessionStale(); 29 | winrt::Windows::Graphics::Capture::GraphicsCaptureItem m_CaptureItem; 30 | winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool; 31 | winrt::Windows::Graphics::Capture::GraphicsCaptureSession m_session; 32 | 33 | std::unique_ptr m_MouseManager; 34 | int m_CursorOffsetX; 35 | int m_CursorOffsetY; 36 | float m_CursorScaleX; 37 | float m_CursorScaleY; 38 | bool m_IsInitialized; 39 | HANDLE m_NewFrameEvent; 40 | bool m_HaveDeliveredFirstFrame; 41 | std::atomic m_closed; 42 | RECT m_LastFrameRect; 43 | GRAPHICS_FRAME_DATA m_CurrentData; 44 | LARGE_INTEGER m_QPCFrequency; 45 | LARGE_INTEGER m_LastSampleReceivedTimeStamp; 46 | LARGE_INTEGER m_LastCaptureSessionRestart; 47 | 48 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WindowsGraphicsCapture.util.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowsGraphicsCapture.util.h" 2 | 3 | namespace Graphics::Capture::Util 4 | { 5 | using _CreateDirect3D11DeviceFromDXGIDevice = HRESULT __stdcall(IDXGIDevice *, IInspectable **); 6 | 7 | HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice *dxgiDevice, IInspectable **graphicsDevice) { 8 | auto libraryModule = LoadLibraryA("D3D11.dll"); 9 | HRESULT hr = E_FAIL; 10 | if (libraryModule != nullptr) 11 | { 12 | auto addr = GetProcAddress(libraryModule, "CreateDirect3D11DeviceFromDXGIDevice"); 13 | if (addr != nullptr) 14 | { 15 | auto createDirect3D11DeviceFromDXGIDevice = reinterpret_cast<_CreateDirect3D11DeviceFromDXGIDevice *>(addr); 16 | hr = createDirect3D11DeviceFromDXGIDevice(dxgiDevice, graphicsDevice); 17 | } 18 | FreeLibrary(libraryModule); 19 | } 20 | return hr; 21 | } 22 | } -------------------------------------------------------------------------------- /ScreenRecorderLibNative/WindowsGraphicsCapture.util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | namespace Graphics::Capture::Util 10 | { 11 | HRESULT CreateDirect3D11DeviceFromDXGIDevice(IDXGIDevice *dxgiDevice,::IInspectable **graphicsDevice); 12 | 13 | struct __declspec(uuid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")) 14 | IDirect3DDxgiInterfaceAccess : ::IUnknown 15 | { 16 | virtual HRESULT __stdcall GetInterface(GUID const &id, void **object) = 0; 17 | }; 18 | 19 | inline auto CreateDirect3DDevice(IDXGIDevice *dxgi_device) 20 | { 21 | winrt::com_ptr<::IInspectable> d3d_device; 22 | winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgi_device, d3d_device.put())); 23 | return d3d_device.as(); 24 | } 25 | 26 | template 27 | auto GetDXGIInterfaceFromObject(winrt::Windows::Foundation::IInspectable const &object) 28 | { 29 | auto access = object.as(); 30 | winrt::com_ptr result; 31 | winrt::check_hresult(access->GetInterface(winrt::guid_of(), result.put_void())); 32 | return result; 33 | } 34 | 35 | inline auto CreateCaptureItemForWindow(HWND hwnd) 36 | { 37 | auto interop_factory = winrt::get_activation_factory(); 38 | winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = { nullptr }; 39 | winrt::check_hresult(interop_factory->CreateForWindow(hwnd, winrt::guid_of(), winrt::put_abi(item))); 40 | return item; 41 | } 42 | 43 | inline auto CreateCaptureItemForMonitor(HMONITOR hmon) 44 | { 45 | auto interop_factory = winrt::get_activation_factory(); 46 | winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = { nullptr }; 47 | winrt::check_hresult(interop_factory->CreateForMonitor(hmon, winrt::guid_of(), winrt::put_abi(item))); 48 | return item; 49 | } 50 | inline bool IsGraphicsCaptureAvailable() { 51 | try 52 | { 53 | return winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8) 54 | && winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(L"Windows.Graphics.Capture.GraphicsCaptureSession") 55 | && winrt::Windows::Graphics::Capture::GraphicsCaptureSession::IsSupported(); 56 | } 57 | catch (winrt::hresult_error err) { 58 | return false; 59 | } 60 | } 61 | 62 | inline bool IsGraphicsCaptureCursorCapturePropertyAvailable() { 63 | try 64 | { 65 | return IsGraphicsCaptureAvailable() 66 | && winrt::Windows::Foundation::Metadata::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsCursorCaptureEnabled"); 67 | } 68 | catch (winrt::hresult_error err) { 69 | return false; 70 | } 71 | } 72 | inline bool IsGraphicsCaptureIsBorderRequiredPropertyAvailable() { 73 | try 74 | { 75 | return IsGraphicsCaptureAvailable() 76 | && winrt::Windows::Foundation::Metadata::ApiInformation::IsPropertyPresent(L"Windows.Graphics.Capture.GraphicsCaptureSession", L"IsBorderRequired"); 77 | } 78 | catch (winrt::hresult_error err) { 79 | return false; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ScreenRecorderLibNative/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #if _DEBUG 10 | #define MEASURE_EXECUTION_TIME false 11 | #else 12 | #define MEASURE_EXECUTION_TIME false 13 | #endif 14 | 15 | #define LOG_BUFFER_SIZE 1024 16 | 17 | #define LOG_LVL_TRACE 0 18 | #define LOG_LVL_DEBUG 1 19 | #define LOG_LVL_INFO 2 20 | #define LOG_LVL_WARN 3 21 | #define LOG_LVL_ERR 4 22 | 23 | #define LOG_TRACE(format, ...) if(isLoggingEnabled && LOG_LVL_TRACE >= logSeverityLevel) {_log(L"%s [TRACE] [%-25.24hs|%20.19hs:%4d] >> " format L"\n", GetTimestamp().c_str(), file_name(__FILE__), __func__, __LINE__, __VA_ARGS__);} 24 | #define LOG_DEBUG(format, ...) if(isLoggingEnabled && LOG_LVL_DEBUG >= logSeverityLevel) {_log(L"%s [DEBUG] [%-25.24hs|%20.19hs:%4d] >> " format L"\n", GetTimestamp().c_str(), file_name(__FILE__), __func__, __LINE__, __VA_ARGS__);} 25 | #define LOG_INFO(format, ...) if(isLoggingEnabled && LOG_LVL_INFO >= logSeverityLevel) {_log(L"%s [INFO] [%-25.24hs|%20.19hs:%4d] >> " format L"\n", GetTimestamp().c_str(), file_name(__FILE__), __func__, __LINE__, __VA_ARGS__);} 26 | #define LOG_WARN(format, ...) if(isLoggingEnabled && LOG_LVL_WARN >= logSeverityLevel) {_log(L"%s [WARN] [%-25.24hs|%20.19hs:%4d] >> " format L"\n", GetTimestamp().c_str(), file_name(__FILE__), __func__, __LINE__, __VA_ARGS__);} 27 | #define LOG_ERROR(format, ...) if(isLoggingEnabled && LOG_LVL_ERR >= logSeverityLevel) {_log(L"%s [ERROR] [%-25.24hs|%20.19hs:%4d] >> " format L"\n", GetTimestamp().c_str(), file_name(__FILE__), __func__, __LINE__, __VA_ARGS__);} 28 | 29 | extern bool isLoggingEnabled; 30 | extern int logSeverityLevel; 31 | extern std::wstring logFilePath; 32 | void _log(PCWSTR format, ...); 33 | std::wstring GetTimestamp(); 34 | 35 | constexpr const char *file_name(const char *path) { 36 | const char *file = path; 37 | while (*path) { 38 | if (*path++ == '\\') { 39 | file = path; 40 | } 41 | } 42 | return file; 43 | } 44 | 45 | struct MeasureExecutionTime { 46 | private: 47 | #if MEASURE_EXECUTION_TIME 48 | std::wstring m_Name; 49 | std::chrono::steady_clock::time_point m_Start; 50 | #endif 51 | public: 52 | MeasureExecutionTime(std::wstring name) { 53 | #if MEASURE_EXECUTION_TIME 54 | m_Name = name; 55 | m_Start = std::chrono::steady_clock::now(); 56 | #endif 57 | } 58 | ~MeasureExecutionTime() { 59 | #if MEASURE_EXECUTION_TIME 60 | std::chrono::duration ms_double = std::chrono::steady_clock::now() - m_Start; 61 | if (!m_Name.empty()) { 62 | LOG_TRACE("Execution time for %ls: %.2f ms", m_Name.c_str(), ms_double.count()); 63 | } 64 | #endif 65 | } 66 | void SetName(std::wstring name) { 67 | #if MEASURE_EXECUTION_TIME 68 | m_Name = name; 69 | #endif 70 | } 71 | }; -------------------------------------------------------------------------------- /ScreenRecorderLibNative/native.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "RecordingManager.h" 3 | #include "fifo_map.h" 4 | #include "DX.util.h" 5 | #include "MF.util.h" 6 | #include "CoreAudio.util.h" 7 | #include "CommonTypes.h" -------------------------------------------------------------------------------- /ScreenRecorderLibNative/screengrab.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/ScreenRecorderLibNative/screengrab.cpp -------------------------------------------------------------------------------- /ScreenRecorderLibNative/screengrab.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #pragma comment(lib, "Windowscodecs.lib") 8 | 9 | HRESULT __cdecl SaveWICTextureToFile( 10 | _In_ ID3D11DeviceContext *pContext, 11 | _In_ ID3D11Resource *pSource, 12 | _In_ REFGUID guidContainerFormat, 13 | _In_z_ const wchar_t *filePath, 14 | _In_opt_ const std::optional destSize = std::nullopt, 15 | _In_opt_ const GUID *targetFormat = nullptr, 16 | _In_opt_ std::function setCustomProps = nullptr); 17 | 18 | HRESULT __cdecl SaveWICTextureToStream( 19 | _In_ ID3D11DeviceContext *pContext, 20 | _In_ ID3D11Resource *pSource, 21 | _In_ REFGUID guidContainerFormat, 22 | _In_ IStream *pStream, 23 | _In_opt_ const std::optional destSize = std::nullopt, 24 | _In_opt_ const GUID *targetFormat = nullptr, 25 | _In_opt_ std::function setCustomProps = nullptr); 26 | 27 | HRESULT __cdecl SaveWICTextureToWicStream( 28 | _In_ ID3D11DeviceContext *pContext, 29 | _In_ ID3D11Resource *pSource, 30 | _In_ REFGUID guidContainerFormat, 31 | _In_ IWICStream *pStream, 32 | _In_opt_ const std::optional destSize = std::nullopt, 33 | _In_opt_ const GUID *targetFormat = nullptr, 34 | _In_opt_ std::function setCustomProps = nullptr); 35 | 36 | HRESULT CreateWICBitmapFromFile( 37 | _In_z_ const wchar_t *filePath, 38 | _In_ const GUID targetFormat, 39 | _Outptr_ IWICBitmapSource **ppIWICBitmapSource); 40 | 41 | HRESULT CreateWICBitmapFromStream( 42 | _In_ IStream *pStream, 43 | _In_ const GUID targetFormat, 44 | _Outptr_ IWICBitmapSource **ppIWICBitmapSource); -------------------------------------------------------------------------------- /TestApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /TestApp/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /TestApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace TestApp 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TestApp/BytesToKilobytesConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Data; 8 | 9 | namespace TestApp 10 | { 11 | public class BytesToKilobytesConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | return System.Convert.ToInt64(value) / 1000; 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 19 | { 20 | return System.Convert.ToInt64(value) * 1000; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TestApp/OverlayModel.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TestApp 10 | { 11 | public class OverlayModel : INotifyPropertyChanged 12 | { 13 | private bool _isEnabled; 14 | public bool IsEnabled 15 | { 16 | get { return _isEnabled; } 17 | set 18 | { 19 | if (_isEnabled != value) 20 | { 21 | _isEnabled = value; 22 | RaisePropertyChanged("IsEnabled"); 23 | } 24 | } 25 | } 26 | 27 | private RecordingOverlayBase _overlay; 28 | public RecordingOverlayBase Overlay 29 | { 30 | get { return _overlay; } 31 | set 32 | { 33 | if (_overlay != value) 34 | { 35 | _overlay = value; 36 | RaisePropertyChanged("Overlay"); 37 | } 38 | } 39 | } 40 | 41 | protected void RaisePropertyChanged(string propertyName) 42 | { 43 | PropertyChangedEventHandler handler = PropertyChanged; 44 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 45 | } 46 | public event PropertyChangedEventHandler PropertyChanged; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /TestApp/OverlayTemplateSelector.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace TestApp 10 | { 11 | public class OverlayTemplateSelector : System.Windows.Controls.DataTemplateSelector 12 | { 13 | public override DataTemplate SelectTemplate(object item, DependencyObject container) 14 | { 15 | OverlayModel model = (OverlayModel)item; 16 | if (model.Overlay is VideoCaptureOverlay) 17 | { 18 | return (container as FrameworkElement).FindResource("CameraCaptureOverlayTemplate") as DataTemplate; 19 | } 20 | else if(model.Overlay is VideoOverlay) 21 | { 22 | return (container as FrameworkElement).FindResource("VideoOverlayTemplate") as DataTemplate; 23 | } 24 | else if (model.Overlay is ImageOverlay) 25 | { 26 | return (container as FrameworkElement).FindResource("PictureOverlayTemplate") as DataTemplate; 27 | } 28 | else if (model.Overlay is DisplayOverlay) 29 | { 30 | return (container as FrameworkElement).FindResource("DisplayOverlayTemplate") as DataTemplate; 31 | } 32 | else if (model.Overlay is WindowOverlay) 33 | { 34 | return (container as FrameworkElement).FindResource("WindowOverlayTemplate") as DataTemplate; 35 | } 36 | return null; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TestApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("TestApp")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("TestApp")] 15 | [assembly: AssemblyCopyright("Copyright © 2017")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /TestApp/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TestApp.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestApp.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TestApp/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 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 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /TestApp/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TestApp.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.11.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TestApp/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TestApp/RecordingSourceTemplateSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using TestApp.Sources; 8 | 9 | namespace TestApp 10 | { 11 | public class RecordingSourceTemplateSelector : System.Windows.Controls.DataTemplateSelector 12 | { 13 | public override DataTemplate SelectTemplate(object item, DependencyObject container) 14 | { 15 | 16 | if (item.GetType().Equals(typeof(CheckableRecordableWindow))) 17 | return (container as FrameworkElement).FindResource("WindowRecordingSourceTemplate") as DataTemplate; 18 | else if (item.GetType().Equals(typeof(CheckableRecordableDisplay))) 19 | return (container as FrameworkElement).FindResource("DisplayRecordingSourceTemplate") as DataTemplate; 20 | else if (item.GetType().Equals(typeof(CheckableRecordableCamera))) 21 | return (container as FrameworkElement).FindResource("CameraRecordingSourceTemplate") as DataTemplate; 22 | else if (item.GetType().Equals(typeof(CheckableRecordableVideo))) 23 | return (container as FrameworkElement).FindResource("VideoRecordingSourceTemplate") as DataTemplate; 24 | else if (item.GetType().Equals(typeof(CheckableRecordableImage))) 25 | return (container as FrameworkElement).FindResource("ImageRecordingSourceTemplate") as DataTemplate; 26 | return null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TestApp/Sources/CheckableRecordableCamera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Data; 9 | using System.Windows.Media.Imaging; 10 | using ScreenRecorderLib; 11 | 12 | namespace TestApp.Sources 13 | { 14 | public class MediaDeviceToDeviceIdConverter : DependencyObject, IValueConverter 15 | { 16 | 17 | 18 | public List MediaDevices 19 | { 20 | get { return (List)GetValue(MediaDevicesProperty); } 21 | set { SetValue(MediaDevicesProperty, value); } 22 | } 23 | 24 | // Using a DependencyProperty as the backing store for MediaDevices. This enables animation, styling, binding, etc... 25 | public static readonly DependencyProperty MediaDevicesProperty = 26 | DependencyProperty.Register("MediaDevices", typeof(List), typeof(MediaDeviceToDeviceIdConverter), new PropertyMetadata(null)); 27 | 28 | 29 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 30 | { 31 | RecordableCamera device; 32 | if (value is null) 33 | { 34 | device = MediaDevices.FirstOrDefault(); 35 | } 36 | else 37 | { 38 | device = MediaDevices.FirstOrDefault(x => value.ToString().Equals(x.DeviceName)); 39 | } 40 | return device; 41 | } 42 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 43 | { 44 | return (value as RecordableCamera)?.DeviceName; 45 | } 46 | } 47 | 48 | public class CheckableRecordableCamera : RecordableCamera, ICheckableRecordingSource 49 | { 50 | private bool _isSelected; 51 | public bool IsSelected 52 | { 53 | get { return _isSelected; } 54 | set 55 | { 56 | if (_isSelected != value) 57 | { 58 | _isSelected = value; 59 | OnPropertyChanged(nameof(IsSelected)); 60 | } 61 | } 62 | } 63 | 64 | private bool _isCheckable = true; 65 | public bool IsCheckable 66 | { 67 | get { return _isCheckable; } 68 | set 69 | { 70 | if (_isCheckable != value) 71 | { 72 | _isCheckable = value; 73 | OnPropertyChanged(nameof(IsCheckable)); 74 | } 75 | } 76 | } 77 | 78 | private bool _isCustomPositionEnabled; 79 | public bool IsCustomPositionEnabled 80 | { 81 | get { return _isCustomPositionEnabled; } 82 | set 83 | { 84 | if (_isCustomPositionEnabled != value) 85 | { 86 | _isCustomPositionEnabled = value; 87 | OnPropertyChanged(nameof(IsCustomPositionEnabled)); 88 | } 89 | } 90 | } 91 | 92 | private bool _isCustomOutputSizeEnabled; 93 | public bool IsCustomOutputSizeEnabled 94 | { 95 | get { return _isCustomOutputSizeEnabled; } 96 | set 97 | { 98 | if (_isCustomOutputSizeEnabled != value) 99 | { 100 | _isCustomOutputSizeEnabled = value; 101 | OnPropertyChanged(nameof(IsCustomOutputSizeEnabled)); 102 | } 103 | } 104 | } 105 | 106 | private bool _isCustomOutputSourceRectEnabled; 107 | public bool IsCustomOutputSourceRectEnabled 108 | { 109 | get { return _isCustomOutputSourceRectEnabled; } 110 | set 111 | { 112 | if (_isCustomOutputSourceRectEnabled != value) 113 | { 114 | _isCustomOutputSourceRectEnabled = value; 115 | OnPropertyChanged(nameof(IsCustomOutputSourceRectEnabled)); 116 | } 117 | } 118 | } 119 | 120 | private WriteableBitmap _previewBitmap; 121 | public WriteableBitmap PreviewBitmap 122 | { 123 | get { return _previewBitmap; } 124 | set 125 | { 126 | if (_previewBitmap != value) 127 | { 128 | _previewBitmap = value; 129 | OnPropertyChanged(nameof(PreviewBitmap)); 130 | } 131 | } 132 | } 133 | public CheckableRecordableCamera() : base() 134 | { 135 | 136 | } 137 | public CheckableRecordableCamera(string friendlyName, string deviceName) : base(friendlyName, deviceName) 138 | { 139 | 140 | } 141 | public CheckableRecordableCamera(RecordableCamera cam) : base(cam.FriendlyName, cam.DeviceName) 142 | { 143 | 144 | } 145 | 146 | public override string ToString() 147 | { 148 | return $"{FriendlyName} ({DeviceName})"; 149 | } 150 | 151 | public void UpdateScreenCoordinates(ScreenPoint position, ScreenSize size) 152 | { 153 | if (!IsCustomOutputSourceRectEnabled) 154 | { 155 | SourceRect = new ScreenRect(0, 0, size.Width, size.Height); 156 | } 157 | if (!IsCustomOutputSizeEnabled) 158 | { 159 | OutputSize = size; 160 | } 161 | if (!IsCustomPositionEnabled) 162 | { 163 | Position = position; 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /TestApp/Sources/CheckableRecordableDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Data; 10 | using System.Windows.Media.Imaging; 11 | using ScreenRecorderLib; 12 | 13 | namespace TestApp.Sources 14 | { 15 | public class RecordableDisplayToDeviceIdConverter : DependencyObject, IValueConverter 16 | { 17 | public List Displays 18 | { 19 | get { return (List)GetValue(DisplaysProperty); } 20 | set { SetValue(DisplaysProperty, value); } 21 | } 22 | 23 | // Using a DependencyProperty as the backing store for Displays. This enables animation, styling, binding, etc... 24 | public static readonly DependencyProperty DisplaysProperty = 25 | DependencyProperty.Register("Displays", typeof(List), typeof(RecordableDisplayToDeviceIdConverter), new PropertyMetadata(null)); 26 | 27 | 28 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 29 | { 30 | RecordableDisplay device; 31 | if (value is null) 32 | { 33 | device = Displays.FirstOrDefault(); 34 | } 35 | else 36 | { 37 | device = Displays.FirstOrDefault(x => value.ToString().Equals(x.DeviceName)); 38 | } 39 | return device; 40 | } 41 | 42 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 43 | { 44 | return (value as RecordableDisplay)?.DeviceName; 45 | } 46 | } 47 | 48 | public class CheckableRecordableDisplay : RecordableDisplay, ICheckableRecordingSource 49 | { 50 | private bool _isSelected; 51 | public bool IsSelected 52 | { 53 | get { return _isSelected; } 54 | set 55 | { 56 | if (_isSelected != value) 57 | { 58 | _isSelected = value; 59 | OnPropertyChanged(nameof(IsSelected)); 60 | } 61 | } 62 | } 63 | 64 | 65 | private bool _isCheckable = true; 66 | public bool IsCheckable 67 | { 68 | get { return _isCheckable; } 69 | set 70 | { 71 | if (_isCheckable != value) 72 | { 73 | _isCheckable = value; 74 | OnPropertyChanged(nameof(IsCheckable)); 75 | } 76 | } 77 | } 78 | 79 | private bool _isCustomPositionEnabled; 80 | public bool IsCustomPositionEnabled 81 | { 82 | get { return _isCustomPositionEnabled; } 83 | set 84 | { 85 | if (_isCustomPositionEnabled != value) 86 | { 87 | _isCustomPositionEnabled = value; 88 | OnPropertyChanged(nameof(IsCustomPositionEnabled)); 89 | } 90 | } 91 | } 92 | 93 | private bool _isCustomOutputSizeEnabled; 94 | public bool IsCustomOutputSizeEnabled 95 | { 96 | get { return _isCustomOutputSizeEnabled; } 97 | set 98 | { 99 | if (_isCustomOutputSizeEnabled != value) 100 | { 101 | _isCustomOutputSizeEnabled = value; 102 | OnPropertyChanged(nameof(IsCustomOutputSizeEnabled)); 103 | } 104 | } 105 | } 106 | 107 | private bool _isCustomOutputSourceRectEnabled; 108 | public bool IsCustomOutputSourceRectEnabled 109 | { 110 | get { return _isCustomOutputSourceRectEnabled; } 111 | set 112 | { 113 | if (_isCustomOutputSourceRectEnabled != value) 114 | { 115 | _isCustomOutputSourceRectEnabled = value; 116 | OnPropertyChanged(nameof(IsCustomOutputSourceRectEnabled)); 117 | } 118 | } 119 | } 120 | 121 | private WriteableBitmap _previewBitmap; 122 | public WriteableBitmap PreviewBitmap 123 | { 124 | get { return _previewBitmap; } 125 | set 126 | { 127 | if (_previewBitmap != value) 128 | { 129 | _previewBitmap = value; 130 | OnPropertyChanged(nameof(PreviewBitmap)); 131 | } 132 | } 133 | } 134 | public CheckableRecordableDisplay() : base() 135 | { 136 | 137 | } 138 | public CheckableRecordableDisplay(string friendlyName, string deviceName) : base(friendlyName, deviceName) 139 | { 140 | 141 | } 142 | public CheckableRecordableDisplay(RecordableDisplay disp) : base(disp.FriendlyName, disp.DeviceName) 143 | { 144 | 145 | } 146 | 147 | public override string ToString() 148 | { 149 | return $"{FriendlyName} ({DeviceName})"; 150 | } 151 | 152 | public void UpdateScreenCoordinates(ScreenPoint position, ScreenSize size) 153 | { 154 | if (!IsCustomOutputSourceRectEnabled) 155 | { 156 | SourceRect = new ScreenRect(0, 0, size.Width, size.Height); 157 | } 158 | if (!IsCustomOutputSizeEnabled) 159 | { 160 | OutputSize = size; 161 | } 162 | if (!IsCustomPositionEnabled) 163 | { 164 | Position = position; 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /TestApp/Sources/CheckableRecordableImage.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Media.Imaging; 8 | 9 | namespace TestApp.Sources 10 | { 11 | public class CheckableRecordableImage : ImageRecordingSource, ICheckableRecordingSource 12 | { 13 | private bool _isSelected; 14 | public bool IsSelected 15 | { 16 | get { return _isSelected; } 17 | set 18 | { 19 | if (_isSelected != value) 20 | { 21 | _isSelected = value; 22 | OnPropertyChanged(nameof(IsSelected)); 23 | } 24 | } 25 | } 26 | 27 | 28 | private bool _isCheckable = true; 29 | public bool IsCheckable 30 | { 31 | get { return _isCheckable; } 32 | set 33 | { 34 | if (_isCheckable != value) 35 | { 36 | _isCheckable = value; 37 | OnPropertyChanged(nameof(IsCheckable)); 38 | } 39 | } 40 | } 41 | 42 | private bool _isCustomPositionEnabled; 43 | public bool IsCustomPositionEnabled 44 | { 45 | get { return _isCustomPositionEnabled; } 46 | set 47 | { 48 | if (_isCustomPositionEnabled != value) 49 | { 50 | _isCustomPositionEnabled = value; 51 | OnPropertyChanged(nameof(IsCustomPositionEnabled)); 52 | } 53 | } 54 | } 55 | 56 | private bool _isCustomOutputSizeEnabled; 57 | public bool IsCustomOutputSizeEnabled 58 | { 59 | get { return _isCustomOutputSizeEnabled; } 60 | set 61 | { 62 | if (_isCustomOutputSizeEnabled != value) 63 | { 64 | _isCustomOutputSizeEnabled = value; 65 | OnPropertyChanged(nameof(IsCustomOutputSizeEnabled)); 66 | } 67 | } 68 | } 69 | 70 | private bool _isCustomOutputSourceRectEnabled; 71 | public bool IsCustomOutputSourceRectEnabled 72 | { 73 | get { return _isCustomOutputSourceRectEnabled; } 74 | set 75 | { 76 | if (_isCustomOutputSourceRectEnabled != value) 77 | { 78 | _isCustomOutputSourceRectEnabled = value; 79 | OnPropertyChanged(nameof(IsCustomOutputSourceRectEnabled)); 80 | } 81 | } 82 | } 83 | 84 | private WriteableBitmap _previewBitmap; 85 | public WriteableBitmap PreviewBitmap 86 | { 87 | get { return _previewBitmap; } 88 | set 89 | { 90 | if (_previewBitmap != value) 91 | { 92 | _previewBitmap = value; 93 | OnPropertyChanged(nameof(PreviewBitmap)); 94 | } 95 | } 96 | } 97 | 98 | public CheckableRecordableImage() : base() 99 | { 100 | 101 | } 102 | public CheckableRecordableImage(string filePath) : base(filePath) 103 | { 104 | 105 | } 106 | public CheckableRecordableImage(ImageRecordingSource image) : base(image.SourcePath) 107 | { 108 | 109 | } 110 | 111 | public override string ToString() 112 | { 113 | return System.IO.Path.GetFileName(SourcePath); 114 | } 115 | 116 | public void UpdateScreenCoordinates(ScreenPoint position, ScreenSize size) 117 | { 118 | if (!IsCustomOutputSourceRectEnabled) 119 | { 120 | SourceRect = new ScreenRect(0, 0, size.Width, size.Height); 121 | } 122 | if (!IsCustomOutputSizeEnabled) 123 | { 124 | OutputSize = size; 125 | } 126 | if (!IsCustomPositionEnabled) 127 | { 128 | Position = position; 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /TestApp/Sources/CheckableRecordableVideo.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Media.Imaging; 8 | 9 | namespace TestApp.Sources 10 | { 11 | public class CheckableRecordableVideo : VideoRecordingSource, ICheckableRecordingSource 12 | { 13 | private bool _isSelected; 14 | public bool IsSelected 15 | { 16 | get { return _isSelected; } 17 | set 18 | { 19 | if (_isSelected != value) 20 | { 21 | _isSelected = value; 22 | OnPropertyChanged(nameof(IsSelected)); 23 | } 24 | } 25 | } 26 | 27 | 28 | private bool _isCheckable = true; 29 | public bool IsCheckable 30 | { 31 | get { return _isCheckable; } 32 | set 33 | { 34 | if (_isCheckable != value) 35 | { 36 | _isCheckable = value; 37 | OnPropertyChanged(nameof(IsCheckable)); 38 | } 39 | } 40 | } 41 | 42 | private bool _isCustomPositionEnabled; 43 | public bool IsCustomPositionEnabled 44 | { 45 | get { return _isCustomPositionEnabled; } 46 | set 47 | { 48 | if (_isCustomPositionEnabled != value) 49 | { 50 | _isCustomPositionEnabled = value; 51 | OnPropertyChanged(nameof(IsCustomPositionEnabled)); 52 | } 53 | } 54 | } 55 | 56 | private bool _isCustomOutputSizeEnabled; 57 | public bool IsCustomOutputSizeEnabled 58 | { 59 | get { return _isCustomOutputSizeEnabled; } 60 | set 61 | { 62 | if (_isCustomOutputSizeEnabled != value) 63 | { 64 | _isCustomOutputSizeEnabled = value; 65 | OnPropertyChanged(nameof(IsCustomOutputSizeEnabled)); 66 | } 67 | } 68 | } 69 | 70 | private bool _isCustomOutputSourceRectEnabled; 71 | public bool IsCustomOutputSourceRectEnabled 72 | { 73 | get { return _isCustomOutputSourceRectEnabled; } 74 | set 75 | { 76 | if (_isCustomOutputSourceRectEnabled != value) 77 | { 78 | _isCustomOutputSourceRectEnabled = value; 79 | OnPropertyChanged(nameof(IsCustomOutputSourceRectEnabled)); 80 | } 81 | } 82 | } 83 | 84 | private WriteableBitmap _previewBitmap; 85 | public WriteableBitmap PreviewBitmap 86 | { 87 | get { return _previewBitmap; } 88 | set 89 | { 90 | if (_previewBitmap != value) 91 | { 92 | _previewBitmap = value; 93 | OnPropertyChanged(nameof(PreviewBitmap)); 94 | } 95 | } 96 | } 97 | 98 | public CheckableRecordableVideo() : base() 99 | { 100 | 101 | } 102 | public CheckableRecordableVideo(string filePath) : base(filePath) 103 | { 104 | 105 | } 106 | public CheckableRecordableVideo(VideoRecordingSource video) : base(video.SourcePath) 107 | { 108 | 109 | } 110 | 111 | public override string ToString() 112 | { 113 | return System.IO.Path.GetFileName(SourcePath); 114 | } 115 | 116 | public void UpdateScreenCoordinates(ScreenPoint position, ScreenSize size) 117 | { 118 | if (!IsCustomOutputSourceRectEnabled) 119 | { 120 | SourceRect = new ScreenRect(0, 0, size.Width, size.Height); 121 | } 122 | if (!IsCustomOutputSizeEnabled) 123 | { 124 | OutputSize = size; 125 | } 126 | if (!IsCustomPositionEnabled) 127 | { 128 | Position = position; 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /TestApp/Sources/CheckableRecordableWindow.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Data; 11 | using System.Windows.Media.Imaging; 12 | 13 | namespace TestApp.Sources 14 | { 15 | public class RecordableWindowToHandleConverter : DependencyObject, IValueConverter 16 | { 17 | public List Windows 18 | { 19 | get { return (List)GetValue(WindowsProperty); } 20 | set { SetValue(WindowsProperty, value); } 21 | } 22 | 23 | // Using a DependencyProperty as the backing store for Displays. This enables animation, styling, binding, etc... 24 | public static readonly DependencyProperty WindowsProperty = 25 | DependencyProperty.Register("Windows", typeof(List), typeof(RecordableWindowToHandleConverter), new PropertyMetadata(null)); 26 | 27 | 28 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 29 | { 30 | RecordableWindow device; 31 | if (value is null) 32 | { 33 | device = Windows.FirstOrDefault(); 34 | } 35 | else 36 | { 37 | device = Windows.FirstOrDefault(x => value.Equals(x.Handle)); 38 | } 39 | return device; 40 | } 41 | 42 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 43 | { 44 | return (value as RecordableWindow)?.Handle; 45 | } 46 | } 47 | 48 | public class CheckableRecordableWindow : RecordableWindow, ICheckableRecordingSource 49 | { 50 | private bool _isSelected; 51 | public bool IsSelected 52 | { 53 | get { return _isSelected; } 54 | set 55 | { 56 | if (_isSelected != value) 57 | { 58 | _isSelected = value; 59 | OnPropertyChanged(nameof(IsSelected)); 60 | } 61 | } 62 | } 63 | 64 | private bool _isCheckable; 65 | public bool IsCheckable 66 | { 67 | get { return _isCheckable; } 68 | set 69 | { 70 | if (_isCheckable != value) 71 | { 72 | _isCheckable = value; 73 | OnPropertyChanged(nameof(IsCheckable)); 74 | } 75 | } 76 | } 77 | 78 | private bool _isCustomPositionEnabled; 79 | public bool IsCustomPositionEnabled 80 | { 81 | get { return _isCustomPositionEnabled; } 82 | set 83 | { 84 | if (_isCustomPositionEnabled != value) 85 | { 86 | _isCustomPositionEnabled = value; 87 | OnPropertyChanged(nameof(IsCustomPositionEnabled)); 88 | } 89 | } 90 | } 91 | 92 | private bool _isCustomOutputSizeEnabled; 93 | public bool IsCustomOutputSizeEnabled 94 | { 95 | get { return _isCustomOutputSizeEnabled; } 96 | set 97 | { 98 | if (_isCustomOutputSizeEnabled != value) 99 | { 100 | _isCustomOutputSizeEnabled = value; 101 | OnPropertyChanged(nameof(IsCustomOutputSizeEnabled)); 102 | } 103 | } 104 | } 105 | 106 | private bool _isCustomOutputSourceRectEnabled; 107 | public bool IsCustomOutputSourceRectEnabled 108 | { 109 | get { return _isCustomOutputSourceRectEnabled; } 110 | set 111 | { 112 | if (_isCustomOutputSourceRectEnabled != value) 113 | { 114 | _isCustomOutputSourceRectEnabled = value; 115 | OnPropertyChanged(nameof(IsCustomOutputSourceRectEnabled)); 116 | } 117 | } 118 | } 119 | 120 | private WriteableBitmap _previewBitmap; 121 | public WriteableBitmap PreviewBitmap 122 | { 123 | get { return _previewBitmap; } 124 | set 125 | { 126 | if (_previewBitmap != value) 127 | { 128 | _previewBitmap = value; 129 | OnPropertyChanged(nameof(PreviewBitmap)); 130 | } 131 | } 132 | } 133 | 134 | public CheckableRecordableWindow(string title, IntPtr handle) : base(title, handle) 135 | { 136 | 137 | } 138 | public CheckableRecordableWindow(RecordableWindow window) : base(window.Title, window.Handle) 139 | { 140 | 141 | } 142 | public override string ToString() 143 | { 144 | return $"{Title}"; 145 | } 146 | 147 | public void UpdateScreenCoordinates(ScreenPoint position, ScreenSize size) 148 | { 149 | if (!IsCustomOutputSourceRectEnabled) 150 | { 151 | SourceRect = new ScreenRect(0, 0, size.Width, size.Height); 152 | } 153 | if (!IsCustomOutputSizeEnabled) 154 | { 155 | OutputSize = size; 156 | } 157 | if (!IsCustomPositionEnabled) 158 | { 159 | Position = position; 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /TestApp/Sources/ICheckableRecordingSource.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Media.Imaging; 10 | 11 | namespace TestApp.Sources 12 | { 13 | public interface ICheckableRecordingSource : INotifyPropertyChanged 14 | { 15 | string ID { get; } 16 | bool IsSelected { get; set; } 17 | bool IsCheckable { get; set; } 18 | 19 | ScreenSize OutputSize { get; set; } 20 | ScreenPoint Position { get; set; } 21 | ScreenRect SourceRect { get; set; } 22 | bool IsCustomPositionEnabled { get; set; } 23 | bool IsCustomOutputSizeEnabled { get; set; } 24 | bool IsCustomOutputSourceRectEnabled { get; set; } 25 | bool IsVideoCaptureEnabled { get; set; } 26 | WriteableBitmap PreviewBitmap { get; set; } 27 | void UpdateScreenCoordinates(ScreenPoint position, ScreenSize size); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TestApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /TestAppWinforms/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TestAppWinforms/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace TestAppWinforms 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new Form1()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TestAppWinforms/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestAppWinforms")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestAppWinforms")] 13 | [assembly: AssemblyCopyright("Copyright © 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("551cbe22-2bb4-40a5-af62-e5334ced17b7")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestAppWinforms/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TestAppWinforms.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestAppWinforms.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TestAppWinforms/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 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 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /TestAppWinforms/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TestAppWinforms.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TestAppWinforms/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestConsoleApp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TestConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Windows.Threading; 11 | 12 | namespace TestConsoleApp 13 | { 14 | class Program 15 | { 16 | private static bool _isRecording; 17 | private static Stopwatch _stopWatch; 18 | static void Main(string[] args) 19 | { 20 | //This is how you can select audio devices. If you want the system default device, 21 | //just leave the AudioInputDevice or AudioOutputDevice properties unset or pass null or empty string. 22 | var audioInputDevices = Recorder.GetSystemAudioDevices(AudioDeviceSource.InputDevices); 23 | var audioOutputDevices = Recorder.GetSystemAudioDevices(AudioDeviceSource.OutputDevices); 24 | string selectedAudioInputDevice = audioInputDevices.Count > 0 ? audioInputDevices.First().DeviceName : null; 25 | string selectedAudioOutputDevice = audioOutputDevices.Count > 0 ? audioOutputDevices.First().DeviceName : null; 26 | 27 | var opts = new RecorderOptions 28 | { 29 | AudioOptions = new AudioOptions 30 | { 31 | AudioInputDevice = selectedAudioInputDevice, 32 | AudioOutputDevice = selectedAudioOutputDevice, 33 | IsAudioEnabled = true, 34 | IsInputDeviceEnabled = true, 35 | IsOutputDeviceEnabled = true, 36 | } 37 | }; 38 | 39 | Recorder rec = Recorder.CreateRecorder(opts); 40 | rec.OnRecordingFailed += Rec_OnRecordingFailed; 41 | rec.OnRecordingComplete += Rec_OnRecordingComplete; 42 | rec.OnStatusChanged += Rec_OnStatusChanged; 43 | Console.WriteLine("Press ENTER to start recording or ESC to exit"); 44 | while (true) 45 | { 46 | ConsoleKeyInfo info = Console.ReadKey(true); 47 | if (info.Key == ConsoleKey.Enter) 48 | { 49 | break; 50 | } 51 | else if (info.Key == ConsoleKey.Escape) 52 | { 53 | return; 54 | } 55 | } 56 | string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss"); 57 | string filePath = Path.Combine(Path.GetTempPath(), "ScreenRecorder", timestamp, timestamp + ".mp4"); 58 | rec.Record(filePath); 59 | CancellationTokenSource cts = new CancellationTokenSource(); 60 | var token = cts.Token; 61 | Task.Run(async () => 62 | { 63 | while (true) 64 | { 65 | if (token.IsCancellationRequested) 66 | return; 67 | if (_isRecording) 68 | { 69 | Dispatcher.CurrentDispatcher.Invoke(() => 70 | { 71 | Console.Write(String.Format("\rElapsed: {0}",_stopWatch.Elapsed.ToString(@"mm\:ss\:fff"))); 72 | }); 73 | } 74 | await Task.Delay(10); 75 | } 76 | }, token); 77 | while (true) 78 | { 79 | ConsoleKeyInfo info = Console.ReadKey(true); 80 | if (info.Key == ConsoleKey.Escape) 81 | { 82 | break; 83 | } 84 | } 85 | cts.Cancel(); 86 | rec.Stop(); 87 | Console.WriteLine(); 88 | 89 | Console.ReadKey(); 90 | } 91 | 92 | private static void Rec_OnStatusChanged(object sender, RecordingStatusEventArgs e) 93 | { 94 | switch (e.Status) 95 | { 96 | case RecorderStatus.Idle: 97 | //Console.WriteLine("Recorder is idle"); 98 | break; 99 | case RecorderStatus.Recording: 100 | _stopWatch = new Stopwatch(); 101 | _stopWatch.Start(); 102 | _isRecording = true; 103 | Console.WriteLine("Recording started"); 104 | Console.WriteLine("Press ESC to stop recording"); 105 | break; 106 | case RecorderStatus.Paused: 107 | Console.WriteLine("Recording paused"); 108 | break; 109 | case RecorderStatus.Finishing: 110 | Console.WriteLine("Finishing encoding"); 111 | break; 112 | default: 113 | break; 114 | } 115 | } 116 | 117 | private static void Rec_OnRecordingComplete(object sender, RecordingCompleteEventArgs e) 118 | { 119 | Console.WriteLine("Recording completed"); 120 | _isRecording = false; 121 | _stopWatch?.Stop(); 122 | Console.WriteLine(String.Format("File: {0}", e.FilePath)); 123 | Console.WriteLine(); 124 | Console.WriteLine("Press any key to exit"); 125 | } 126 | 127 | private static void Rec_OnRecordingFailed(object sender, RecordingFailedEventArgs e) 128 | { 129 | Console.WriteLine("Recording failed with: " + e.Error); 130 | _isRecording = false; 131 | _stopWatch?.Stop(); 132 | Console.WriteLine(); 133 | Console.WriteLine("Press any key to exit"); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /TestConsoleApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestConsoleApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestConsoleApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fda56bbe-5019-47c5-af1e-386d123c02e3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestConsoleAppCpp/Demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace System::IO; 6 | using namespace System; 7 | using namespace ScreenRecorderLib; 8 | using namespace System::Threading; 9 | using namespace System::Threading::Tasks; 10 | using namespace System::Diagnostics; 11 | 12 | void PrintElapsedTime(Object^ obj); 13 | void Rec_OnStatusChanged(Object^ sender, RecordingStatusEventArgs^ e); 14 | void Rec_OnRecordingComplete(Object^ sender, RecordingCompleteEventArgs^ e); 15 | void Rec_OnRecordingFailed(System::Object^ sender, ScreenRecorderLib::RecordingFailedEventArgs^ e); 16 | 17 | constexpr int ESC = 27; 18 | constexpr int ENTER = 13; 19 | 20 | static bool _isRecording; 21 | gcroot _stopWatch; 22 | 23 | int main() 24 | { 25 | Recorder^ rec = Recorder::CreateRecorder(); 26 | rec->OnRecordingFailed += gcnew System::EventHandler(&Rec_OnRecordingFailed); 27 | rec->OnRecordingComplete += gcnew System::EventHandler(&Rec_OnRecordingComplete); 28 | rec->OnStatusChanged += gcnew System::EventHandler(&Rec_OnStatusChanged); 29 | 30 | Console::WriteLine("Press ENTER to start recording or ESC to exit"); 31 | 32 | while (true) 33 | { 34 | ConsoleKeyInfo info = Console::ReadKey(true); 35 | if (info.Key == ConsoleKey::Enter) 36 | { 37 | break; 38 | } 39 | else if (info.Key == ConsoleKey::Escape) 40 | { 41 | return 0; 42 | } 43 | } 44 | auto timestamp = DateTime::Now.ToString("yyyy-MM-dd HH-mm-ss"); 45 | auto filePath = Path::Combine(Path::GetTempPath(), "ScreenRecorder", timestamp, timestamp + ".mp4"); 46 | rec->Record(filePath); 47 | auto cts = gcnew CancellationTokenSource(); 48 | auto token = cts->Token; 49 | 50 | Task::Factory->StartNew((Action^)gcnew Action(&PrintElapsedTime), token); 51 | 52 | while (true) 53 | { 54 | ConsoleKeyInfo info = Console::ReadKey(true); 55 | if (info.Key == ConsoleKey::Escape) 56 | { 57 | break; 58 | } 59 | } 60 | cts->Cancel(); 61 | rec->Stop(); 62 | Console::WriteLine(); 63 | Console::ReadKey(); 64 | return 0; 65 | } 66 | 67 | void PrintElapsedTime(Object^ obj) { 68 | CancellationToken^ token = (CancellationToken^)obj; 69 | while (true) { 70 | if (token->IsCancellationRequested) 71 | return; 72 | if (_isRecording) 73 | { 74 | Console::Write(String::Format("\rElapsed: {0}s:{1}ms", _stopWatch->Elapsed.Seconds, _stopWatch->Elapsed.Milliseconds)); 75 | } 76 | Thread::Sleep(10); 77 | } 78 | } 79 | 80 | void Rec_OnStatusChanged(Object^ sender, RecordingStatusEventArgs^ e) 81 | { 82 | switch (e->Status) 83 | { 84 | case RecorderStatus::Idle: 85 | break; 86 | case RecorderStatus::Recording: 87 | _stopWatch = gcnew Stopwatch(); 88 | _stopWatch->Start(); 89 | _isRecording = true; 90 | Console::WriteLine("Recording started"); 91 | Console::WriteLine("Press ESC to stop recording"); 92 | break; 93 | case RecorderStatus::Paused: 94 | Console::WriteLine("Recording paused"); 95 | break; 96 | case RecorderStatus::Finishing: 97 | Console::WriteLine("Finishing encoding"); 98 | break; 99 | default: 100 | break; 101 | } 102 | } 103 | 104 | void Rec_OnRecordingComplete(Object^ sender, RecordingCompleteEventArgs^ e) 105 | { 106 | Console::WriteLine("Recording completed"); 107 | _isRecording = false; 108 | _stopWatch->Stop(); 109 | Console::WriteLine(String::Format("File: {0}", e->FilePath)); 110 | Console::WriteLine(); 111 | Console::WriteLine("Press any key to exit"); 112 | } 113 | 114 | void Rec_OnRecordingFailed(System::Object^ sender, ScreenRecorderLib::RecordingFailedEventArgs^ e) 115 | { 116 | Console::WriteLine("Recording failed with: " + e->Error); 117 | _isRecording = false; 118 | _stopWatch->Stop(); 119 | Console::WriteLine(); 120 | Console::WriteLine("Press any key to exit"); 121 | } -------------------------------------------------------------------------------- /TestConsoleAppCpp/TestConsoleAppCpp.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;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 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /TestConsoleAppDotNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using ScreenRecorderLib; 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Linq; 8 | 9 | 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | var source = DisplayRecordingSource.MainMonitor; 15 | source.RecorderApi = RecorderApi.WindowsGraphicsCapture; 16 | var opts = new RecorderOptions 17 | { 18 | SourceOptions = new SourceOptions 19 | { 20 | RecordingSources = { { source } } 21 | }, 22 | AudioOptions = new AudioOptions { IsInputDeviceEnabled = true, IsOutputDeviceEnabled = true, IsAudioEnabled=true } 23 | }; 24 | Recorder rec = Recorder.CreateRecorder(opts); 25 | Recorder rec2 = Recorder.CreateRecorder(opts); 26 | rec.OnRecordingComplete += (source, e) => 27 | { 28 | Console.WriteLine($"Recording complete: {e.FilePath}"); 29 | }; 30 | rec2.OnRecordingComplete += (source, e) => 31 | { 32 | Console.WriteLine($"Recording complete: {e.FilePath}"); 33 | }; 34 | 35 | Console.WriteLine("Press ENTER to start recording or ESC to exit"); 36 | while (true) 37 | { 38 | ConsoleKeyInfo info = Console.ReadKey(true); 39 | if (info.Key == ConsoleKey.Enter) 40 | { 41 | break; 42 | } 43 | else if (info.Key == ConsoleKey.Escape) 44 | { 45 | return; 46 | } 47 | } 48 | ManualResetEvent completionEvent = new ManualResetEvent(false); 49 | string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss"); 50 | Console.WriteLine("Starting recording"); 51 | int count = 50; 52 | Task.Run(async () => 53 | { 54 | for (int i = 0; i < count; i++) 55 | { 56 | var currentRecorder = i % 2 == 0 ? rec : rec2; 57 | var nextRecorder = i % 2 == 0 ? rec2 : rec; 58 | 59 | if (currentRecorder.Status == RecorderStatus.Paused) 60 | { 61 | currentRecorder.Resume(); 62 | } 63 | else 64 | { 65 | currentRecorder.Record(Path.Combine(Path.GetTempPath(), "ScreenRecorder", timestamp, i + ".mp4")); 66 | } 67 | 68 | await WaitForIdle(nextRecorder); 69 | if (i < count) 70 | { 71 | nextRecorder.Record(Path.Combine(Path.GetTempPath(), "ScreenRecorder", timestamp, i + 1 + ".mp4")); 72 | nextRecorder.Pause(); 73 | } 74 | await Task.Delay(5000); 75 | currentRecorder.Stop(); 76 | } 77 | completionEvent.Set(); 78 | }); 79 | 80 | completionEvent.WaitOne(); 81 | Console.WriteLine("Press any key to exit"); 82 | Console.ReadKey(); 83 | } 84 | 85 | private static async Task WaitForIdle(Recorder rec) 86 | { 87 | if (rec.Status == RecorderStatus.Idle) 88 | { 89 | return; 90 | } 91 | else 92 | { 93 | SemaphoreSlim semaphore = new SemaphoreSlim(0, 1); 94 | EventHandler handler = delegate (object s, RecordingStatusEventArgs args) 95 | { 96 | if (args.Status == RecorderStatus.Idle) 97 | { 98 | semaphore.Release(); 99 | } 100 | }; 101 | rec.OnStatusChanged += handler; 102 | await semaphore.WaitAsync(); 103 | rec.OnStatusChanged -= handler; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /TestConsoleAppDotNetCore/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "TestConsoleAppDotNetCore": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /TestConsoleAppDotNetCore/TestConsoleAppDotNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | AnyCPU;x86;x64;ARM64 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Testmedia/alphatest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Testmedia/alphatest.png -------------------------------------------------------------------------------- /Testmedia/cat.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Testmedia/cat.mp4 -------------------------------------------------------------------------------- /Testmedia/cat2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Testmedia/cat2.mp4 -------------------------------------------------------------------------------- /Testmedia/earth.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Testmedia/earth.gif -------------------------------------------------------------------------------- /Testmedia/giftest.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Testmedia/giftest.gif -------------------------------------------------------------------------------- /Testmedia/renault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sskodje/ScreenRecorderLib/5611b7266a5a87477313e892dd997bf720c1c5e4/Testmedia/renault.png -------------------------------------------------------------------------------- /Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2018")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("7199cba5-acdd-4751-b819-f74c5211ede1")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------