├── .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
--------------------------------------------------------------------------------