├── .appveyor.yml ├── .gitignore ├── LICENSE ├── README.md ├── autogen ├── OpenH264.Autogen │ ├── App.config │ ├── OpenH264.AutoGen.csproj │ ├── OpenH264Generator.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── Vpx.AutoGen │ ├── App.config │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Vpx.AutoGen.csproj │ ├── VpxGenerator.cs │ └── packages.config └── h264bsd.Autogen │ ├── App.config │ ├── Program.cs │ ├── h264bsd.AutoGen.csproj │ ├── h264bsd.AutoGen.sln │ ├── h264bsdGenerator.cs │ └── packages.config ├── lib ├── libvpx-LICENSE ├── libvpx_build_steps.txt ├── nuspec │ └── SIPSorceryMedia.Encoders.targets ├── x64 │ └── vpxmd.dll └── x86 │ └── vpxmd.dll ├── src ├── SIPSorceryMedia.Encoders.csproj ├── SIPSorceryMedia.Encoders.nuspec ├── SIPSorceryMedia.Encoders.sln ├── SymbolResolver.cs ├── VideoEncoderEndPoint.cs ├── VpxVideoEncoder.cs ├── codecs │ ├── OpenH264Encoder.cs │ ├── Vp8Codec.cs │ └── vpxmd.g.cs └── icon.png └── test ├── SIPSorceryMedia.Encoders.UnitTest ├── HexStr.cs ├── SIPSorceryMedia.Encoders.UnitTest.csproj ├── TestLogger.cs ├── VpxVideoEncoderUnitTest.cs └── img │ ├── testpattern_32x24.bmp │ ├── testpattern_32x24.i420 │ ├── testpattern_640x480.bmp │ ├── testpattern_640x480.i420 │ ├── testpattern_720x405.bmp │ └── testpattern_keyframe_640x480.vp8 ├── VpxCppTest ├── VpxCppTest.cpp ├── VpxCppTest.vcxproj ├── VpxCppTest.vcxproj.filters ├── strutils.h └── test-decode.bmp ├── VpxTest ├── Program.cs ├── VpxTest.csproj ├── ffplay-vp8.sdp └── media │ └── testpattern.jpeg └── h264bsdDecodeTest ├── h264bsd.cs ├── h264bsdDecodeTest.cs ├── h264bsdDecodeTest.csproj ├── h264bsdDecodeTest.sln └── test_640x360.h264 /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.{build} 2 | image: Visual Studio 2022 3 | configuration: Release 4 | before_build: 5 | - cmd: nuget restore -DisableParallelProcessing src\SIPSorceryMedia.Encoders.csproj 6 | build: 7 | project: src\SIPSorceryMedia.Encoders.csproj 8 | publish_nuget: false 9 | verbosity: quiet 10 | after_build: 11 | - dotnet pack src\SIPSorceryMedia.Encoders.csproj -p:NuspecFile=SIPSorceryMedia.Encoders.nuspec -c Release --no-build 12 | artifacts: 13 | - path: '**\*.nupkg' 14 | # - path: '**\*.snupkg' 15 | deploy: 16 | - provider: NuGet 17 | server: # remove to push to NuGet.org 18 | api_key: 19 | secure: GWtnKGaBRjWgQ8jTe+9zzlr83Gr15mS/poFyqLWWEeWAIndh0uyaBpAXxozsCcC5 20 | skip_symbols: false 21 | symbol_server: # remove to push symbols to SymbolSource.org 22 | artifact: /.*(\.|\.s)nupkg/ 23 | on: 24 | APPVEYOR_REPO_TAG: true # deploy on tag push only 25 | - provider: NuGet 26 | server: https://nuget.pkg.github.com/sipsorcery/index.json 27 | artifact: /.*(\.|\.s)nupkg/ 28 | username: sipsorcery 29 | api_key: 30 | secure: E58r+OknoQn8+bsPRT6l3U2K4kfOpDiGCo1C75LkVg+R/RBHpY//J8UCXEfVvyRB 31 | on: 32 | APPVEYOR_REPO_TAG: true # deploy on tag push only 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Aaron Clauson 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SIPSorceryMedia.Encoders 2 | 3 | This project is an example of developing a C# library that can use a video codec from a native library and that inegrates with the [SIPSorcery](https://github.com/sipsorcery-org/sipsorcery) real-time communications library. 4 | 5 | The classes in this project provide functions to: 6 | 7 | - VP8 video decoding. 8 | - VP8 video encdoing. 9 | -------------------------------------------------------------------------------- /autogen/OpenH264.Autogen/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /autogen/OpenH264.Autogen/OpenH264.AutoGen.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7E921C9C-9B25-4EF0-ACA9-003014D62FB7} 8 | Exe 9 | OpenH264.AutoGen 10 | OpenH264.AutoGen 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | true 39 | bin\x86\Debug\ 40 | DEBUG;TRACE 41 | full 42 | x86 43 | 7.3 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | true 47 | 48 | 49 | bin\x86\Release\ 50 | TRACE 51 | true 52 | pdbonly 53 | x86 54 | 7.3 55 | prompt 56 | MinimumRecommendedRules.ruleset 57 | true 58 | 59 | 60 | true 61 | bin\x64\Debug\ 62 | DEBUG;TRACE 63 | full 64 | x64 65 | 7.3 66 | prompt 67 | MinimumRecommendedRules.ruleset 68 | true 69 | 70 | 71 | bin\x64\Release\ 72 | TRACE 73 | true 74 | pdbonly 75 | x64 76 | 7.3 77 | prompt 78 | MinimumRecommendedRules.ruleset 79 | true 80 | 81 | 82 | 83 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.dll 84 | 85 | 86 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.AST.dll 87 | 88 | 89 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Generator.dll 90 | 91 | 92 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Parser.dll 93 | 94 | 95 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Parser.CLI.dll 96 | 97 | 98 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Runtime.dll 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /autogen/OpenH264.Autogen/OpenH264Generator.cs: -------------------------------------------------------------------------------- 1 | using CppSharp; 2 | using CppSharp.AST; 3 | using CppSharp.Generators; 4 | 5 | namespace OpenH264.AutoGen 6 | { 7 | public class OpenH264Generator : ILibrary 8 | { 9 | public void Postprocess(Driver driver, ASTContext ctx) 10 | { 11 | //throw new NotImplementedException(); 12 | } 13 | 14 | public void Preprocess(Driver driver, ASTContext ctx) 15 | { 16 | //throw new NotImplementedException(); 17 | } 18 | 19 | public void Setup(Driver driver) 20 | { 21 | var options = driver.Options; 22 | options.GeneratorKind = GeneratorKind.CSharp; 23 | var module = options.AddModule("openh264"); 24 | // TODO. 25 | //module.IncludeDirs.Add(@"C:\Dev\github\openh264\"); 26 | //module.Headers.Add("vpx_encoder.h"); 27 | //module.Headers.Add("vpx_decoder.h"); 28 | //module.Headers.Add("vp8cx.h"); 29 | //module.Headers.Add("vp8dx.h"); 30 | //module.LibraryDirs.Add(@"C:\Dev\sipsorcery\SIPSorceryMedia.Windows\lib\x64"); 31 | //module.Libraries.Add("vpxmd.dll"); 32 | } 33 | 34 | public void SetupPasses(Driver driver) 35 | { 36 | //throw new NotImplementedException(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /autogen/OpenH264.Autogen/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CppSharp; 7 | 8 | namespace OpenH264.AutoGen 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | Console.WriteLine("OpenH264 C# bindings auto-generator."); 15 | ConsoleDriver.Run(new OpenH264Generator()); 16 | Console.WriteLine("Finished."); 17 | Console.ReadLine(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /autogen/OpenH264.Autogen/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("Vpx.AutoGen")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Vpx.AutoGen")] 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("0c2d25bb-65ca-462a-8d50-9880bba1bebe")] 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 | -------------------------------------------------------------------------------- /autogen/OpenH264.Autogen/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /autogen/Vpx.AutoGen/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /autogen/Vpx.AutoGen/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using CppSharp; 7 | 8 | namespace Vpx.AutoGen 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | Console.WriteLine("libvpx C# bindings auto-generator."); 15 | ConsoleDriver.Run(new VpxGenerator()); 16 | Console.WriteLine("Finished."); 17 | Console.ReadLine(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /autogen/Vpx.AutoGen/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("Vpx.AutoGen")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Vpx.AutoGen")] 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("0c2d25bb-65ca-462a-8d50-9880bba1bebe")] 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 | -------------------------------------------------------------------------------- /autogen/Vpx.AutoGen/Vpx.AutoGen.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE} 8 | Exe 9 | Vpx.AutoGen 10 | Vpx.AutoGen 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | true 39 | bin\x86\Debug\ 40 | DEBUG;TRACE 41 | full 42 | x86 43 | 7.3 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | true 47 | 48 | 49 | bin\x86\Release\ 50 | TRACE 51 | true 52 | pdbonly 53 | x86 54 | 7.3 55 | prompt 56 | MinimumRecommendedRules.ruleset 57 | true 58 | 59 | 60 | true 61 | bin\x64\Debug\ 62 | DEBUG;TRACE 63 | full 64 | x64 65 | 7.3 66 | prompt 67 | MinimumRecommendedRules.ruleset 68 | true 69 | 70 | 71 | bin\x64\Release\ 72 | TRACE 73 | true 74 | pdbonly 75 | x64 76 | 7.3 77 | prompt 78 | MinimumRecommendedRules.ruleset 79 | true 80 | 81 | 82 | 83 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.dll 84 | 85 | 86 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.AST.dll 87 | 88 | 89 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Generator.dll 90 | 91 | 92 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Parser.dll 93 | 94 | 95 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Parser.CLI.dll 96 | 97 | 98 | ..\..\src\packages\CppSharp.0.10.5\lib\CppSharp.Runtime.dll 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /autogen/Vpx.AutoGen/VpxGenerator.cs: -------------------------------------------------------------------------------- 1 | using CppSharp; 2 | using CppSharp.AST; 3 | using CppSharp.Generators; 4 | using System; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace Vpx.AutoGen 9 | { 10 | public class VpxGenerator : ILibrary 11 | { 12 | public static string[] _requiredDefines = { 13 | "VPX_CODEC_ABI_VERSION", 14 | "VPX_ENCODER_ABI_VERSION", 15 | "VPX_DECODER_ABI_VERSION", 16 | "VPX_DL_REALTIME", 17 | "VPX_EFLAG_FORCE_KF", 18 | "VPX_FRAME_IS_KEY" 19 | }; 20 | 21 | public void Postprocess(Driver driver, ASTContext ctx) 22 | { 23 | foreach(var tu in ctx.TranslationUnits) 24 | { 25 | foreach (var macro in tu.PreprocessedEntities.OfType().Where(x => _requiredDefines.Any(y => y == x.Name))) 26 | { 27 | Console.WriteLine($"{macro.Name}->{macro.Expression}"); 28 | 29 | // Do Something? 30 | } 31 | } 32 | } 33 | 34 | public void Preprocess(Driver driver, ASTContext ctx) 35 | { 36 | //throw new NotImplementedException(); 37 | } 38 | 39 | public void Setup(Driver driver) 40 | { 41 | var options = driver.Options; 42 | options.GeneratorKind = GeneratorKind.CSharp; 43 | var module = options.AddModule("vpxmd"); 44 | module.IncludeDirs.Add(@"C:\Dev\github\libvpx\vpx\"); 45 | module.Headers.Add("vpx_encoder.h"); 46 | module.Headers.Add("vpx_decoder.h"); 47 | module.Headers.Add("vp8cx.h"); 48 | module.Headers.Add("vp8dx.h"); 49 | module.LibraryDirs.Add(@"C:\Dev\sipsorcery\SIPSorceryMedia.Windows\lib\x64"); 50 | module.Libraries.Add("vpxmd.dll"); 51 | } 52 | 53 | public void SetupPasses(Driver driver) 54 | { 55 | //throw new NotImplementedException(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /autogen/Vpx.AutoGen/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /autogen/h264bsd.Autogen/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /autogen/h264bsd.Autogen/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CppSharp; 3 | 4 | namespace h264bsd.AutoGen 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Console.WriteLine("h264bsd C# bindings auto-generator."); 11 | ConsoleDriver.Run(new h264bsdGenerator()); 12 | Console.WriteLine("Finished."); 13 | Console.ReadLine(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /autogen/h264bsd.Autogen/h264bsd.AutoGen.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE} 8 | Exe 9 | h264bsd.AutoGen 10 | h264bsd.AutoGen 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | true 39 | bin\x86\Debug\ 40 | DEBUG;TRACE 41 | full 42 | x86 43 | 7.3 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | true 47 | 48 | 49 | bin\x86\Release\ 50 | TRACE 51 | true 52 | pdbonly 53 | x86 54 | 7.3 55 | prompt 56 | MinimumRecommendedRules.ruleset 57 | true 58 | 59 | 60 | true 61 | bin\x64\Debug\ 62 | DEBUG;TRACE 63 | full 64 | x64 65 | 7.3 66 | prompt 67 | MinimumRecommendedRules.ruleset 68 | true 69 | 70 | 71 | bin\x64\Release\ 72 | TRACE 73 | true 74 | pdbonly 75 | x64 76 | 7.3 77 | prompt 78 | MinimumRecommendedRules.ruleset 79 | true 80 | 81 | 82 | 83 | packages\CppSharp.0.10.5\lib\CppSharp.dll 84 | 85 | 86 | packages\CppSharp.0.10.5\lib\CppSharp.AST.dll 87 | 88 | 89 | packages\CppSharp.0.10.5\lib\CppSharp.Generator.dll 90 | 91 | 92 | packages\CppSharp.0.10.5\lib\CppSharp.Parser.dll 93 | 94 | 95 | packages\CppSharp.0.10.5\lib\CppSharp.Parser.CLI.dll 96 | 97 | 98 | packages\CppSharp.0.10.5\lib\CppSharp.Runtime.dll 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /autogen/h264bsd.Autogen/h264bsd.AutoGen.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30524.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "h264bsd.AutoGen", "h264bsd.AutoGen.csproj", "{0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Debug|x64.ActiveCfg = Debug|x64 21 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Debug|x64.Build.0 = Debug|x64 22 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Debug|x86.ActiveCfg = Debug|x86 23 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Debug|x86.Build.0 = Debug|x86 24 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Release|x64.ActiveCfg = Release|x64 27 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Release|x64.Build.0 = Release|x64 28 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Release|x86.ActiveCfg = Release|x86 29 | {0C2D25BB-65CA-462A-8D50-9880BBA1BEBE}.Release|x86.Build.0 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {C1026829-EA40-43BC-8751-DD370F237E2A} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /autogen/h264bsd.Autogen/h264bsdGenerator.cs: -------------------------------------------------------------------------------- 1 | using CppSharp; 2 | using CppSharp.AST; 3 | using CppSharp.Generators; 4 | using System; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace h264bsd.AutoGen 9 | { 10 | public class h264bsdGenerator : ILibrary 11 | { 12 | //public static string[] _requiredDefines = { 13 | // "VPX_CODEC_ABI_VERSION", 14 | // "VPX_ENCODER_ABI_VERSION", 15 | // "VPX_DECODER_ABI_VERSION", 16 | // "VPX_DL_REALTIME", 17 | // "VPX_EFLAG_FORCE_KF", 18 | // "VPX_FRAME_IS_KEY" 19 | //}; 20 | 21 | public void Postprocess(Driver driver, ASTContext ctx) 22 | { 23 | //foreach(var tu in ctx.TranslationUnits) 24 | //{ 25 | // foreach (var macro in tu.PreprocessedEntities.OfType().Where(x => _requiredDefines.Any(y => y == x.Name))) 26 | // { 27 | // Console.WriteLine($"{macro.Name}->{macro.Expression}"); 28 | 29 | // // Do Something? 30 | // } 31 | //} 32 | } 33 | 34 | public void Preprocess(Driver driver, ASTContext ctx) 35 | { 36 | //throw new NotImplementedException(); 37 | } 38 | 39 | public void Setup(Driver driver) 40 | { 41 | var options = driver.Options; 42 | options.GeneratorKind = GeneratorKind.CSharp; 43 | var module = options.AddModule("h264bsd"); 44 | module.IncludeDirs.Add(@"C:\Dev\github\h264bsd\src"); 45 | module.Headers.Add("h264bsd_decoder.h"); 46 | module.LibraryDirs.Add(@"C:\Dev\github\h264bsd\win\bin\Debug\x64"); 47 | module.Libraries.Add("h264bsd.dll"); 48 | module.Libraries.Add("h264bsd.lib"); 49 | } 50 | 51 | public void SetupPasses(Driver driver) 52 | { 53 | //throw new NotImplementedException(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /autogen/h264bsd.Autogen/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /lib/libvpx-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, The WebM Project authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | * Neither the name of Google, nor the WebM Project, nor the names 16 | of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written 18 | permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- /lib/libvpx_build_steps.txt: -------------------------------------------------------------------------------- 1 | Building libvpx: 2 | git clone https://github.com/webmproject/libvpx.git 3 | 4 | ==> 64 bit build 5 | mkdir build-win-x64 6 | cd build-win-64 7 | ../configure --disable-static --disable-examples --disable-tools --disable-docs --target=x86_64-win64-vs16 8 | make 9 | Probably fail due to msbuild not in path but should still produce a vpx.sln 10 | open vpx.sln: 11 | - change "vpx" General->Configuration Type from "Static Library" to "Dynamic Library". 12 | - set the mdoule definition file, Linker->Input->Module Definition File to vpx.def, the file should have been created by the make step. 13 | change to Release build 14 | build 15 | 16 | ==> 32 bit build 17 | mkdir build-win-x86 18 | cd build-win-x86 19 | ../configure --disable-static --disable-examples --disable-tools --disable-docs --target=x86-win32-vs16 20 | make 21 | Probably fail due to msbuild not in path but should still produce a vpx.sln 22 | open vpx.sln: 23 | - change "vpx" General->Configuration Type from "Static Library" to "Dynamic Library". 24 | - set the mdoule definition file, Linker->Input->Module Definition File to vpx.def, the file should have been created by the make step. 25 | change to Release build 26 | build 27 | -------------------------------------------------------------------------------- /lib/nuspec/SIPSorceryMedia.Encoders.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Always 7 | %(Filename)%(Extension) 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/x64/vpxmd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/lib/x64/vpxmd.dll -------------------------------------------------------------------------------- /lib/x86/vpxmd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/lib/x86/vpxmd.dll -------------------------------------------------------------------------------- /src/SIPSorceryMedia.Encoders.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461;net8.0 5 | 10.0 6 | true 7 | 8 | $(NoWarn);CS1591;CS1573 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Always 26 | %(Filename)%(Extension) 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/SIPSorceryMedia.Encoders.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SIPSorceryMedia.Encoders 5 | Aaron Clauson 6 | Copyright © 2020-2025 Aaron Clauson 7 | BSD-3-Clause 8 | SIPSorceryMedia.Encoders 9 | Audio and video codecs with media end point wrappers. 10 | Provides audio and video encode and decode capabilities plus media end points that can be used with the main SIPSorcery real-time communications library. 11 | http://www.sipsorcery.com/mainsite/favicon.ico 12 | icon.png 13 | https://github.com/sipsorcery-org/SIPSorceryMedia.Encoders 14 | 15 | WebRTC VoIP SIPSorcery Audio Video Codecs Encoders Decoders 16 | 17 | 18 | 19 | 20 | 21 | -v8.0.7: Updated to use latest abstractions nuget package. 22 | -v0.0.13: Updated to use latest abstractions nuget package. 23 | -v0.0.12-pre: Bug fixes. 24 | -v0.0.10-pre: Updated to use latest abstractons package with change to IAudioEncoder and IVideoEncoder interfaces. 25 | -v0.0.9-pre: Updated to use latest abstractions nuget package and video format parameter on IVideoSink.GotVideoFrame. 26 | -v0.0.8-pre: Updated VP8 codec to handle frames with uneven dimensions. 27 | -v0.0.7-pre: Updated VP8 video encoder class to support the IVideoEncoder interface. 28 | -v0.0.6-pre: Updated to use latest abstractions nuget package. 29 | -v0.0.5-pre: Updated to use latest abstractions nuget package. 30 | -v0.0.4-pre: Updated to use latest abstractions nuget package. 31 | -v0.0.3-pre: Use pixel conversion class from abstractions package. 32 | -v0.0.2-pre: Added support for I420 input samples. 33 | -v0.0.1-pre: Initial pre-release 34 | 8.0.7 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/SIPSorceryMedia.Encoders.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30320.27 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SIPSorceryMedia.Encoders", "SIPSorceryMedia.Encoders.csproj", "{A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E1699410-8154-46AF-92CF-BAD6E16E15F4}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SIPSorceryMedia.Encoders.UnitTest", "..\test\SIPSorceryMedia.Encoders.UnitTest\SIPSorceryMedia.Encoders.UnitTest.csproj", "{E700A8AA-B2DE-45BA-A9E4-F3665720265D}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Debug|x64.ActiveCfg = Debug|Any CPU 25 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Debug|x64.Build.0 = Debug|Any CPU 26 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Debug|x86.Build.0 = Debug|Any CPU 28 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Release|x64.ActiveCfg = Release|Any CPU 31 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Release|x64.Build.0 = Release|Any CPU 32 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Release|x86.ActiveCfg = Release|Any CPU 33 | {A9A6BE8C-A7B2-4B7F-9401-3B13676DC5FB}.Release|x86.Build.0 = Release|Any CPU 34 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Debug|x64.ActiveCfg = Debug|Any CPU 37 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Debug|x64.Build.0 = Debug|Any CPU 38 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Debug|x86.ActiveCfg = Debug|Any CPU 39 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Debug|x86.Build.0 = Debug|Any CPU 40 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Release|x64.ActiveCfg = Release|Any CPU 43 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Release|x64.Build.0 = Release|Any CPU 44 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Release|x86.ActiveCfg = Release|Any CPU 45 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D}.Release|x86.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {E700A8AA-B2DE-45BA-A9E4-F3665720265D} = {E1699410-8154-46AF-92CF-BAD6E16E15F4} 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {49C7CDF5-E940-4CEB-AE5D-4A8377759FC7} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /src/SymbolResolver.cs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013 Xamarin, Inc and contributors 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining 4 | * a copy of this software and associated documentation files (the 5 | * "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, 7 | * distribute, sublicense, and/or sell copies of the Software, and to 8 | * permit persons to whom the Software is furnished to do so, subject to 9 | * the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be 12 | * included in all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 21 | 22 | using System; 23 | using System.Collections.Generic; 24 | using System.IO; 25 | using System.Reflection; 26 | using System.Runtime.InteropServices; 27 | 28 | namespace CppSharp 29 | { 30 | public static class SymbolResolver 31 | { 32 | static readonly string[] formats; 33 | static readonly Func loadImage; 34 | static readonly Func resolveSymbol; 35 | 36 | static SymbolResolver() 37 | { 38 | switch (Environment.OSVersion.Platform) 39 | { 40 | case PlatformID.Unix: 41 | case PlatformID.MacOSX: 42 | loadImage = dlopen; 43 | resolveSymbol = dlsym; 44 | formats = new[] { 45 | "{0}", 46 | "{0}.so", 47 | "{0}.dylib", 48 | "lib{0}.so", 49 | "lib{0}.dylib", 50 | "{0}.bundle" 51 | }; 52 | break; 53 | default: 54 | loadImage = LoadLibrary; 55 | resolveSymbol = GetProcAddress; 56 | formats = new[] { "{0}", "{0}.dll" }; 57 | break; 58 | } 59 | } 60 | 61 | public static IntPtr LoadImage(ref string name) 62 | { 63 | var pathValues = Environment.GetEnvironmentVariable("PATH"); 64 | var paths = new List(pathValues == null ? new string[0] : 65 | pathValues.Split(Path.PathSeparator)); 66 | paths.Insert(0, Directory.GetCurrentDirectory()); 67 | paths.Insert(0, Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); 68 | 69 | foreach (var format in formats) 70 | { 71 | // Search the Current or specified directory for the library 72 | string filename = string.Format(format, name); 73 | string attempted = null; 74 | foreach (var path in paths) 75 | { 76 | var fullPath = Path.Combine(path, filename); 77 | if (File.Exists(fullPath)) 78 | { 79 | attempted = fullPath; 80 | break; 81 | } 82 | } 83 | if (!File.Exists(attempted)) 84 | continue; 85 | 86 | var ptr = loadImage(attempted); 87 | 88 | if (ptr == IntPtr.Zero) 89 | continue; 90 | 91 | name = attempted; 92 | return ptr; 93 | } 94 | 95 | return IntPtr.Zero; 96 | } 97 | 98 | public static IntPtr ResolveSymbol(string name, string symbol) 99 | { 100 | var image = LoadImage(ref name); 101 | return ResolveSymbol(image, symbol); 102 | } 103 | 104 | public static IntPtr ResolveSymbol(IntPtr image, string symbol) 105 | { 106 | if (image != IntPtr.Zero) 107 | return resolveSymbol(image, symbol); 108 | 109 | return IntPtr.Zero; 110 | } 111 | 112 | #region POSIX 113 | 114 | private const int RTLD_LAZY = 0x1; 115 | 116 | static IntPtr dlopen(string path) 117 | { 118 | return dlopen(path, RTLD_LAZY); 119 | } 120 | 121 | [DllImport("dl", CharSet = CharSet.Ansi)] 122 | static extern IntPtr dlopen(string path, int flags); 123 | 124 | [DllImport("dl", CharSet = CharSet.Ansi)] 125 | static extern IntPtr dlsym(IntPtr handle, string symbol); 126 | 127 | #endregion 128 | 129 | #region Win32 130 | 131 | [DllImport("kernel32", SetLastError = true)] 132 | static extern IntPtr LoadLibrary(string lpFileName); 133 | 134 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 135 | static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 136 | 137 | #endregion 138 | 139 | } 140 | } -------------------------------------------------------------------------------- /src/VideoEncoderEndPoint.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: VideoEncoderEndPoint.cs 3 | // 4 | // Description: Implements a video source and sink that is for encode/decode 5 | // only, i.e. no hooks for system audio or video devices. 6 | // 7 | // Author(s): 8 | // Aaron Clauson (aaron@sipsorcery.com) 9 | // 10 | // History: 11 | // 20 Aug 2020 Aaron Clauson Created, Dublin, Ireland. 12 | // 29 Sep 2020 Aaron Clauson Moved from SIPSorceryMedia.Windows assembly into 13 | // a new dedicated project for x-platform support. 14 | // 15 | // License: 16 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 17 | //----------------------------------------------------------------------------- 18 | 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Net; 22 | using System.Threading.Tasks; 23 | using Microsoft.Extensions.Logging; 24 | using SIPSorceryMedia.Abstractions; 25 | 26 | namespace SIPSorceryMedia.Encoders 27 | { 28 | public class VideoEncoderEndPoint : IVideoSource, IVideoSink, IDisposable 29 | { 30 | private const int VIDEO_SAMPLING_RATE = 90000; 31 | private const int DEFAULT_FRAMES_PER_SECOND = 30; 32 | private const int VP8_FORMAT_ID = 96; 33 | 34 | private ILogger logger = SIPSorcery.LogFactory.CreateLogger(); 35 | 36 | public static readonly List SupportedFormats = new List 37 | { 38 | new VideoFormat(VideoCodecsEnum.VP8, VP8_FORMAT_ID, VIDEO_SAMPLING_RATE) 39 | }; 40 | 41 | private MediaFormatManager _formatManager; 42 | private VpxVideoEncoder _videoEncoder; 43 | private bool _isClosed; 44 | 45 | /// 46 | /// This video source DOES NOT generate raw samples. Subscribe to the encoded samples event 47 | /// to get samples ready for passing to the RTP transport layer. 48 | /// 49 | [Obsolete("This video source only generates encoded samples. No raw video samples will be supplied to this event.")] 50 | public event RawVideoSampleDelegate OnVideoSourceRawSample { add { } remove { } } 51 | 52 | /// 53 | /// This event will be fired whenever a video sample is encoded and is ready to transmit to the remote party. 54 | /// 55 | public event EncodedSampleDelegate OnVideoSourceEncodedSample; 56 | 57 | /// 58 | /// This event is fired after the sink decodes a video frame from the remote party. 59 | /// 60 | public event VideoSinkSampleDecodedDelegate OnVideoSinkDecodedSample; 61 | 62 | #pragma warning disable CS0067 63 | public event SourceErrorDelegate OnVideoSourceError; 64 | public event RawVideoSampleFasterDelegate OnVideoSourceRawSampleFaster; 65 | public event VideoSinkSampleDecodedFasterDelegate OnVideoSinkDecodedSampleFaster; 66 | #pragma warning restore CS0067 67 | 68 | /// 69 | /// Creates a new video source that can encode and decode samples. 70 | /// 71 | public VideoEncoderEndPoint() 72 | { 73 | _formatManager = new MediaFormatManager(SupportedFormats); 74 | _videoEncoder = new VpxVideoEncoder(); 75 | } 76 | 77 | public void RestrictFormats(Func filter) => _formatManager.RestrictFormats(filter); 78 | public List GetVideoSourceFormats() => _formatManager.GetSourceFormats(); 79 | public void SetVideoSourceFormat(VideoFormat videoFormat) => _formatManager.SetSelectedFormat(videoFormat); 80 | public List GetVideoSinkFormats() => _formatManager.GetSourceFormats(); 81 | public void SetVideoSinkFormat(VideoFormat videoFormat) => _formatManager.SetSelectedFormat(videoFormat); 82 | 83 | public void ForceKeyFrame() => _videoEncoder.ForceKeyFrame(); 84 | public void GotVideoRtp(IPEndPoint remoteEndPoint, uint ssrc, uint seqnum, uint timestamp, int payloadID, bool marker, byte[] payload) => 85 | throw new ApplicationException("The Windows Video End Point requires full video frames rather than individual RTP packets."); 86 | public bool HasEncodedVideoSubscribers() => OnVideoSourceEncodedSample != null; 87 | public bool IsVideoSourcePaused() => false; 88 | public Task PauseVideo() => Task.CompletedTask; 89 | public Task ResumeVideo() => Task.CompletedTask; 90 | public Task StartVideo() => Task.CompletedTask; 91 | public Task CloseVideoSink() => Task.CompletedTask; 92 | public Task PauseVideoSink() => Task.CompletedTask; 93 | public Task ResumeVideoSink() => Task.CompletedTask; 94 | public Task StartVideoSink() => Task.CompletedTask; 95 | 96 | public MediaEndPoints ToMediaEndPoints() 97 | { 98 | return new MediaEndPoints 99 | { 100 | VideoSource = this, 101 | VideoSink = this 102 | }; 103 | } 104 | 105 | public void ExternalVideoSourceRawSample(uint durationMilliseconds, int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat) 106 | { 107 | if (!_isClosed) 108 | { 109 | if (OnVideoSourceEncodedSample != null) 110 | { 111 | var encodedBuffer = _videoEncoder.EncodeVideo(width, height, sample, pixelFormat, VideoCodecsEnum.VP8); 112 | 113 | if (encodedBuffer != null) 114 | { 115 | uint fps = (durationMilliseconds > 0) ? 1000 / durationMilliseconds : DEFAULT_FRAMES_PER_SECOND; 116 | uint durationRtpTS = VIDEO_SAMPLING_RATE / fps; 117 | OnVideoSourceEncodedSample.Invoke(durationRtpTS, encodedBuffer); 118 | } 119 | } 120 | } 121 | } 122 | 123 | public void GotVideoFrame(IPEndPoint remoteEndPoint, uint timestamp, byte[] frame, VideoFormat format) 124 | { 125 | if (!_isClosed) 126 | { 127 | foreach (var decoded in _videoEncoder.DecodeVideo(frame, VideoPixelFormatsEnum.Bgr, VideoCodecsEnum.VP8)) 128 | { 129 | OnVideoSinkDecodedSample(decoded.Sample, decoded.Width, decoded.Height, (int)(decoded.Width * 3), VideoPixelFormatsEnum.Bgr); 130 | } 131 | } 132 | } 133 | 134 | public Task CloseVideo() 135 | { 136 | if (!_isClosed) 137 | { 138 | _isClosed = true; 139 | Dispose(); 140 | } 141 | 142 | return Task.CompletedTask; 143 | } 144 | 145 | public void Dispose() 146 | { 147 | _videoEncoder?.Dispose(); 148 | } 149 | 150 | public void ExternalVideoSourceRawSampleFaster(uint durationMilliseconds, RawImage rawImage) 151 | { 152 | throw new NotImplementedException(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/VpxVideoEncoder.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: VpxVideoEncoder.cs 3 | // 4 | // Description: Implements a VP8 video encoder. 5 | // 6 | // Author(s): 7 | // Aaron Clauson (aaron@sipsorcery.com) 8 | // 9 | // History: 10 | // 20 Aug 2020 Aaron Clauson Created, Dublin, Ireland. 11 | // 17 Dec 2020 Aaron Clauson Renamed from VideoEncoder to VpxVideoEncoder. 12 | // 13 | // License: 14 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 15 | //----------------------------------------------------------------------------- 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using Microsoft.Extensions.Logging; 20 | using SIPSorceryMedia.Abstractions; 21 | using SIPSorceryMedia.Encoders.Codecs; 22 | 23 | namespace SIPSorceryMedia.Encoders 24 | { 25 | public class VpxVideoEncoder : IVideoEncoder, IDisposable 26 | { 27 | public const int VP8_FORMATID = 96; 28 | 29 | private ILogger logger = SIPSorcery.LogFactory.CreateLogger(); 30 | 31 | private static readonly List _supportedFormats = new List 32 | { 33 | new VideoFormat(VideoCodecsEnum.VP8, VP8_FORMATID) 34 | }; 35 | 36 | public List SupportedFormats 37 | { 38 | get => _supportedFormats; 39 | } 40 | 41 | uint? _targetKbps; 42 | public uint? TargetKbps 43 | { 44 | get => _targetKbps; 45 | set 46 | { 47 | lock (_encoderLock) 48 | { 49 | if (_vp8Encoder != null) 50 | { 51 | _vp8Encoder.Dispose(); 52 | _vp8Encoder = null; 53 | } 54 | 55 | _forceKeyFrame = true; 56 | _targetKbps = value; 57 | } 58 | } 59 | } 60 | 61 | private Vp8Codec _vp8Encoder; 62 | private Vp8Codec _vp8Decoder; 63 | private bool _forceKeyFrame = false; 64 | private Object _decoderLock = new object(); 65 | private Object _encoderLock = new object(); 66 | 67 | /// 68 | /// Creates a new video encoder can encode and decode samples. 69 | /// 70 | public VpxVideoEncoder() 71 | { } 72 | 73 | public void ForceKeyFrame() => _forceKeyFrame = true; 74 | 75 | public byte[] EncodeVideo(int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat, VideoCodecsEnum codec) 76 | { 77 | lock (_encoderLock) 78 | { 79 | if (_vp8Encoder == null) 80 | { 81 | _vp8Encoder = new Vp8Codec(); 82 | _vp8Encoder.InitialiseEncoder((uint)width, (uint)height, targetKbps: _targetKbps); 83 | } 84 | 85 | byte[] encodedBuffer = null; 86 | 87 | if (pixelFormat == VideoPixelFormatsEnum.NV12) 88 | { 89 | encodedBuffer = _vp8Encoder.Encode(sample, vpxmd.VpxImgFmt.VPX_IMG_FMT_NV12, _forceKeyFrame); 90 | } 91 | else if (pixelFormat == VideoPixelFormatsEnum.I420) 92 | { 93 | encodedBuffer = _vp8Encoder.Encode(sample, vpxmd.VpxImgFmt.VPX_IMG_FMT_I420, _forceKeyFrame); 94 | } 95 | else 96 | { 97 | int stride = pixelFormat == VideoPixelFormatsEnum.Bgra ? width * 4 : width * 3; 98 | var i420Buffer = PixelConverter.ToI420(width, height, stride, sample, pixelFormat); 99 | encodedBuffer = _vp8Encoder.Encode(i420Buffer, vpxmd.VpxImgFmt.VPX_IMG_FMT_I420, _forceKeyFrame); 100 | } 101 | 102 | if (_forceKeyFrame) 103 | { 104 | _forceKeyFrame = false; 105 | } 106 | 107 | return encodedBuffer; 108 | } 109 | } 110 | 111 | public IEnumerable DecodeVideo(byte[] frame, VideoPixelFormatsEnum pixelFormat, VideoCodecsEnum codec) 112 | { 113 | lock (_decoderLock) 114 | { 115 | if (_vp8Decoder == null) 116 | { 117 | _vp8Decoder = new Vp8Codec(); 118 | _vp8Decoder.InitialiseDecoder(); 119 | } 120 | 121 | List decodedFrames = _vp8Decoder.Decode(frame, frame.Length, out var width, out var height); 122 | 123 | if (decodedFrames == null) 124 | { 125 | logger.LogWarning("VPX decode of video sample failed."); 126 | } 127 | else 128 | { 129 | foreach (var decodedFrame in decodedFrames) 130 | { 131 | byte[] rgb = PixelConverter.I420toBGR(decodedFrame, (int)width, (int)height, out _); 132 | yield return new VideoSample { Width = width, Height = height, Sample = rgb }; 133 | } 134 | } 135 | } 136 | } 137 | 138 | public void Dispose() 139 | { 140 | _vp8Encoder?.Dispose(); 141 | _vp8Decoder?.Dispose(); 142 | } 143 | 144 | public byte[] EncodeVideoFaster(RawImage rawImage, VideoCodecsEnum codec) 145 | { 146 | throw new NotImplementedException(); 147 | } 148 | 149 | public IEnumerable DecodeVideoFaster(byte[] encodedSample, VideoPixelFormatsEnum pixelFormat, VideoCodecsEnum codec) 150 | { 151 | throw new NotImplementedException(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/codecs/OpenH264Encoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Runtime.ExceptionServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace SIPSorceryMedia.Windows.Codecs 8 | { 9 | public class OpenH264Encoder 10 | { 11 | [DllImport("openh264-2.1.1-win64", EntryPoint = "WelsGetCodecVersion")] 12 | public static extern IntPtr GetCodecVersion(); 13 | 14 | private bool IsDisposed; 15 | public Action EncodeResult; 16 | [DllImport("OpenH264Lib", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] 17 | static extern void InitEncoder(string dllName, int width, int height, int bps, int fps, int keyframeInterval, EncodeFunc encodeFunc); 18 | [DllImport("OpenH264Lib", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] 19 | static extern void Dispose(); 20 | 21 | [DllImport("OpenH264Lib", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] 22 | public static extern void EncodeFrame(byte_ptrArray8 data, float timeStamp, bool forceKeyFrame); 23 | 24 | [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 25 | unsafe delegate void EncodeFunc(byte* data, int* sizes, int sizeCount, int layerSize, FrameType frameType, uint frameNum); 26 | unsafe struct EncodeFuncContext 27 | { 28 | public IntPtr Pointer; 29 | public static implicit operator EncodeFuncContext(EncodeFunc func) => new EncodeFuncContext { Pointer = Marshal.GetFunctionPointerForDelegate(func) }; 30 | } 31 | 32 | private EncodeFuncContext encodeFuncContext; 33 | 34 | private List delegateRefs = new List(); 35 | private float FrameNum = 0; 36 | private uint EncodeNum = 0; 37 | private int Width; 38 | private int Height; 39 | private float KeyframeInterval; 40 | private int IFrameCount = 0; 41 | private bool forceKeyFrame = false; 42 | private ConcurrentQueue dateTimeQueue = new ConcurrentQueue(); 43 | 44 | enum FrameType { Invalid, IDR, I, P, Skip, IPMixed }; 45 | 46 | public OpenH264Encoder(string dllName, int width, int height, int bps, int fps, int keyframeInterval) 47 | { 48 | this.Width = width; 49 | this.Height = height; 50 | this.KeyframeInterval = keyframeInterval; 51 | bps = bps / 2; 52 | 53 | unsafe 54 | { 55 | EncodeFunc encodeFunc = (byte* data, int* sizes, int sizeCount, int layerSize, FrameType frameType, uint frameNum) => 56 | { 57 | var keyFrame = (frameType == FrameType.IDR) || (frameType == FrameType.I); 58 | var d = new byte[layerSize]; 59 | Marshal.Copy((IntPtr)data, d, 0, layerSize); 60 | var now = DateTime.Now; 61 | if (dateTimeQueue.TryDequeue(out var dt)) 62 | { 63 | now = dt; 64 | } 65 | var df = new DataFrame() 66 | { 67 | StartTime = now, 68 | Data = d, 69 | DataLength = layerSize, 70 | FrameNum = EncodeNum++, 71 | KeyFrame = keyFrame 72 | }; 73 | EncodeResult?.Invoke(df); 74 | }; 75 | 76 | delegateRefs.Add(encodeFunc); 77 | 78 | this.encodeFuncContext = new EncodeFuncContext { Pointer = Marshal.GetFunctionPointerForDelegate(encodeFunc) }; 79 | 80 | InitEncoder(dllName, width, height, bps, fps, keyframeInterval, encodeFunc); 81 | } 82 | } 83 | 84 | [HandleProcessCorruptedStateExceptions] 85 | public bool EncodeData(byte[] frame, DateTime dt) 86 | { 87 | try 88 | { 89 | dateTimeQueue.Enqueue(dt); 90 | 91 | IFrameCount++; 92 | if (IFrameCount > KeyframeInterval) 93 | { 94 | forceKeyFrame = true; 95 | IFrameCount = 0; 96 | } 97 | 98 | unsafe 99 | { 100 | fixed (byte* start = frame) 101 | { 102 | var data = new byte_ptrArray8 { [0] = start }; 103 | EncodeFrame(data, FrameNum++, forceKeyFrame); 104 | } 105 | } 106 | forceKeyFrame = false; 107 | return true; 108 | } 109 | catch 110 | { 111 | if (!IsDisposed) 112 | { 113 | throw; 114 | } 115 | return false; 116 | } 117 | } 118 | 119 | //public void EncodeData(byte[] data, float timeStamp) 120 | //{ 121 | // EncodeFrame(data, timeStamp); 122 | //} 123 | 124 | public void Close() 125 | { 126 | IsDisposed = true; 127 | Dispose(); 128 | } 129 | } 130 | 131 | public struct DataFrame 132 | { 133 | public int DataLength { get; set; } 134 | public byte[] Data { get; set; } 135 | public uint FrameNum { get; set; } 136 | public int pDataLength { get; set; } 137 | public bool KeyFrame { get; set; } 138 | public DateTime StartTime { get; set; } 139 | } 140 | 141 | public unsafe struct byte_ptrArray8 142 | { 143 | public static readonly int Size = 8; 144 | byte* _0; byte* _1; byte* _2; byte* _3; byte* _4; byte* _5; byte* _6; byte* _7; 145 | 146 | public byte* this[uint i] 147 | { 148 | get { if (i >= Size) throw new ArgumentOutOfRangeException(); fixed (byte** p0 = &_0) { return *(p0 + i); } } 149 | set { if (i >= Size) throw new ArgumentOutOfRangeException(); fixed (byte** p0 = &_0) { *(p0 + i) = value; } } 150 | } 151 | public byte*[] ToArray() 152 | { 153 | fixed (byte** p0 = &_0) { var a = new byte*[Size]; for (uint i = 0; i < Size; i++) a[i] = *(p0 + i); return a; } 154 | } 155 | public void UpdateFrom(byte*[] array) 156 | { 157 | fixed (byte** p0 = &_0) { uint i = 0; foreach (var value in array) { *(p0 + i++) = value; if (i >= Size) return; } } 158 | } 159 | public static implicit operator byte*[](byte_ptrArray8 @struct) => @struct.ToArray(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/codecs/Vp8Codec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | using vpxmd; 7 | 8 | namespace SIPSorceryMedia.Encoders.Codecs 9 | { 10 | public class Vp8Codec : IDisposable 11 | { 12 | /// 13 | /// This is defined in vpx_encoder.h but is currently not being pulled across by CppSharp, 14 | /// see https://github.com/mono/CppSharp/issues/1399. Once the issue is solved this constant 15 | /// can be removed. 16 | /// 17 | private const int VPX_ENCODER_ABI_VERSION = 23; 18 | 19 | private const int VPX_DECODER_ABI_VERSION = 12; 20 | 21 | /// 22 | /// The parameter to use for the "soft deadline" when encoding. 23 | /// 24 | /// 25 | /// Defined in vpx_encoder.h. 26 | /// 27 | private const int VPX_DL_REALTIME = 1; 28 | 29 | /// 30 | /// Encoder flag to force the current sample to be a key frame. 31 | /// 32 | /// 33 | /// Defined in vpx_encoder.h. 34 | /// 35 | private const int VPX_EFLAG_FORCE_KF = 1; 36 | 37 | /// 38 | /// Indicates whether an encoded packet is a key frame. 39 | /// 40 | /// 41 | /// Defined in vpx_encoder.h. 42 | /// 43 | private const byte VPX_FRAME_IS_KEY = 0x1; 44 | 45 | private ILogger logger = NullLogger.Instance; 46 | 47 | private VpxCodecCtx _vpxEncodeCtx; 48 | private VpxImage _vpxEncodeImg; 49 | private VpxCodecCtx _vpxDecodeCtx; 50 | private bool _isVpxImageAllocated; 51 | private bool _isDisposing; 52 | 53 | uint _encodeWidth = 0; 54 | uint _encodeHeight = 0; 55 | 56 | public Vp8Codec() 57 | { 58 | logger = SIPSorcery.LogFactory.CreateLogger(); 59 | } 60 | 61 | // Setting config parameters in Chromium source. 62 | // https://chromium.googlesource.com/external/webrtc/stable/src/+/b8671cb0516ec9f6c7fe22a6bbe331d5b091cdbb/modules/video_coding/codecs/vp8/vp8.cc 63 | // Updated link 15 Jun 2020. 64 | // https://chromium.googlesource.com/external/webrtc/stable/src/+/refs/heads/master/modules/video_coding/codecs/vp8/vp8_impl.cc 65 | public void InitialiseEncoder(uint width, uint height, uint? targetKbps = null) 66 | { 67 | _encodeWidth = width; 68 | _encodeHeight = height; 69 | 70 | _vpxEncodeCtx = new VpxCodecCtx(); 71 | _vpxEncodeImg = new VpxImage(); 72 | 73 | VpxCodecEncCfg vp8EncoderCfg = new VpxCodecEncCfg(); 74 | if (targetKbps is { } kbps) 75 | { 76 | vp8EncoderCfg.RcTargetBitrate = kbps; 77 | } 78 | 79 | var setConfigRes = vpx_encoder.VpxCodecEncConfigDefault(vp8cx.VpxCodecVp8Cx(), vp8EncoderCfg, 0); 80 | if (setConfigRes != VpxCodecErrT.VPX_CODEC_OK) 81 | { 82 | throw new ApplicationException($"Failed to set VP8 encoder configuration to default values, {setConfigRes}."); 83 | } 84 | 85 | vp8EncoderCfg.GW = _encodeWidth; 86 | vp8EncoderCfg.GH = _encodeHeight; 87 | 88 | // vpxConfig.g_w = width; 89 | // vpxConfig.g_h = height; 90 | // vpxConfig.rc_target_bitrate = _rc_target_bitrate;// 300; // 5000; // in kbps. 91 | // vpxConfig.rc_min_quantizer = _rc_min_quantizer;// 20; // 50; 92 | // vpxConfig.rc_max_quantizer = _rc_max_quantizer;// 30; // 60; 93 | // vpxConfig.g_pass = VPX_RC_ONE_PASS; 94 | // if (_rc_is_cbr) 95 | // { 96 | // vpxConfig.rc_end_usage = VPX_CBR; 97 | // } 98 | // else 99 | // { 100 | // vpxConfig.rc_end_usage = VPX_VBR; 101 | // } 102 | 103 | // vpxConfig.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; 104 | // vpxConfig.g_lag_in_frames = 0; 105 | // vpxConfig.rc_resize_allowed = 0; 106 | // vpxConfig.kf_max_dist = 20; 107 | 108 | var initEncoderRes = vpx_encoder.VpxCodecEncInitVer(_vpxEncodeCtx, vp8cx.VpxCodecVp8Cx(), vp8EncoderCfg, 0, VPX_ENCODER_ABI_VERSION); 109 | if (initEncoderRes != VpxCodecErrT.VPX_CODEC_OK) 110 | { 111 | throw new ApplicationException($"Failed to initialise VP8 encoder, {vpx_codec.VpxCodecErrToString(initEncoderRes)}."); 112 | } 113 | } 114 | 115 | public void InitialiseDecoder() 116 | { 117 | _vpxDecodeCtx = new VpxCodecCtx(); 118 | 119 | var initDecoderRes = vpx_decoder.VpxCodecDecInitVer(_vpxDecodeCtx, vp8dx.VpxCodecVp8Dx(), null, 0, VPX_DECODER_ABI_VERSION); 120 | if (initDecoderRes != VpxCodecErrT.VPX_CODEC_OK) 121 | { 122 | throw new ApplicationException($"Failed to initialise VP8 decoder, {vpx_codec.VpxCodecErrToString(initDecoderRes)}."); 123 | } 124 | } 125 | 126 | public byte[] Encode(byte[] frame, VpxImgFmt inputPixelFormat = VpxImgFmt.VPX_IMG_FMT_I420, bool forceKeyFrame = false) 127 | { 128 | if(!_isVpxImageAllocated) 129 | { 130 | _isVpxImageAllocated = true; 131 | VpxImage.VpxImgAlloc(_vpxEncodeImg, inputPixelFormat, _encodeWidth, _encodeHeight, 1); 132 | } 133 | 134 | byte[] encodedSample = null; 135 | 136 | unsafe 137 | { 138 | fixed (byte* pFrame = frame) 139 | { 140 | VpxImage.VpxImgWrap(_vpxEncodeImg, inputPixelFormat, _encodeWidth, _encodeHeight, 1, pFrame); 141 | 142 | int flags = (forceKeyFrame) ? VPX_EFLAG_FORCE_KF : 0; 143 | 144 | var encodeRes = vpx_encoder.VpxCodecEncode(_vpxEncodeCtx, _vpxEncodeImg, 1, 1, flags, VPX_DL_REALTIME); 145 | if (encodeRes != VpxCodecErrT.VPX_CODEC_OK) 146 | { 147 | throw new ApplicationException($"VP8 encode attempt failed, {vpx_codec.VpxCodecErrToString(encodeRes)}."); 148 | } 149 | 150 | IntPtr iter = IntPtr.Zero; 151 | 152 | var pkt = vpx_encoder.VpxCodecGetCxData(_vpxEncodeCtx, (void**)&iter); 153 | 154 | while (pkt != null) 155 | { 156 | switch (pkt.Kind) 157 | { 158 | case VpxCodecCxPktKind.VPX_CODEC_CX_FRAME_PKT: 159 | //Console.WriteLine($"is key frame={(pkt.data.frame.Flags & VPX_FRAME_IS_KEY) > 0}, length {pkt.data.Raw.Sz}."); 160 | encodedSample = new byte[pkt.data.Raw.Sz]; 161 | Marshal.Copy(pkt.data.Raw.Buf, encodedSample, 0, encodedSample.Length); 162 | break; 163 | default: 164 | throw new ApplicationException($"Unexpected packet type received from encoder, {pkt.Kind}."); 165 | } 166 | 167 | pkt = vpx_encoder.VpxCodecGetCxData(_vpxEncodeCtx, (void**)&iter); 168 | } 169 | } 170 | } 171 | 172 | return encodedSample; 173 | } 174 | 175 | // https://swift.im/git/swift-contrib/tree/Swiften/ScreenSharing/VP8Decoder.cpp?id=6247ed394302ff2cf1f33a71df808bebf7241242 176 | public List Decode(byte[] buffer, int bufferSize, out uint width, out uint height) 177 | { 178 | List decodedBuffers = new List(); 179 | width = 0; 180 | height = 0; 181 | 182 | if (!_isDisposing) 183 | { 184 | unsafe 185 | { 186 | fixed (byte* pBuffer = buffer) 187 | { 188 | var decodeRes = vpx_decoder.VpxCodecDecode(_vpxDecodeCtx, pBuffer, (uint)bufferSize, IntPtr.Zero, 1); 189 | if (decodeRes != VpxCodecErrT.VPX_CODEC_OK) 190 | { 191 | // The reason not to throw an exception here is that a partial frame can easily be passed to the decoder. 192 | // This will result in a decode failure but should not affect the decode of the next full frame. 193 | //throw new ApplicationException($"VP8 decode attempt failed, {vpx_codec.VpxCodecErrToString(decodeRes)}."); 194 | logger.LogWarning($"VP8 decode attempt failed, {vpx_codec.VpxCodecErrToString(decodeRes)}."); 195 | } 196 | else 197 | { 198 | IntPtr iter = IntPtr.Zero; 199 | 200 | VpxImage img = vpx_decoder.VpxCodecGetFrame(_vpxDecodeCtx, (void**)&iter); 201 | while (img != null) 202 | { 203 | // Convert the VPX image buffer to an I420 buffer WITHOUT the stride. 204 | width = img.DW; 205 | height = img.DH; 206 | int ySize = (int)(width * height); 207 | int uvSize = (int)(((width + 1) / 2) * ((height + 1) / 2) * 2); 208 | int uvWidth = (int)(width + 1) / 2; 209 | 210 | var yPlane = (byte*)img.PlaneY; 211 | var uPlane = (byte*)img.PlaneU; 212 | var vPlane = (byte*)img.PlaneV; 213 | 214 | byte[] decodedBuffer = new byte[ySize + uvSize]; 215 | 216 | for (int row = 0; row < height; row++) 217 | { 218 | Marshal.Copy((IntPtr)(yPlane + row * img.Stride[0]), decodedBuffer, (int)(row * width), (int)width); 219 | 220 | if (row < height / 2) 221 | { 222 | Marshal.Copy((IntPtr)(uPlane + row * img.Stride[1]), decodedBuffer, ySize + row * uvWidth, uvWidth); 223 | Marshal.Copy((IntPtr)(vPlane + row * img.Stride[2]), decodedBuffer, ySize + uvSize / 2 + row * uvWidth, uvWidth); 224 | } 225 | } 226 | 227 | decodedBuffers.Add(decodedBuffer); 228 | 229 | VpxImage.VpxImgFree(img); 230 | 231 | img = vpx_decoder.VpxCodecGetFrame(_vpxDecodeCtx, (void**)&iter); 232 | } 233 | } 234 | } 235 | } 236 | } 237 | 238 | return decodedBuffers; 239 | } 240 | 241 | public static int GetCodecVersion() 242 | { 243 | return vpxmd.vpx_codec.VpxCodecVersion(); 244 | } 245 | 246 | public static string GetCodecVersionStr() 247 | { 248 | return vpxmd.vpx_codec.VpxCodecVersionStr(); 249 | } 250 | 251 | public void Dispose() 252 | { 253 | _isDisposing = true; 254 | 255 | if (_vpxEncodeCtx != null) 256 | { 257 | vpx_codec.VpxCodecDestroy(_vpxEncodeCtx); 258 | } 259 | 260 | if (_vpxEncodeImg != null) 261 | { 262 | VpxImage.VpxImgFree(_vpxEncodeImg); 263 | } 264 | 265 | if (_vpxDecodeCtx != null) 266 | { 267 | vpx_codec.VpxCodecDestroy(_vpxDecodeCtx); 268 | } 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/src/icon.png -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/HexStr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: HexStr.cs 3 | // 4 | // Description: Helper method to load test frames to and from hex strings. 5 | // 6 | // Author(s): 7 | // Aaron Clauson (aaron@sipsorcery.com) 8 | // 9 | // History: 10 | // 04 Nov 2020 Aaron Clauson Created, Dublin, Ireland. 11 | // 12 | // License: 13 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 14 | //----------------------------------------------------------------------------- 15 | 16 | using System.Collections.Generic; 17 | 18 | namespace SIPSorceryMedia.Encoders.UnitTest 19 | { 20 | public static class HexStr 21 | { 22 | private static readonly sbyte[] _hexDigits = 23 | { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 24 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 25 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 26 | 0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1, 27 | -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, 28 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 29 | -1,0xa,0xb,0xc,0xd,0xe,0xf,-1,-1,-1,-1,-1,-1,-1,-1,-1, 30 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 31 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 32 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 33 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 34 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 35 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 36 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 37 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 38 | -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, }; 39 | 40 | private static readonly char[] hexmap = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 41 | 42 | public static string ToHexStr(this byte[] buffer, char? separator = null) 43 | { 44 | return buffer.ToHexStr(buffer.Length, separator); 45 | } 46 | 47 | public static string ToHexStr(this byte[] buffer, int length, char? separator = null) 48 | { 49 | string rv = string.Empty; 50 | 51 | for (int i = 0; i < length; i++) 52 | { 53 | var val = buffer[i]; 54 | rv += hexmap[val >> 4]; 55 | rv += hexmap[val & 15]; 56 | 57 | if (separator != null && i != length - 1) 58 | { 59 | rv += separator; 60 | } 61 | } 62 | 63 | return rv.ToLower(); 64 | } 65 | 66 | public static byte[] ParseHexStr(string hexStr) 67 | { 68 | List buffer = new List(); 69 | var chars = hexStr.ToLower().ToCharArray(); 70 | int posn = 0; 71 | while (posn < hexStr.Length) 72 | { 73 | while (char.IsWhiteSpace(chars[posn])) 74 | { 75 | posn++; 76 | } 77 | sbyte c = _hexDigits[chars[posn++]]; 78 | if (c == -1) 79 | { 80 | break; 81 | } 82 | sbyte n = (sbyte)(c << 4); 83 | c = _hexDigits[chars[posn++]]; 84 | if (c == -1) 85 | { 86 | break; 87 | } 88 | n |= c; 89 | buffer.Add((byte)n); 90 | } 91 | return buffer.ToArray(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/SIPSorceryMedia.Encoders.UnitTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | all 29 | 30 | 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | all 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | PreserveNewest 49 | 50 | 51 | PreserveNewest 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | PreserveNewest 58 | 59 | 60 | PreserveNewest 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/TestLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Serilog; 3 | using Serilog.Extensions.Logging; 4 | 5 | namespace SIPSorceryMedia.Encoders.UnitTest 6 | { 7 | public class TestLogger 8 | { 9 | public static ILoggerFactory GetLogger(Xunit.Abstractions.ITestOutputHelper output) 10 | { 11 | string template = "{Timestamp:HH:mm:ss.ffff} [{Level}] {Scope} {Message}{NewLine}{Exception}"; 12 | var serilog = new LoggerConfiguration() 13 | .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug) 14 | .Enrich.WithProperty("ThreadId", System.Threading.Thread.CurrentThread.ManagedThreadId) 15 | .WriteTo.TestOutput(output, outputTemplate: template) 16 | .WriteTo.Console(outputTemplate: template) 17 | .CreateLogger(); 18 | return new SerilogLoggerFactory(serilog); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/VpxVideoEncoderUnitTest.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: VpxVideoEncoderUnitTest.cs 3 | // 4 | // Description: Unit tests for logic in VP8Codec.cs. 5 | // 6 | // Author(s): 7 | // Aaron Clauson (aaron@sipsorcery.com) 8 | // 9 | // History: 10 | // 19 Dec 2020 Aaron Clauson Created, Dublin, Ireland. 11 | // 12 | // License: 13 | // BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file. 14 | //----------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Drawing; 18 | using System.Drawing.Imaging; 19 | using System.IO; 20 | using System.Linq; 21 | using System.Runtime.InteropServices; 22 | using SIPSorceryMedia.Abstractions; 23 | using Xunit; 24 | 25 | namespace SIPSorceryMedia.Encoders.UnitTest 26 | { 27 | public class VpxVideoEncoderUnitTest 28 | { 29 | private Microsoft.Extensions.Logging.ILogger logger = null; 30 | 31 | public VpxVideoEncoderUnitTest(Xunit.Abstractions.ITestOutputHelper output) 32 | { 33 | logger = TestLogger.GetLogger(output).CreateLogger(this.GetType().Name); 34 | } 35 | 36 | /// 37 | /// Tests that an I420 640x480 buffer can be encoded. 38 | /// 39 | [Fact] 40 | public void Encode_I420_640x480() 41 | { 42 | VpxVideoEncoder vpxEncoder = new VpxVideoEncoder { 43 | TargetKbps = 400, 44 | }; 45 | 46 | using (StreamReader sr = new StreamReader("img/testpattern_640x480.i420")) 47 | { 48 | byte[] buffer = new byte[sr.BaseStream.Length]; 49 | sr.BaseStream.Read(buffer, 0, buffer.Length); 50 | byte[] encodedSample = vpxEncoder.EncodeVideo(640, 480, buffer, VideoPixelFormatsEnum.I420, VideoCodecsEnum.VP8); 51 | 52 | Assert.NotNull(encodedSample); 53 | Assert.Equal(15399, encodedSample.Length); 54 | } 55 | } 56 | 57 | /// 58 | /// Tests that a VP8 encoded key frame can be decoded. 59 | /// 60 | [Fact] 61 | public void DecodeKeyFrameUnitTest() 62 | { 63 | VpxVideoEncoder vpxEncoder = new VpxVideoEncoder(); 64 | 65 | string hexKeyFrame = File.ReadAllText("img/testpattern_keyframe_640x480.vp8"); 66 | byte[] buffer = HexStr.ParseHexStr(hexKeyFrame.Trim()); 67 | 68 | var frame = vpxEncoder.DecodeVideo(buffer, VideoPixelFormatsEnum.I420, VideoCodecsEnum.VP8).First(); 69 | 70 | Assert.NotNull(frame.Sample); 71 | Assert.Equal(921600, frame.Sample.Length); 72 | Assert.Equal(640U, frame.Width); 73 | Assert.Equal(480U, frame.Height); 74 | 75 | //fixed (byte* bmpPtr = encodedSample.Sample) 76 | //{ 77 | // Bitmap bmp = new Bitmap((int)encodedSample.Width, (int)encodedSample.Height, (int)encodedSample.Width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, new IntPtr(bmpPtr)); 78 | // bmp.Save("decodetestpattern.bmp"); 79 | // bmp.Dispose(); 80 | //} 81 | } 82 | 83 | /// 84 | /// Tests that a 640x480 test pattern I420 buffer can be encoded and decoded successfully. 85 | /// 86 | [Fact] 87 | public unsafe void Roundtrip_I420_640x480() 88 | { 89 | VpxVideoEncoder vpxEncoder = new VpxVideoEncoder(); 90 | 91 | using (StreamReader sr = new StreamReader("img/testpattern_640x480.i420")) 92 | { 93 | byte[] buffer = new byte[sr.BaseStream.Length]; 94 | sr.BaseStream.Read(buffer, 0, buffer.Length); 95 | 96 | var encodedFrame = vpxEncoder.EncodeVideo(640, 480, buffer, VideoPixelFormatsEnum.I420, VideoCodecsEnum.VP8); 97 | 98 | Assert.NotNull(encodedFrame); 99 | Assert.Equal(15399, encodedFrame.Length); 100 | 101 | var decodedFrame = vpxEncoder.DecodeVideo(encodedFrame, VideoPixelFormatsEnum.Bgr, VideoCodecsEnum.VP8).First(); 102 | 103 | Assert.NotNull(decodedFrame.Sample); 104 | Assert.Equal(921600, decodedFrame.Sample.Length); 105 | Assert.Equal(640U, decodedFrame.Width); 106 | Assert.Equal(480U, decodedFrame.Height); 107 | 108 | fixed (byte* pBgr = decodedFrame.Sample) 109 | { 110 | Bitmap bmp = new Bitmap((int)decodedFrame.Width, (int)decodedFrame.Height, (int)decodedFrame.Width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, new IntPtr(pBgr)); 111 | bmp.Save("roundtrip_i420_640x480.bmp"); 112 | bmp.Dispose(); 113 | } 114 | } 115 | } 116 | 117 | /// 118 | /// Tests that a 640x480 test pattern bitmap can be encoded and decoded successfully. 119 | /// 120 | [Fact] 121 | public unsafe void Roundtrip_Bitmap_640x480() 122 | { 123 | VpxVideoEncoder vpxEncoder = new VpxVideoEncoder(); 124 | 125 | using (Bitmap bmp = new Bitmap("img/testpattern_640x480.bmp")) 126 | { 127 | byte[] i420 = BitmapToI420(bmp); 128 | 129 | var encodedFrame = vpxEncoder.EncodeVideo(640, 480, i420, VideoPixelFormatsEnum.I420, VideoCodecsEnum.VP8); 130 | 131 | Assert.NotNull(encodedFrame); 132 | Assert.Equal(14207, encodedFrame.Length); 133 | 134 | var decodedFrame = vpxEncoder.DecodeVideo(encodedFrame, VideoPixelFormatsEnum.Bgr, VideoCodecsEnum.VP8).First(); 135 | 136 | Assert.NotNull(decodedFrame.Sample); 137 | Assert.Equal(921600, decodedFrame.Sample.Length); 138 | Assert.Equal(640U, decodedFrame.Width); 139 | Assert.Equal(480U, decodedFrame.Height); 140 | 141 | fixed (byte* pBgr = decodedFrame.Sample) 142 | { 143 | Bitmap rtBmp = new Bitmap((int)decodedFrame.Width, (int)decodedFrame.Height, (int)decodedFrame.Width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, new IntPtr(pBgr)); 144 | rtBmp.Save("roundtrip_bitmap_640x480.bmp"); 145 | rtBmp.Dispose(); 146 | } 147 | } 148 | } 149 | 150 | /// 151 | /// Tests that an image with an uneven dimension can be encoded and decoded successfully. 152 | /// 153 | [Fact] 154 | public unsafe void Roundtrip_Bitmap_720x405() 155 | { 156 | VpxVideoEncoder vpxEncoder = new VpxVideoEncoder(); 157 | 158 | using (Bitmap bmp = new Bitmap("img/testpattern_720x405.bmp")) 159 | { 160 | byte[] i420 = BitmapToI420(bmp); 161 | 162 | var encodedFrame = vpxEncoder.EncodeVideo(720, 405, i420, VideoPixelFormatsEnum.I420, VideoCodecsEnum.VP8); 163 | 164 | Assert.NotNull(encodedFrame); 165 | //Assert.Equal(14207, encodedFrame.Length); 166 | 167 | var decodedFrame = vpxEncoder.DecodeVideo(encodedFrame, VideoPixelFormatsEnum.Bgr, VideoCodecsEnum.VP8).First(); 168 | 169 | Assert.NotNull(decodedFrame.Sample); 170 | //Assert.Equal(921600, decodedFrame.Sample.Length); 171 | Assert.Equal(720U, decodedFrame.Width); 172 | Assert.Equal(405U, decodedFrame.Height); 173 | 174 | fixed (byte* pBgr = decodedFrame.Sample) 175 | { 176 | Bitmap rtBmp = new Bitmap((int)decodedFrame.Width, (int)decodedFrame.Height, (int)decodedFrame.Width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, new IntPtr(pBgr)); 177 | rtBmp.Save("roundtrip_bitmap_720x405.bmp"); 178 | rtBmp.Dispose(); 179 | } 180 | } 181 | } 182 | 183 | private static byte[] BitmapToByteArray(Bitmap bitmap, out int stride) 184 | { 185 | BitmapData bmpdata = null; 186 | 187 | try 188 | { 189 | bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat); 190 | stride = bmpdata.Stride; 191 | int numbytes = stride * bitmap.Height; 192 | byte[] bytedata = new byte[numbytes]; 193 | IntPtr ptr = bmpdata.Scan0; 194 | 195 | Marshal.Copy(ptr, bytedata, 0, numbytes); 196 | 197 | return bytedata; 198 | } 199 | finally 200 | { 201 | if (bmpdata != null) 202 | { 203 | bitmap.UnlockBits(bmpdata); 204 | } 205 | } 206 | } 207 | 208 | private static byte[] BitmapToI420(Bitmap bmp) 209 | { 210 | var buffer = BitmapToByteArray(bmp, out int stride); 211 | return PixelConverter.BGRtoI420(buffer, bmp.Width, bmp.Height, stride); 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_32x24.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_32x24.bmp -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_32x24.i420: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_32x24.i420 -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_640x480.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_640x480.bmp -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_640x480.i420: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_640x480.i420 -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_720x405.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_720x405.bmp -------------------------------------------------------------------------------- /test/SIPSorceryMedia.Encoders.UnitTest/img/testpattern_keyframe_640x480.vp8: -------------------------------------------------------------------------------- 1 | 508C019D012A8002E0010787088585889984880F03B674B4694EDFFF9F9A551DEFC019E435B78DF294BC073F8CD994842C7499F6479BAEA6C1DDF0BB476B9F747E97FB7FEDBFB7CF2EF873EA0EFA7CA45F39E0D963F409FB5FCDEFF3DF407FDE7FC0F6CFFDF7FC2FF8FF709FD4DFF5DD5FBFB1FA1AFD62FD88F77AFFC3FB29EF77FB9FF8AF60CFF13FE53AEFBF71BD8CFF6DFD69BFF0FB507F69FF95FB71ED31FFFF5783E6FDCEBFA5FF75F973E92FA39F87FB89F141E6D79B6EC6FF9BF941E3DDF5BFE0BF77FE28FF97FF73F27FD95F5A9EA59EDFE244EA7DEDFFEEF8EA7EA7D8A3514FC6371B7FFD1FE6FE12711AFBDE4278A4E776A4B0664BF9F50AD6D0113892D62259247958AD9C6B3F6DFF596B6A545D7AB91AE42C442F3CAC4372D28796BC8516A21769933D834060055F806B51DC6C873C5A8821535693EBF8A1979BB32C6622A3325E44CBC0C4E08610F1C0284EAA086A5EE19FC349ADC3025DD23A289721A54CE89ACBCEF0FE51C8E142118265CE3E4F38DAA83D72B2E68B0F22A110EFE341784E47C4FA060A9EC35C26F6E7A4DE778C470B46F9FADE510A70127A7A197CB5DC05D2BB7D0CE8370B4356A261DC3929C547FEB4F05F76000F71E2F66AC1E90D328BF4CB2EC07B5BE5D2FB5B0B2A6420AF2E5C7E9AC4CE08BB4F3E6EA43D812535C572594A069BAD63A30EE9E739361B5EE248FF2D21E39380701B61EA3D0A80B330ECE614DF5A742EB2BE76A3052ED3562936BB60F267FF25753704CA41938B43624FED8D34C4D89CC32B1FED8C4492A677E2DB4C1CACE2655109FFF210D37C2839AD6E05F375E7C9D30AD3A3FC316F4720BADF58BE1A768062BB520093C352E5C2625FD27282EA0DA4F8DDC01A1118AF73C2B4D569E1BBDE4CB4E6CF00B33245DD0368A83212074261B5651B5FF9343B7B8A62CD644B7E6A3DB545D4F1A8B0A32B73550F2ADFA522B42CABAD1339B51C1E2EC9A25945087CD6A899E2E4FC9764C1B16F6D8512344E35EE4EDEFE23A993F760685D518BDF6F54741F5142B385F000B9782EFA43AF4A54F1CA360E9604AFF084D7F78234C88D46A58A92832AF09227E840E50EF6D179AF3EB81F94B428756B4C4A7E64E26133522E2C8D3EC06CC6EA2458B2862A348B78F758BD0450ACF00B26C8614B7CB9D092E52B93D71686A2F79240C191FCB845CB6C0A9EEA6BC7D509B30038C1F007DD9F668B4483E2D8A1BB6541EE4034AAC8C47A73855B359CDC15C5DD5AF12F2001365E978574269EC5BEC9D49FAF54C54598016B5F4BAEEEB1CDC114E0873F998B8A98D91D6571B07FDDA9945E1215F64350990D0AB3F7D8A37DE3C042131D9835DA9423BF127E8559F09ECF62E00FAAC97944F0551E5B6CA41015CAFA38AC42113FD6F24C883C06560A77E69F815005D077B3E190F66551411843A1820DD94558CD03C23EFA0CF4192C7E70C5693D75CCA0B0843D4B7A1155EBA772083395DA4EA1D06E3A92F8030C5C1A35916AACCA8498444B0F13B0D6F6A2A2774805918BEAC5EB39DBE6BA66277DEF92B4FCFCCD88C25BC9C6F8CD9464035DE56D1CD443698D7701791F5CF603ECB6ECCEAF3321BDF4A58C038981C076C25E6B03F4E6B48110E58A0E4270F54EFBE6D819B481054E81C4434F7517060A592B8F0860430A4634357AA81C5C74CB6C07ADE7711EA4904D68C271DD841F3878C0CE0EE9F0FEE96F0C3FE5B58D3131019450A6EC7F7C39430DAB1F5D99C97FB93990F1F63952395934E8B69BE919B32C94788969D8D480C326E02D70C048D09FB704A90F67C55B3D9E8BA88436D679536D1642024856D49E1D890B5B1AB3366C5FF9599F0139AEC5ABA443C3EACADD4A9BF331F5D33A54707A89D74C6D3CB5A4B3DFA8344B77501D5E5092530BA91B7D3EF1EAABCF1FDD8E410DA866D82F93D3D6C9FE720580824721ABDACAE49E6940896FBB67FCB31B49E69D5D9E7E9993AAB2CE1330FBB51BD23B78D31D3D45466A9E10260E3A43EC440FFAC51F1CCFBDA4A46B1EA7DEF77DDB3739E1E6C829B287592CCC8AD5B695C04279E7D172D379803060408B11A39C8979D48EA5BEC21E65D170053017C82E6522104CEF794D912D0C436A528D49E08FC90CAC14B40D8BBCD3D9F330483D1905407F0DAE79F0EF0323F22B6F5E28D75E792BFD53423F6397D84ADA52B9460286614302D855A5D0D2120FD5C3BC59911F518CBD4D02EE6B9379491436BCF1B0EB11EB62659278CA0DEFDBD1735D45DB7F89ACEEC9A898DBDC49C611FF6439EAE9CD0B7266322C1B5E3018BBBCCD0722D2005FA8BD4CBB21D4AFDD6251C85637597F61830BC5FADBBFA3AA6C5B80C6AE0A37F60F7FAEF6843569A7950DBABE98BB292A75D809D8CFAC750852BCC1FFB1E5B11687F28F1AA8149B92E8D966C0EF4492E454397E3EF021B10F10BBBF0C863ED8B92A05A1831058D047AA0CB894695E573A240B4A58744AD40DA916BBD296F4D862AC0CCE2EA31A985B9D844BC99FFFB9926277FD1CCFBEAD633A5FF4685E8049197B5D5059F11C71EFFDC98A814831D617D0830D6702DBD5471FCFF59EFD5D1FA6BD827B51A27A0B148DC5B80614944C1DEEAE17E433EED3F72F43326CE9D7EC80057807974A3C66DE4E98C97A13A53A4CD5CA1D956D1D89985A3110149E3FC96EEBDD8DCACC668947D75D39FFD014E838B29A33FD640DD8D700CA9E548AF89340A95647538F6A9D0DF5F7FE3DABEB0A72C1736645280208FF7D45A057082747A97EE93B93851E9F4A437438B6EBDFF3F219447BEAAEE9260E9B876B43427331C5FFE4E134DEBB02A4FB3A3E8A6AEF7CB8FAA252D5C7847064243F49083162AA901B547DD249E72C86EE960F53AC7D088BC8FD6300BBF7E07FB1D4F6F41EAFF46FA83F05EF61C3858BF5B07FECBB3033FFAA2C2146205E6ADF5C3EB6EE5A792D03BB20CBE1D681E1AC3AD991F7748AEBA39B4BEFC89233BD41BDC44C0F823B675068FC3353692A0928989C61DD4FFC4B57AD6B191185F3D1503919D7518539238AD11D7BA7CD42AED5FC35A14584F24580E55DCE8FF775D5520A96D9682FDCCE3A03A02DBDB6C1600AA6E4CFC5D8CE10E2155F213A3169B1BB557D6EACD6DC6520B1FEF90AD5D715E10CCCB68E86EDE1877A40058473CDDB04EA60658C15BABECCE7F83FAF3A1ED775186071E45EEE85511CC0820B66D199A1BEE8A5E8A5DE4389F5CD4CD112475B5BCF1F726A6DDB6C791D75E09209F22DBEDB34092C236738EA231104E712370A90EF036DD9E8A6E6619FDC030E0FC56329F5FFAF09CC24AC85A402E95AD6268051A290EAB562320BF49B3BDE380CF0E9A294D38D71247AA3BDA9DC1193D801B4CB2BFB55EFA52384AD4FB337741F477E25465859E087028A3E0091DE6384C03E4E63DC4D55B051500E83DDB4487406436F8656140E1E589EC463C41222DA19FF8497383595F2F0840C7EB6A471D0519AD72F40E1408F6A3E4880B2CEFD2AD83E41226F4F567E5939547754164CFC9C1ACDF4AC5DCA818C107A298AFA661ACAF00F5CAE8957A5ABD9525EAD405DB5B6D6095A16AC3BDF206695148C3B00B504B88DEF844B65C04342BD20FED2EE2198C16CCA808ECD51883262FD77074050EBD74792BD5363AFF400B7B02B1B5BD3A2CB4662D91CE2FD731CE57579960BC03B792CE35BCF431882A6487F1C5954AC2F2910D33B426C9B3AD97A5AC6057DF0C113A7128664CAAFD4A77AB1AFAC3F94CED63E7B7515DD8724E2D39E26C3C144FFE3E0047E30EA95CF57F112B2202D07B02C76065E92E652DB53EC26580720BA9BD52B8A1825D184496605B2B53D10E9094F6D53E47C1615B7650140F6EDC8AFF7F85F83AF71FB89AE4F00CE0C34DA95C9D25BDB843D46A84FDCA65C8695F95254D92387A463B4EDFB313867A8CE86947422D7EEB8817F31041F9D4418B7786D0D7B436506AC7B1AE9D3AED957C8F380A1DBA2FAC43DA2B03D3BCEDD59D8439BD31150041CDBBCDEE18B2D9A1D451B84EFAB5A512291DBAAC299CE78DC7F9511DBC0E4995823CDD2C1A9BD305FBB249B11686F036A28428DB45148B2AA4A99D6C6C81E5F6D13A990BCDC54F5102EF20C0AEE07B002120CF879DDEFF9174BFB95B50A4FD0B37865216371313338A5E39BBC0AA4E18B4C7AD050172511A33EBC62C52943E26630A35643E31CE853854946DC26C18547C040920DBE69FB7288E339E1A5544B3B3A713F0098E4BF4DF4C313AD23821AC66D0E2E5176E23895E29E36F985941AD7B67A6926C23F473E166E7140001A3A26AD7E843B468BE41AAE56FBE68CE9E0577FFBDD01C06DB3E9003DC03793437E02A1B2F22DC6B4025FCC08C098E9E6BA9100C6E7D1CF6098EEF33274D6374E6DC65A144D93B2652765E93165428797B5EDD7AEE0957B08C94031DE09D521E562FE2DA0455943E9234165EBC09BF8C6E232095897D411B2FB36A377721E78AFA5F8A05DBA0E334B04DE7F9335EB343D353ABC00FEFF039F1BF64D6993F21B7C8FF5087FC7991BD108EC43F0AC9F29BDC6E8D16D0EE39669E44C7D6FDBE6504893B22AD758D777DEAB6F92AEBAEFD0CCB44325D1E23C6753C56F3EC095A255252CF356C331EC16502073D08CADE95105788A1A15E38DC5BDC3B3B0BFFF3A7C59C92B6D241BAEDDACC9D5F48C22FFAAFF3CA4DF0D4B145A665E01B56C074CFA8D049ABBD3A45B883C5E9E07245DD2CB716B5D5255AF38CB22C2D96CEBD3DBA55812C64F2095A6812C3C14C9A3E6DA07C18D3DF8846523CE4F6F53B7DC44B1B8E55F248434F909BA5616712C53E4A34CC37D9E9BABF160DF9FAA2FC37B628892CBC3B1C2C86F7E1E2CC1CF029B9DF2286B32CE9DD6A133C4891CCC06007BFF995D16126B8DE0F87AC59483EF2F089E78BD41510ECD3316A915E2C67C1597428855B10260DA6FC769DDECA27851BCE0DD6A28E90E07FFDCBF3E8978D27F5353E4A3175F775E5BEB47CBD5A717F18DFCE4FC93C12CB07D4B97B3E382502256FF7FB744F641C1455F9A05F370BEE97B94CA82D259E2062EE946EEA366D83B73CD172CA48631683A1D0284D44CF1C56C168CE8033AF1453374068FF4EBBEBC4E408A4DDB7A5DFE1A04D7F1A44D7AA8A49CBEE8DEFDA1BE72B7D23DE0254CDF27776FFC3FEAB1F1AD5532EF428F2550311FD162FBB78415F0E85CCC7F91F6E02B647F6713FB4B21C050D6FF39FA36095CE93250BCBEBF33CA42716D8BDFC03B4EF0AD99AD7400B85ACC9ABC25599F5DE4A07EFA1D407ABB8F6B607E2E036449041FA6770CBD0751892DFE0CD11695441EA0DC2EC1577F40A33CB1210EDB85711AD322EBF34AA78A62947C048514E2AD39DC18BDF85B0F4A1D0DE29EAB0EC8733E5CC4D478033BCC36DCB63C24A1691B8A495363AA3201954808266B2BC47ABA203CFD1E589658AE4642D69ED4D3E6C0EE4CFBB38202CF4D81F4C7BC147AF41DB0B6A8DE04C4BD732ECE692CBE48D5751D4CA8688CAA122FB2E5E98C7E5699C283B8C0272131EB9D136DC8A25483BDFDF179885214658843DAD8096A74CC3F1CBBAF9EABE323CC483B4B1D22F4387ABCCCB29B361B5FE5012582AD249C316F49C02BAA5AE5BF9EA17C60CE848433DBB03BA4D0964BE7A51A25E28A758B202C410BD2921D4165089EEC02FF6502D1AC7CC8CA6267F5D776655506F0B4E81166DAA804B885E57054E568E15A66807DA53E535014F7D6EC0A225C0A4013AB90FBE91F894A21212D5877C2E4BC2F712059BD998443F2A566B4A594099EDBC90EF95FD8070A2FE92E01BC905FF303EC6D4784DC5FCB91292D97CCF5409F4CD5CFEE769200212332ED583C9B1DA1DF07D9D51E86E41BC33249EA30D574051307EAA2F903A25EBA0B50D78E96666B3AF51CF650169B4D46EF663048629FF03CC7D68191C837C0FD5236BEC9623A324F45215614390A30F5CEA15C6C65675DC332AF517BF444B482478E4D3EBD956A0AE78A137E8D47CD8147E94EECD9C3F1110E57CEEA336494C934A2E5A1F6C710492A534AD4D7F2FF25D7F2870EE6DBA9259FE0098AC571C88770F5A82CF2B5675754F0607929102D5846DFE0CAB21E10F6EE2E23BA582F6B0FA546D88D99370E81C19316FCC26EB35B8E4D572379B1D93253ECF5AA8725A3C0EF167C01561EEEE2F63D33E3A1E12F92898231C928241EE164C45956FAC283E034B69885B155A2282CF62EF3083FFAFC5AD0C7BEC500714B956549C973DE3804CC1B50FA6628616C96F50A13F549FCA0BB69A1BBC179B1D9775BAF6793167B56D352A501A3539162FA92C5A84048FCB3098ABA31CBC2C04DDD225C82D7C237FEEC931A89A3D8C5C49E0F187B8D82B55FF0B359BAF93A250E488909ED167DE6141D57065011B8034D840D2440869C04CC97A8E0B3C7E78CBDD1DC57BC8D2B4F2C0E3C66C6D7DD993408803DE4F598D9E36A716FF7295452F15E59DE0FD2293EC8B2A6B0AF32EA0CDD134398926CDDAAA8ADF46CDBB6F2B27449F0E2F306E48B1AAB54D00951A49F5CC93BFF9EEE36ABD8B246B9EAD2929C80D81438D4502F7FFA3DDA5B1666E8BA265D30B0ECD9954FF4FB5E10DBD2EF9BF0C12C92B1CF65DD2D03FF2F8DF5F794E03AF88D4B1A28D57F2906218D4B1A7477118B18666C95F07FB1F1B8F907EADEC2E6086B708B6CDE527A07E4E5DDD4C8D22B74174346043A79818D70A9ECF672028A57238D02FB5225EBB968CB46599B6CD2B28E19DE704A3409822752AC5D4605F64A7F99EC25C1150DBF33989F1738B0724167D325D9C996C1549BBAD2A43249834AC25B8199C57BE343EFC8DE34F272E30663E31F260CADAACAE63128FDF5C3FCB7102A41A6FACE7343FFE042AF6FFE84DE428F19DE230FDEFE706A6D4A31D45E97E322C6DAAF86683E306752347CD4C1DFD4FAD0E79A9323C9E28C57D4EC5AC454AA6906C8CA82BAC2569B4D080D45DB92E2574AF80C0619F52E30B1E3F6E6FF8655F2E320FEBABB114BA57889CFAA644E2AB2899864EFBB140BF8F2B4928E47DF45064B302AA03CA155FE67FB2C8FFCF1A7D15775E7ECA327EE8555A925BC27D1E8F86C0933FE7FA63BDE795DF833AFF38AF63D2153C3FB9575DBF8DDDE43339B10157583B5F3FB2906D667BB91038B153A9C9F1B6E30A3390B90D9B2E2EE5F1239C66F545299BE132FD3EEDD2DDE5E535C35C6FE4A97007304CA459E5F44B8BA0FBC37D7219F0B3D2906B2D92E9B26A865A97CE602508E82FF402458557FBB89C1E63392467F122F053F6038620924DA5F61BDEA216101040A1A700079F37E3E72BB0C8472BC1C38F3929CF5BEF8968854E17019A5DD849D988F43802312291DD54BEE865AEEBCD6C6EC8957168DF7ADE59234B0E0E0BBF9D1732D5DF9B3EAA85897E7BD609F8937952D7FB491C1569034840D84022CB3ABF1C493201B1890432F6F90F1FE5757C48F7DCD1261073C9D11374FB5F96ED052A5AFA2EA088CFBD7653EAEA5FBF0359B86646A348C3020656AE90F44AD7224B34DA89F9F313BA86B4C3E4D4443E19655FAE55C18E87BA52F0AB6DD69578AE10848EB1CC4378149A040F0AB4B8464AABF0521DE72CE378BB0A42626EC1DEF9679B539446F3995ED3ACBAB62146EE8701EA85FBE4732DD6D3E999D6DE67943DDB74B82A1F08772FD372E95ABD178CFD412ADEF4BE29D71C538FEB3F3E7E25233BCBA0DDF5D6CBECF495E2ED9AB45F38AB0597EFCE86509E4006D7316B7548420E6F4956EE9C8DF33FD2108D88DBA928F5BA7D3C2C1884DFB63AD92BBE4AD05839B499195E5F7610BBEC52BD6314DFB9396248096AD15E9BB80833C47C87F6693F5F48D4520F9C99B251396D62BFC1FC26FBD297217BD1D8A2146C16AF9B6FF255919F0AFED741A6B04D8108B7C4D04888DE4AEF19AEF0DDA05D1A3A04720525B5C6DCD31E6F35C9C1CE73F48097E8C3BE2FEA57C650C16FA69AE58896783D2FBAD2CD8C8D57EAC0D5E2B81EAEB80995D591759B6DD7D0FE7EA9960EAAF720DB28BF71D62198663ED0FE9F2894C4310D8996B820284BAEB6E5237B74946547BA245211FAB89DACFDF54A3BF145E03BBF11D6EEF331B8E98F58A17DA522E4130ED611020441C5B5180B7C87667DB9D1722CAB409BD4EFFF04F287A702E10F06CDCC6F2E5E77A540F39C005E01FC6BE3F21E7E255DBFA2CBE6C5FC5E60CBC7B1D949DF25BBA559B07E066D6AC5EB50D68B5CFBCB18C9D0264879EEFF821D9D727232CFC5B1B2E7E7EEFA137CD444A54E9042DC83434CD87C1B111BDAB4BFFFC0F7D115E87A36814688C91A732FED4C1DB35DB97A66D661F62E03B58C51919D2C22BFC5580413717057F36CD246520264412B7EED18FA0E25624E10F27A48835C1FD436B3E712123972DA16DABC4128752DB8D6A7D571A5C0518F4F131EE6B74DB3B8E7CD000A1A815629E9B1ECAF3C8B8C5D4859A3AACEB797E8878496D8601DA39FDEA1BD8777D1CC1ED5206E524C1D78B16040627D892CD4DF768E7316DFA7A525709373B0B0FBB1CAF27C3A547F250A18834145605B23FF506B75A197FBD72C199DB1C91DBCC4353D4F8ED015760AE48BA125A1019EBA92B5FE2B1CC4163CD166E8C16C1BF0C7A0B6A0D8B357BC665D20FE07F43B8936F95F2A4250B8512B1C968AD253CC12AA81CB125B7A84A5A5BA33E90FD0AE9A77055E8A9A00FE86BC996B7659E5FAF947A2E139E6A631C3A1D3D9FFC4E390F789B8A51C933DDD3AB569043A6E1F20819FE8AACD049CE90EE12F173AC0D5ED363E7823408AE329251CEDB8B4CB3B79927FEA86CA0F679131B203115095700123F93BFD212999F646199F71FE7D4C3E1D4E208552478F62E5B228BD3FB7BA7091CF362F7DE6EE35C603291A658617CA02B6B0AE24D46BABEC3E73BDE1778447EB7883823D67BF84EADF8E933F4F5320542BD1EFC9E17DF48F4B7840CE3CB120941C147E9C67B7BB4825E38394C6EB28A1BD442C7F0B2CAFE9A1BA717F489BF75A74B51B54929EABC9979DDE259AC6C3F32CD5A696B7C4FA9B2DDCCE6561A622E407F26EDA316C485C51B72181CEC76DB9D69D930A1F00CBBE072DC4336189273CA752DC7A4F86CA6D974456CA7F3C32D692FEE13D9EC94CBD598EFAF9A821CDAC4A98BED4562FB6C59145A685C7D251A0464266E56D104359BB8DB1C2C242EBA4BFA2B96BB622817D3EAF3BECFEBCD941753C38406FD01A4BF18E386FC5BEA25B4993C1E02EBE15029BFA2455561DD815FBDD12C186CC8325BBB7BC5BEA906B4683610F14DDFB5B6A024B53433B2E4C416CA0ABEB0B92AFC21CE3886370BEA42B3D52CE4867B18DAFA6986FD09D8BB15D8B71034AA096FA974AEFB1168D9F32231B0D60A9419F7458D01817C417CDC122A8A69F506DB086684B592B371AFE16B5252B184F8B507EB838511CC63A7C2BD73C26106C0AD0FBD89E907D22BDAF09ADE9D254ACB6806595CBC48232C326CC971AD3FC3075EC065D82243F9A99CABDBC26BF77DF783B67EAAE0788BA276CB03E5BEBA9929A8E000D57F06FFC739DD50DF82355E6863ACFAD0FCD971C6C93EDF02AA89D52407AD1130AA2E1E275303C1345758716CE4CF8A0A2B56FB69A8E933F1FC0F97ED61B548E20C3C2A3F938A6526E4C94C189FF2DF75337B1D24391F766F585CA1D5FFE21F7C3CD43A07C9BBC6C0759259763C84007E2FA026977866FDDD9B3A87D42A7C5AED083DE6078ECBACB83F8C6CA4851D30974BA2B8E11B40E4E8609F2EC9FA2EF7DE9F5074A1B1491DF7E263D13E18DFC26B7C03EAE04B9DE3AE0743E763C186DCC930CF68BA0DD43362F6FFB718CBAEEC6DD0A5EFC2F57BF0BFCC6CFC024B51DAD89E33FCFFE3E7D017DB6A3E0B601200961EEBBF2101637A4B6B81F20C7D7807953E9FBE1190F600745039105159DAD9B3675721EC559E08DE4AB2AF62A9594A452C0C957E2A48DA1FC6185248DE478F42FC7E7786F5B1F4005DCE20AA330F38461DF33AC8DFAD182E03A89DEA2DF83C49A7C055AD417EF61D6648EFC8DAA9066A09D5DB25381CF596B0F1B9F4FD20C2C06A37FDA42D250004406D320848C881A31084ABE846E1AC44111C84A8DCA0B0F4EF223A4560082F554CADCF338614A098D2B831F8005F7292A1CB3229DDAEA2E4B3DE22B46C6E91594461891B7F52195D7F061B5BCAF58ADCCE28EF02565B561CEB46EFDBC1EBCDF5B7A8553FEBD5619F7D4C8114E1D422008F2375B41EDA5B0F17710F22FD8C31431C8238E039C881683697D1F0E851036403A1D9A2342A3C68D0192D733D61AAB1F8800348A0C9AC9CC766A68BF2A6B1DD25902FB48025BB8FD975E1DDC7212583AC3B6905C5304DB04EDEAD337F4442693B604EE775C4C6C5D4D062C290F4CC6938D5F74D9D78AD84ABF0989700203D1CB29DB847F8BB634B4F388068D397C0F06D8854C2477042F6B90B56DD155F3F896E1C19A1485EAB0F4041D34E60BCEEEFE79249E819E3858FAD8616A49E0C6BD34EA19CA8972A27F0773AA777553C49746A729083BE9346A159DD8AA446EC558B92422EF7F9390659B656EB57885DDCB6F1D40E297C4EC6310F239B4EAABEBCEE53D807B9B50440D6F718B089AF712813EF0286A99D3A8C534AF9B7B704CC4F98A977223B99ECF1483FF82CDC355C1CF8F69011C3B21FBA72309EBDC255FB7E911486CB6972E4FCB9B7B81548038009E2D9338B0145F39FE1CC5747F5C9C888306A48D79718CB7882D555241A3F62B224DFEBA69AC7E4B05F441C29E04CA34F266FA83827B45CB1B3C58BF13AA7FEB57D6B173FD9D4122943ADC260F559C6662E113102E20270232DBCDB205B7010E57A551BAF1AB8513E148234730BD57109173836ED1FAF27213BC50760ECDED9242C45D834F68CDB4BF7070F7827EDE92F616140C3D38A7821B745B32932C3F041F12FF97A3F747E3CCB4B6FEE7CDB851CDFCD129F61DF081E8277C1B630CB672618B4F90666F64FF4535E7A97A11776BEFD8ADEBC7758ECBCDF0A82799086B59FCFFAFB1C1CCE49C1C53BB6915350DA80A33E51D0049985F50B360D9A81BA223AFEAF7E8EF56AE6A2D98C2E1EA05ADAA14A4BA8FB13E3710AE4220C04D83B089A04058E83A10D9AD739BA4E8AD8ADD80332DD473FAAFDC46FCFE05AF2052B7C48A7C3E67BAC4738DF8F59271C07373360D0BFF958CA960788E8E34AAD1B8C0F37388275322377A2CA5A930B351703AC53C2055E4A5141AA9DD1E702EA8DF26EA2EC58F7ADCBEB21728A1FE6FBEB510BEB9AF222F028B25D85BE7C3609DED77C96E30D89CFA07A5446AF704042A4253B2FCCCAE3DC11D82D3D889D606FAA44834303DECA6A0F5935A4F40A3BB127CCA20333AD3519C6FB07566D0FF6E7CA736038F84A5EF5E23724E5C2943DCBCA2EFB772A1083B71C3BBDCDB66434226003D11D7318268E2B5AAAB97E5D14F0638C5AA499337151AA70F2F2F9CEC46B0948107BAF10EBFE7BAF9E9368B7F304C355896B8DEBCE3D696E803FF478E2AF58FC8173620298F10956637AE4E26494EB33BDBE650A77F14D60AFC3E36F0F4058EFDC10823DA299549BB90B6F08EBE750889DCB6D2381965D7458747C7D7B68CF954D652322A20F2113667324342D53932258DB00E7608327D94C74DF0A1DFA826FAE9B16562DB507CE762C573144070184F7506CD6879B96D067DF78AC6D91725D66F5EA0F5432D5F4399F8C2CD17CA885146FC7DEFFA2E7D097BAA93244F3E1517CCA3E9C1414CF30D78125F07BB1A2CD63D36B506A41DD145FE0169DBA5AD5C9071C28D5D13E246908CA3AA27D417A19EE96F0B449C288156FBD0A7C9EC053CB328F643DE07DE87C764F4D46FDD58E5E5F220F51223D78F36A3DDA66969E5E0C151190FFF4E4A5BB73DBD5D956B429F654669D6B2C9CB890EE66549F101B166E675D351BB39EF7020D06361B2EA82E904A9D5BD7273F9EAB407FC195F5606D24ADA7D5254289F43A46E1923E5F348F17B5087B140C1A92574FA24A5CF0B49DAC77D77166C7C2DA90B8BADCEE5DA6BE4F6FEB37710EA4008C9AB0A2DEDC7F1A4E73EE69FB6066DC28103FFD02A0423D5B6FB1B2F9824DDC16F22CFE6501B97E296A3CFAA84E1BB9DFB6F01D3C2EA48CFC5477BBC8392CC379D8E1B44084F34E9B4F20BD1E7DCF5F1B3A9C38EA06D9060DD8D9C83AF0BDCBF031FA3ABF77826AA46C5BD34446189F9322FD3B5C8489551B3B6A7116208D159A8DDD31C6D096F03F4F7677D5EB71F3CD98638C28E7F1DD5B0EC44D8544C2EFD980AFC7323B0DFDBFFC621247F85E179407208374E0E92DE24AF631C8ADFF612AC8D1B12601FA9D5390CDCACF909599448EBF71BEB7EF764EFE9870958DA0B11F242FF511A8C32E0625D33FD82B4B4BE4D41508AADEDC53454A16DC6F5D517B88F4DD71DAA4436B212A1D3781A22889B43A393CEACDC52301037BF9D2852327792862BD3F41FD0F34C214230F51720830257D2C9D72A5A7D4FA2F12E36E746AE24AD9C0B6A8AD1F830B760AB178DDF40DE4550D5C2C9EAF540C587B76231430E8F9CB9DDDDF2EA033E513A0301A3555BF4B4121B5F9C1A7DF6A3553B461FF09D18E25F90E11795D98DBBA1517F7D73F1FE03A6F20E2A19836714BA3821AFC44852544DF1E1B34226AB29D465DB032C910DAD5EFB8ED71EF202F91973AC2534B28887616D16741A201C0118D80CFEB3B664CA9C10B54940E39BE5D3B872FBD55E527F28740FA6657206F4041CEF27822DF64C9CAB8A6E526CFCBC8449BADC9930EFE1DCE4926C54517E4D7E1349E37A4BE5FF5854A95DA361658B9850DAF60A106848A249AC31222E5FE3AD36F931A5AA80D27235957B41780D4D0070BA6E6C06E58129E9FB000EEFD8D314F2095F4792E601DAFC8423D980CB60B12DD1E0E0FF5CF0BE924EAEDC65CEE257892578F956E99C302C405C984C9270D9AC6EB1AD0E06EC19AA1C7DC08BC57ED6B8B129EDADF53CCBAAB00757816B68471C10F472621B9ED3696410F3FD6608140AEAC1E4557BDD216B222C58ED34EF40614F554309EB74FD70235BAF0906B06775FD73B7D337C9F52118F2B2171A048F2D1A3D26C55A6DC02B10A81DD94619B797EA4A939F76E8DC24161C00FA5781002EA335C49907C028772C6B083E061A97FE1C9E2E110E7A153537ED3264E51720E29A93ABBBB1CD8B39E183F6C507BD4E1F58C1E21DA843581EACB224985E1A5BBEBD83DE61E3923BAB674C74EE19DEFAFBDF6BAB9740EAA77D445A4A37CD96645218123B1AD8AD4705473C790C5CBB4A38E13A1BBB4931D17D1C6BEF45BC67E247D2E9DB0DFA17BD560945F676D8094D373F6C5B932B329F29BCFC7B7BEECDD4F26853BD07A3224FC01FE57FF85ABC4BFBAB4ECCDF6289369FFDBCC1C3FE7B4FD23803BFE65E41B9682B01E2DBF63D48D69E18AD72A6B766FE1B3783F7AD927A826C1F3717BA87AFE7B8C3E55406428F885399E7EEDE89B5878016B6858098C8F6866A88EF106A87968BC1BD69A9738284AA9461AFD525C54C3F67E43A8F9694FE8EF0F24D2FAA2A6B5E1A584D3F20DE102DAEA52DB6176CDCE202D560846281D6EDCDC9B583578EA12DB9787E23DF7321A99C11A52E3C627A875083D5294FDC07E90FFE60E1932B275C693BC683E5C9B77D2ED3BF5CD2FA1EB5AC087BC051825DCD04AF90C306E422FFCEE8A753A2E77D6878469BFD7348BD190CD77251CB9FCCE796B2C7CE072FD1A339F9C92AF5177B5C689B1A4B8481C86640307611829B57161D789B92FEE9E080D42C9BA05E30FDC5F23D7216D5EE5AC376930D89153A3345D7DFB4AF681115B537412DA193A27687222AA3E3FB73BC59FE2327139B69CA2A663270F58156DA031D195F9B55000A44902CA91512E52E90EE0A36050820B2035100A36C8CEE6CA513D1887F88B48018DB41DFE04536D083C57EB9B908F1225A85F51DA5DE3C5827A507C3EB2D29EE746667AD7466773F144F74B982535CDC8C16FAF398FEA8AE6F322F683F96793653F66157632D889121558748DA1BB46A7B2C28376D14C91C617379C96E7AC57A88014B23BD368AA35D705501DBF9885E120C701655EDF9030ED4E436D0C745C5FDA1C8AE40D374E741F2800CF70C70E80373DA2830B96A47CB50DDE4EE3664EB1FA60B26B1E701EF15537D9F376B99900C8F7AA8C7E6DFFC718FFF4D5AF92ABE4AAF9177C5E75E61859271FA5092D94D0DB82B493BE79743FFADF75D63302CB72ED1FCB1D85825342EFBEE7924E038154BF1282550A29D543C6FE35C6E63B9BF92850A90A0212410F7980FD2CC4133AAE6310033C4C564D4C3E707EAEF7A1440D90CF7969BB4D5767B09DCDF85AF1BB1E90E4876B5B60F8783F39C2254AB462920F0A9D766FFBF4F8F4FFDE216C8B3AEFA01FF05174DBA1724DCC027FA1E99E9F6DEEE502D1D18AED4863FCDC7168B2CD36AD7C78AAA9AD7BFAE02955FBB7ED5A768D543DE59EB3D899E656E3A711C82B333C00B9E50390E056959D526430F26D5233880E2E54C1A0EDA358748A83EAB8FD47E668F2C59484757A349887AB8844255770CCFAA7C1BA7BA7C98659B5BF2041D055B873C3D7800C07513811DC6B79F27C28DE349A09D93051A29A189B04CEFC76C1C57EBF842DA732C924A20D87163E1C25AE405DA42BA8F5DBAF92B221E5221858AA2C068A242AC0C952E93795927F0418AA7A737E9DD3EE0B604D1A0703AF9C321850690F9FA52DED8B7BEB60982A533B2AA1E9C34B2C59ED25558A9BC4D0E240E1BA9C452959B67C502CE2D0532CB62B84B9492B6F2026AE13AB823532F6D8B3E1835ACA9109D95465AC31B5B502EF153C9D445278EE0B49F1BA439CCC0E48E2B0EC4831ADBF4E83DBE3819C7BD5B1B906E8E0E7068CD432FFF1E79B8AC83B0323339FA377E260346781F8BA3E5D8388785C51B608EB42440ED08D1BFAE74241B77B2F78BE9F5D66885960E6468194119E7B7378792FDE1057975D05F9FD83798DE387D45BAF494A31F807479E4E7C854A216A30F093EE02C23D1509507139B03943FC582507296F6450A48D6A83C41717C2E3435952B1F106DE51F60002A5776BF8295EC0BC7435B037EC587F39679C4E7F46879DB0C3084A8DEA78F25355FA4DBEC2CB2881F0502A0142B227A352B48DCF2D0429A333A1F3ACEDAB763DAE6802231301040463E5FAF8A47C3FC15D35116289010C7B2C5735D0C8E7AA70AC99F7EBC8E56F00F1AF3859EE2400C10A8F5B0863D200A270476152BB4F25BDB4790E3305DEA13F83433032367282595040FB3722188689F871E01C0BCB10C9915704D1F0F13AE2BE2F03BF0EAB363B1346D36373ADB93B4D429D67B106D603406E2641EDC88B144A2B427E71E86F33224A1917EC3FEEB3E7572B7880DE0530D7B698F20FE547836F85261ABF844766BEB317D625EE9DB0002929BE340DF9068EB6FAC32D51A34C5ADFE78AD3B27C2D05ACB9D191D408BB3EAACB63F398D9B2B8CA90AC6290DEC09EF2231029AB3BACFBA0794461ECBF7E1053724AA9923D5D36DF6A6900BE3139E68B5AB5A9E2D3C292EA47C5B7AE8F27FCF25CD373E7D1D24B5045B8782B3FF070D45283B9C014E4AFEE370AF9941AC7AAC9C77D2DA55CF129AFACF10301B497795E1154982348B311F10FC2CF1DBE0C1F6B977A048B1C3E132B1EECB56D49A69368936453E8BE6D1D6B160FD5A2388A7EDBB020EC4937DEFB2180D91A9E0130E7E5B62539E6351396E4ABBD7A06A5607F49E65C18BA50ACE38F6943242126CE9B203E003644EE59A4C6A27FBB5F8F33DEFD7C698063DFA9F15AB382309E48AB87CF7DD89271D38C012BD55FF76478F9D18FB7FD3FF4AF1D4A7C548A20FD6026BF7DBE47A6BB317AE17D2773E9B49BC1C85F5E88378B968F6508B19F1FD0AFB006EB08ADCDF6E439AAE6CBFD13578CE749A652C10F6592BB4E50337EC95ED7430B1B9438FC5CF463BD95A99DCBE027BCFA3B1BCE5D8492570123A6E728ACA50C4596E3EA0EE292539D9239F547835B01FB7CA5ADDF4B96D4121DA7A7AC3DB044CAF4E4622A9B5088001B9BB0FE0648051302C452EEE2CB4E1A3A95179D3D191EE1AFCA7EA8ED10B0E6F187809B775779D64BBE076687B8D8C69BFE93AD1B1B643B14CBC509861BB923840400A4D53321055B9E828D7C6A103E7CD1F7115A6E6AA3D155432C1C2140CE0374AC1732DD8A709BCC38ADBFC8A98D14AEEEA70AF5168175028B162CF057619A39D3CD61670C5E133D8BE297157712817E99ABD0F08E1BBF752D72A8FF263A5938B7AB3C8232471DF55FB2CFBCF2B458A65C10B02D36F2960F6CE67122ED987E3B92CA4B6F6465A6D7E4DC027EDC20C41DF51DCADB33AE572E0E063F1D0AD0F89B21CE3AAE6EF8520010601D6873F0E459E3E6C65AB965393F0600CFAFBD274C5B01E39072F8EB12299E72E139EB893639D0BFB042B824E1309BC6426254921E8E8F169F577B9024C9DD31D6821E99A8540ADC96A9749E8C5DD574DCAA72A160ECF71FB603D6202DD44E7FC295A24CCB402440D2F57C1031D56B96E8D0E152FB963C5DFE624E2A5907A87323977A28E0DF3D4051D780593FD4BAFB304FAD8868EE3B0027398B27DC3FF8826E682A9114C19F1E6022F5AE2576A53EB9DB747EBA10E1318D276E90D0A39DC79910D39F8103DEE220174B4429816C9244962DC0A7FF480F518FB64B81E872F329EFC80D054A8328E932DF975FC5059A2A25E024F0B0DBCEAE138662B0B4730CBD74C847D516D042B1C07E5A14BEF13B75E9EF714FFE840FED3014C1EE4049D7D7205E33BDB1910451E54405D088DA733CBA0AC0209EDFA140C8289B3EDFD7256D266C6C5F1E001F1FC04C6EA08BDC2425D121187F54FEB514A1BC834406A2968A8D8D6B8E234767B1F3C048F8349681E1FE033B14B429B6B36D2633F2B62C77784FF585FBFBA5387F7F248E182A20E34838D56435D7864AFEE18377B0494262808C9A1EA5A7C6019AA4AA2607D4235839EAB6C07CA766A43E50E64E5A525C6CAC7243E6D41BB44B91AFB60E528AF9027062275C36A9B738568C3A879F5CA30353BD49A3B99853182AA15E802DB9B2D00A54D4A608F89E1A040A94909A75E47E0484A9FCE88D195F9A68175663AD76950D667DD527F270104D6F3F0C363FDDB43C5A76B5F4286C982EA76E7754BF1315A3343030057641C484727DD6E96BF83BD2D8FDC10669122180153C3FF5415F64D60FFC1193359ECA2C7A204A9B684CB414D4A8C9CCE9050278702304698A9BCC535BB5EF731B609B25B1CDC66D57D8C58CA3E03D68AA32D6EC0F3F3F174411BE55ADDE553F6B6B633BEBDCAC12A3BF636B2302CDE6D75A1F46EE70A4EC40993669C48614A0C1FFCDF57AE52B6CFFFEBD84CD878DBD911C384A57F23B2AE3F9AEA8399F41106C03CD4C6595D24CAA0D4C63349209DD71D08B402564F8C68A2EC910ECB92CBA4897B3FFBCEA8F3BEC3DAF94E01C319FA4A33A5E7AA0AD9BCF4B8AD8CAF391F1F3477F44B8FB86122DACD490A292911E2BC531E39C99A3C207347EFB20BD6B2C481454B00A3F6126F54D44AEE82A10B3F98AEEFFA3A4DD0A4F21E728F3FDCDFADA2F7E263787D375A99680726C1CF8C309C7953ABBF3956436CC6322AA797FC53BF7A36DE1D2B6A1DCE2FABE286976A416E3B7655E2A1A0BDB750E7CDC2A2F2C0B28D4267C92E6218CA20173E0FAB0C0EFFA1ED6787D04FCAE79E6B5476BBA79C8F40E49B7849B6163749BF397F2B90CD685AA14C662550F8AE4FECA5C11EA1ABF240C511894DAB586E44131AEF22A5271F69954489205A1BEE9665C920170A3968513F26A543593F6FC99E694EA77F713FABE640B718827787E4A8F2538782C02061D11336A8B474A49C83701068DA0F305F1EB2981E679FD2BE31F68EDDDFC61DEFA57A364B554A268A28F19C2630F33E85C6D137BC66D39B9E36EBE613A1C88E187024010179C2F2D1B2E425AE28D822734349BAD17D0BC41E34B8C96C2DEE84BC3B886B7778CC54338FB56782C9707B28B260697476BAC977409FCD5331C5BADFC4C47C136BFAC6EF42383F24A1368C41565D08DC7A783333A15B5E74AA0CA207F3212D5A1BECFA6DEF8A229D4CE2C2755A84EAE714C3EB262544447AFA5E62C8710A94958F6041797A163139662F188542CAEE6F107C8E222484CF2CFB66201C990C85B3F17F566B7E801D86F0594BC31A3928448D52158B60839F47CA2817CBC7969844837AEB86570DDB24EC9A7743114B253CDEFA6E4BC6351A192B10EC6A3AB6CBF324743B9F6EA1981C795AB0F996EFB786E7173B545C5A5C71E6AEE5036B5B43D7718699F77AEA9D0CB67EF3E1BC720BF3DAA6A51939CC850AED1C3A22164B7969639C5B9E5543FE093B91F940DB3452DE5F06651873C0E00202BFE387C0C4DC9C478639BE36651336CB54BF7CA68AC717102303C5AE44CD06DD696641F04F08AB3D8C58EB48E4FDEFC3FCF1DCD6503BD51DBF8AEC6D2D0D4CDF13A5DB6AA8A3EFCAB9DF69CC97ACC9117566EB85622BC5BE7FB25614138D7EC57256E1C8ABD0C76ACD263D6FC081C7E4565521CB989BD4EB8BAA8DE885FF0074C0CEE83413FCD2957831317571D666782AA2A2BF32402B224F9F0837F0648C6F9CDEA2F0F1791356ADE1BEE6511F495C64CF3C53973B4EE961889EB8D6E407E7C4F453F1DBF96997FB71EC7C747BE91E7E153743B8D0EF483900A02CB7E75CC70964F70F694AD07BAFC25A28386EA7FB53E31ABAA98B3AB1D3296B3E80DAFD84A5B77812FCCC566726E5A62768C2A4069D6A67C3E1E7EC8BF811133BC9D9855C80BE022EC0FDC0E3787E91F2524229162D731184B3FE86BE1A28BB49D7837EA5A9F843E78E4D37501B592A6998DCCF275AA71026E8FE4F0D05955535C9E42651D71070270200F4A5701AB26BA5F552C4AE06F5D6A0AE4B4453DC7611B5BE185C7B94C4952BF3367B1681B057C97CD567BF1BCEBDD5C0A64A2F51DD4F03972537C9216E645A8FC50E85E374FD9740E4B02368ED32A8CCE53295E755EAB8A72B7979EA8AF58B8F4C98B77BAE2ABC53EA9FE024C4D2980A8FFDE353ABDB4EE3AC24BF1B09CED1CEFA510E03A85B9DCBD9027946C6A4F097D319483BF576B584DCE7A6B59D5A69B8A6310F38BD6843FF70D019A3C4C9E48011FFB703FBE1A216D4508D39D670F4C4F193391A84F78A628AB4C67148E77501C3A4F8582FBF0E0872C38F37BAAFC6ABF7CB5937AACFCB0FAFB014ADA48665F45C30566BF7D7B0E80C3E686040C0398B63FA9E243AF065174E6160C2B6EA0C8FD0CC39CB66C98C166D3F6CB2B49B98D6D64B829AEE4983779594DCC638316554FAF00452F6B19C4934ED73ED749E126CCC0D346FFF1A941D9A1E1D893279F33E3F1A4E6BF1F1C64239069B14C5C3F97D9249E0BAA8B775C5AA9E7E6970D22D11ACA3DC87A78BA7D1D66FC8DAF1B40FC794866BD824A4D998DB02103517CD76F84096DDC8BDA4B95DA590FEA43F39CF0B10558885BE83E73B5C148266D899863B89AFBA50723460D323E670A6F33BB49A776DD9C3140DEED86C3E9BBE484C0FEF90503D6EFCDE363CBB25D0E8CC7FF9E22F06B2BD00885F9F358D5D49E82F5FC76A02B1CFBFBF0E19B3DA2F46ED57FD68B2385DC27880C79E7B0573C1F43825D8FDC5FC6A2AF88521A212789D2FD7E5194E505BDB4A33C27C81F7CB486C39BE3D53C611FAAB659D2CADB442E3C0BA6F7ED0BD5F3681F173532228FD97BD1105D8B85E6F769546CD8EC61C853E27DCBCBF7AA696ACA8DF9731FFDCC3B27AE60C71FE800971BDD12EA4B0EC7F3F9AB2FBE4A5B9795C11EA28E307AF8184AC911FB78B9EDCCB9398A50183E03F770F95AE0BAFFA9E6FBC8FCAA1A086B667AF5C85E9E7C5BC902FF479E302CED9F8E40CCA257837CE41273BE0B4D41A49B46FD3FAFE7C2F7B0832389DC67F9D5F5447F074ACCBAA9EAA8D68032D34E5C9A0516209CD07D0289EF5F1E72209108A38318CE7A088A02A7867081BFF9348D002D87E5F5D2CE1D92F00913574EAB4A2E0D37801C0B0F2DBAC44F40724E0494A904CC9C00570406931FE320B5552F834B78782A50DBD033A46B08A6DC7A190535B0969E5AA37A3E3989395A1A37B365B76AF1446C6E92314F771E0E78B3E8450324125157DBF523DB421FAED94F8D7F26FB56C8EF44ADD8C30B921A31114BB1B9E4DD463CA236A80FF5276C0679033D9D532595F6EC28388968FFE3D8E85AFBE1268D9A72A91D305E699A473F99BFFD4A6A5B0853FD1F1EFB4BBCB509DB65866D6B4EBFC95BF043D4D55907179E45E4291C86DB6FF9C55CAE5CB2221AA35517C90F2F36E827775BAAEC76DBC99DE7647837931ED2E002C6F1D091424F4EFC3FA12829643DB29307367146D2AFBF4CA97E55D2E605568249C81177BC20C3BF321C6BA03482AAEF4D0E99D8377F671110FCFDEC798604CCE26577E4ECBD33E27D814148EA7CC8A5065A3CF8D464F0EEC3D27C3FAD99376057BEA5ADE3D349EF5E290A24ACC592374E0FB46DBAFB98525A7F6A6C367E241BDEB5E2745056E8A0683BAA0B2FF929CFB4EE5E86C0741BE7074B71E0AF170E00FB6E563B84ECD1FABB46670ACFD279867821DD80D5F6230EFEA01DA80EDE5859F413E33B0DB3734AF569EAFA71CF78C017C65E5371D89C19B23D28C01614C8C86E2A9CA444D75EB2071F5744944D9B6085712B7C7C6BE5BD6FCB6E09B50B0A53B6A987B632A38D6303910082A76CACB9AAF87EEBD1C75A5A11995B37A73B3C4D3820DAFDB961A7E3C91C82236A0880BDBE60FB9AA024BFED87C6D6106772AAA12ABDD88F454D63D9463694209548325201645AAFC3504C3FBC008439DE6C5BF3C74DAAFF460FF66D99713C538723EF21BB2418016C97B09AFC13140DD8AEA75E97BF47492715D63189006CE262B55B0C1DCFB684771D5FBDDCD1828C93F2CCCC1EB0C02D25D277C7E790B02EAB38EF439C34E5C39E18D82A43244A0720C46F566FA156E64E975EBB00FAA71D0B6F3A6EF7A9FE8E526661371D2BBAC65937C80680ECA62191F1FB269FE4E130717F3E2D325ED4960A2BECE38DE4F24583903CD329F756502B052EEC306EF4861EB2E76A43DDF6E54E6467AFE4AD2EAD73F3FD33C67A814EE754BCF0F1B1916FDECFED141627093543627703A794FD5C28A0732C877A51BBFBCFB83DF19851DAB292C6B9933A873BBCA1D0C42AD57C56D2A8B63231D387E4B9E74ABAB79E63D0C63CFA97BBA49C7A0CE4B244348ED9D635EE13073E940ABF8925CC851C703F375DA4006FC9F24E0B9CF332424E2D9A1F3E39E8B3D7E90D7D48E863335DA1719F50874BA95B619B03EF29A319769A2BD11BA0ACE140EDE421375CE70FD70D74541E404D8776EDB76CBEF1A645C792DC832C5989E4A81F8B9FCA8727543CDA12F7C7CD71B22357EC381B5363F62C722B6A6BFC03411F8BD60275425A6D8155AF50FCFA645ABA7C2C31059EC9A6D6AD7D06E84B2DF9EC871B63855E337ADBEEC3FABDC4BCDDB0D2AE156D23A6DA90BD3BCBA8E5C38021C2DB7E9F74EFA9A30CCAEF370D795715DA72D6E2AC7298B350AE82B3C08F17EA50EA00C93EB3C590D5D42A51D3EBCF9F1EAEE81C5E7AD76C06DA3FC28B54909336631D6CB79D838A4900A469C4E9196AE2917312D1BB5725DEF626F7B3C591D5FC4D8E68290FCB39CD61CAF2FDA5316FB9B97E53592BE823F81D29FAB809932A021AA079C9349FF70E73B55DCDA1E725CC8187E90C3ABC25AFF04F9FCD2772754A9CB6FA114B43A6E5CD1BD47CC113E3EFAC25A90728C68A1AE51700 -------------------------------------------------------------------------------- /test/VpxCppTest/VpxCppTest.cpp: -------------------------------------------------------------------------------- 1 | #include "Windows.h"" 2 | #include "strutils.h" 3 | #include "vp8cx.h" 4 | #include "vp8dx.h" 5 | #include "vpx_decoder.h" 6 | #include "vpx_encoder.h" 7 | 8 | #include 9 | #include 10 | 11 | std::vector convertYV12toRGB(const vpx_image_t* img); 12 | void CreateBitmapFile(LPCWSTR fileName, long width, long height, WORD bitsPerPixel, BYTE* bitmapData, DWORD bitmapDataLength); 13 | 14 | inline int clamp8(int v) 15 | { 16 | return min(max(v, 0), 255); 17 | } 18 | 19 | int main() 20 | { 21 | std::cout << "libvpx test console\n"; 22 | 23 | std::cout << "vp8 encoder version " << vpx_codec_version_str() << "." << std::endl; 24 | std::cout << "VPX_ENCODER_ABI_VERSION=" << VPX_ENCODER_ABI_VERSION << "." << std::endl; 25 | std::cout << "VPX_DECODER_ABI_VERSION=" << VPX_DECODER_ABI_VERSION << "." << std::endl; 26 | 27 | int width = 640; 28 | int height = 480; 29 | int stride = 1; 30 | 31 | vpx_codec_ctx_t codec; 32 | vpx_image_t* img{ nullptr }; 33 | vpx_codec_ctx_t decoder; 34 | 35 | img = vpx_img_alloc(NULL, VPX_IMG_FMT_I420, width, height, stride); 36 | 37 | vpx_codec_enc_cfg_t vpxConfig; 38 | vpx_codec_err_t res; 39 | 40 | // Initialise codec configuration. 41 | res = vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &vpxConfig, 0); 42 | 43 | if (res) { 44 | printf("Failed to get VPX codec config: %s\n", vpx_codec_err_to_string(res)); 45 | return -1; 46 | } 47 | 48 | vpxConfig.g_w = width; 49 | vpxConfig.g_h = height; 50 | 51 | // Initialise codec. 52 | res = vpx_codec_enc_init(&codec, vpx_codec_vp8_cx(), &vpxConfig, 0); 53 | 54 | if (res) { 55 | printf("Failed to initialise VPX codec: %s\n", vpx_codec_err_to_string(res)); 56 | return -1; 57 | } 58 | 59 | // Do a test encode. 60 | std::vector dummyI420(640 * 480 * 3 / 2); 61 | vpx_enc_frame_flags_t flags = 0; 62 | 63 | vpx_img_wrap(img, VPX_IMG_FMT_I420, width, height, 1, dummyI420.data()); 64 | 65 | res = vpx_codec_encode(&codec, img, 1, 1, flags, VPX_DL_REALTIME); 66 | if (res) { 67 | printf("VPX codec failed to encode dummy frame. %s\n", vpx_codec_err_to_string(res)); 68 | return -1; 69 | } 70 | 71 | vpx_codec_iter_t iter = NULL; 72 | const vpx_codec_cx_pkt_t* pkt; 73 | 74 | //while ((pkt = vpx_codec_get_cx_data(&codec, &iter))) { 75 | pkt = vpx_codec_get_cx_data(&codec, &iter); 76 | switch (pkt->kind) { 77 | case VPX_CODEC_CX_FRAME_PKT: 78 | printf("Encode success %s %i\n", (pkt->data.frame.flags & VPX_FRAME_IS_KEY) ? "K" : ".", pkt->data.frame.sz); 79 | break; 80 | default: 81 | printf("Got unknown packet type %d.\n", pkt->kind); 82 | break; 83 | } 84 | //} 85 | 86 | // Attempt to decode. 87 | res = vpx_codec_dec_init(&decoder, vpx_codec_vp8_dx(), NULL, 0); 88 | if (res) { 89 | printf("Failed to initialise VPX decoder: %s\n", vpx_codec_err_to_string(res)); 90 | return -1; 91 | } 92 | 93 | res = vpx_codec_decode(&decoder, (const uint8_t*)pkt->data.frame.buf, pkt->data.frame.sz, nullptr, 0); 94 | if (res) { 95 | printf("Failed to decode buffer: %s\n", vpx_codec_err_to_string(res)); 96 | return -1; 97 | } 98 | 99 | vpx_codec_iter_t decoder_iter = NULL; 100 | vpx_image_t* decodedImg = vpx_codec_get_frame(&decoder, &decoder_iter); 101 | 102 | if (decodedImg != NULL) { 103 | printf("Decode successful, width %d, height %d.\n", decodedImg->d_w, decodedImg->d_h); 104 | 105 | for (int i = 0; i < 4; i++) { 106 | printf("stride[%d]=%d, plane[%d]=%d.\n", i, decodedImg->stride[i], i, decodedImg->planes[i]); 107 | } 108 | 109 | auto rgb = convertYV12toRGB(decodedImg); 110 | 111 | CreateBitmapFile(L"test-decode.bmp", width, height, 24, rgb.data(), width * height * 3); 112 | } 113 | } 114 | 115 | std::vector convertYV12toRGB(const vpx_image_t* img) 116 | { 117 | std::vector data (img->d_w * img->d_h * 3); 118 | 119 | uint8_t* yPlane = img->planes[VPX_PLANE_Y]; 120 | uint8_t* uPlane = img->planes[VPX_PLANE_U]; 121 | uint8_t* vPlane = img->planes[VPX_PLANE_V]; 122 | 123 | int i = 0; 124 | for (unsigned int imgY = 0; imgY < img->d_h; imgY++) { 125 | for (unsigned int imgX = 0; imgX < img->d_w; imgX++) { 126 | int y = yPlane[imgY * img->stride[VPX_PLANE_Y] + imgX]; 127 | int u = uPlane[(imgY / 2) * img->stride[VPX_PLANE_U] + (imgX / 2)]; 128 | int v = vPlane[(imgY / 2) * img->stride[VPX_PLANE_V] + (imgX / 2)]; 129 | 130 | int c = y - 16; 131 | int d = (u - 128); 132 | int e = (v - 128); 133 | 134 | // TODO: adjust colors ? 135 | 136 | int r = clamp8((298 * c + 409 * e + 128) >> 8); 137 | int g = clamp8((298 * c - 100 * d - 208 * e + 128) >> 8); 138 | int b = clamp8((298 * c + 516 * d + 128) >> 8); 139 | 140 | // TODO: cast instead of clamp8 141 | 142 | data[i + 0] = static_cast(r); 143 | data[i + 1] = static_cast(g); 144 | data[i + 2] = static_cast(b); 145 | 146 | i += 3; 147 | } 148 | } 149 | return data; 150 | } 151 | 152 | /** 153 | * Creates a bitmap file and writes to disk. 154 | * @param[in] fileName: the path to save the file at. 155 | * @param[in] width: the width of the bitmap. 156 | * @param[in] height: the height of the bitmap. 157 | * @param[in] bitsPerPixel: colour depth of the bitmap pixels (typically 24 or 32). 158 | * @param[in] bitmapData: a pointer to the bytes containing the bitmap data. 159 | * @param[in] bitmapDataLength: the number of pixels in the bitmap. 160 | */ 161 | void CreateBitmapFile(LPCWSTR fileName, long width, long height, WORD bitsPerPixel, BYTE* bitmapData, DWORD bitmapDataLength) 162 | { 163 | HANDLE file; 164 | BITMAPFILEHEADER fileHeader; 165 | BITMAPINFOHEADER fileInfo; 166 | DWORD writePosn = 0; 167 | 168 | file = CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); //Sets up the new bmp to be written to 169 | 170 | fileHeader.bfType = 19778; //Sets our type to BM or bmp 171 | fileHeader.bfSize = sizeof(fileHeader.bfOffBits) + sizeof(RGBTRIPLE); //Sets the size equal to the size of the header struct 172 | fileHeader.bfReserved1 = 0; //sets the reserves to 0 173 | fileHeader.bfReserved2 = 0; 174 | fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //Sets offbits equal to the size of file and info header 175 | fileInfo.biSize = sizeof(BITMAPINFOHEADER); 176 | fileInfo.biWidth = width; 177 | fileInfo.biHeight = height; 178 | fileInfo.biPlanes = 1; 179 | fileInfo.biBitCount = bitsPerPixel; 180 | fileInfo.biCompression = BI_RGB; 181 | fileInfo.biSizeImage = width * height * (bitsPerPixel / 8); 182 | fileInfo.biXPelsPerMeter = 2400; 183 | fileInfo.biYPelsPerMeter = 2400; 184 | fileInfo.biClrImportant = 0; 185 | fileInfo.biClrUsed = 0; 186 | 187 | WriteFile(file, &fileHeader, sizeof(fileHeader), &writePosn, NULL); 188 | 189 | WriteFile(file, &fileInfo, sizeof(fileInfo), &writePosn, NULL); 190 | 191 | WriteFile(file, bitmapData, bitmapDataLength, &writePosn, NULL); 192 | 193 | CloseHandle(file); 194 | } 195 | 196 | -------------------------------------------------------------------------------- /test/VpxCppTest/VpxCppTest.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {be7cf335-177e-45e5-a632-e40c68570f21} 25 | VpxCppTest 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | $(ProjectDir)$(Platform)\$(Configuration)\ 82 | 83 | 84 | false 85 | $(ProjectDir)$(Platform)\$(Configuration)\ 86 | 87 | 88 | 89 | Level3 90 | true 91 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | C:\Dev\github\libvpx\vpx; 94 | 95 | 96 | Console 97 | true 98 | 99 | 100 | 101 | 102 | Level3 103 | true 104 | true 105 | true 106 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 107 | true 108 | C:\Dev\github\libvpx\vpx; 109 | 110 | 111 | Console 112 | true 113 | true 114 | true 115 | 116 | 117 | 118 | 119 | Level3 120 | true 121 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 122 | true 123 | C:\Dev\github\libvpx\vpx; 124 | 125 | 126 | Console 127 | true 128 | C:\Dev\github\libvpx\build-win-x64\x64\Debug\vpxmdd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 129 | 130 | 131 | 132 | 133 | Level3 134 | true 135 | true 136 | true 137 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 138 | true 139 | C:\Dev\github\libvpx\vpx; 140 | 141 | 142 | Console 143 | true 144 | true 145 | true 146 | C:\Dev\github\libvpx\build-win-x64\x64\Release\vpxmd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /test/VpxCppTest/VpxCppTest.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 | 23 | 24 | Header Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/VpxCppTest/strutils.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Filename: strutils.h 3 | // 4 | // Description: Useful string utilities originally from Bitcoin Core. 5 | // 6 | // Copyright (c) 2009-2010 Satoshi Nakamoto 7 | // Copyright (c) 2009-2017 The Bitcoin Core developers 8 | // Distributed under the MIT software license, see the accompanying 9 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. 10 | //----------------------------------------------------------------------------- 11 | 12 | #ifndef STRUTILS_H 13 | #define STRUTILS_H 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace 21 | { 22 | const signed char p_util_hexdigit[256] = 23 | { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 25 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, 27 | -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 29 | -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 31 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 32 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 33 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 34 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 35 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 37 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 38 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; 39 | 40 | signed char HexDigit(char c) 41 | { 42 | return p_util_hexdigit[(unsigned char)c]; 43 | } 44 | 45 | bool IsHex(const std::string& str) 46 | { 47 | for (std::string::const_iterator it(str.begin()); it != str.end(); ++it) 48 | { 49 | if (HexDigit(*it) < 0) 50 | return false; 51 | } 52 | return (str.size() > 0) && (str.size() % 2 == 0); 53 | } 54 | 55 | bool IsHexNumber(const std::string& str) 56 | { 57 | size_t starting_location = 0; 58 | if (str.size() > 2 && *str.begin() == '0' && *(str.begin() + 1) == 'x') { 59 | starting_location = 2; 60 | } 61 | for (auto c : str.substr(starting_location)) { 62 | if (HexDigit(c) < 0) return false; 63 | } 64 | // Return false for empty string or "0x". 65 | return (str.size() > starting_location); 66 | } 67 | 68 | std::vector ParseHex(const char* psz) 69 | { 70 | // convert hex dump to vector 71 | std::vector vch; 72 | while (true) 73 | { 74 | while (isspace(*psz)) 75 | psz++; 76 | signed char c = HexDigit(*psz++); 77 | if (c == (signed char)-1) 78 | break; 79 | unsigned char n = (c << 4); 80 | c = HexDigit(*psz++); 81 | if (c == (signed char)-1) 82 | break; 83 | n |= c; 84 | vch.push_back(n); 85 | } 86 | return vch; 87 | } 88 | 89 | std::vector ParseHex(const std::string& str) 90 | { 91 | return ParseHex(str.c_str()); 92 | } 93 | 94 | template < class T > 95 | const std::string toHex(const T& begin, const T& end) 96 | { 97 | std::ostringstream str; 98 | for (T it = begin; it != end; ++it) 99 | str << std::setw(2) << std::setfill('0') << std::hex << (unsigned)(*it & 0xff); 100 | 101 | return str.str(); 102 | } 103 | 104 | template < class T > 105 | const std::string toHex(const T& v) 106 | { 107 | return toHex(v.begin(), v.end()); 108 | } 109 | } 110 | 111 | #endif STRUTILS_H -------------------------------------------------------------------------------- /test/VpxCppTest/test-decode.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/VpxCppTest/test-decode.bmp -------------------------------------------------------------------------------- /test/VpxTest/Program.cs: -------------------------------------------------------------------------------- 1 | // ffplay -probesize 32 -protocol_whitelist "file,rtp,udp" -i ffplay-vp8.sdp 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Drawing; 6 | using System.Drawing.Drawing2D; 7 | using System.Drawing.Imaging; 8 | using System.Linq; 9 | using System.Net; 10 | using System.Runtime.InteropServices; 11 | using System.Threading; 12 | using SIPSorceryMedia.Windows.Codecs; 13 | using SIPSorcery.Net; 14 | using System.IO; 15 | 16 | namespace Vpx 17 | { 18 | class Program 19 | { 20 | private static string TEST_PATTERN_IMAGE_PATH = "media/testpattern.jpeg"; 21 | private const int FRAMES_PER_SECOND = 30; 22 | private const int TEST_PATTERN_SPACING_MILLISECONDS = 33; 23 | private const float TEXT_SIZE_PERCENTAGE = 0.035f; // height of text as a percentage of the total image height 24 | private const float TEXT_OUTLINE_REL_THICKNESS = 0.02f; // Black text outline thickness is set as a percentage of text height in pixels 25 | private const int TEXT_MARGIN_PIXELS = 5; 26 | private const int POINTS_PER_INCH = 72; 27 | private const int VIDEO_TIMESTAMP_SPACING = 3000; 28 | private const int FFPLAY_DEFAULT_VIDEO_PORT = 5024; 29 | 30 | private static Bitmap _testPattern; 31 | private static Timer _sendTestPatternTimer; 32 | private static Vp8Codec _vp8Encoder; 33 | private static Vp8Codec _vp8Decoder; 34 | private static long _presentationTimestamp = 0; 35 | 36 | private static event Action OnTestPatternSampleReady; 37 | 38 | static void Main(string[] args) 39 | { 40 | Console.WriteLine("VPX Encoding Test Console"); 41 | 42 | Initialise(); 43 | 44 | //StreamToFFPlay(); 45 | 46 | //RoundTripNoEncoding(); 47 | 48 | for (int i = 0; i < 25; i++) 49 | { 50 | DateTime startTime = DateTime.Now; 51 | RoundTripTestPattern(); 52 | Console.WriteLine($"encode+decode took {DateTime.Now.Subtract(startTime).TotalMilliseconds}ms."); 53 | } 54 | 55 | Console.WriteLine("Press any key to exit..."); 56 | Console.ReadLine(); 57 | } 58 | 59 | private static void Initialise() 60 | { 61 | _testPattern = new Bitmap(TEST_PATTERN_IMAGE_PATH); 62 | _vp8Encoder = new Vp8Codec(); 63 | _vp8Encoder.InitialiseEncoder((uint)_testPattern.Width, (uint)_testPattern.Height); 64 | _vp8Decoder = new Vp8Codec(); 65 | _vp8Decoder.InitialiseDecoder(); 66 | } 67 | 68 | private static void RoundTripNoEncoding() 69 | { 70 | int width = 32; 71 | int height = 32; 72 | 73 | // Create dummy bitmap. 74 | byte[] srcRgb = new byte[width * height * 3]; 75 | for (int row = 0; row < 32; row++) 76 | { 77 | for (int col = 0; col < 32; col++) 78 | { 79 | int index = row * width * 3 + col * 3; 80 | 81 | int red = (row < 16 && col < 16) ? 255 : 0; 82 | int green = (row < 16 && col > 16) ? 255 : 0; 83 | int blue = (row > 16 && col < 16) ? 255 : 0; 84 | 85 | srcRgb[index] = (byte)red; 86 | srcRgb[index + 1] = (byte)green; 87 | srcRgb[index + 2] = (byte)blue; 88 | } 89 | } 90 | 91 | //Console.WriteLine(srcRgb.HexStr()); 92 | 93 | unsafe 94 | { 95 | fixed (byte* src = srcRgb) 96 | { 97 | System.Drawing.Bitmap bmpImage = new System.Drawing.Bitmap(width, height, srcRgb.Length / height, PixelFormat.Format24bppRgb, (IntPtr)src); 98 | bmpImage.Save("test-source.bmp"); 99 | bmpImage.Dispose(); 100 | } 101 | } 102 | 103 | // Convert bitmap to i420. 104 | byte[] i420Buffer = PixelConverter.RGBtoI420(srcRgb, width, height); 105 | 106 | Console.WriteLine($"Converted rgb to i420."); 107 | 108 | byte[] rgbResult = PixelConverter.I420toRGB(i420Buffer, width, height); 109 | 110 | unsafe 111 | { 112 | fixed (byte* s = rgbResult) 113 | { 114 | System.Drawing.Bitmap bmpImage = new System.Drawing.Bitmap(width, height, rgbResult.Length / height, PixelFormat.Format24bppRgb, (IntPtr)s); 115 | bmpImage.Save("test-result.bmp"); 116 | bmpImage.Dispose(); 117 | } 118 | } 119 | } 120 | 121 | private static void RoundTripTestPatternNoEncoding() 122 | { 123 | var stampedTestPattern = _testPattern.Clone() as System.Drawing.Image; 124 | int tWidth = _testPattern.Width; 125 | int tHeight = _testPattern.Height; 126 | AddTimeStampAndLocation(stampedTestPattern, DateTime.UtcNow.ToString("dd MMM yyyy HH:mm:ss:fff"), "Test Pattern"); 127 | var sampleBuffer = PixelConverter.BitmapToRGBA(stampedTestPattern as System.Drawing.Bitmap, _testPattern.Width, _testPattern.Height); 128 | byte[] i420 = PixelConverter.RGBAtoYUV420Planar(sampleBuffer, _testPattern.Width, _testPattern.Height); 129 | 130 | byte[] rgb = PixelConverter.I420toRGB(i420, tWidth, tHeight); 131 | 132 | unsafe 133 | { 134 | fixed (byte* s = rgb) 135 | { 136 | System.Drawing.Bitmap bmpImage = new System.Drawing.Bitmap(tWidth, tHeight, rgb.Length / tHeight, PixelFormat.Format24bppRgb, (IntPtr)s); 137 | bmpImage.Save("roundtrip.bmp"); 138 | bmpImage.Dispose(); 139 | } 140 | } 141 | } 142 | 143 | private static void RoundTripTestPattern() 144 | { 145 | var stampedTestPattern = _testPattern.Clone() as System.Drawing.Image; 146 | int tWidth = _testPattern.Width; 147 | int tHeight = _testPattern.Height; 148 | AddTimeStampAndLocation(stampedTestPattern, DateTime.UtcNow.ToString("dd MMM yyyy HH:mm:ss:fff"), "Test Pattern"); 149 | var sampleBuffer = PixelConverter.BitmapToRGBA(stampedTestPattern as System.Drawing.Bitmap, _testPattern.Width, _testPattern.Height); 150 | byte[] i420 = PixelConverter.RGBAtoYUV420Planar(sampleBuffer, _testPattern.Width, _testPattern.Height); 151 | 152 | var encodedBuffer = _vp8Encoder.Encode(i420, false); 153 | 154 | Console.WriteLine($"VP8 encoded buffer length {encodedBuffer.Length}."); 155 | 156 | List i420Frames = _vp8Decoder.Decode(encodedBuffer, encodedBuffer.Length, out var dWidth, out var dHeight); 157 | 158 | Console.WriteLine($"VP8 decoded frames count {i420Frames.Count}, first frame length {i420Frames.First().Length}, width {dWidth}, height {dHeight}."); 159 | 160 | byte[] rgb = i420Frames.First(); 161 | 162 | unsafe 163 | { 164 | fixed (byte* s = rgb) 165 | { 166 | System.Drawing.Bitmap bmpImage = new System.Drawing.Bitmap((int)dWidth, (int)dHeight, rgb.Length / (int)dHeight, PixelFormat.Format24bppRgb, (IntPtr)s); 167 | bmpImage.Save("encodedroundtrip.bmp"); 168 | bmpImage.Dispose(); 169 | } 170 | } 171 | } 172 | 173 | private static void StreamToFFPlay() 174 | { 175 | var videoCapabilities = new List 176 | { 177 | new SDPMediaFormat(SDPMediaFormatsEnum.VP8) 178 | }; 179 | int payloadID = Convert.ToInt32(videoCapabilities.First().FormatID); 180 | 181 | var rtpSession = CreateRtpSession(videoCapabilities); 182 | OnTestPatternSampleReady += (media, duration, payload) => rtpSession.SendVp8Frame(duration, payloadID, payload); 183 | rtpSession.Start(); 184 | 185 | Console.WriteLine("press any key to start..."); 186 | Console.ReadKey(); 187 | 188 | _sendTestPatternTimer = new Timer(SendTestPattern, null, 0, TEST_PATTERN_SPACING_MILLISECONDS); 189 | } 190 | 191 | private static RTPSession CreateRtpSession(List videoFormats) 192 | { 193 | var rtpSession = new RTPSession(false, false, false, IPAddress.Loopback); 194 | 195 | MediaStreamTrack videoTrack = new MediaStreamTrack(SDPMediaTypesEnum.video, false, videoFormats, MediaStreamStatusEnum.SendRecv); 196 | rtpSession.addTrack(videoTrack); 197 | 198 | rtpSession.SetDestination(SDPMediaTypesEnum.video, new IPEndPoint(IPAddress.Loopback, FFPLAY_DEFAULT_VIDEO_PORT), new IPEndPoint(IPAddress.Loopback, FFPLAY_DEFAULT_VIDEO_PORT + 1)); 199 | 200 | return rtpSession; 201 | } 202 | 203 | private static void SendTestPattern(object state) 204 | { 205 | lock (_sendTestPatternTimer) 206 | { 207 | unsafe 208 | { 209 | if (OnTestPatternSampleReady != null) 210 | { 211 | var stampedTestPattern = _testPattern.Clone() as System.Drawing.Image; 212 | AddTimeStampAndLocation(stampedTestPattern, DateTime.UtcNow.ToString("dd MMM yyyy HH:mm:ss:fff"), "Test Pattern"); 213 | var sampleBuffer = PixelConverter.BitmapToRGBA(stampedTestPattern as System.Drawing.Bitmap, _testPattern.Width, _testPattern.Height); 214 | 215 | byte[] i420Buffer = PixelConverter.RGBAtoYUV420Planar(sampleBuffer, _testPattern.Width, _testPattern.Height); 216 | var encodedBuffer = _vp8Encoder.Encode(i420Buffer, false); 217 | 218 | _presentationTimestamp += VIDEO_TIMESTAMP_SPACING; 219 | 220 | if (encodedBuffer != null) 221 | { 222 | OnTestPatternSampleReady?.Invoke(SDPMediaTypesEnum.video, VIDEO_TIMESTAMP_SPACING, encodedBuffer); 223 | } 224 | 225 | stampedTestPattern.Dispose(); 226 | } 227 | } 228 | } 229 | } 230 | 231 | private static void AddTimeStampAndLocation(System.Drawing.Image image, string timeStamp, string locationText) 232 | { 233 | int pixelHeight = (int)(image.Height * TEXT_SIZE_PERCENTAGE); 234 | 235 | Graphics g = Graphics.FromImage(image); 236 | g.SmoothingMode = SmoothingMode.AntiAlias; 237 | g.InterpolationMode = InterpolationMode.HighQualityBicubic; 238 | g.PixelOffsetMode = PixelOffsetMode.HighQuality; 239 | 240 | using (StringFormat format = new StringFormat()) 241 | { 242 | format.LineAlignment = StringAlignment.Center; 243 | format.Alignment = StringAlignment.Center; 244 | 245 | using (Font f = new Font("Tahoma", pixelHeight, GraphicsUnit.Pixel)) 246 | { 247 | using (var gPath = new GraphicsPath()) 248 | { 249 | float emSize = g.DpiY * f.Size / POINTS_PER_INCH; 250 | if (locationText != null) 251 | { 252 | gPath.AddString(locationText, f.FontFamily, (int)FontStyle.Bold, emSize, new Rectangle(0, TEXT_MARGIN_PIXELS, image.Width, pixelHeight), format); 253 | } 254 | 255 | gPath.AddString(timeStamp /* + " -- " + fps.ToString("0.00") + " fps" */, f.FontFamily, (int)FontStyle.Bold, emSize, new Rectangle(0, image.Height - (pixelHeight + TEXT_MARGIN_PIXELS), image.Width, pixelHeight), format); 256 | g.FillPath(Brushes.White, gPath); 257 | g.DrawPath(new Pen(Brushes.Black, pixelHeight * TEXT_OUTLINE_REL_THICKNESS), gPath); 258 | } 259 | } 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /test/VpxTest/VpxTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/VpxTest/ffplay-vp8.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 1012165843 0 IN IP4 127.0.0.1 3 | s=- 4 | c=IN IP4 127.0.0.1 5 | t=0 0 6 | m=video 5024 RTP/AVP 100 7 | a=rtpmap:100 VP8/90000 8 | a=sendonly 9 | -------------------------------------------------------------------------------- /test/VpxTest/media/testpattern.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/VpxTest/media/testpattern.jpeg -------------------------------------------------------------------------------- /test/h264bsdDecodeTest/h264bsdDecodeTest.cs: -------------------------------------------------------------------------------- 1 | // ==> Extract h264 byte stream from mp4 2 | // ffmpeg - i max_intro.mp4 - profile:v baseline -f h264 max_intro.h264 3 | 4 | using System; 5 | 6 | using System.IO; 7 | 8 | namespace h264bsd 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | Console.WriteLine("h264bsd Decoding Test Console"); 15 | 16 | Console.WriteLine("Press any key to exit..."); 17 | Console.ReadLine(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/h264bsdDecodeTest/h264bsdDecodeTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/h264bsdDecodeTest/h264bsdDecodeTest.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30611.23 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "h264bsdDecodeTest", "h264bsdDecodeTest.csproj", "{D2984BF1-0B40-4857-993A-20D39A92A9BD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D2984BF1-0B40-4857-993A-20D39A92A9BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D2984BF1-0B40-4857-993A-20D39A92A9BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D2984BF1-0B40-4857-993A-20D39A92A9BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D2984BF1-0B40-4857-993A-20D39A92A9BD}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F7FD6F35-BDF1-4B00-A6C2-E98B46385646} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /test/h264bsdDecodeTest/test_640x360.h264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sipsorcery-org/SIPSorceryMedia.Encoders/a12d06a05c4e4e9d588b7597e4ef1c1e82f6e445/test/h264bsdDecodeTest/test_640x360.h264 --------------------------------------------------------------------------------