├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── docs └── Migrating your native_mobile application to Unified Plan_WebRTC 1 - Google Docs.pdf ├── webrtc-dotnet-console-demo ├── ConsoleDemoProgram.cs ├── Properties │ └── launchSettings.json ├── background-small.jpg └── webrtc-dotnet-console-demo.csproj ├── webrtc-dotnet-core.sln ├── webrtc-dotnet-core.sln.DotSettings ├── webrtc-dotnet-graphics ├── BitmapFrameD2D1.cs ├── BouncingBallRenderer.cs ├── GpuVendorId.cs ├── IRenderer.cs ├── MaybeSendableFrame.cs ├── PreciseWaitableClock.cs ├── RendererOptions.cs ├── SDL2 │ └── README-SDL.txt ├── SdlWindow.cs ├── VideoFrameBuffer.cs ├── VideoRenderer.cs ├── Y8.cs └── webrtc-dotnet-graphics-d3d11.csproj ├── webrtc-dotnet-leak-test ├── App.config ├── LeakTestProgram.cs ├── Properties │ └── AssemblyInfo.cs └── webrtc-dotnet-leak-test.csproj ├── webrtc-dotnet-signalling ├── IceCandidate.cs ├── SessionDescription.cs └── webrtc-dotnet-signalling.csproj ├── webrtc-dotnet-tests ├── PeerConnectionTests.cs ├── RendererTests.cs └── webrtc-dotnet-tests.csproj ├── webrtc-dotnet-web-demo ├── .gitignore ├── ComObjectComparer.cs ├── ComReflection.cs ├── ImageSharpRenderer.cs ├── MouseEventKind.cs ├── MouseMessage.cs ├── Properties │ └── launchSettings.json ├── README-SDL.txt ├── RtcRenderingServer.cs ├── Startup.cs ├── WebDemoProgram.cs ├── WebSocketExt.cs ├── WebSocketReader.cs ├── appsettings.Development.json ├── appsettings.json ├── background-small.jpg ├── tsconfig.json ├── webrtc-dotnet-web-demo.csproj └── wwwroot │ ├── .gitignore │ ├── favicon.ico │ ├── index.css │ ├── index.html │ ├── index.min.css │ └── index.ts ├── webrtc-dotnet ├── .gitignore ├── Callbacks.cs ├── ConnectionState.cs ├── DataChannelOptions.cs ├── DataMessage.cs ├── Disposable.cs ├── DisposableList.cs ├── EnumerableExtensions.cs ├── GlobalOptions.cs ├── IceConnectionState.cs ├── IceGatheringState.cs ├── MessageEncoding.cs ├── Native.cs ├── ObservableExtensions.cs ├── ObservablePeerConnection.cs ├── ObservableVideoTrack.cs ├── PeerConnection.cs ├── PeerConnectionOptions.cs ├── PeerConnectionState.cs ├── RemoteTrackChange.cs ├── SignalingState.cs ├── TraceLevelExtensions.cs ├── VideoEncoderOptions.cs ├── VideoFrame.cs ├── VideoFrameFormat.cs ├── VideoFrameMessage.cs ├── VideoFrameTexture.cs ├── VideoFrameYuvAlpha.cs ├── VideoMotion.cs ├── VideoTrack.cs └── webrtc-dotnet.csproj ├── webrtc-native-nvenc ├── AppEncUtilsD3D11.h ├── NvCodec │ └── NvEncoder │ │ ├── NvEncoder.cpp │ │ ├── NvEncoder.h │ │ ├── NvEncoderD3D11.cpp │ │ ├── NvEncoderD3D11.h │ │ ├── NvEncoderD3D9.cpp │ │ ├── NvEncoderD3D9.h │ │ ├── NvEncoderOutputInVidMemD3D11.cpp │ │ └── NvEncoderOutputInVidMemD3D11.h ├── NvEnc │ ├── Lib │ │ ├── Win32 │ │ │ └── nvcuvid.lib │ │ └── linux │ │ │ └── stubs │ │ │ └── x86_64 │ │ │ ├── libnvcuvid.so │ │ │ └── libnvidia-encode.so │ └── include │ │ └── nvEncodeAPI.h ├── NvEncFacadeD3D11.cpp ├── NvEncFacadeD3D11.h ├── pch.cpp ├── pch.h ├── webrtc-native-nvenc.vcxproj └── webrtc-native-nvenc.vcxproj.filters ├── webrtc-native ├── DummySetSessionDescriptionObserver.cpp ├── DummySetSessionDescriptionObserver.h ├── EncoderFactory.cpp ├── EncoderFactory.h ├── InjectableVideoTrackSource.cpp ├── InjectableVideoTrackSource.h ├── LICENSE ├── NativeInterface.cpp ├── NativeInterface.h ├── NativeVideoBuffer.cpp ├── NativeVideoBuffer.h ├── NvEncoderH264.cpp ├── NvEncoderH264.h ├── PeerConnection.cpp ├── PeerConnection.h ├── TestVideoCapturer.cpp ├── TestVideoCapturer.h ├── VideoCameraCapturer.cpp ├── VideoCameraCapturer.h ├── VideoFrameEvents.h ├── VideoObserver.cpp ├── VideoObserver.h ├── filesystem.h ├── libs.cpp ├── macros.h ├── main.cpp ├── main.h ├── pch.cpp ├── pch.h ├── webrtc-native.vcxproj └── webrtc-native.vcxproj.filters └── windows_build_nvpipe.bat /.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | ############################### 3 | # Core EditorConfig Options # 4 | ############################### 5 | # All files 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | insert_final_newline = true 10 | 11 | # Code files 12 | [*.{cs,csx,vb,vbx}] 13 | charset = utf-8-bom 14 | ############################### 15 | # .NET Coding Conventions # 16 | ############################### 17 | [*.{cs,vb}] 18 | # Organize usings 19 | dotnet_sort_system_directives_first = true 20 | # this. preferences 21 | dotnet_style_qualification_for_field = false:silent 22 | dotnet_style_qualification_for_property = false:silent 23 | dotnet_style_qualification_for_method = false:silent 24 | dotnet_style_qualification_for_event = false:silent 25 | # Language keywords vs BCL types preferences 26 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 27 | dotnet_style_predefined_type_for_member_access = true:silent 28 | # Parentheses preferences 29 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 30 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 31 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 32 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 33 | # Modifier preferences 34 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 35 | dotnet_style_readonly_field = true:suggestion 36 | # Expression-level preferences 37 | dotnet_style_object_initializer = true:suggestion 38 | dotnet_style_collection_initializer = true:suggestion 39 | dotnet_style_explicit_tuple_names = true:suggestion 40 | dotnet_style_null_propagation = true:suggestion 41 | dotnet_style_coalesce_expression = true:suggestion 42 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 43 | dotnet_prefer_inferred_tuple_names = true:suggestion 44 | dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion 45 | dotnet_style_prefer_auto_properties = true:silent 46 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 47 | dotnet_style_prefer_conditional_expression_over_return = true:silent 48 | ############################### 49 | # Naming Conventions # 50 | ############################### 51 | # Style Definitions 52 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 53 | # Use PascalCase for constant fields 54 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 55 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 56 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 57 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 58 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 59 | dotnet_naming_symbols.constant_fields.required_modifiers = const 60 | ############################### 61 | # C# Coding Conventions # 62 | ############################### 63 | [*.cs] 64 | # var preferences 65 | csharp_style_var_for_built_in_types = true:silent 66 | csharp_style_var_when_type_is_apparent = true:silent 67 | csharp_style_var_elsewhere = true:silent 68 | # Expression-bodied members 69 | csharp_style_expression_bodied_methods = false:silent 70 | csharp_style_expression_bodied_constructors = false:silent 71 | csharp_style_expression_bodied_operators = false:silent 72 | csharp_style_expression_bodied_properties = true:silent 73 | csharp_style_expression_bodied_indexers = true:silent 74 | csharp_style_expression_bodied_accessors = true:silent 75 | # Pattern matching preferences 76 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 77 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 78 | # Null-checking preferences 79 | csharp_style_throw_expression = true:suggestion 80 | csharp_style_conditional_delegate_call = true:suggestion 81 | # Modifier preferences 82 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 83 | # Expression-level preferences 84 | csharp_prefer_braces = true:silent 85 | csharp_style_deconstructed_variable_declaration = true:suggestion 86 | csharp_prefer_simple_default_expression = true:suggestion 87 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 88 | csharp_style_inlined_variable_declaration = true:suggestion 89 | ############################### 90 | # C# Formatting Rules # 91 | ############################### 92 | # New line preferences 93 | csharp_new_line_before_open_brace = all 94 | csharp_new_line_before_else = true 95 | csharp_new_line_before_catch = true 96 | csharp_new_line_before_finally = true 97 | csharp_new_line_before_members_in_object_initializers = true 98 | csharp_new_line_before_members_in_anonymous_types = true 99 | csharp_new_line_between_query_expression_clauses = true 100 | # Indentation preferences 101 | csharp_indent_case_contents = true 102 | csharp_indent_switch_labels = true 103 | csharp_indent_labels = flush_left 104 | # Space preferences 105 | csharp_space_after_cast = false 106 | csharp_space_after_keywords_in_control_flow_statements = true 107 | csharp_space_between_method_call_parameter_list_parentheses = false 108 | csharp_space_between_method_declaration_parameter_list_parentheses = false 109 | csharp_space_between_parentheses = false 110 | csharp_space_before_colon_in_inheritance_clause = true 111 | csharp_space_after_colon_in_inheritance_clause = true 112 | csharp_space_around_binary_operators = before_and_after 113 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 114 | csharp_space_between_method_call_name_and_opening_parenthesis = false 115 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 116 | # Wrapping preferences 117 | csharp_preserve_single_line_statements = true 118 | csharp_preserve_single_line_blocks = true 119 | ############################### 120 | # VB Coding Conventions # 121 | ############################### 122 | [*.vb] 123 | # Modifier preferences 124 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 125 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dependencies/webrtc-build"] 2 | path = dependencies/webrtc-build 3 | url = https://github.com/WonderMediaProductions/webrtc-build.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Wonder Media Productions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Portions of this code are based on the webrtc source code, with the following license: 24 | 25 | Copyright (c) 2011, The WebRTC project authors. All rights reserved. 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions are 29 | met: 30 | 31 | * Redistributions of source code must retain the above copyright 32 | notice, this list of conditions and the following disclaimer. 33 | 34 | * Redistributions in binary form must reproduce the above copyright 35 | notice, this list of conditions and the following disclaimer in 36 | the documentation and/or other materials provided with the 37 | distribution. 38 | 39 | * Neither the name of Google nor the names of its contributors may 40 | be used to endorse or promote products derived from this software 41 | without specific prior written permission. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # NOTE: This project is no longer maintained 3 | 4 | * Prequisites 5 | * a modern NVidia card installed with NVENC support 6 | * Visual Studio 2017 7 | * You should have installed the ASP.NET, C++ Desktop, and .NET Core workloads 8 | * GIT LFS 9 | * If you installed it after cloning, run `git submodule foreach --recursive git lfs pull` 10 | * Open Visual Studio 2017 11 | * Load the `webrtc-dotnet-core.sln` solution 12 | * Set the `webrtc-dotnet-web-demo` as startup project 13 | * Build and run the `x64` target 14 | * Open a webpage at `https://localhost:3000` 15 | * Only tested on Windows Chrome and MacOS Safari 16 | * Clip on the right page 17 | * You should see a bouncing ball in the web browser 18 | -------------------------------------------------------------------------------- /docs/Migrating your native_mobile application to Unified Plan_WebRTC 1 - Google Docs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/docs/Migrating your native_mobile application to Unified Plan_WebRTC 1 - Google Docs.pdf -------------------------------------------------------------------------------- /webrtc-dotnet-console-demo/ConsoleDemoProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using SixLabors.ImageSharp; 9 | using SixLabors.ImageSharp.Advanced; 10 | using SixLabors.ImageSharp.PixelFormats; 11 | using SixLabors.ImageSharp.Processing; 12 | 13 | namespace WonderMediaProductions.WebRtc 14 | { 15 | 16 | // TODO: This demo doesn't work anymore, because receiving video on the server side is not supported for now. 17 | class ConsoleDemoProgram 18 | { 19 | int remoteFrameIndex = 0; 20 | 21 | static void Main(string[] args) 22 | { 23 | var program = new ConsoleDemoProgram(); 24 | program.Run(); 25 | } 26 | 27 | private void Run() 28 | { 29 | try 30 | { 31 | // For debugging, run everything on this thread. 32 | // Should never be done in production. 33 | // Note that webrtc callbacks are done on the signaling thread, and must return asap. 34 | PeerConnection.Configure(new GlobalOptions 35 | { 36 | UseWorkerThread = false, 37 | UseSignalingThread = false, 38 | ForceSoftwareVideoEncoder = true, 39 | MinimumLogLevel = System.Diagnostics.TraceLevel.Info, 40 | LogToStandardError = false, 41 | LogToDebugOutput = false 42 | }); 43 | 44 | PeerConnection.MessageLogged += (message, severity) => 45 | { 46 | severity.WriteToConsole(message); 47 | }; 48 | 49 | Console.OutputEncoding = Encoding.UTF8; 50 | 51 | const int frameWidth = 320; 52 | const int frameHeight = 180; 53 | const int frameRate = 10; 54 | 55 | using (var senderOutgoingMessages = new ReplaySubject()) 56 | using (var sender = new ObservablePeerConnection(new PeerConnectionOptions 57 | { 58 | Name = "Sender" 59 | })) 60 | using (var receiver = new ObservablePeerConnection(new PeerConnectionOptions 61 | { 62 | Name = "Receiver", 63 | CanReceiveVideo = true 64 | })) 65 | using (var background = Image.Load("background-small.jpg")) 66 | using (receiver.ReceivedVideoStream.Buffer(2).Subscribe(SaveFrame)) 67 | using (var imageFrame = new Image(frameWidth, frameHeight)) 68 | using (var videoTrack = new VideoTrack(sender, 69 | VideoEncoderOptions.OptimizedFor(frameWidth, frameHeight, frameRate))) 70 | { 71 | background.Mutate(ctx => ctx.Resize(frameWidth, frameHeight)); 72 | 73 | senderOutgoingMessages.OnNext(new DataMessage("data", "Hello")); 74 | 75 | sender.CreateOffer(); 76 | 77 | sender.Connect(senderOutgoingMessages, receiver.LocalSessionDescriptionStream, receiver.LocalIceCandidateStream); 78 | 79 | var receiverOutgoingMessages = receiver 80 | .ReceivedDataStream 81 | .Where(msg => msg.AsText == "Hello") 82 | .Do(msg => Console.WriteLine($"Received message {msg.AsText}")) 83 | .Select(msg => new DataMessage(msg.Label, "World")); 84 | 85 | receiver.Connect(receiverOutgoingMessages, sender.LocalSessionDescriptionStream, sender.LocalIceCandidateStream); 86 | 87 | sender.AddDataChannel(new DataChannelOptions()); 88 | 89 | Console.WriteLine("Press any key to exit"); 90 | 91 | int localFrameIndex = 0; 92 | 93 | var timeout = TimeSpan.FromMilliseconds(1000.0 / frameRate); 94 | //while (!Console.KeyAvailable && PeerConnection.PumpQueuedMessages(timeout)) 95 | while (PeerConnection.PumpQueuedMessages(timeout)) 96 | { 97 | var frame = imageFrame.Frames[0]; 98 | var pixels = MemoryMarshal.Cast(frame.GetPixelSpan()); 99 | videoTrack.SendVideoFrame(MemoryMarshal.GetReference(pixels), 100 | frame.Width * 4, 101 | frame.Width, 102 | frame.Height, 103 | VideoFrameFormat.Argb32); 104 | 105 | imageFrame.Mutate(ctx => ctx.DrawImage(GraphicsOptions.Default, background).Rotate(localFrameIndex * 10).Crop(frameWidth, frameHeight)); 106 | 107 | ++localFrameIndex; 108 | } 109 | 110 | sender.RemoveDataChannel("data"); 111 | } 112 | } 113 | catch (Exception ex) 114 | { 115 | Console.WriteLine($"*** FAILURE: {ex}"); 116 | } 117 | 118 | Console.WriteLine("Press ENTER to exit"); 119 | Console.ReadLine(); 120 | } 121 | 122 | private unsafe void SaveFrame(IList frames) 123 | { 124 | var frame0 = frames[0]; 125 | 126 | // Save as JPEG for debugging. SLOW! 127 | if (frame0 is VideoFrameYuvAlpha yuvFrame && yuvFrame.Width == yuvFrame.StrideY) 128 | { 129 | var span = new ReadOnlySpan(yuvFrame.DataY.ToPointer(), yuvFrame.Width * yuvFrame.Height); 130 | using (var image = Image.LoadPixelData(span, yuvFrame.Width, yuvFrame.Height)) 131 | { 132 | image.Save($@"frame_{remoteFrameIndex:D000000}.bmp"); 133 | } 134 | 135 | ++remoteFrameIndex; 136 | } 137 | else 138 | { 139 | Console.WriteLine("Unsupported frame layout"); 140 | } 141 | 142 | if (frames.Count == 2) 143 | { 144 | var frame1 = frames[1]; 145 | var dt = frame1.TimeStamp - frame0.TimeStamp; 146 | Console.WriteLine($"Received video frame\t{frame1.Width}x{frame1.Height}\tΔt={dt.TotalMilliseconds:000.0}\tutc={frame1.TimeStamp:G}"); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /webrtc-dotnet-console-demo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "webrtc-dotnet-console-demo": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /webrtc-dotnet-console-demo/background-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/webrtc-dotnet-console-demo/background-small.jpg -------------------------------------------------------------------------------- /webrtc-dotnet-console-demo/webrtc-dotnet-console-demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | WonderMediaProductions.WebRtc 7 | $(ProjectDir)..\out\bin\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 8 | true 9 | latest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /webrtc-dotnet-core.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "webrtc-native", "webrtc-native\webrtc-native.vcxproj", "{31CC4ECC-E4A0-448B-8969-3C08C951A461}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-dotnet", "webrtc-dotnet\webrtc-dotnet.csproj", "{21F815B4-9154-4472-B03C-E1C2D1F98A26}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {31CC4ECC-E4A0-448B-8969-3C08C951A461} = {31CC4ECC-E4A0-448B-8969-3C08C951A461} 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-dotnet-web-demo", "webrtc-dotnet-web-demo\webrtc-dotnet-web-demo.csproj", "{B3FFA253-FE93-48C6-8506-CF296BD04835}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-dotnet-console-demo", "webrtc-dotnet-console-demo\webrtc-dotnet-console-demo.csproj", "{12ECC036-0488-4A87-B5AC-958163D7AB22}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E9481380-4A26-4FF2-A7A5-169E5A8CA980}" 18 | ProjectSection(SolutionItems) = preProject 19 | .editorconfig = .editorconfig 20 | .gitignore = .gitignore 21 | EndProjectSection 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-dotnet-tests", "webrtc-dotnet-tests\webrtc-dotnet-tests.csproj", "{A9DE998A-F5B8-4EBA-8D2F-03B1D5F641DA}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-dotnet-graphics-d3d11", "webrtc-dotnet-graphics\webrtc-dotnet-graphics-d3d11.csproj", "{AC928C86-F000-43CA-8A3C-2BF243379534}" 26 | EndProject 27 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "webrtc-native-nvenc", "webrtc-native-nvenc\webrtc-native-nvenc.vcxproj", "{4645FFF5-9866-4CD4-B432-8BAC320E560E}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "webrtc-dotnet-signalling", "webrtc-dotnet-signalling\webrtc-dotnet-signalling.csproj", "{6B365CEF-CAD2-43D3-A690-8BC61A640D5B}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "webrtc-dotnet-leak-test", "webrtc-dotnet-leak-test\webrtc-dotnet-leak-test.csproj", "{6C5204D6-12EE-4DD3-90C8-0894E359744B}" 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|x64 = Debug|x64 36 | Release|x64 = Release|x64 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {31CC4ECC-E4A0-448B-8969-3C08C951A461}.Debug|x64.ActiveCfg = Debug|x64 40 | {31CC4ECC-E4A0-448B-8969-3C08C951A461}.Debug|x64.Build.0 = Debug|x64 41 | {31CC4ECC-E4A0-448B-8969-3C08C951A461}.Release|x64.ActiveCfg = Release|x64 42 | {31CC4ECC-E4A0-448B-8969-3C08C951A461}.Release|x64.Build.0 = Release|x64 43 | {21F815B4-9154-4472-B03C-E1C2D1F98A26}.Debug|x64.ActiveCfg = Debug|Any CPU 44 | {21F815B4-9154-4472-B03C-E1C2D1F98A26}.Debug|x64.Build.0 = Debug|Any CPU 45 | {21F815B4-9154-4472-B03C-E1C2D1F98A26}.Release|x64.ActiveCfg = Release|Any CPU 46 | {21F815B4-9154-4472-B03C-E1C2D1F98A26}.Release|x64.Build.0 = Release|Any CPU 47 | {B3FFA253-FE93-48C6-8506-CF296BD04835}.Debug|x64.ActiveCfg = Debug|Any CPU 48 | {B3FFA253-FE93-48C6-8506-CF296BD04835}.Debug|x64.Build.0 = Debug|Any CPU 49 | {B3FFA253-FE93-48C6-8506-CF296BD04835}.Release|x64.ActiveCfg = Release|Any CPU 50 | {B3FFA253-FE93-48C6-8506-CF296BD04835}.Release|x64.Build.0 = Release|Any CPU 51 | {12ECC036-0488-4A87-B5AC-958163D7AB22}.Debug|x64.ActiveCfg = Debug|Any CPU 52 | {12ECC036-0488-4A87-B5AC-958163D7AB22}.Debug|x64.Build.0 = Debug|Any CPU 53 | {12ECC036-0488-4A87-B5AC-958163D7AB22}.Release|x64.ActiveCfg = Release|Any CPU 54 | {12ECC036-0488-4A87-B5AC-958163D7AB22}.Release|x64.Build.0 = Release|Any CPU 55 | {A9DE998A-F5B8-4EBA-8D2F-03B1D5F641DA}.Debug|x64.ActiveCfg = Debug|Any CPU 56 | {A9DE998A-F5B8-4EBA-8D2F-03B1D5F641DA}.Debug|x64.Build.0 = Debug|Any CPU 57 | {A9DE998A-F5B8-4EBA-8D2F-03B1D5F641DA}.Release|x64.ActiveCfg = Release|Any CPU 58 | {A9DE998A-F5B8-4EBA-8D2F-03B1D5F641DA}.Release|x64.Build.0 = Release|Any CPU 59 | {AC928C86-F000-43CA-8A3C-2BF243379534}.Debug|x64.ActiveCfg = Debug|Any CPU 60 | {AC928C86-F000-43CA-8A3C-2BF243379534}.Debug|x64.Build.0 = Debug|Any CPU 61 | {AC928C86-F000-43CA-8A3C-2BF243379534}.Release|x64.ActiveCfg = Release|Any CPU 62 | {AC928C86-F000-43CA-8A3C-2BF243379534}.Release|x64.Build.0 = Release|Any CPU 63 | {4645FFF5-9866-4CD4-B432-8BAC320E560E}.Debug|x64.ActiveCfg = Debug|x64 64 | {4645FFF5-9866-4CD4-B432-8BAC320E560E}.Debug|x64.Build.0 = Debug|x64 65 | {4645FFF5-9866-4CD4-B432-8BAC320E560E}.Release|x64.ActiveCfg = Release|x64 66 | {4645FFF5-9866-4CD4-B432-8BAC320E560E}.Release|x64.Build.0 = Release|x64 67 | {6B365CEF-CAD2-43D3-A690-8BC61A640D5B}.Debug|x64.ActiveCfg = Debug|Any CPU 68 | {6B365CEF-CAD2-43D3-A690-8BC61A640D5B}.Debug|x64.Build.0 = Debug|Any CPU 69 | {6B365CEF-CAD2-43D3-A690-8BC61A640D5B}.Release|x64.ActiveCfg = Release|Any CPU 70 | {6B365CEF-CAD2-43D3-A690-8BC61A640D5B}.Release|x64.Build.0 = Release|Any CPU 71 | {6C5204D6-12EE-4DD3-90C8-0894E359744B}.Debug|x64.ActiveCfg = Debug|x64 72 | {6C5204D6-12EE-4DD3-90C8-0894E359744B}.Debug|x64.Build.0 = Debug|x64 73 | {6C5204D6-12EE-4DD3-90C8-0894E359744B}.Release|x64.ActiveCfg = Release|x64 74 | {6C5204D6-12EE-4DD3-90C8-0894E359744B}.Release|x64.Build.0 = Release|x64 75 | EndGlobalSection 76 | GlobalSection(SolutionProperties) = preSolution 77 | HideSolutionNode = FALSE 78 | EndGlobalSection 79 | GlobalSection(ExtensibilityGlobals) = postSolution 80 | SolutionGuid = {51174990-FC99-4732-8EDE-9081955C38C9} 81 | EndGlobalSection 82 | EndGlobal 83 | -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/BitmapFrameD2D1.cs: -------------------------------------------------------------------------------- 1 | using D2D1 = SharpDX.Direct2D1; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public sealed class BitmapFrameD2D1 : GraphicsD3D11.VideoFrameBuffer 6 | { 7 | public readonly D2D1.Bitmap1 Bitmap; 8 | 9 | public BitmapFrameD2D1(BouncingBallRenderer renderer, D2D1.DeviceContext context2D) 10 | : base(renderer) 11 | { 12 | using (var surface = Texture.QueryInterface()) 13 | { 14 | Bitmap = new D2D1.Bitmap1(context2D, surface); 15 | } 16 | } 17 | protected override void OnDispose(bool isDisposing) 18 | { 19 | if (isDisposing) 20 | { 21 | Bitmap?.Dispose(); 22 | } 23 | 24 | base.OnDispose(isDisposing); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/GpuVendorId.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc.GraphicsD3D11 2 | { 3 | public static class GpuVendorId 4 | { 5 | public const int NVidia = 4318; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/IRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SharpDX.Mathematics.Interop; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public interface IRenderer : IDisposable 7 | { 8 | RawVector2? MousePosition { get; set; } 9 | 10 | bool SendFrame(TimeSpan elapsedTime); 11 | } 12 | } -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/MaybeSendableFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace WonderMediaProductions.WebRtc.GraphicsD3D11 5 | { 6 | /// 7 | /// An frame waiting to be send, or null 8 | /// 9 | public struct MaybeSendableFrame : IDisposable 10 | { 11 | [CanBeNull] 12 | internal readonly VideoRenderer Renderer; 13 | 14 | [CanBeNull] 15 | internal readonly VideoFrameBuffer Frame; 16 | 17 | internal MaybeSendableFrame(VideoRenderer renderer, VideoFrameBuffer frame) 18 | { 19 | Renderer = renderer; 20 | Frame = frame; 21 | } 22 | 23 | public bool TryGetFrame(out VideoFrameBuffer frame) 24 | { 25 | frame = Frame; 26 | return frame != null; 27 | } 28 | 29 | public bool TryGetFrame(out T frame) where T: VideoFrameBuffer 30 | { 31 | frame = Frame as T; 32 | return frame != null; 33 | } 34 | 35 | public void Dispose() 36 | { 37 | Renderer?.TransmitSendableFrame(this); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/PreciseWaitableClock.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable InconsistentNaming 2 | 3 | using System; 4 | using System.ComponentModel; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | using Microsoft.Win32.SafeHandles; 8 | 9 | namespace WonderMediaProductions.WebRtc.GraphicsD3D11 10 | { 11 | public class PreciseWaitableClock : Disposable 12 | { 13 | [StructLayout(LayoutKind.Sequential)] 14 | public struct FILETIME 15 | { 16 | public uint dwLowDateTime; 17 | public uint dwHighDateTime; 18 | } 19 | 20 | [DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Auto)] 21 | private static extern SafeWaitHandle CreateWaitableTimerEx(IntPtr lpTimerAttributes, IntPtr lpTimerName, uint dwFlags, uint dwDesiredAccess); 22 | 23 | [DllImport("Kernel32", SetLastError = false, ExactSpelling = true)] 24 | private static extern void GetSystemTimePreciseAsFileTime(out FILETIME lpSystemTimeAsFileTime); 25 | 26 | [DllImport("Kernel32", SetLastError = true, ExactSpelling = true)] 27 | [return: MarshalAs(UnmanagedType.Bool)] 28 | public static extern bool SetWaitableTimer( 29 | [In] SafeWaitHandle hTimer, in FILETIME pDueTime, int lPeriod, [In] IntPtr pfnCompletionRoutine, 30 | [In] IntPtr lpArgToCompletionRoutine, [MarshalAs(UnmanagedType.Bool)] bool fResume); 31 | 32 | 33 | [DllImport("Kernel32", SetLastError = true, ExactSpelling = true)] 34 | [return: MarshalAs(UnmanagedType.Bool)] 35 | public static extern bool CancelWaitableTimer([In] SafeWaitHandle hTimer); 36 | 37 | public PreciseWaitableClock(EventResetMode eventResetMode) 38 | { 39 | const uint TIMER_ALL_ACCESS = 0x1F0003; 40 | const uint CREATE_WAITABLE_TIMER_MANUAL_RESET = 1; 41 | 42 | uint timerFlags = eventResetMode.HasFlag(EventResetMode.ManualReset) ? CREATE_WAITABLE_TIMER_MANUAL_RESET : 0; 43 | var handle = CreateWaitableTimerEx(IntPtr.Zero, IntPtr.Zero, timerFlags, TIMER_ALL_ACCESS); 44 | if (handle == null || handle.IsInvalid) 45 | throw new Win32Exception("CreateWaitableTimerEx failed"); 46 | 47 | WaitHandle = new EventWaitHandle(false, eventResetMode) 48 | { 49 | SafeWaitHandle = handle 50 | }; 51 | } 52 | 53 | public EventWaitHandle WaitHandle { get; } 54 | 55 | public DateTime GetCurrentTime() 56 | { 57 | unchecked 58 | { 59 | GetSystemTimePreciseAsFileTime(out var fileTime); 60 | long ticks = (((long)fileTime.dwHighDateTime) << 32) | fileTime.dwLowDateTime; 61 | return DateTime.FromFileTimeUtc(ticks); 62 | } 63 | } 64 | 65 | /// 66 | /// Sets the timer's wait-handle to fire once at some point in the future. 67 | /// 68 | /// 69 | public void SetFutureEventTime(DateTime eventTime) 70 | { 71 | unchecked 72 | { 73 | FILETIME fileTime; 74 | long ticks = eventTime.ToFileTimeUtc(); 75 | fileTime.dwLowDateTime = (uint)(ticks & 0xFFFFFFFF); 76 | fileTime.dwHighDateTime = (uint)(ticks >> 32); 77 | if (!SetWaitableTimer(WaitHandle.SafeWaitHandle, fileTime, 0, IntPtr.Zero, IntPtr.Zero, false)) 78 | throw new Win32Exception("SetWaitableTimer failed"); 79 | } 80 | } 81 | 82 | /// 83 | /// Cancels a previous call to 84 | /// 85 | public void CancelFutureEventTime() 86 | { 87 | CancelWaitableTimer(WaitHandle.SafeWaitHandle); 88 | } 89 | 90 | protected override void OnDispose(bool isDisposing) 91 | { 92 | if (isDisposing) 93 | { 94 | WaitHandle?.Dispose(); 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/RendererOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using D3D = SharpDX.Direct3D; 3 | using D3D11 = SharpDX.Direct3D11; 4 | 5 | namespace WonderMediaProductions.WebRtc.GraphicsD3D11 6 | { 7 | public class PreviewWindowOptions 8 | { 9 | /// 10 | /// If null, uses the 11 | /// 12 | public int? Width; 13 | 14 | /// 15 | /// If null, uses the , 16 | /// unless the is set, 17 | /// then computes aspect-ratio-preserving height. 18 | /// 19 | public int? Height; 20 | } 21 | 22 | public class RendererOptions 23 | { 24 | public int VideoFrameWidth = 1920; 25 | public int VideoFrameHeight = 1080; 26 | 27 | public int VideoFrameQueueSize = 3; 28 | 29 | public int AdapterVendorId = GpuVendorId.NVidia; 30 | 31 | public D3D.FeatureLevel[] FeatureLevels = {D3D.FeatureLevel.Level_11_1}; 32 | public D3D11.DeviceCreationFlags CreationFlags = D3D11.DeviceCreationFlags.BgraSupport; 33 | 34 | public PreviewWindowOptions PreviewWindowOptions; 35 | 36 | public RendererOptions() 37 | { 38 | #if DEBUG 39 | CreationFlags |= D3D11.DeviceCreationFlags.Debug; 40 | #endif 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/SDL2/README-SDL.txt: -------------------------------------------------------------------------------- 1 | 2 | Please distribute this file with the SDL runtime environment: 3 | 4 | The Simple DirectMedia Layer (SDL for short) is a cross-platform library 5 | designed to make it easy to write multi-media software, such as games 6 | and emulators. 7 | 8 | The Simple DirectMedia Layer library source code is available from: 9 | https://www.libsdl.org/ 10 | 11 | This library is distributed under the terms of the zlib license: 12 | http://www.zlib.net/zlib_license.html 13 | 14 | -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/SdlWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using Vanara.PInvoke; 5 | using static SDL2.SDL; 6 | 7 | namespace WonderMediaProductions.WebRtc.GraphicsD3D11 8 | { 9 | internal class SdlWindow : Disposable 10 | { 11 | private IntPtr _nativePtr; 12 | private static int _initCounter; 13 | 14 | static SdlWindow() 15 | { 16 | // Allow native DLLs to be found in our assembly directory 17 | var directory = AppDomain.CurrentDomain.BaseDirectory; 18 | Kernel32.SetDllDirectory(directory); 19 | } 20 | 21 | public SdlWindow(string title, int width, int height) 22 | { 23 | if (Interlocked.Increment(ref _initCounter) == 1) 24 | { 25 | SDL_Init(0); 26 | SDL_SetHint(SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, "1"); 27 | } 28 | 29 | _nativePtr = SDL_CreateWindow(title, 30 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 31 | width, height, 32 | SDL_WindowFlags.SDL_WINDOW_SHOWN | SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI); 33 | } 34 | 35 | public IntPtr NativeHandle 36 | { 37 | get 38 | { 39 | var windowInfo = new SDL_SysWMinfo(); 40 | SDL_GetWindowWMInfo(_nativePtr, ref windowInfo); 41 | return windowInfo.info.win.window; 42 | } 43 | } 44 | 45 | public void PollAllPendingEvents() 46 | { 47 | while (SDL_PollEvent(out var ev) > 0) 48 | { 49 | } 50 | } 51 | 52 | protected override void OnDispose(bool isDisposing) 53 | { 54 | SDL_DestroyWindow(_nativePtr); 55 | _nativePtr = IntPtr.Zero; 56 | 57 | Debug.Assert(_initCounter > 0); 58 | 59 | if (Interlocked.Decrement(ref _initCounter) == 0) 60 | { 61 | SDL_Quit(); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/VideoFrameBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using D3D11 = SharpDX.Direct3D11; 3 | using DXGI = SharpDX.DXGI; 4 | 5 | namespace WonderMediaProductions.WebRtc.GraphicsD3D11 6 | { 7 | public class VideoFrameBuffer : Disposable 8 | { 9 | public D3D11.Texture2D Texture { get; } 10 | 11 | public VideoFrameBuffer(D3D11.Device device3D, D3D11.Texture2DDescription textureDescription) 12 | { 13 | Texture = new D3D11.Texture2D(device3D, textureDescription); 14 | } 15 | 16 | public VideoFrameBuffer(D3D11.Device device3D, int width, int height) 17 | : this(device3D, new D3D11.Texture2DDescription() 18 | { 19 | Width = width, 20 | Height = height, 21 | Format = DXGI.Format.B8G8R8A8_UNorm, 22 | ArraySize = 1, 23 | MipLevels = 1, 24 | BindFlags = D3D11.BindFlags.RenderTarget, 25 | SampleDescription = new DXGI.SampleDescription(1, 0), 26 | OptionFlags = D3D11.ResourceOptionFlags.None, 27 | CpuAccessFlags = D3D11.CpuAccessFlags.None, 28 | Usage = D3D11.ResourceUsage.Default 29 | }) 30 | { 31 | } 32 | 33 | public VideoFrameBuffer(VideoRenderer renderer) 34 | : this(renderer.Device3D, renderer.VideoFrameWidth, renderer.VideoFrameHeight) 35 | { 36 | 37 | } 38 | 39 | protected override void OnDispose(bool isDisposing) 40 | { 41 | if (isDisposing) 42 | { 43 | Texture?.Dispose(); 44 | } 45 | } 46 | 47 | public virtual void Send(VideoTrack videoTrack) 48 | { 49 | var description = Texture.Description; 50 | 51 | videoTrack.SendVideoFrame(Texture.NativePointer, 52 | 0, description.Width, description.Height, 53 | VideoFrameFormat.GpuTextureD3D11); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /webrtc-dotnet-graphics/webrtc-dotnet-graphics-d3d11.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | WonderMediaProductions.WebRtc.GraphicsD3D11 6 | $(ProjectDir)..\out\bin\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 7 | 7.3 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | %(RecursiveDir)%(FileName)%(Extension) 36 | PreserveNewest 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /webrtc-dotnet-leak-test/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /webrtc-dotnet-leak-test/LeakTestProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reactive.Linq; 4 | using System.Threading; 5 | using SixLabors.ImageSharp; 6 | using WonderMediaProductions.WebRtc.GraphicsD3D11; 7 | 8 | namespace WonderMediaProductions.WebRtc 9 | { 10 | 11 | public class LeakTestProgram 12 | { 13 | // TODO: Can't get this test running with async/await, gets stuck while disposing, deadlocks 14 | 15 | public static void Main() 16 | { 17 | PeerConnection.Configure(new GlobalOptions 18 | { 19 | UseFakeDecoders = true, 20 | LogToDebugOutput = false, 21 | MinimumLogLevel = TraceLevel.Info 22 | }); 23 | 24 | while (true) 25 | { 26 | Render(); 27 | 28 | GC.Collect(); 29 | GC.WaitForPendingFinalizers(); 30 | 31 | GC.Collect(); 32 | GC.WaitForPendingFinalizers(); 33 | 34 | Console.WriteLine("Press ENTER"); 35 | Console.ReadLine(); 36 | } 37 | } 38 | 39 | private static unsafe void Render() 40 | { 41 | const int frameWidth = 2560; 42 | const int frameHeight = 1440; 43 | const int frameRate = 60; 44 | 45 | // var options = VideoEncoderOptions.OptimizedFor(frameWidth, frameHeight, frameRate); 46 | var options = new VideoEncoderOptions 47 | { 48 | MaxBitsPerSecond = 12_000_000, 49 | MinBitsPerSecond = 10_000_000, 50 | MaxFramesPerSecond = frameRate 51 | }; 52 | 53 | using (var sender = new ObservablePeerConnection(new PeerConnectionOptions())) 54 | using (var receiver = new ObservablePeerConnection(new PeerConnectionOptions { CanReceiveVideo = true })) 55 | { 56 | using (var vt = new ObservableVideoTrack(sender, options)) 57 | { 58 | using (var rnd = new BouncingBallRenderer(vt, 10, new BoundingBallOptions 59 | { 60 | VideoFrameWidth = frameWidth, 61 | VideoFrameHeight = frameHeight, 62 | VideoFrameQueueSize = 2 63 | })) 64 | { 65 | receiver.Connect( 66 | Observable.Never(), 67 | sender.LocalSessionDescriptionStream, 68 | sender.LocalIceCandidateStream); 69 | 70 | sender.Connect( 71 | Observable.Never(), 72 | receiver.LocalSessionDescriptionStream, 73 | receiver.LocalIceCandidateStream); 74 | 75 | sender.CreateOffer(); 76 | 77 | int remoteVideoFrameReceivedCount = 0; 78 | 79 | receiver.RemoteVideoFrameReceived += (pc, frame) => 80 | { 81 | remoteVideoFrameReceivedCount += 1; 82 | 83 | // Save as JPEG for debugging. SLOW! 84 | // TODO: Doesn't work yet, H264 decoding not yet supported, only VP8 85 | //if (frame is VideoFrameYuvAlpha yuvFrame && yuvFrame.Width == yuvFrame.StrideY) 86 | //{ 87 | // var span = new ReadOnlySpan(yuvFrame.DataY.ToPointer(), yuvFrame.Width * yuvFrame.Height); 88 | // using (var image = Image.LoadPixelData(span, yuvFrame.Width, yuvFrame.Height)) 89 | // { 90 | // image.Save($@"frame_{remoteVideoFrameReceivedCount:D000000}.bmp"); 91 | // } 92 | //} 93 | }; 94 | 95 | using (var clock = new PreciseWaitableClock(EventResetMode.AutoReset)) 96 | { 97 | var startTime = clock.GetCurrentTime().AddSeconds(1); 98 | 99 | var nextTime = startTime; 100 | // The remote peer connection is not immediately ready to receive frames, 101 | // so we keep sending until it succeeds. 102 | // TODO: Figure out what webrtc event can be used for this. 103 | while (!Console.KeyAvailable) 104 | { 105 | clock.SetFutureEventTime(nextTime); 106 | 107 | clock.WaitHandle.WaitOne(); 108 | 109 | var elapsedTime = clock.GetCurrentTime() - startTime; 110 | rnd.SendFrame(elapsedTime); 111 | 112 | nextTime = nextTime.AddSeconds(1.0 / frameRate); 113 | } 114 | } 115 | } 116 | 117 | // The video renderer is now disposed while the video track is still encoding some textures 118 | // This should not crash. 119 | // We need to wait a while before disposing the video-track and peer-connection to check this. 120 | Thread.Sleep(100); 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /webrtc-dotnet-leak-test/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("ConsoleApp1")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConsoleApp1")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 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("6c5204d6-12ee-4dd3-90c8-0894e359744b")] 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 | -------------------------------------------------------------------------------- /webrtc-dotnet-leak-test/webrtc-dotnet-leak-test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | PackageReference 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | {6C5204D6-12EE-4DD3-90C8-0894E359744B} 12 | Exe 13 | WonderMediaProductions.WebRtc 14 | webrtc-dotnet-leak-test 15 | v4.7.2 16 | 512 17 | true 18 | true 19 | $(ProjectDir)..\out\bin\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 20 | 21 | 22 | true 23 | bin\x64\Debug\ 24 | DEBUG;TRACE 25 | full 26 | x64 27 | prompt 28 | MinimumRecommendedRules.ruleset 29 | true 30 | true 31 | 32 | 33 | bin\x64\Release\ 34 | TRACE 35 | true 36 | pdbonly 37 | x64 38 | prompt 39 | MinimumRecommendedRules.ruleset 40 | true 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 1.0.0-beta0005 57 | 58 | 59 | 4.5.3 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {ac928c86-f000-43ca-8a3c-2bf243379534} 77 | webrtc-dotnet-graphics-d3d11 78 | 79 | 80 | {6b365cef-cad2-43d3-a690-8bc61a640d5b} 81 | webrtc-dotnet-signalling 82 | 83 | 84 | {21f815b4-9154-4472-b03c-e1c2d1f98a26} 85 | webrtc-dotnet 86 | 87 | 88 | 89 | 90 | background-small.jpg 91 | PreserveNewest 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /webrtc-dotnet-signalling/IceCandidate.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public sealed class IceCandidate 6 | { 7 | public IceCandidate(string candidate, int sdpMLineIndex, string sdpMid) 8 | { 9 | Candidate = candidate; 10 | SdpMLineIndex = sdpMLineIndex; 11 | SdpMid = sdpMid; 12 | } 13 | 14 | [JsonProperty("candidate")] 15 | public readonly string Candidate; 16 | 17 | [JsonProperty("sdpMLineIndex")] 18 | public readonly int SdpMLineIndex; 19 | 20 | [JsonProperty("sdpMid")] 21 | public readonly string SdpMid; 22 | 23 | public override string ToString() 24 | { 25 | return $"{nameof(Candidate)}: {Candidate}, {nameof(SdpMLineIndex)}: {SdpMLineIndex}, {nameof(SdpMid)}: {SdpMid}"; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /webrtc-dotnet-signalling/SessionDescription.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public sealed class SessionDescription 6 | { 7 | public SessionDescription(string type, string sdp) 8 | { 9 | Type = type; 10 | Sdp = sdp; 11 | } 12 | 13 | [JsonProperty("type")] 14 | public readonly string Type; 15 | 16 | [JsonProperty("sdp")] 17 | public readonly string Sdp; 18 | 19 | public override string ToString() 20 | { 21 | return $"{nameof(Type)}: {Type}, {nameof(Sdp)}: {Sdp}"; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webrtc-dotnet-signalling/webrtc-dotnet-signalling.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | WonderMediaProductions.WebRtc 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /webrtc-dotnet-tests/PeerConnectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace WonderMediaProductions.WebRtc 7 | { 8 | [TestClass] 9 | public class PeerConnectionTests 10 | { 11 | [TestMethod] 12 | public void SingleThreadedLifetime() 13 | { 14 | for (int iteration = 0; iteration < 2; ++iteration) 15 | { 16 | // Test if a single peer-connection creation/destruction auto-shutdowns the global factory 17 | using (new PeerConnection(new PeerConnectionOptions())) 18 | { 19 | Assert.IsTrue(PeerConnection.HasFactory); 20 | } 21 | 22 | Assert.IsFalse(PeerConnection.HasFactory); 23 | 24 | // Test if we can still create a peer-connection after a previous shutdown 25 | using (new PeerConnection(new PeerConnectionOptions())) 26 | { 27 | Assert.IsTrue(PeerConnection.HasFactory); 28 | } 29 | 30 | Assert.IsFalse(PeerConnection.HasFactory); 31 | 32 | // Test if we can create multiple peer connections 33 | using (new PeerConnection(new PeerConnectionOptions())) 34 | using (new PeerConnection(new PeerConnectionOptions())) 35 | { 36 | Assert.IsTrue(PeerConnection.HasFactory); 37 | } 38 | 39 | Assert.IsFalse(PeerConnection.HasFactory); 40 | 41 | // Disable auto-shutdown 42 | PeerConnection.Configure(new GlobalOptions { AutoShutdown = false }); 43 | 44 | // Test if a single peer-connection creation/destruction does not auto-shutdown the global factory anymore 45 | using (new PeerConnection(new PeerConnectionOptions())) 46 | { 47 | Assert.IsTrue(PeerConnection.HasFactory); 48 | } 49 | 50 | Assert.IsTrue(PeerConnection.HasFactory); 51 | 52 | // Test if we can still create a peer-connection 53 | using (new PeerConnection(new PeerConnectionOptions())) 54 | { 55 | Assert.IsTrue(PeerConnection.HasFactory); 56 | } 57 | 58 | Assert.IsTrue(PeerConnection.HasFactory); 59 | 60 | // Test if we can create multiple peer connections 61 | using (new PeerConnection(new PeerConnectionOptions())) 62 | using (new PeerConnection(new PeerConnectionOptions())) 63 | { 64 | Assert.IsTrue(PeerConnection.HasFactory); 65 | } 66 | 67 | // Shutdown manually 68 | PeerConnection.Shutdown(); 69 | 70 | Assert.IsFalse(PeerConnection.HasFactory); 71 | 72 | // Enable auto-shutdown 73 | PeerConnection.Configure(new GlobalOptions { AutoShutdown = true }); 74 | } 75 | } 76 | 77 | [TestMethod] 78 | public void MultiThreadedLifetime() 79 | { 80 | for (int iteration = 0; iteration < 2; ++iteration) 81 | { 82 | // Now check if connections can be created/destroyed on multiple threads without crashing 83 | var threadIds = new HashSet(); 84 | 85 | Parallel.For(0, 16, i => 86 | { 87 | using (new PeerConnection(new PeerConnectionOptions())) 88 | using (new PeerConnection(new PeerConnectionOptions())) 89 | { 90 | Assert.IsTrue(PeerConnection.HasFactory); 91 | } 92 | 93 | threadIds.Add(Thread.CurrentThread.ManagedThreadId); 94 | }); 95 | 96 | Assert.IsTrue(threadIds.Count >= 2); 97 | Assert.IsFalse(PeerConnection.HasFactory); 98 | 99 | // Disable auto-shutdown 100 | PeerConnection.Configure(new GlobalOptions { AutoShutdown = false }); 101 | 102 | Parallel.For(0, 16, i => 103 | { 104 | using (new PeerConnection(new PeerConnectionOptions())) 105 | using (new PeerConnection(new PeerConnectionOptions())) 106 | { 107 | Assert.IsTrue(PeerConnection.HasFactory); 108 | } 109 | 110 | threadIds.Add(Thread.CurrentThread.ManagedThreadId); 111 | }); 112 | 113 | // Shutdown manually 114 | PeerConnection.Shutdown(); 115 | 116 | Assert.IsFalse(PeerConnection.HasFactory); 117 | 118 | // Enable auto-shutdown 119 | PeerConnection.Configure(new GlobalOptions { AutoShutdown = true }); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /webrtc-dotnet-tests/RendererTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reactive.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Threading; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using WonderMediaProductions.WebRtc.GraphicsD3D11; 8 | 9 | namespace WonderMediaProductions.WebRtc 10 | { 11 | [TestClass] 12 | public class RendererTests 13 | { 14 | // TODO: Can't get this test running with async/await, gets stuck while disposing, deadlocks 15 | [TestMethod] 16 | public void RendersAndSendsFrameUsingD3D11() 17 | { 18 | bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 19 | bool hasNvEnc = PeerConnection.SupportsHardwareTextureEncoding; 20 | 21 | if (isWindows && hasNvEnc) 22 | { 23 | PeerConnection.Configure(new GlobalOptions 24 | { 25 | UseFakeDecoders = true, 26 | LogToDebugOutput = false, 27 | MinimumLogLevel = TraceLevel.Info 28 | }); 29 | 30 | using (var sender = new ObservablePeerConnection(new PeerConnectionOptions())) 31 | using (var receiver = new ObservablePeerConnection(new PeerConnectionOptions { CanReceiveVideo = true })) 32 | using (var vt = new ObservableVideoTrack(sender, VideoEncoderOptions.OptimizedFor(320, 240, 10))) 33 | { 34 | using (var rnd = new VideoRenderer(vt, new RendererOptions { VideoFrameQueueSize = 2 })) 35 | { 36 | // Wait until sender and receiver are connected, 37 | // signaling is complete, 38 | // and video track is added. 39 | 40 | // TODO: When using tasks for this, this test hangs when disposing! 41 | 42 | // ReSharper disable once InvokeAsExtensionMethod 43 | //var ready = Observable.Zip( 44 | // receiver.ConnectionStateStream.FirstAsync(s => s == ConnectionState.Connected), 45 | // sender.ConnectionStateStream.FirstAsync(s => s == ConnectionState.Connected), 46 | // receiver.SignalingStateStream.FirstAsync(s => s == SignalingState.Stable), 47 | // sender.SignalingStateStream.FirstAsync(s => s == SignalingState.Stable), 48 | // receiver.RemoteTrackChangeStream.FirstAsync( 49 | // c => !string.IsNullOrEmpty(c.TransceiverMid) & 50 | // c.MediaKind == TrackMediaKind.Video && 51 | // c.ChangeKind == TrackChangeKind.Changed), 52 | // (a, b, c, d, e) => true); 53 | //// Wait until connected and video track is ready. 54 | //var ev = new AutoResetEvent(false); 55 | //ready.Subscribe(_ => ev.Set()); 56 | 57 | receiver.Connect( 58 | Observable.Never(), 59 | sender.LocalSessionDescriptionStream, 60 | sender.LocalIceCandidateStream); 61 | 62 | sender.Connect( 63 | Observable.Never(), 64 | receiver.LocalSessionDescriptionStream, 65 | receiver.LocalIceCandidateStream); 66 | 67 | sender.CreateOffer(); 68 | 69 | int remoteVideoFrameReceivedCount = 0; 70 | 71 | receiver.RemoteVideoFrameReceived += (pc, frame) => 72 | { 73 | remoteVideoFrameReceivedCount += 1; 74 | }; 75 | 76 | // The remote peer connection is not immediately ready to receive frames, 77 | // so we keep sending until it succeeds. 78 | // TODO: Figure out what webrtc event can be used for this. 79 | while (remoteVideoFrameReceivedCount == 0) 80 | { 81 | using (rnd.TakeNextFrameForSending()) 82 | { 83 | } 84 | } 85 | 86 | // Continue sending until the video queue is empty 87 | while (rnd.VideoFrameQueueCount > 0) 88 | { 89 | using (rnd.TakeNextFrameForSending()) 90 | { 91 | } 92 | } 93 | } 94 | 95 | // The video renderer is now disposed while the video track is still encoding some textures 96 | // This should not crash. 97 | // We need to wait a while before disposing the video-track and peer-connection to check this. 98 | Thread.Sleep(100); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /webrtc-dotnet-tests/webrtc-dotnet-tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | WonderMediaProductions.WebRtc 6 | false 7 | $(ProjectDir)..\out\bin\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Never 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/.gitignore: -------------------------------------------------------------------------------- 1 | SDL2.dll 2 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/ComObjectComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SharpDX; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public sealed class ComObjectComparer : IEqualityComparer 7 | { 8 | public bool Equals(ComObject x, ComObject y) 9 | { 10 | return x.NativePointer == y.NativePointer; 11 | } 12 | 13 | public int GetHashCode(ComObject obj) 14 | { 15 | return obj.NativePointer.GetHashCode(); 16 | } 17 | 18 | public static readonly ComObjectComparer Instance = new ComObjectComparer(); 19 | } 20 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/ComReflection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using SharpDX; 6 | 7 | namespace WonderMediaProductions.WebRtc 8 | { 9 | public sealed class ComReflection 10 | { 11 | public static IEnumerable GetComObjectFields(object self) 12 | { 13 | if (self == null) 14 | return Enumerable.Empty(); 15 | 16 | return self.GetType() 17 | .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) 18 | .Where(f => typeof(ComObject).IsAssignableFrom(f.FieldType) && 19 | f.GetCustomAttribute() == null) 20 | .Select(f => f.GetValue(self)) 21 | .OfType() 22 | .Distinct(ComObjectComparer.Instance); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/ImageSharpRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using SharpDX.Mathematics.Interop; 4 | using SixLabors.ImageSharp; 5 | using SixLabors.ImageSharp.Advanced; 6 | using SixLabors.ImageSharp.PixelFormats; 7 | using SixLabors.ImageSharp.Processing; 8 | using SixLabors.Shapes; 9 | using PixelColor = SixLabors.ImageSharp.PixelFormats.Bgra32; 10 | 11 | namespace WonderMediaProductions.WebRtc 12 | { 13 | /// 14 | /// Renders a bouncing ball using ImageSharp 15 | /// 16 | public class ImageSharpRenderer : Disposable, IRenderer 17 | { 18 | private const int FrameCount = 60; 19 | 20 | private readonly DisposableList> _videoFrames = new DisposableList>(); 21 | 22 | private int _frameIndex = 0; 23 | 24 | public ImageSharpRenderer(int frameWidth, int frameHeight, VideoTrack videoTrack) 25 | { 26 | VideoTrack = videoTrack; 27 | 28 | using (var background = Image.Load("background-small.jpg")) 29 | { 30 | background.Mutate(ctx => ctx.Resize(frameWidth, frameHeight)); 31 | 32 | // Pre-created bouncing ball frames. 33 | // ImageSharp is not that fast yet, and our goal is to benchmark webrtc and NvEnc, not ImageSharp. 34 | var ballRadius = background.Width / 20f; 35 | var ballPath = new EllipsePolygon(0, 0, ballRadius); 36 | var ballColor = new PixelColor(255, 255, 128); 37 | 38 | for (int i = 0; i < FrameCount; ++i) 39 | { 40 | // ReSharper disable once AccessToDisposedClosure 41 | var image = background.Clone(); 42 | 43 | var a = Math.PI * i / FrameCount; 44 | var h = image.Height - ballRadius; 45 | var y = image.Height - (float) (Math.Abs(Math.Sin(a) * h)); 46 | 47 | image.Mutate(ctx => ctx 48 | .Fill(GraphicsOptions.Default, ballColor, ballPath.Translate(image.Width / 2f, y))); 49 | 50 | _videoFrames.Add(image); 51 | } 52 | } 53 | } 54 | 55 | public VideoTrack VideoTrack { get; } 56 | public RawVector2? MousePosition { get; set; } 57 | 58 | public bool SendFrame(TimeSpan elapsedTime) 59 | { 60 | var imageFrameIndex = (_frameIndex++) % FrameCount; 61 | var imageFrame = _videoFrames[imageFrameIndex].Frames[0]; 62 | var pixels = MemoryMarshal.Cast(imageFrame.GetPixelSpan()); 63 | 64 | var format = PeerConnection.SupportsHardwareTextureEncoding 65 | ? VideoFrameFormat.CpuTexture 66 | : VideoFrameFormat.Bgra32; 67 | 68 | VideoTrack.SendVideoFrame(MemoryMarshal.GetReference(pixels), 69 | imageFrame.Width * 4, 70 | imageFrame.Width, 71 | imageFrame.Height, 72 | format); 73 | 74 | return true; 75 | } 76 | 77 | protected override void OnDispose(bool isDisposing) 78 | { 79 | if (isDisposing) 80 | { 81 | DisposeAllFields(); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/MouseEventKind.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | public enum MouseEventKind 4 | { 5 | Down = 0, 6 | Move = 1, 7 | Up = 2, 8 | } 9 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/MouseMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using SharpDX.Mathematics.Interop; 5 | 6 | namespace WonderMediaProductions.WebRtc 7 | { 8 | public sealed class MouseMessage 9 | { 10 | [JsonProperty("kind")] 11 | public readonly MouseEventKind Kind; 12 | 13 | [JsonProperty("x")] 14 | public readonly float X; 15 | 16 | [JsonProperty("y")] 17 | public readonly float Y; 18 | 19 | public MouseMessage(MouseEventKind kind, float x, float y) 20 | { 21 | Kind = kind; 22 | X = x; 23 | Y = y; 24 | } 25 | 26 | [JsonIgnore] 27 | public RawVector2 Pos => new RawVector2(X, Y); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "web-demo (mixed)": { 4 | "commandName": "Project", 5 | "nativeDebugging": true, 6 | "launchBrowser": false, 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "applicationUrl": "https://localhost:5000/" 11 | }, 12 | "web-demo (managed)": { 13 | "commandName": "Project", 14 | "nativeDebugging": false, 15 | "launchBrowser": false, 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "applicationUrl": "https://localhost:5000/" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/README-SDL.txt: -------------------------------------------------------------------------------- 1 | 2 | Please distribute this file with the SDL runtime environment: 3 | 4 | The Simple DirectMedia Layer (SDL for short) is a cross-platform library 5 | designed to make it easy to write multi-media software, such as games 6 | and emulators. 7 | 8 | The Simple DirectMedia Layer library source code is available from: 9 | https://www.libsdl.org/ 10 | 11 | This library is distributed under the terms of the zlib license: 12 | http://www.zlib.net/zlib_license.html 13 | 14 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.WebSockets; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace WonderMediaProductions.WebRtc 10 | { 11 | public class Startup 12 | { 13 | public Startup(IConfiguration configuration) 14 | { 15 | Configuration = configuration; 16 | } 17 | 18 | public IConfiguration Configuration { get; } 19 | 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | } 23 | 24 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime) 25 | { 26 | loggerFactory.AddConsole(LogLevel.Debug); 27 | 28 | var logger = loggerFactory.CreateLogger("WD"); 29 | 30 | app.UseHttpsRedirection(); 31 | app.UseDeveloperExceptionPage(); 32 | app.UseWebSockets(); 33 | app.UseFileServer(); 34 | 35 | lifetime.ApplicationStopping.Register(() => Console.WriteLine("Application stopping")); 36 | lifetime.ApplicationStopped.Register(() => Console.WriteLine("Application stopped")); 37 | 38 | app.Use(async (context, next) => 39 | { 40 | if (context.Request.Path == "/signaling") 41 | { 42 | if (context.WebSockets.IsWebSocketRequest) 43 | { 44 | try 45 | { 46 | WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); 47 | await RtcRenderingServer.Run(webSocket, lifetime.ApplicationStopping, logger); 48 | } 49 | catch (Exception ex) 50 | { 51 | Console.WriteLine(ex); 52 | } 53 | } 54 | else 55 | { 56 | context.Response.StatusCode = 400; 57 | } 58 | } 59 | else 60 | { 61 | await next(); 62 | } 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/WebDemoProgram.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace WonderMediaProductions.WebRtc 6 | { 7 | public class WebDemoProgram 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateWebHostBuilder(args).Build().Run(); 12 | } 13 | 14 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseSetting("https_port", "8080") 17 | .UseUrls("https://0.0.0.0:8080") 18 | .UseStartup() 19 | .ConfigureLogging((hostingContext, logging) => logging.AddConsole()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/WebSocketExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.WebSockets; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Serialization; 8 | 9 | namespace WonderMediaProductions.WebRtc 10 | { 11 | public static class WebSocketExt 12 | { 13 | private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings 14 | { 15 | ContractResolver = new CamelCasePropertyNamesContractResolver() 16 | }; 17 | 18 | public static Task SendDataAsync(this WebSocket socket, ArraySegment message, CancellationToken cancellation = default) 19 | { 20 | lock (socket) 21 | { 22 | return socket.SendAsync(message, WebSocketMessageType.Binary, true, cancellation); 23 | } 24 | } 25 | 26 | public static Task SendTextAsync(this WebSocket socket, string message, CancellationToken cancellation = default) 27 | { 28 | var data = Encoding.UTF8.GetBytes(message); 29 | lock (socket) 30 | { 31 | return socket.SendAsync(new ArraySegment(data), WebSocketMessageType.Text, true, cancellation); 32 | } 33 | } 34 | 35 | public static Task SendJsonAsync(this WebSocket socket, string action, object payload, CancellationToken cancellation = default) 36 | { 37 | var message = new {action, payload}; 38 | var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message, serializerSettings)); 39 | lock (socket) 40 | { 41 | return socket.SendAsync(new ArraySegment(data), WebSocketMessageType.Text, true, cancellation); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/WebSocketReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.WebSockets; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace WonderMediaProductions.WebRtc 9 | { 10 | public sealed class WebSocketReader 11 | { 12 | private byte[] _data; 13 | private int _size; 14 | private readonly CancellationToken _cancellation; 15 | private readonly int _blockSize; 16 | 17 | public WebSocketReader(WebSocket socket, CancellationToken cancellation, int blockSize = 4096) 18 | { 19 | Socket = socket; 20 | _cancellation = cancellation; 21 | _blockSize = blockSize; 22 | _data = new byte[blockSize]; 23 | _size = 0; 24 | } 25 | 26 | public WebSocket Socket { get; } 27 | 28 | public bool CanRead => !Socket.CloseStatus.HasValue; 29 | 30 | private Task ReceiveAsync() 31 | { 32 | // Make room for one more block if needed 33 | if (_size + _blockSize > _data.Length) 34 | { 35 | Array.Resize(ref _data, _size + _blockSize); 36 | } 37 | 38 | lock (Socket) 39 | { 40 | return Socket.ReceiveAsync(new ArraySegment(_data, _size, _data.Length - _size), _cancellation); 41 | } 42 | } 43 | 44 | /// 45 | /// Reads a full message. Returns null when the socket got closed. 46 | /// 47 | public async Task> ReadBytesAsync() 48 | { 49 | WebSocketReceiveResult result; 50 | 51 | _size = 0; 52 | 53 | do 54 | { 55 | result = await ReceiveAsync(); 56 | if (result.CloseStatus.HasValue) 57 | return null; 58 | _size += result.Count; 59 | } while (!result.EndOfMessage); 60 | 61 | return new ArraySegment(_data, 0, _size); 62 | } 63 | 64 | public async Task ReadJsonAsync() 65 | { 66 | var segment = await ReadBytesAsync(); 67 | if (segment.Array == null) 68 | return null; 69 | 70 | var message = Encoding.UTF8.GetString(segment.Array, 0, segment.Count); 71 | return JObject.Parse(message); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/background-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/webrtc-dotnet-web-demo/background-small.jpg -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "noEmitOnError": true, 5 | "removeComments": false, 6 | "sourceMap": true, 7 | "target": "es2015", 8 | "module": "none" 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/webrtc-dotnet-web-demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | $(ProjectDir)..\out\bin\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 6 | 3.1 7 | latest 8 | 9 | Exe 10 | 11 | WonderMediaProductions.WebRtc 12 | win10-x64 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ES2016 63 | None 64 | None 65 | True 66 | True 67 | False 68 | 69 | 70 | False 71 | True 72 | True 73 | 74 | 75 | 76 | 77 | 78 | x64 79 | 80 | 81 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/wwwroot/.gitignore: -------------------------------------------------------------------------------- 1 | *.js* 2 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/webrtc-dotnet-web-demo/wwwroot/favicon.ico -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/wwwroot/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | width: 100vw; 5 | height: 100vh; 6 | background: skyblue; 7 | } 8 | 9 | .left-panel { 10 | float: left; 11 | width: 50%; 12 | height: 100%; 13 | overflow: scroll; 14 | } 15 | 16 | .right-panel { 17 | float: right; 18 | width: 50%; 19 | background: #aaa; 20 | height: 100%; 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: center; 24 | position: relative; 25 | } 26 | 27 | video { 28 | max-width: 100%; 29 | max-height: 100%; 30 | } 31 | 32 | #play-trigger { 33 | position: absolute; 34 | left: 0; 35 | top: 0; 36 | right: 0; 37 | bottom: 0; 38 | display: flex; 39 | align-items: center; 40 | justify-items: center; 41 | background: dimgray; 42 | } 43 | 44 | #play-trigger > span { 45 | width: 100%; 46 | font-size: 10rem; 47 | text-align: center; 48 | user-select: none; 49 | pointer-events: none; 50 | color: yellow; 51 | } -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Wonder Media webrtc demo 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 |
15 | 16 |
Click to play
17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/wwwroot/index.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /webrtc-dotnet-web-demo/wwwroot/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let retryHandle: number = NaN; 4 | 5 | function isPlaying(media: HTMLMediaElement): boolean { 6 | return media.currentTime > 0 && !media.paused && !media.ended && media.readyState > 2; 7 | } 8 | 9 | // https://github.com/webrtc/samples/blob/gh-pages/src/content/peerconnection/bandwidth/js/main.js 10 | function removeBandwidthRestriction(sdp: string) { 11 | // TODO: This this is actually work? Test! 12 | return sdp.replace(/b=AS:.*\r\n/, '').replace(/b=TIAS:.*\r\n/, ''); 13 | } 14 | 15 | function main() { 16 | 17 | retryHandle = NaN; 18 | 19 | const video = document.querySelector('video'); 20 | const logElem = document.getElementById('log'); 21 | const playElem = document.getElementById('play-trigger'); 22 | playElem.style.visibility = "hidden"; 23 | 24 | // Clear log 25 | logElem.innerText = ""; 26 | 27 | function log(text: string) { 28 | 29 | console.log(text); 30 | 31 | const line = document.createElement("pre"); 32 | line.innerText = text; 33 | logElem.appendChild(line); 34 | } 35 | 36 | function getSignalingSocketUrl() { 37 | const scheme = location.protocol === "https:" ? "wss" : "ws"; 38 | const port = location.port ? (":" + location.port) : ""; 39 | const url = scheme + "://" + location.hostname + port + "/signaling"; 40 | return url; 41 | } 42 | 43 | var pc_config: RTCConfiguration = { 44 | iceServers: [ 45 | { urls: "stun:stun.l.google.com:19302" } 46 | ] 47 | }; 48 | 49 | // https://docs.google.com/document/d/1-ZfikoUtoJa9k-GZG1daN0BU3IjIanQ_JSscHxQesvU/edit# 50 | (pc_config as any)["sdpSemantics"] = "unified-plan"; 51 | 52 | let pc = new RTCPeerConnection(pc_config); 53 | 54 | let ws = new WebSocket(getSignalingSocketUrl()); 55 | ws.binaryType = "arraybuffer"; 56 | 57 | function send(action: "ice" | "sdp" | "pos", payload: any) { 58 | const msg = JSON.stringify({ action, payload }); 59 | log(`🛈 send ${msg}`); 60 | ws.send(msg); 61 | } 62 | 63 | function retry(reason: string) { 64 | clearTimeout(retryHandle); 65 | retryHandle = NaN; 66 | 67 | log(`✘ retrying in 1 second: ${reason}`); 68 | 69 | ws.close(); 70 | pc.close(); 71 | 72 | pc = null; 73 | ws = null; 74 | 75 | setTimeout(main, 1000); 76 | } 77 | 78 | video.addEventListener("readystatechange", () => log(`🛈 Video ready state = ${video.readyState}`)); 79 | 80 | function sendMousePos(e: MouseEvent, kind: number) { 81 | const bounds = video.getBoundingClientRect(); 82 | const x = (e.clientX - bounds.left) / bounds.width; 83 | const y = (e.clientY - bounds.top) / bounds.height; 84 | send("pos", { kind, x, y }); 85 | 86 | if (kind === 2) { 87 | video.onmousemove = video.onmouseup = null; 88 | } 89 | } 90 | 91 | video.onmousedown = async (e: MouseEvent) => { 92 | if (e.button === 0) { 93 | sendMousePos(e, 0); 94 | video.onmousemove = (e2: MouseEvent) => sendMousePos(e2, 1); 95 | video.onmouseup = (e2: MouseEvent) => sendMousePos(e2, 2); 96 | } 97 | } 98 | 99 | playElem.onmousedown = async (e: MouseEvent) => { 100 | try { 101 | if (e.button === 0) { 102 | log(`🛈 Playing video`); 103 | await video.play(); 104 | playElem.style.visibility = "hidden"; 105 | } 106 | } catch (err) { 107 | log(`✘ ${err}`); 108 | } 109 | } 110 | 111 | video.oncanplay = () => { 112 | log(`🛈 Video can play`); 113 | playElem.style.visibility = "visible"; 114 | }; 115 | 116 | ws.onerror = () => log(`✘ websocket error`); 117 | ws.onclose = () => retry("websocket closed"); 118 | 119 | ws.onopen = () => { 120 | pc.onicecandidate = e => { 121 | send("ice", e.candidate); 122 | }; 123 | 124 | pc.onicegatheringstatechange = e => { 125 | log(`🛈 ice gathering state = ${pc && pc.iceGatheringState}`); 126 | }; 127 | 128 | pc.oniceconnectionstatechange = e => { 129 | log(`🛈 ice connection state = ${pc && pc.iceConnectionState}`); 130 | }; 131 | 132 | pc.onicecandidateerror = e => { 133 | log(`✘ ice candidate error = ${e.errorText}#${e.errorCode}`); 134 | }; 135 | 136 | pc.ontrack = ({ transceiver }) => { 137 | log(`✔ received track`); 138 | let track = transceiver.receiver.track; 139 | 140 | video.srcObject = new MediaStream([track]); 141 | 142 | track.onunmute = () => { 143 | log(`✔ track unmuted`); 144 | } 145 | 146 | track.onended = () => { 147 | log(`✘ track ended`); 148 | } 149 | 150 | track.onmute = () => { 151 | log(`✘ track muted`); 152 | }; 153 | } 154 | } 155 | 156 | ws.onmessage = async e => { 157 | const { action, payload } = JSON.parse(e.data); 158 | log(`🛈 received ${e.data}`); 159 | 160 | try { 161 | switch (action) { 162 | case "ice": 163 | { 164 | await pc.addIceCandidate(payload); 165 | log(`✔ addIceCandidate`); 166 | break; 167 | } 168 | 169 | case "sdp": 170 | { 171 | await pc.setRemoteDescription(payload); 172 | log(`✔ setRemoteDescription`); 173 | let { sdp, type } = await pc.createAnswer({ offerToReceiveVideo: true }); 174 | log(`✔ createAnswer`); 175 | sdp = removeBandwidthRestriction(sdp); 176 | await pc.setLocalDescription({ sdp, type }); 177 | log(`✔ setLocalDescription`); 178 | send("sdp", { sdp, type }); 179 | } 180 | } 181 | } catch (err) { 182 | log(`✘ ${err}`); 183 | } 184 | } 185 | } 186 | 187 | main(); 188 | -------------------------------------------------------------------------------- /webrtc-dotnet/.gitignore: -------------------------------------------------------------------------------- 1 | webrtc-native_* 2 | -------------------------------------------------------------------------------- /webrtc-dotnet/Callbacks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public enum TrackMediaKind 7 | { 8 | Audio, 9 | Video, 10 | Data, 11 | } 12 | 13 | public enum TrackChangeKind 14 | { 15 | Changed, 16 | Removed, 17 | Stopped 18 | } 19 | 20 | public delegate void LoggingDelegate(string message, TraceLevel severity); 21 | 22 | public delegate void AudioBusReadyDelegate(PeerConnection pc, IntPtr data, int bitsPerSample, 23 | int sampleRate, int numberOfChannels, int numberOfFrames); 24 | 25 | public delegate void DataAvailableDelegate(PeerConnection pc, DataMessage msg); 26 | 27 | public delegate void FailureMessageDelegate(PeerConnection pc, string msg); 28 | 29 | public delegate void VideoFrameReadyDelegate(PeerConnection pc, VideoFrame frame); 30 | 31 | public delegate void IceCandidateReadyToSendDelegate(PeerConnection pc, IceCandidate ice); 32 | 33 | public delegate void LocalDataChannelReadyDelegate(PeerConnection pc, string label); 34 | 35 | public delegate void LocalSdpReadyToSendDelegate(PeerConnection pc, SessionDescription sd); 36 | 37 | public delegate void SignalingStateChangedDelegate(PeerConnection pc, SignalingState state); 38 | 39 | public delegate void ConnectionStateChangedDelegate(PeerConnection pc, ConnectionState state); 40 | 41 | public delegate void VideoFrameProcessedDelegate(PeerConnection pc, int trackId, IntPtr rgbaPixels, bool isEncoded); 42 | 43 | public delegate void RemoteTrackChangedDelegate(PeerConnection pc, string transceiverMid, TrackMediaKind mediaKind, TrackChangeKind changeKind); 44 | } 45 | -------------------------------------------------------------------------------- /webrtc-dotnet/ConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | /// 4 | /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate 5 | /// 6 | public enum ConnectionState 7 | { 8 | New, 9 | Connecting, 10 | Connected, 11 | Disconnected, 12 | Failed, 13 | Closed, 14 | }; 15 | } -------------------------------------------------------------------------------- /webrtc-dotnet/DataChannelOptions.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | public sealed class DataChannelOptions 4 | { 5 | public string Label = "data"; 6 | public bool IsReliable = false; 7 | public bool IsOrdered = false; 8 | } 9 | } -------------------------------------------------------------------------------- /webrtc-dotnet/DataMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public sealed class DataMessage 7 | { 8 | public string Label { get; } 9 | public ArraySegment Content { get; } 10 | public MessageEncoding Encoding { get; } 11 | 12 | public string AsText => Content.Array == null 13 | ? null 14 | : System.Text.Encoding.UTF8.GetString(Content.Array, Content.Offset, Content.Count); 15 | 16 | public DataMessage(string label, ArraySegment content, MessageEncoding encoding = MessageEncoding.Binary) 17 | { 18 | Label = label; 19 | Content = content; 20 | Encoding = encoding; 21 | } 22 | 23 | public DataMessage(string label, byte[] data, MessageEncoding encoding = MessageEncoding.Binary) 24 | { 25 | Label = label; 26 | Content = new ArraySegment(data); 27 | Encoding = encoding; 28 | } 29 | 30 | public DataMessage(string label, string text, MessageEncoding encoding = MessageEncoding.Utf8) 31 | { 32 | Label = label; 33 | Content = new ArraySegment(System.Text.Encoding.UTF8.GetBytes(text)); 34 | Encoding = encoding; 35 | } 36 | 37 | public override string ToString() 38 | { 39 | var array = Content.Array; 40 | var offset = Content.Offset; 41 | 42 | if (array == null || Content.Count == 0) 43 | { 44 | return $"{Label} => empty message"; 45 | } 46 | 47 | if (Encoding == MessageEncoding.Utf8) 48 | { 49 | var maxLength = Math.Min(100, Content.Count); 50 | var text = System.Text.Encoding.UTF8.GetString(array, offset, maxLength); 51 | var suffix = maxLength < Content.Count ? "..." : ""; 52 | return $"{Label} => text message of length {Content.Count}: '{text}{suffix}'"; 53 | } 54 | 55 | var header = string.Join(", ", array.Take(5).Select(b => b.ToString("X02"))); 56 | return $"{Label} => binary message of length {Content.Count}: {header}..."; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /webrtc-dotnet/Disposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace WonderMediaProductions.WebRtc 8 | { 9 | public abstract class Disposable : IDisposable 10 | { 11 | public bool IsDisposed { get; private set; } 12 | 13 | public void Dispose() 14 | { 15 | if (IsDisposed) 16 | return; 17 | 18 | IsDisposed = true; 19 | 20 | GC.SuppressFinalize(this); 21 | OnDispose(true); 22 | } 23 | 24 | /// 25 | /// Dispose all disposable fields except some 26 | /// 27 | /// 28 | /// The fields are disposed in reverse order of declaration 29 | /// 30 | protected void DisposeAllFieldsExcept(params string[] excludeFieldNames) 31 | { 32 | var disposableFields = GetType() 33 | .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) 34 | .Where(f => !excludeFieldNames.Contains(f.Name) && 35 | typeof(IDisposable).IsAssignableFrom(f.FieldType) && 36 | f.GetCustomAttribute() == null) 37 | .Select(f => f.GetValue(this)) 38 | .Distinct() 39 | .OfType() 40 | .ToArray(); 41 | 42 | for (var index = disposableFields.Length; --index >= 0; ) 43 | { 44 | var disposable = disposableFields[index]; 45 | 46 | // Disposing one field can clear another field, so check this 47 | if (disposable != null) 48 | { 49 | var type = disposable.GetType(); 50 | Debug.WriteLine($"Disposing {type.Namespace}.{type.Name}..."); 51 | disposable.Dispose(); 52 | } 53 | } 54 | } 55 | 56 | /// 57 | /// Dispose all disposable fields 58 | /// 59 | /// 60 | /// The fields are disposed in reverse order of declaration 61 | /// 62 | protected void DisposeAllFields() 63 | { 64 | DisposeAllFieldsExcept(); 65 | } 66 | 67 | ~Disposable() 68 | { 69 | if (IsDisposed) 70 | return; 71 | 72 | Debug.WriteLine($"WARNING: {GetType().Name} was not disposed!"); 73 | 74 | if (Debugger.IsAttached) 75 | Debugger.Break(); 76 | 77 | OnDispose(false); 78 | } 79 | 80 | /// 81 | /// When is true, dispose both managed and native resources. 82 | /// Otherwise this object is not disposing but finalizing (on the finalizer thread), 83 | /// and only native resources should be disposed. 84 | /// 85 | protected abstract void OnDispose(bool isDisposing); 86 | } 87 | } -------------------------------------------------------------------------------- /webrtc-dotnet/DisposableList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | /// 7 | /// When this list is disposed, it will dispose each item. 8 | /// 9 | public sealed class DisposableList : List, IDisposable where T : IDisposable 10 | { 11 | public DisposableList() 12 | { 13 | } 14 | 15 | public DisposableList(IEnumerable collection) : base(collection) 16 | { 17 | } 18 | 19 | public void Dispose() 20 | { 21 | foreach (var obj in this) 22 | { 23 | obj?.Dispose(); 24 | } 25 | 26 | Clear(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /webrtc-dotnet/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public static class EnumerableExtensions 7 | { 8 | /// 9 | /// Lifts a sequence of disposable items into a disposable list of items 10 | /// 11 | public static DisposableList ToDisposableList(this IEnumerable items) 12 | where T : IDisposable 13 | { 14 | return new DisposableList(items); 15 | } 16 | 17 | public static void DisposeAll(this IEnumerable items) 18 | where T : IDisposable 19 | { 20 | foreach (var item in items) 21 | { 22 | item.Dispose(); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /webrtc-dotnet/GlobalOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public class GlobalOptions 6 | { 7 | public bool UseSignalingThread = true; 8 | public bool UseWorkerThread = true; 9 | public bool ForceSoftwareVideoEncoder = false; 10 | public bool AutoShutdown = true; 11 | 12 | public bool UseFakeEncoders = false; 13 | public bool UseFakeDecoders = false; 14 | 15 | public TraceLevel MinimumLogLevel = TraceLevel.Verbose; 16 | public bool LogToStandardError = true; 17 | public bool LogToDebugOutput = false; 18 | 19 | public bool IsSingleThreaded 20 | { 21 | set => UseSignalingThread = UseWorkerThread = value; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /webrtc-dotnet/IceConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | /// 4 | /// https://w3c.github.io/webrtc-pc/#dom-rtciceconnectionstate 5 | /// 6 | public enum IceConnectionState 7 | { 8 | New, 9 | Checking, 10 | Connected, 11 | Completed, 12 | Failed, 13 | Disconnected, 14 | Closed, 15 | Max, 16 | }; 17 | } -------------------------------------------------------------------------------- /webrtc-dotnet/IceGatheringState.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | /// 4 | /// See https://w3c.github.io/webrtc-pc/#dom-rtcicegatheringstate 5 | /// 6 | public enum IceGatheringState 7 | { 8 | New, 9 | Gathering, 10 | Complete 11 | }; 12 | } -------------------------------------------------------------------------------- /webrtc-dotnet/MessageEncoding.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | public enum MessageEncoding 4 | { 5 | Utf8, 6 | Binary 7 | } 8 | } -------------------------------------------------------------------------------- /webrtc-dotnet/ObservableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Subjects; 4 | 5 | namespace WonderMediaProductions.WebRtc 6 | { 7 | public static class ObservableExtensions 8 | { 9 | /// 10 | /// Swallows all object thrown. 11 | /// 12 | /// 13 | /// It is possible an observer is being disposed on thread A while being invoked on thread B. 14 | /// We could puts locks everywhere, but that would defeat the async nature of RX. 15 | /// 16 | public static bool TryOnNext(this IObserver observer, in T value) 17 | { 18 | try 19 | { 20 | observer.OnNext(value); 21 | return true; 22 | } 23 | catch(ObjectDisposedException) 24 | { 25 | return false; 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /webrtc-dotnet/ObservablePeerConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Subjects; 6 | 7 | namespace WonderMediaProductions.WebRtc 8 | { 9 | public class ObservablePeerConnection : PeerConnection 10 | { 11 | private bool _canConnect = true; 12 | private readonly CompositeDisposable _disposables = new CompositeDisposable(); 13 | private readonly Subject _localSessionDescriptionStream = new Subject(); 14 | private readonly Subject _localIceCandidateStream = new Subject(); 15 | private readonly BehaviorSubject _connectionStateStream = new BehaviorSubject(ConnectionState.Closed); 16 | private readonly BehaviorSubject _signalingStateStream = new BehaviorSubject(SignalingState.Closed); 17 | 18 | private readonly Subject _receivedDataStream = new Subject(); 19 | private readonly Subject _receivedVideoStream = new Subject(); 20 | private readonly Subject _localVideoFrameProcessedStream = new Subject(); 21 | private readonly Subject _remoteTrackChangeStream = new Subject(); 22 | private readonly Subject _failureMessageStream = new Subject(); 23 | 24 | public IObservable LocalSessionDescriptionStream => _localSessionDescriptionStream; 25 | public IObservable LocalIceCandidateStream => _localIceCandidateStream; 26 | public IObservable SignalingStateStream => _signalingStateStream; 27 | public IObservable ConnectionStateStream => _connectionStateStream; 28 | 29 | public IObservable ReceivedDataStream => _receivedDataStream; 30 | public IObservable ReceivedVideoStream => _receivedVideoStream; 31 | 32 | public IObservable RemoteTrackChangeStream => _remoteTrackChangeStream; 33 | 34 | public IObservable LocalVideoFrameProcessedStream => _localVideoFrameProcessedStream; 35 | 36 | public IObservable FailureMessageStream => _failureMessageStream; 37 | 38 | public ObservablePeerConnection(PeerConnectionOptions options) : base(options) 39 | { 40 | _disposables.Add(_localSessionDescriptionStream); 41 | _disposables.Add(_localIceCandidateStream); 42 | _disposables.Add(_receivedDataStream); 43 | _disposables.Add(_receivedVideoStream); 44 | _disposables.Add(_localVideoFrameProcessedStream); 45 | _disposables.Add(_remoteTrackChangeStream); 46 | } 47 | 48 | public SignalingState SignalingState => _signalingStateStream.Value; 49 | 50 | public void Connect( 51 | IObservable outgoingMessages, 52 | IObservable receivedSessionDescriptions, 53 | IObservable receivedIceCandidates) 54 | { 55 | if (!_canConnect) 56 | throw new Exception($"{GetType().Name}.{nameof(Connect)} can only be called once!"); 57 | 58 | _canConnect = false; 59 | 60 | LocalDataChannelReady += (pc, label) => 61 | { 62 | DebugLog($"{Name} is ready to send data on channel '{label}'"); 63 | _disposables.Add(outgoingMessages.Where(data => data.Label == label).Subscribe(SendData)); 64 | }; 65 | 66 | DataAvailable += (pc, msg) => 67 | { 68 | DebugLog($"{Name} received data: {msg}"); 69 | _receivedDataStream.TryOnNext(msg); 70 | }; 71 | 72 | LocalSdpReadyToSend += (pc, sd) => 73 | { 74 | DebugLog($"{Name} received local session description: {sd}"); 75 | _localSessionDescriptionStream.TryOnNext(sd); 76 | }; 77 | 78 | IceCandidateReadyToSend += (pc, ice) => 79 | { 80 | DebugLog($"{Name} received local ice candidate: {ice}"); 81 | _localIceCandidateStream.TryOnNext(ice); 82 | }; 83 | 84 | RemoteTrackChanged += (pc, transceiverMid, mediaKind, changeKind) => 85 | { 86 | DebugLog($"{Name} transceiver {transceiverMid} {mediaKind} track {changeKind}"); 87 | _remoteTrackChangeStream.TryOnNext(new RemoteTrackChange(transceiverMid, mediaKind, changeKind)); 88 | }; 89 | 90 | RemoteVideoFrameReceived += (pc, frame) => 91 | _receivedVideoStream.TryOnNext(frame); 92 | 93 | LocalVideoFrameProcessed += (pc, trackId, pixels, isEncoded) => 94 | _localVideoFrameProcessedStream.TryOnNext(new VideoFrameMessage(trackId, pixels, isEncoded)); 95 | 96 | SignalingStateChanged += (pc, state) => 97 | { 98 | DebugLog($"{Name} signaling state changed: {state}"); 99 | _signalingStateStream.TryOnNext(state); 100 | 101 | if (state == SignalingState.HaveRemoteOffer) 102 | { 103 | CreateAnswer(); 104 | } 105 | }; 106 | 107 | ConnectionStateChanged += (pc, state) => 108 | { 109 | DebugLog($"{Name} connection state changed: {state}"); 110 | _connectionStateStream.TryOnNext(state); 111 | }; 112 | 113 | _disposables.Add(receivedIceCandidates.Subscribe(ice => 114 | { 115 | DebugLog($"{Name} received remote ICE candidate: {ice}"); 116 | AddIceCandidate(ice); 117 | })); 118 | 119 | _disposables.Add(receivedSessionDescriptions.Subscribe(sd => 120 | { 121 | DebugLog($"{Name} received remote session description: {sd}"); 122 | SetRemoteDescription(sd); 123 | })); 124 | } 125 | 126 | [Conditional("DEBUG")] 127 | private void DebugLog(string msg) 128 | { 129 | Console.WriteLine(msg); 130 | } 131 | 132 | protected override void OnDispose(bool isDisposing) 133 | { 134 | if (isDisposing) 135 | { 136 | _disposables?.Dispose(); 137 | } 138 | 139 | base.OnDispose(isDisposing); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /webrtc-dotnet/ObservableVideoTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Subjects; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public class ObservableVideoTrack : VideoTrack 7 | { 8 | private readonly Subject _localVideoFrameProcessedStream = new Subject(); 9 | 10 | public new ObservablePeerConnection PeerConnection => (ObservablePeerConnection) base.PeerConnection; 11 | 12 | public ObservableVideoTrack(ObservablePeerConnection peerConnection, VideoEncoderOptions options) 13 | : base(peerConnection, options) 14 | { 15 | } 16 | 17 | public IObservable LocalVideoFrameProcessedStream => _localVideoFrameProcessedStream; 18 | 19 | protected override void OnLocalVideoFrameProcessed(PeerConnection pc, int trackId, IntPtr rgbaPixels, bool isEncoded) 20 | { 21 | _localVideoFrameProcessedStream.TryOnNext(new VideoFrameMessage(trackId, rgbaPixels, isEncoded)); 22 | base.OnLocalVideoFrameProcessed(pc, trackId, rgbaPixels, isEncoded); 23 | } 24 | 25 | protected override void OnDispose(bool isDisposing) 26 | { 27 | if (isDisposing) 28 | { 29 | _localVideoFrameProcessedStream.Dispose(); 30 | } 31 | 32 | base.OnDispose(isDisposing); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /webrtc-dotnet/PeerConnectionOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public class PeerConnectionOptions 7 | { 8 | public string Name; 9 | public List IceServers = new List(); 10 | public string IceUsername; // TODO: Allow username/password per ICE server 11 | public string IcePassword; 12 | public bool CanReceiveAudio; 13 | public bool CanReceiveVideo; 14 | public bool IsDtlsSrtpEnabled = true; 15 | } 16 | } -------------------------------------------------------------------------------- /webrtc-dotnet/PeerConnectionState.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | /// 4 | /// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstate 5 | /// 6 | public enum PeerConnectionState 7 | { 8 | New, 9 | Connecting, 10 | Connected, 11 | Disconnected, 12 | Failed, 13 | Closed, 14 | }; 15 | } -------------------------------------------------------------------------------- /webrtc-dotnet/RemoteTrackChange.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | public sealed class RemoteTrackChange 4 | { 5 | /// 6 | /// Can be string.Empty during negotiation 7 | /// 8 | public readonly string TransceiverMid; 9 | 10 | public readonly TrackMediaKind MediaKind; 11 | 12 | public readonly TrackChangeKind ChangeKind; 13 | 14 | public RemoteTrackChange(string transceiverMid, TrackMediaKind mediaKind, TrackChangeKind changeKind) 15 | { 16 | TransceiverMid = transceiverMid ?? string.Empty; 17 | MediaKind = mediaKind; 18 | ChangeKind = changeKind; 19 | } 20 | 21 | public override string ToString() 22 | { 23 | return $"{nameof(TransceiverMid)}: {TransceiverMid}, {nameof(MediaKind)}: {MediaKind}, {nameof(ChangeKind)}: {ChangeKind}"; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webrtc-dotnet/SignalingState.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | /// 4 | /// See https://w3c.github.io/webrtc-pc/#dom-rtcsignalingstate 5 | /// 6 | public enum SignalingState 7 | { 8 | Stable, 9 | HaveLocalOffer, 10 | HaveLocalPrAnswer, 11 | HaveRemoteOffer, 12 | HaveRemotePrAnswer, 13 | Closed, 14 | }; 15 | } -------------------------------------------------------------------------------- /webrtc-dotnet/TraceLevelExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace WonderMediaProductions.WebRtc 5 | { 6 | public static class TraceLevelExtensions 7 | { 8 | public static ConsoleColor ToConsoleColor(this TraceLevel level) 9 | { 10 | switch (level) 11 | { 12 | case TraceLevel.Error: 13 | return ConsoleColor.Red; 14 | case TraceLevel.Info: 15 | return ConsoleColor.DarkCyan; 16 | case TraceLevel.Verbose: 17 | return ConsoleColor.DarkGreen; 18 | case TraceLevel.Warning: 19 | return ConsoleColor.Yellow; 20 | default: 21 | return ConsoleColor.DarkCyan; 22 | } 23 | } 24 | 25 | public static void WriteToConsole(this TraceLevel level, string line) 26 | { 27 | var color = Console.ForegroundColor; 28 | Console.ForegroundColor = ToConsoleColor(level); 29 | Console.WriteLine($"[WebRTC {level:G}]:\t{line}"); 30 | Console.ForegroundColor = color; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /webrtc-dotnet/VideoEncoderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | public sealed class VideoEncoderOptions 4 | { 5 | public string Label = "video"; 6 | 7 | public int MaxFramesPerSecond = 30; 8 | 9 | public int MinBitsPerSecond = OptimalBitsPerSecond(640, 480, 30, VideoMotion.Low); 10 | 11 | public int MaxBitsPerSecond = OptimalBitsPerSecond(640, 480, 30, VideoMotion.High); 12 | 13 | public static VideoEncoderOptions OptimizedFor( 14 | int width, 15 | int height, 16 | int maxFramesPerSecond, 17 | VideoMotion minVideoMotion = VideoMotion.Low, 18 | VideoMotion maxVideoMotion = VideoMotion.High) 19 | { 20 | return new VideoEncoderOptions 21 | { 22 | MaxFramesPerSecond = maxFramesPerSecond, 23 | MinBitsPerSecond = OptimalBitsPerSecond(width, height, maxFramesPerSecond, minVideoMotion), 24 | MaxBitsPerSecond = OptimalBitsPerSecond(width, height, maxFramesPerSecond, maxVideoMotion), 25 | }; 26 | } 27 | 28 | /// 29 | /// http://blog.sporv.com/restoration-tips-kush-gauge/ 30 | /// 31 | public static int OptimalBitsPerSecond(int width, int height, int framesPerSecond, VideoMotion motionAmount) 32 | { 33 | long area = width * height; 34 | return (int)(area * framesPerSecond * (int)motionAmount * 7 / 100); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /webrtc-dotnet/VideoFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public abstract class VideoFrame 6 | { 7 | public const long TicksPerUs = TimeSpan.TicksPerMillisecond / 1000; 8 | 9 | public readonly int Width; 10 | 11 | public readonly int Height; 12 | 13 | /// 14 | /// TimeStamp since this system started 15 | /// TODO: We would like to have NTP absolute timestamps, but the webrtc code says this is deprecated? 16 | /// 17 | /// 18 | /// WARNING: TimeSpan is not very precise, it always converts to milliseconds. 19 | /// 20 | public readonly TimeSpan TimeStamp; 21 | 22 | protected VideoFrame(int width, int height, long timeStampUs) 23 | { 24 | Width = width; 25 | Height = height; 26 | TimeStamp = TimeSpan.FromTicks(timeStampUs * TicksPerUs); 27 | } 28 | 29 | internal static VideoFrame FromNative( 30 | IntPtr texture, 31 | IntPtr dataY, IntPtr dataU, IntPtr dataV, IntPtr dataA, 32 | int strideY, int strideU, int strideV, int strideA, 33 | int width, int height, long timeStampUs) 34 | { 35 | if (texture == IntPtr.Zero) 36 | return new VideoFrameYuvAlpha( 37 | dataY, dataU, dataV, dataA, 38 | strideY, strideU, strideV, strideA, 39 | width, height, timeStampUs); 40 | 41 | return new VideoFrameTexture(texture, width, height, timeStampUs); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /webrtc-dotnet/VideoFrameFormat.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | /// 4 | /// Pixel formats that are supported by the encoder. 5 | /// 6 | /// 7 | /// Currently if a hardware encoder is found, 8 | /// you must pass , 9 | /// and the format must be Bgra32. 10 | /// 11 | /// The software encoder on the other hand does not support 12 | /// 13 | /// This of course should be refactored at some point, 14 | /// since although high-performance, it is not user friendly. 15 | /// 16 | public enum VideoFrameFormat 17 | { 18 | /// 19 | /// Software encoder pixel format, 8-bit R G B A in system memory 20 | /// 21 | Rgba32, 22 | 23 | /// 24 | /// Software encoder pixel format, 8-bit B G R A in system memory 25 | /// 26 | Bgra32, 27 | 28 | /// 29 | /// Software encoder pixel format, 8-bit A R G B in system memory 30 | /// 31 | Argb32, 32 | 33 | /// 34 | /// Software encoder pixel format, 8-bit A B G R in system memory 35 | /// 36 | Abgr32, 37 | 38 | /// 39 | /// Hardware encoder pixel format, 8-bit B G R A in system memory 40 | /// 41 | /// 42 | /// The texture must reside in CPU memory, 43 | /// and is just a plain pointer to the first pixel 44 | /// 45 | CpuTexture, 46 | 47 | /// 48 | /// Hardware encoder pixel format, 8-bit B G R A, D3D11 Texture2D in GPU host memory 49 | /// 50 | GpuTextureD3D11, 51 | 52 | /// 53 | /// Pre-encoded H264 data. 54 | /// 55 | EncodedH264, 56 | }; 57 | } -------------------------------------------------------------------------------- /webrtc-dotnet/VideoFrameMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public struct VideoFrameMessage 6 | { 7 | public readonly int TrackId; 8 | public readonly IntPtr RgbaPixels; 9 | public readonly bool IsEncoded; 10 | 11 | public VideoFrameMessage(int trackId, IntPtr rgbaPixels, bool isEncoded) 12 | { 13 | TrackId = trackId; 14 | RgbaPixels = rgbaPixels; 15 | IsEncoded = isEncoded; 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return $"{nameof(TrackId)}: {TrackId}, {nameof(RgbaPixels)}: {RgbaPixels.ToInt64():X16}, {nameof(IsEncoded)}: {IsEncoded}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webrtc-dotnet/VideoFrameTexture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public sealed class VideoFrameTexture : VideoFrame 6 | { 7 | public readonly IntPtr TexturePtr; 8 | 9 | public VideoFrameTexture(IntPtr texturePtr, int width, int height, long timeStampUs) 10 | : base(width, height, timeStampUs) 11 | { 12 | TexturePtr = texturePtr; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /webrtc-dotnet/VideoFrameYuvAlpha.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | public sealed class VideoFrameYuvAlpha : VideoFrame 6 | { 7 | // TODO: Convert IntPtr to Memory as soon as this is part of .NET Standard 8 | public readonly IntPtr DataY; 9 | public readonly IntPtr DataU; 10 | public readonly IntPtr DataV; 11 | public readonly IntPtr DataA; 12 | public readonly int StrideY; 13 | public readonly int StrideU; 14 | public readonly int StrideV; 15 | public readonly int StrideA; 16 | 17 | public VideoFrameYuvAlpha(IntPtr dataY, IntPtr dataU, IntPtr dataV, IntPtr dataA, int strideY, int strideU, int strideV, int strideA, int width, int height, long timeStampUs) 18 | : base(width, height, timeStampUs) 19 | { 20 | DataY = dataY; 21 | DataU = dataU; 22 | DataV = dataV; 23 | DataA = dataA; 24 | StrideY = strideY; 25 | StrideU = strideU; 26 | StrideV = strideV; 27 | StrideA = strideA; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /webrtc-dotnet/VideoMotion.cs: -------------------------------------------------------------------------------- 1 | namespace WonderMediaProductions.WebRtc 2 | { 3 | public enum VideoMotion 4 | { 5 | Low = 1, 6 | Medium = 2, 7 | Fluid = 3, 8 | High = 4 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webrtc-dotnet/VideoTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WonderMediaProductions.WebRtc 4 | { 5 | /// 6 | /// TODO: Current a video track must be created before a connection is established! 7 | /// 8 | public class VideoTrack : Disposable 9 | { 10 | public int TrackId { get; } 11 | 12 | public PeerConnection PeerConnection { get; } 13 | 14 | // TODO: Must be a rational number 15 | public int FrameRate { get; } 16 | 17 | public event VideoFrameProcessedDelegate LocalVideoFrameProcessed; 18 | 19 | public VideoTrack(PeerConnection peerConnection, VideoEncoderOptions options) 20 | { 21 | PeerConnection = peerConnection; 22 | FrameRate = options.MaxFramesPerSecond; 23 | TrackId = peerConnection.AddVideoTrack(options); 24 | PeerConnection.LocalVideoFrameProcessed += OnLocalVideoFrameProcessed; 25 | } 26 | 27 | public unsafe void SendVideoFrame(in uint rgbaPixels, int stride, int width, int height, VideoFrameFormat videoFrameFormat) 28 | { 29 | fixed (uint* ptr = &rgbaPixels) 30 | { 31 | PeerConnection.SendVideoFrame(TrackId, new IntPtr(ptr), stride, width, height, videoFrameFormat); 32 | } 33 | } 34 | 35 | public void SendVideoFrame(IntPtr rgbaPixels, int stride, int width, int height, VideoFrameFormat videoFrameFormat) 36 | { 37 | PeerConnection.SendVideoFrame(TrackId, rgbaPixels, stride, width, height, videoFrameFormat); 38 | } 39 | 40 | protected override void OnDispose(bool isDisposing) 41 | { 42 | if (isDisposing) 43 | { 44 | PeerConnection.LocalVideoFrameProcessed -= OnLocalVideoFrameProcessed; 45 | LocalVideoFrameProcessed = null; 46 | } 47 | } 48 | 49 | protected virtual void OnLocalVideoFrameProcessed(PeerConnection pc, int trackId, IntPtr rgbaPixels, bool isEncoded) 50 | { 51 | if (TrackId == trackId) 52 | { 53 | LocalVideoFrameProcessed?.Invoke(pc, trackId, rgbaPixels, isEncoded); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /webrtc-dotnet/webrtc-dotnet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | WonderMediaProductions.WebRtc 6 | $(ProjectDir)..\out\bin\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 7 | 7.3 8 | AnyCPU 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/AppEncUtilsD3D11.h: -------------------------------------------------------------------------------- 1 | ///* 2 | //* Copyright 2019 NVIDIA Corporation. All rights reserved. 3 | //* 4 | //* Please refer to the NVIDIA end user license agreement (EULA) associated 5 | //* with this source code for terms and conditions that govern your use of 6 | //* this software. Any use, reproduction, disclosure, or distribution of 7 | //* this software and related documentation outside the terms of the EULA 8 | //* is strictly prohibited. 9 | //* 10 | //*/ 11 | // 12 | //#pragma once 13 | // 14 | //class RGBToNV12ConverterD3D11 { 15 | //public: 16 | // RGBToNV12ConverterD3D11(ID3D11Device *pDevice, ID3D11DeviceContext *pContext, int nWidth, int nHeight) 17 | // : pD3D11Device(pDevice), pD3D11Context(pContext) 18 | // { 19 | // pD3D11Device->AddRef(); 20 | // pD3D11Context->AddRef(); 21 | // 22 | // pTexBgra = NULL; 23 | // D3D11_TEXTURE2D_DESC desc; 24 | // ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC)); 25 | // desc.Width = nWidth; 26 | // desc.Height = nHeight; 27 | // desc.MipLevels = 1; 28 | // desc.ArraySize = 1; 29 | // desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 30 | // desc.SampleDesc.Count = 1; 31 | // desc.Usage = D3D11_USAGE_DEFAULT; 32 | // desc.BindFlags = D3D11_BIND_RENDER_TARGET; 33 | // desc.CPUAccessFlags = 0; 34 | // ck(pDevice->CreateTexture2D(&desc, NULL, &pTexBgra)); 35 | // 36 | // ck(pDevice->QueryInterface(__uuidof(ID3D11VideoDevice), (void **)&pVideoDevice)); 37 | // ck(pContext->QueryInterface(__uuidof(ID3D11VideoContext), (void **)&pVideoContext)); 38 | // 39 | // D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc = 40 | // { 41 | // D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE, 42 | // { 1, 1 }, desc.Width, desc.Height, 43 | // { 1, 1 }, desc.Width, desc.Height, 44 | // D3D11_VIDEO_USAGE_PLAYBACK_NORMAL 45 | // }; 46 | // ck(pVideoDevice->CreateVideoProcessorEnumerator(&contentDesc, &pVideoProcessorEnumerator)); 47 | // 48 | // ck(pVideoDevice->CreateVideoProcessor(pVideoProcessorEnumerator, 0, &pVideoProcessor)); 49 | // D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputViewDesc = { 0, D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } }; 50 | // ck(pVideoDevice->CreateVideoProcessorInputView(pTexBgra, pVideoProcessorEnumerator, &inputViewDesc, &pInputView)); 51 | // } 52 | // 53 | // ~RGBToNV12ConverterD3D11() 54 | // { 55 | // for (auto& it : outputViewMap) 56 | // { 57 | // ID3D11VideoProcessorOutputView* pOutputView = it.second; 58 | // pOutputView->Release(); 59 | // } 60 | // 61 | // pInputView->Release(); 62 | // pVideoProcessorEnumerator->Release(); 63 | // pVideoProcessor->Release(); 64 | // pVideoContext->Release(); 65 | // pVideoDevice->Release(); 66 | // pTexBgra->Release(); 67 | // pD3D11Context->Release(); 68 | // pD3D11Device->Release(); 69 | // } 70 | // void ConvertRGBToNV12(ID3D11Texture2D*pRGBSrcTexture, ID3D11Texture2D* pDestTexture) 71 | // { 72 | // pD3D11Context->CopyResource(pTexBgra, pRGBSrcTexture); 73 | // ID3D11VideoProcessorOutputView* pOutputView = nullptr; 74 | // auto it = outputViewMap.find(pDestTexture); 75 | // if (it == outputViewMap.end()) 76 | // { 77 | // D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputViewDesc = { D3D11_VPOV_DIMENSION_TEXTURE2D }; 78 | // ck(pVideoDevice->CreateVideoProcessorOutputView(pDestTexture, pVideoProcessorEnumerator, &outputViewDesc, &pOutputView)); 79 | // outputViewMap.insert({ pDestTexture, pOutputView }); 80 | // } 81 | // else 82 | // { 83 | // pOutputView = it->second; 84 | // } 85 | // 86 | // D3D11_VIDEO_PROCESSOR_STREAM stream = { TRUE, 0, 0, 0, 0, NULL, pInputView, NULL }; 87 | // ck(pVideoContext->VideoProcessorBlt(pVideoProcessor, pOutputView, 0, 1, &stream)); 88 | // return; 89 | // } 90 | // 91 | //private: 92 | // ID3D11Device *pD3D11Device = NULL; 93 | // ID3D11DeviceContext *pD3D11Context = NULL; 94 | // ID3D11VideoDevice *pVideoDevice = NULL; 95 | // ID3D11VideoContext *pVideoContext = NULL; 96 | // ID3D11VideoProcessor *pVideoProcessor = NULL; 97 | // ID3D11VideoProcessorInputView *pInputView = NULL; 98 | // ID3D11VideoProcessorOutputView *pOutputView = NULL; 99 | // ID3D11Texture2D *pTexBgra = NULL; 100 | // ID3D11VideoProcessorEnumerator *pVideoProcessorEnumerator = nullptr; 101 | // std::unordered_map outputViewMap; 102 | //}; 103 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvCodec/NvEncoder/NvEncoderD3D11.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 NVIDIA Corporation. All rights reserved. 3 | * 4 | * Please refer to the NVIDIA end user license agreement (EULA) associated 5 | * with this source code for terms and conditions that govern your use of 6 | * this software. Any use, reproduction, disclosure, or distribution of 7 | * this software and related documentation outside the terms of the EULA 8 | * is strictly prohibited. 9 | * 10 | */ 11 | 12 | 13 | #include "pch.h" 14 | #include "NvEncoder/NvEncoderD3D11.h" 15 | 16 | #ifndef MAKEFOURCC 17 | #define MAKEFOURCC(a,b,c,d) (((unsigned int)a) | (((unsigned int)b)<< 8) | (((unsigned int)c)<<16) | (((unsigned int)d)<<24) ) 18 | #endif 19 | 20 | DXGI_FORMAT GetD3D11Format(NV_ENC_BUFFER_FORMAT eBufferFormat) 21 | { 22 | switch (eBufferFormat) 23 | { 24 | case NV_ENC_BUFFER_FORMAT_NV12: 25 | return DXGI_FORMAT_NV12; 26 | case NV_ENC_BUFFER_FORMAT_ARGB: 27 | return DXGI_FORMAT_B8G8R8A8_UNORM; 28 | default: 29 | return DXGI_FORMAT_UNKNOWN; 30 | } 31 | } 32 | 33 | NvEncoderD3D11::NvEncoderD3D11(ID3D11Device* pD3D11Device, uint32_t nWidth, uint32_t nHeight, 34 | NV_ENC_BUFFER_FORMAT eBufferFormat, uint32_t nExtraOutputDelay, bool bMotionEstimationOnly, bool bOutputInVideoMemory) : 35 | NvEncoder(NV_ENC_DEVICE_TYPE_DIRECTX, pD3D11Device, nWidth, nHeight, eBufferFormat, nExtraOutputDelay, bMotionEstimationOnly, bOutputInVideoMemory) 36 | { 37 | if (!pD3D11Device) 38 | { 39 | NVENC_THROW_ERROR("Bad d3d11device ptr", NV_ENC_ERR_INVALID_PTR); 40 | return; 41 | } 42 | 43 | if (GetD3D11Format(GetPixelFormat()) == DXGI_FORMAT_UNKNOWN) 44 | { 45 | NVENC_THROW_ERROR("Unsupported Buffer format", NV_ENC_ERR_INVALID_PARAM); 46 | } 47 | 48 | if (!m_hEncoder) 49 | { 50 | NVENC_THROW_ERROR("Encoder Initialization failed", NV_ENC_ERR_INVALID_DEVICE); 51 | } 52 | 53 | m_pD3D11Device = pD3D11Device; 54 | m_pD3D11Device->GetImmediateContext(&m_pD3D11DeviceContext); 55 | } 56 | 57 | NvEncoderD3D11::~NvEncoderD3D11() 58 | { 59 | std::cout << __FUNCTION__ << std::endl; 60 | ReleaseD3D11Resources(); 61 | } 62 | 63 | void NvEncoderD3D11::AllocateInputBuffers(int32_t numInputBuffers) 64 | { 65 | if (!IsHWEncoderInitialized()) 66 | { 67 | NVENC_THROW_ERROR("Encoder intialization failed", NV_ENC_ERR_ENCODER_NOT_INITIALIZED); 68 | } 69 | 70 | // for MEOnly mode we need to allocate seperate set of buffers for reference frame 71 | int numCount = m_bMotionEstimationOnly ? 2 : 1; 72 | for (int count = 0; count < numCount; count++) 73 | { 74 | std::vector inputFrames; 75 | for (int i = 0; i < numInputBuffers; i++) 76 | { 77 | ID3D11Texture2D *pInputTextures = NULL; 78 | D3D11_TEXTURE2D_DESC desc; 79 | ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC)); 80 | desc.Width = GetMaxEncodeWidth(); 81 | desc.Height = GetMaxEncodeHeight(); 82 | desc.MipLevels = 1; 83 | desc.ArraySize = 1; 84 | desc.Format = GetD3D11Format(GetPixelFormat()); 85 | desc.SampleDesc.Count = 1; 86 | desc.Usage = D3D11_USAGE_DEFAULT; 87 | desc.BindFlags = D3D11_BIND_RENDER_TARGET; 88 | desc.CPUAccessFlags = 0; 89 | if (m_pD3D11Device->CreateTexture2D(&desc, NULL, &pInputTextures) != S_OK) 90 | { 91 | NVENC_THROW_ERROR("Failed to create d3d11textures", NV_ENC_ERR_OUT_OF_MEMORY); 92 | } 93 | inputFrames.push_back(pInputTextures); 94 | } 95 | RegisterInputResources(inputFrames, NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX, 96 | GetMaxEncodeWidth(), GetMaxEncodeHeight(), 0, GetPixelFormat(), count == 1 ? true : false); 97 | } 98 | } 99 | 100 | void NvEncoderD3D11::ReleaseInputBuffers() 101 | { 102 | ReleaseD3D11Resources(); 103 | } 104 | 105 | void NvEncoderD3D11::ReleaseD3D11Resources() 106 | { 107 | if (!m_hEncoder) 108 | { 109 | return; 110 | } 111 | 112 | UnregisterInputResources(); 113 | 114 | for (uint32_t i = 0; i < m_vInputFrames.size(); ++i) 115 | { 116 | if (m_vInputFrames[i].inputPtr) 117 | { 118 | reinterpret_cast(m_vInputFrames[i].inputPtr)->Release(); 119 | } 120 | } 121 | m_vInputFrames.clear(); 122 | 123 | for (uint32_t i = 0; i < m_vReferenceFrames.size(); ++i) 124 | { 125 | if (m_vReferenceFrames[i].inputPtr) 126 | { 127 | reinterpret_cast(m_vReferenceFrames[i].inputPtr)->Release(); 128 | } 129 | } 130 | m_vReferenceFrames.clear(); 131 | 132 | m_pD3D11DeviceContext = nullptr; 133 | m_pD3D11Device = nullptr; 134 | } 135 | 136 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvCodec/NvEncoder/NvEncoderD3D11.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 NVIDIA Corporation. All rights reserved. 3 | * 4 | * Please refer to the NVIDIA end user license agreement (EULA) associated 5 | * with this source code for terms and conditions that govern your use of 6 | * this software. Any use, reproduction, disclosure, or distribution of 7 | * this software and related documentation outside the terms of the EULA 8 | * is strictly prohibited. 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "NvEncoder.h" 15 | 16 | class NvEncoderD3D11 : public NvEncoder 17 | { 18 | public: 19 | NvEncoderD3D11(ID3D11Device* pD3D11Device, uint32_t nWidth, uint32_t nHeight, NV_ENC_BUFFER_FORMAT eBufferFormat, 20 | uint32_t nExtraOutputDelay, bool bMotionEstimationOnly = false, bool bOPInVideoMemory = false); 21 | virtual ~NvEncoderD3D11(); 22 | 23 | protected: 24 | /** 25 | * @brief This function is used to release the input buffers allocated for encoding. 26 | * This function is an override of virtual function NvEncFacadeD3D11::ReleaseInputBuffers(). 27 | */ 28 | virtual void ReleaseInputBuffers() override; 29 | 30 | private: 31 | /** 32 | * @brief This function is used to allocate input buffers for encoding. 33 | * This function is an override of virtual function NvEncFacadeD3D11::AllocateInputBuffers(). 34 | * This function creates ID3D11Texture2D textures which is used to accept input data. 35 | * To obtain handle to input buffers application must call NvEncFacadeD3D11::GetNextInputFrame() 36 | */ 37 | virtual void AllocateInputBuffers(int32_t numInputBuffers) override; 38 | 39 | private: 40 | /** 41 | * @brief This is a private function to release ID3D11Texture2D textures used for encoding. 42 | */ 43 | void ReleaseD3D11Resources(); 44 | 45 | protected: 46 | Microsoft::WRL::ComPtr m_pD3D11Device; 47 | 48 | private: 49 | Microsoft::WRL::ComPtr m_pD3D11DeviceContext; 50 | }; 51 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvCodec/NvEncoder/NvEncoderD3D9.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 NVIDIA Corporation. All rights reserved. 3 | * 4 | * Please refer to the NVIDIA end user license agreement (EULA) associated 5 | * with this source code for terms and conditions that govern your use of 6 | * this software. Any use, reproduction, disclosure, or distribution of 7 | * this software and related documentation outside the terms of the EULA 8 | * is strictly prohibited. 9 | * 10 | */ 11 | 12 | 13 | #include "pch.h" 14 | #include "NvEncoder/NvEncoderD3D9.h" 15 | 16 | #ifndef MAKEFOURCC 17 | #define MAKEFOURCC(a,b,c,d) (((unsigned int)a) | (((unsigned int)b)<< 8) | (((unsigned int)c)<<16) | (((unsigned int)d)<<24) ) 18 | #endif 19 | 20 | D3DFORMAT GetD3D9Format(NV_ENC_BUFFER_FORMAT eBufferFormat) 21 | { 22 | switch (eBufferFormat) 23 | { 24 | case NV_ENC_BUFFER_FORMAT_NV12: 25 | return (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'); 26 | case NV_ENC_BUFFER_FORMAT_ARGB: 27 | return D3DFMT_A8R8G8B8; 28 | default: 29 | return D3DFMT_UNKNOWN; 30 | } 31 | } 32 | 33 | NvEncoderD3D9::NvEncoderD3D9(IDirect3DDevice9* pD3D9Device, uint32_t nWidth, uint32_t nHeight, NV_ENC_BUFFER_FORMAT eBufferFormat, 34 | IDirectXVideoAccelerationService* pDXVAService, uint32_t nExtraOutputDelay, bool bMotionEstimationOnly) : 35 | NvEncoder(NV_ENC_DEVICE_TYPE_DIRECTX, pD3D9Device, nWidth, nHeight, eBufferFormat, nExtraOutputDelay, bMotionEstimationOnly) 36 | { 37 | if (!pD3D9Device) 38 | { 39 | NVENC_THROW_ERROR("Bad d3d9device ptr", NV_ENC_ERR_INVALID_PTR); 40 | } 41 | 42 | if (GetD3D9Format(GetPixelFormat()) == D3DFMT_UNKNOWN) 43 | { 44 | NVENC_THROW_ERROR("Unsupported Buffer format", NV_ENC_ERR_INVALID_PARAM); 45 | } 46 | 47 | if (!m_hEncoder) 48 | { 49 | NVENC_THROW_ERROR("Encoder Initialization failed", NV_ENC_ERR_INVALID_DEVICE); 50 | } 51 | 52 | m_pD3D9Device = pD3D9Device; 53 | m_pD3D9Device->AddRef(); 54 | 55 | m_pDXVAService = pDXVAService; 56 | if (m_pDXVAService) 57 | { 58 | m_pDXVAService->AddRef(); 59 | } 60 | } 61 | 62 | NvEncoderD3D9::~NvEncoderD3D9() 63 | { 64 | ReleaseD3D9Resources(); 65 | } 66 | 67 | void NvEncoderD3D9::AllocateInputBuffers(int32_t numInputBuffers) 68 | { 69 | if (!IsHWEncoderInitialized()) 70 | { 71 | NVENC_THROW_ERROR("Encoder intialization failed", NV_ENC_ERR_ENCODER_NOT_INITIALIZED); 72 | } 73 | 74 | 75 | // for MEOnly mode we need to allocate seperate set of buffers for reference frame 76 | int numCount = m_bMotionEstimationOnly ? 2 : 1; 77 | 78 | for (int count = 0; count < numCount; count++) 79 | { 80 | std::vector inputFrames; 81 | for (int i = 0; i < numInputBuffers; i++) 82 | { 83 | IDirect3DSurface9* pD3D9Surface; 84 | HRESULT res = S_OK; 85 | if (m_pDXVAService) 86 | { 87 | res = m_pDXVAService->CreateSurface(GetMaxEncodeWidth(), GetMaxEncodeHeight(), 0, GetD3D9Format(GetPixelFormat()), D3DPOOL_DEFAULT, 0, DXVA2_VideoProcessorRenderTarget, &pD3D9Surface, nullptr); 88 | } 89 | else 90 | { 91 | res = m_pD3D9Device->CreateOffscreenPlainSurface(GetMaxEncodeWidth(), GetMaxEncodeHeight(), GetD3D9Format(GetPixelFormat()), D3DPOOL_DEFAULT, &pD3D9Surface, nullptr); 92 | } 93 | if (res != S_OK) 94 | { 95 | NVENC_THROW_ERROR("Failed to create d3d9Surfaces", NV_ENC_ERR_OUT_OF_MEMORY); 96 | } 97 | inputFrames.push_back(pD3D9Surface); 98 | } 99 | RegisterInputResources(inputFrames, NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX, GetMaxEncodeWidth(), GetMaxEncodeHeight(), 0, GetPixelFormat(), count == 1 ? true : false); 100 | } 101 | } 102 | 103 | void NvEncoderD3D9::ReleaseInputBuffers() 104 | { 105 | ReleaseD3D9Resources(); 106 | } 107 | 108 | void NvEncoderD3D9::ReleaseD3D9Resources() 109 | { 110 | if (!m_hEncoder) 111 | { 112 | return; 113 | } 114 | 115 | UnregisterInputResources(); 116 | 117 | for (uint32_t i = 0; i < m_vInputFrames.size(); ++i) 118 | { 119 | if (m_vInputFrames[i].inputPtr) 120 | { 121 | reinterpret_cast(m_vInputFrames[i].inputPtr)->Release(); 122 | } 123 | } 124 | m_vInputFrames.clear(); 125 | 126 | for (uint32_t i = 0; i < m_vReferenceFrames.size(); ++i) 127 | { 128 | if (m_vReferenceFrames[i].inputPtr) 129 | { 130 | reinterpret_cast(m_vReferenceFrames[i].inputPtr)->Release(); 131 | } 132 | } 133 | m_vReferenceFrames.clear(); 134 | 135 | if (m_pDXVAService) 136 | { 137 | m_pDXVAService->Release(); 138 | m_pDXVAService = nullptr; 139 | } 140 | 141 | if (m_pD3D9Device) 142 | { 143 | m_pD3D9Device->Release(); 144 | m_pD3D9Device = nullptr; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvCodec/NvEncoder/NvEncoderD3D9.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2018 NVIDIA Corporation. All rights reserved. 3 | * 4 | * Please refer to the NVIDIA end user license agreement (EULA) associated 5 | * with this source code for terms and conditions that govern your use of 6 | * this software. Any use, reproduction, disclosure, or distribution of 7 | * this software and related documentation outside the terms of the EULA 8 | * is strictly prohibited. 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "NvEncoder.h" 15 | 16 | 17 | class NvEncoderD3D9 : public NvEncoder 18 | { 19 | public: 20 | NvEncoderD3D9(IDirect3DDevice9* pD3D9Device, uint32_t nWidth, uint32_t nHeight, NV_ENC_BUFFER_FORMAT eBufferFormat, 21 | IDirectXVideoAccelerationService* pDXVAService = nullptr, uint32_t nExtraOutputDelay = 3, bool bMotionEstimationOnly = false); 22 | virtual ~NvEncoderD3D9(); 23 | private: 24 | 25 | /** 26 | * @brief This function is used to allocate input buffers for encoding. 27 | * This function is an override of virtual function NvEncFacadeD3D11::AllocateInputBuffers(). 28 | * This function creates IDirect3DSurface9* which is used to accept input data. 29 | * To obtain handle to input buffers application must call NvEncFacadeD3D11::GetNextInputFrame() 30 | */ 31 | virtual void AllocateInputBuffers(int32_t numInputBuffers) override; 32 | 33 | /** 34 | * @brief This function is used to release the input buffers allocated for encoding. 35 | * This function is an override of virtual function NvEncFacadeD3D11::ReleaseInputBuffers(). 36 | */ 37 | virtual void ReleaseInputBuffers() override; 38 | private: 39 | /** 40 | * @brief This is a private function to release IDirect3DSurface9 surfaces used for encoding. 41 | */ 42 | void ReleaseD3D9Resources(); 43 | private: 44 | IDirect3DDevice9* m_pD3D9Device = nullptr; 45 | IDirectXVideoAccelerationService* m_pDXVAService = nullptr; 46 | }; 47 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvCodec/NvEncoder/NvEncoderOutputInVidMemD3D11.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 NVIDIA Corporation. All rights reserved. 3 | * 4 | * Please refer to the NVIDIA end user license agreement (EULA) associated 5 | * with this source code for terms and conditions that govern your use of 6 | * this software. Any use, reproduction, disclosure, or distribution of 7 | * this software and related documentation outside the terms of the EULA 8 | * is strictly prohibited. 9 | * 10 | */ 11 | 12 | #pragma once 13 | 14 | #include "nvEncodeAPI.h" 15 | #include "NvEncoder/NvEncoderD3D11.h" 16 | 17 | #define ALIGN_UP(s,a) (((s) + (a) - 1) & ~((a) - 1)) 18 | 19 | 20 | /** 21 | * @brief Class for encode or ME only output in video memory feature for D3D11 interfaces. 22 | */ 23 | class NvEncoderOutputInVidMemD3D11 : public NvEncoderD3D11 24 | { 25 | public: 26 | /** 27 | * @brief NvEncoderOutputInVidMemD3D11 class constructor. 28 | */ 29 | NvEncoderOutputInVidMemD3D11(ID3D11Device* pD3D11Device, uint32_t nWidth, uint32_t nHeight, NV_ENC_BUFFER_FORMAT eBufferFormat, 30 | bool bMotionEstimationOnly = false); 31 | 32 | /** 33 | * @brief NvEncFacadeD3D11 class virtual destructor. 34 | */ 35 | virtual ~NvEncoderOutputInVidMemD3D11(); 36 | 37 | /** 38 | * @brief This function is used to initialize the encoder session. 39 | * Application must call this function to initialize the encoder, before 40 | * starting to encode or motion estimate any frames. 41 | */ 42 | void CreateEncoder(const NV_ENC_INITIALIZE_PARAMS* pEncoderParams); 43 | 44 | /** 45 | * @brief This function is used to encode a frame. 46 | * Applications must call EncodeFrame() function to encode the uncompressed 47 | * data, which has been copied to an input buffer obtained from the 48 | * GetNextInputFrame() function. 49 | * This function returns video memory buffer pointers containing compressed data 50 | * in pOutputBuffer. If there is buffering enabled, this may return without 51 | * any data in pOutputBuffer. 52 | */ 53 | void EncodeFrame(std::vector &pOutputBuffer, NV_ENC_PIC_PARAMS *pPicParams = nullptr); 54 | 55 | /** 56 | * @brief This function to flush the encoder queue. 57 | * The encoder might be queuing frames for B picture encoding or lookahead; 58 | * the application must call EndEncode() to get all the queued encoded frames 59 | * from the encoder. The application must call this function before destroying 60 | * an encoder session. Video memory buffer pointer containing compressed data 61 | * is returned in pOutputBuffer. 62 | */ 63 | void EndEncode(std::vector &pOutputBuffer); 64 | 65 | /** 66 | * @brief This function is used to run motion estimation. 67 | * This is used to run motion estimation on a a pair of frames. The 68 | * application must copy the reference frame data to the buffer obtained 69 | * by calling GetNextReferenceFrame(), and copy the input frame data to 70 | * the buffer obtained by calling GetNextInputFrame() before calling the 71 | * RunMotionEstimation() function. 72 | * This function returns video memory buffer pointers containing 73 | * motion vector data in pOutputBuffer. 74 | */ 75 | void RunMotionEstimation(std::vector &pOutputBuffer); 76 | 77 | /** 78 | * @brief This function is used to destroy the encoder session. 79 | * Application must call this function to destroy the encoder session and 80 | * clean up any allocated resources. The application must call EndEncode() 81 | * function to get any queued encoded frames before calling DestroyEncoder(). 82 | */ 83 | void DestroyEncoder(); 84 | 85 | /** 86 | * @brief This function is used to get the size of output buffer required to be 87 | * allocated in order to store the output. 88 | */ 89 | uint32_t GetOutputBufferSize(); 90 | 91 | private: 92 | 93 | /** 94 | * @brief This function is used to allocate output buffers in video memory for storing 95 | * encode or motion estimation output. 96 | */ 97 | void AllocateOutputBuffers(uint32_t numOutputBuffers); 98 | 99 | /** 100 | * @brief This function is used to release output buffers. 101 | */ 102 | void ReleaseOutputBuffers(); 103 | 104 | /** 105 | * @brief This function is used to register output buffers with NvEncodeAPI. 106 | */ 107 | void RegisterOutputResources(uint32_t bfrSize); 108 | 109 | /** 110 | * @brief This function is used to unregister output resources which had been previously registered for encoding 111 | * using RegisterOutputResources() function. 112 | */ 113 | void UnregisterOutputResources(); 114 | 115 | /** 116 | * @brief This function is used to map the input and output buffers to NvEncodeAPI. 117 | */ 118 | void MapResources(uint32_t bfrIdx); 119 | 120 | /** 121 | * @brief This is a private function which is used to get video memory buffer pointer containing compressed data 122 | * or motion estimation output from the encoder HW. 123 | * This is called by EncodeFrame() function. If there is buffering enabled, 124 | * this may return without any output data. 125 | */ 126 | void GetEncodedPacket(std::vector &pOutputBuffer, bool bOutputDelay); 127 | 128 | /** 129 | * @brief This function is used to flush the encoder queue. 130 | */ 131 | void FlushEncoder(); 132 | 133 | private: 134 | std::vector m_vMappedOutputBuffers; 135 | std::vector m_pOutputBuffers; 136 | std::vector m_vRegisteredResourcesOutputBuffer; 137 | }; 138 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvEnc/Lib/Win32/nvcuvid.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/webrtc-native-nvenc/NvEnc/Lib/Win32/nvcuvid.lib -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvEnc/Lib/linux/stubs/x86_64/libnvcuvid.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/webrtc-native-nvenc/NvEnc/Lib/linux/stubs/x86_64/libnvcuvid.so -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvEnc/Lib/linux/stubs/x86_64/libnvidia-encode.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/webrtc-native-nvenc/NvEnc/Lib/linux/stubs/x86_64/libnvidia-encode.so -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvEncFacadeD3D11.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2019 NVIDIA Corporation. All rights reserved. 3 | * 4 | * Please refer to the NVIDIA end user license agreement (EULA) associated 5 | * with this source code for terms and conditions that govern your use of 6 | * this software. Any use, reproduction, disclosure, or distribution of 7 | * this software and related documentation outside the terms of the EULA 8 | * is strictly prohibited. 9 | * 10 | */ 11 | 12 | #include "pch.h" 13 | #include "NvCodec/NvEncoder/NvEncoderD3D11.h" 14 | #include "NvCodec/NvEncoder/NvEncoder.h" 15 | #include "NvEncFacadeD3D11.h" 16 | #include 17 | 18 | #define SHOW_ENCODING_DURATION 19 | 20 | using Microsoft::WRL::ComPtr; 21 | 22 | NvEncFacadeD3D11::NvEncFacadeD3D11(int width, int height, int bitrate, int targetFrameRate, int extraOutputDelay) 23 | : width(width) 24 | , height(height) 25 | , bitrate(bitrate) 26 | , targetFrameRate(targetFrameRate) 27 | , extraOutputDelay(extraOutputDelay) 28 | { 29 | } 30 | 31 | void NvEncFacadeD3D11::SetBitrate(int bitrate, int targetFrameRate) 32 | { 33 | this->bitrate = bitrate; 34 | this->targetFrameRate = targetFrameRate; 35 | this->doReconfigure = true; 36 | } 37 | 38 | void NvEncFacadeD3D11::Reconfigure() const 39 | { 40 | // printf("UPDATE target frame rate to %d and bitrate to %d\n", this->targetFrameRate, this->bitrate); 41 | 42 | NV_ENC_CONFIG config; 43 | memset(&config, 0, sizeof(config)); 44 | config.version = NV_ENC_CONFIG_VER; 45 | config.rcParams.averageBitRate = bitrate; 46 | 47 | NV_ENC_RECONFIGURE_PARAMS reconfigureParams; 48 | memset(&reconfigureParams, 0, sizeof(reconfigureParams)); 49 | reconfigureParams.version = NV_ENC_RECONFIGURE_PARAMS_VER; 50 | reconfigureParams.resetEncoder = 1; 51 | reconfigureParams.forceIDR = 1; 52 | reconfigureParams.reInitEncodeParams.encodeConfig = &config; 53 | 54 | encoder->GetInitializeParams(&reconfigureParams.reInitEncodeParams); 55 | reconfigureParams.reInitEncodeParams.frameRateNum = targetFrameRate; 56 | reconfigureParams.reInitEncodeParams.frameRateDen = 1; 57 | 58 | encoder->Reconfigure(&reconfigureParams); 59 | } 60 | 61 | void NvEncFacadeD3D11::EncodeFrame(ID3D11Texture2D* source, std::vector& vPacket) 62 | { 63 | // get the device & context of the source texture 64 | ComPtr device; 65 | ComPtr pContext; 66 | source->GetDevice(&device); 67 | device->GetImmediateContext(&pContext); 68 | 69 | // if the encoder was created with a different device, we re-create it 70 | if (encoder != nullptr && encoder->GetDevice() != device.Get()) 71 | { 72 | encoder->DestroyEncoder(); 73 | delete encoder; 74 | encoder = nullptr; 75 | } 76 | 77 | // if the encoder isn't created yet, we do so now 78 | if (encoder == nullptr) 79 | { 80 | encoder = new NvEncoderD3D11(device.Get(), width, height, NV_ENC_BUFFER_FORMAT_ARGB, extraOutputDelay); 81 | 82 | // create the initial structures to hold the config 83 | NV_ENC_INITIALIZE_PARAMS initializeParams = { NV_ENC_INITIALIZE_PARAMS_VER }; 84 | NV_ENC_CONFIG encodeConfig = { NV_ENC_CONFIG_VER }; 85 | initializeParams.encodeConfig = &encodeConfig; 86 | 87 | // fill them with default values 88 | encoder->CreateDefaultEncoderParams(&initializeParams, NV_ENC_CODEC_H264_GUID, NV_ENC_PRESET_LOW_LATENCY_HQ_GUID); 89 | 90 | // override some values to configure them according to the requirements of the user 91 | /*initializeParams.frameRateNum = this->targetFrameRate; 92 | initializeParams.frameRateDen = 1;*/ 93 | //printf("INIT target frame rate to %d and bitrate to %d\n", this->targetFrameRate, this->bitrate); 94 | 95 | // set the max bit rate 96 | encodeConfig.rcParams.averageBitRate = bitrate; 97 | encodeConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR_LOWDELAY_HQ; 98 | 99 | // these are the recommended settings for low-latency use cases like game streaming, 100 | // as defined in the 9.0 documentation by nVidia. 101 | encodeConfig.rcParams.disableBadapt = 1; 102 | encodeConfig.rcParams.vbvBufferSize = encodeConfig.rcParams.averageBitRate * initializeParams.frameRateDen / initializeParams.frameRateNum; // bitrate / framerate = one frame 103 | encodeConfig.gopLength = NVENC_INFINITE_GOPLENGTH; 104 | encodeConfig.rcParams.enableAQ = 1; 105 | 106 | encoder->CreateEncoder(&initializeParams); 107 | 108 | // if we triggered a reconfigure before this point, we don't need to do it anymore, 109 | // since it is already dealt with by the encoder creation. 110 | doReconfigure = false; 111 | } 112 | 113 | // Reconfigure the encoder if requested. 114 | if (doReconfigure) 115 | { 116 | doReconfigure = false; 117 | Reconfigure(); 118 | } 119 | 120 | std::chrono::high_resolution_clock sw; 121 | const auto t1 = sw.now(); 122 | 123 | // copy the frame into an internal buffer of nvEnc so we can encode it 124 | const NvEncInputFrame* encoderInputFrame = encoder->GetNextInputFrame(); 125 | const auto target = reinterpret_cast(encoderInputFrame->inputPtr); 126 | pContext->CopyResource(target, source); 127 | encoder->EncodeFrame(vPacket); 128 | 129 | const auto t2 = sw.now(); 130 | 131 | #ifdef SHOW_ENCODING_DURATION 132 | // 100 = 10000 microsec. 133 | char buffer[6 + 10*11+1]; 134 | int ms = static_cast(std::chrono::duration_cast(t2 - t1).count() * 100 / 10000); 135 | 136 | ms = std::min(100, ms); 137 | 138 | strcpy_s(buffer, "nvenc:"); 139 | 140 | char* ptr = buffer; 141 | int digit = '0'; 142 | while (ms > 0) 143 | { 144 | auto len = std::min(ms, 10); 145 | memset(ptr, digit, len); 146 | ptr += len; 147 | *ptr++ = ' '; 148 | ++digit; 149 | ms -= 10; 150 | } 151 | *ptr = 0; 152 | 153 | //sprintf_s(buffer, "nvenc %05lld microsec", std::chrono::duration_cast(t2 - t1).count()); 154 | SetConsoleTitleA(buffer); 155 | #endif 156 | } 157 | 158 | NvEncFacadeD3D11::~NvEncFacadeD3D11() 159 | { 160 | std::cout << __FUNCTION__ << std::endl; 161 | 162 | if (encoder) 163 | { 164 | // Flush! This means that some packets might be lost and never sent, because we don't do anything with it here. 165 | // [PV] Uncommented for now, since this is also done by the NvEncoder itself (at least under some conditions) 166 | // std::vector vPacket; 167 | // encoder->EndEncode(vPacket); 168 | encoder->DestroyEncoder(); 169 | delete encoder; 170 | encoder = nullptr; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/NvEncFacadeD3D11.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Simple facade around the more complicated NvEncFacadeD3D11 classes 5 | */ 6 | class NvEncFacadeD3D11 final 7 | { 8 | public: 9 | NvEncFacadeD3D11(int width, int height, int bitrate, int targetFrameRate, int extraOutputDelay = 3); 10 | ~NvEncFacadeD3D11(); 11 | 12 | /** For best performance, set the vPacket to large capacity */ 13 | void EncodeFrame(struct ID3D11Texture2D* source, std::vector& vPacket); 14 | 15 | void SetBitrate(int bitrate, int targetFrameRate); 16 | 17 | private: 18 | 19 | int width; 20 | int height; 21 | int bitrate; 22 | int targetFrameRate; 23 | int extraOutputDelay; 24 | 25 | int nPackets = 0; 26 | bool doReconfigure = false; 27 | class NvEncoderD3D11* encoder = nullptr; 28 | 29 | void Reconfigure() const; 30 | }; 31 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define NOMINMAX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/webrtc-native-nvenc.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Create 21 | Create 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 15.0 36 | {4645FFF5-9866-4CD4-B432-8BAC320E560E} 37 | Win32Proj 38 | webrtcnativenvenc 39 | 10.0.17763.0 40 | 41 | 42 | 43 | StaticLibrary 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | StaticLibrary 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | true 69 | $(ProjectDir)..\out\lib\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 70 | $(ProjectDir)..\out\obj\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 71 | 72 | 73 | false 74 | $(ProjectDir)..\out\lib\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 75 | $(ProjectDir)..\out\obj\$(MSBuildProjectName)_$(Configuration)_$(Platform)\ 76 | 77 | 78 | 79 | Use 80 | Level3 81 | Disabled 82 | true 83 | _DEBUG;_LIB;_HAS_ITERATOR_DEBUGGING=0;%(PreprocessorDefinitions) 84 | true 85 | $(ProjectDir)NvCodec;$(ProjectDir);$(ProjectDir)NvEnc/include 86 | MultiThreadedDebug 87 | pch.h 88 | 89 | 90 | Windows 91 | true 92 | 93 | 94 | 95 | 96 | Use 97 | Level3 98 | MaxSpeed 99 | true 100 | true 101 | true 102 | NDEBUG;_LIB;_HAS_ITERATOR_DEBUGGING=0;%(PreprocessorDefinitions) 103 | true 104 | $(ProjectDir)NvCodec;$(ProjectDir);$(ProjectDir)NvEnc/include 105 | MultiThreaded 106 | pch.h 107 | 108 | 109 | Windows 110 | true 111 | true 112 | true 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /webrtc-native-nvenc/webrtc-native-nvenc.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /webrtc-native/DummySetSessionDescriptionObserver.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DummySetSessionDescriptionObserver.h" 3 | 4 | DummySetSessionDescriptionObserver* DummySetSessionDescriptionObserver::Create() 5 | { 6 | return new rtc::RefCountedObject(); 7 | } 8 | 9 | DummySetSessionDescriptionObserver::DummySetSessionDescriptionObserver() = default; 10 | 11 | DummySetSessionDescriptionObserver::~DummySetSessionDescriptionObserver() = default; 12 | 13 | void DummySetSessionDescriptionObserver::OnSuccess() 14 | { 15 | RTC_LOG(INFO) << __FUNCTION__; 16 | } 17 | 18 | void DummySetSessionDescriptionObserver::OnFailure(webrtc::RTCError error) 19 | { 20 | RTC_LOG(INFO) << __FUNCTION__ << " " << ToString(error.type()) << ": " << error.message(); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /webrtc-native/DummySetSessionDescriptionObserver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class DummySetSessionDescriptionObserver abstract 4 | : public webrtc::SetSessionDescriptionObserver { 5 | public: 6 | static DummySetSessionDescriptionObserver* Create(); 7 | 8 | void OnSuccess() override; 9 | void OnFailure(webrtc::RTCError error) override; 10 | 11 | protected: 12 | DummySetSessionDescriptionObserver(); 13 | ~DummySetSessionDescriptionObserver(); 14 | }; 15 | -------------------------------------------------------------------------------- /webrtc-native/EncoderFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "EncoderFactory.h" 3 | #include "NvEncoderH264.h" 4 | 5 | using namespace webrtc; 6 | 7 | namespace 8 | { 9 | bool IsFormatSupported(const std::vector& supported_formats, 10 | const SdpVideoFormat& format) 11 | { 12 | for (const SdpVideoFormat& supported_format : supported_formats) 13 | { 14 | if (cricket::IsSameCodec(format.name, format.parameters, 15 | supported_format.name, 16 | supported_format.parameters)) 17 | { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | } 24 | 25 | SdpVideoFormat CreateH264Format(H264::Profile profile, H264::Level level, const std::string& packetization_mode) { 26 | const absl::optional profile_string = H264::ProfileLevelIdToString(H264::ProfileLevelId(profile, level)); 27 | RTC_CHECK(profile_string); 28 | return SdpVideoFormat( 29 | cricket::kH264CodecName, 30 | { {cricket::kH264FmtpProfileLevelId, *profile_string}, 31 | {cricket::kH264FmtpLevelAsymmetryAllowed, "1"}, 32 | {cricket::kH264FmtpPacketizationMode, packetization_mode} }); 33 | } 34 | 35 | class NvEncoderFactory : public VideoEncoderFactory 36 | { 37 | private: 38 | std::vector supported_formats_; 39 | 40 | public: 41 | NvEncoderFactory() 42 | { 43 | // TODO: Check capability of NVENC hardware to figure out supported formats. 44 | // https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Levels 45 | supported_formats_.push_back(CreateH264Format(H264::kProfileBaseline, H264::kLevel5, "0")); 46 | supported_formats_.push_back(CreateH264Format(H264::kProfileConstrainedBaseline, H264::kLevel5, "0")); 47 | } 48 | 49 | CodecInfo QueryVideoEncoder(const SdpVideoFormat& format) const override 50 | { 51 | // Format must be one of the internal formats. 52 | RTC_DCHECK(IsFormatSupported(supported_formats_, format)); 53 | 54 | CodecInfo info; 55 | info.has_internal_source = false; 56 | info.is_hardware_accelerated = false; 57 | return info; 58 | } 59 | 60 | std::unique_ptr CreateVideoEncoder(const SdpVideoFormat& format) override 61 | { 62 | RTC_DCHECK(IsFormatSupported(supported_formats_, format)); 63 | return std::make_unique(); 64 | } 65 | 66 | std::vector GetSupportedFormats() const override 67 | { 68 | return supported_formats_; 69 | } 70 | }; 71 | 72 | std::unique_ptr CreateEncoderFactory(bool force_software_encoder) 73 | { 74 | if (!force_software_encoder && NvEncoderH264::IsAvailable()) 75 | return std::make_unique(); 76 | 77 | // Fallback to VP8 if no licensed NVEnc hardware encoder is found. 78 | return std::make_unique(); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /webrtc-native/EncoderFactory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | std::unique_ptr CreateEncoderFactory(bool force_software_encoder); 4 | -------------------------------------------------------------------------------- /webrtc-native/InjectableVideoTrackSource.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "InjectableVideoTrackSource.h" 3 | 4 | namespace webrtc 5 | { 6 | rtc::scoped_refptr InjectableVideoTrackSource::Create(bool is_screencast) 7 | { 8 | return new rtc::RefCountedObject(is_screencast); 9 | } 10 | 11 | InjectableVideoTrackSource::InjectableVideoTrackSource(bool is_screencast) 12 | : VideoTrackSource(false /* remote */) 13 | , is_screencast_(is_screencast) 14 | { 15 | } 16 | 17 | InjectableVideoTrackSource::~InjectableVideoTrackSource() = default; 18 | 19 | rtc::VideoSourceInterface* InjectableVideoTrackSource::source() 20 | { 21 | return &video_broadcaster_; 22 | } 23 | 24 | void InjectableVideoTrackSource::OnFrame(const VideoFrame& frame) 25 | { 26 | video_broadcaster_.OnFrame(frame); 27 | } 28 | } // namespace webrtc 29 | -------------------------------------------------------------------------------- /webrtc-native/InjectableVideoTrackSource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace webrtc { 6 | 7 | // A minimal implementation of VideoTrackSource. 8 | // Includes a VideoBroadcaster for injection of frames. 9 | class InjectableVideoTrackSource : public VideoTrackSource, public rtc::VideoSinkInterface { 10 | public: 11 | static rtc::scoped_refptr Create(bool is_screencast = false); 12 | 13 | bool is_screencast() const override { return is_screencast_; } 14 | 15 | void OnFrame(const VideoFrame& frame) override; 16 | 17 | protected: 18 | explicit InjectableVideoTrackSource(bool is_screencast = false); 19 | ~InjectableVideoTrackSource() override; 20 | 21 | rtc::VideoSourceInterface* source() override; 22 | 23 | private: 24 | const bool is_screencast_; 25 | rtc::VideoBroadcaster video_broadcaster_; 26 | }; 27 | 28 | } // namespace webrtc 29 | -------------------------------------------------------------------------------- /webrtc-native/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Mots of this source code was originally based on code from The WebRTC project authors. 3 | 4 | (c)2013-2019 All Rights Reserved. 5 | 6 | https://webrtc.org/native-code/ 7 | 8 | Use of this source code is governed by a BSD-style license 9 | that can be found in the LICENSE file in the root of the source 10 | tree. An additional intellectual property rights grant can be found 11 | in the file PATENTS. All contributing project authors may 12 | be found in the AUTHORS file in the root of the source tree. 13 | -------------------------------------------------------------------------------- /webrtc-native/NativeInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO: Split this into a pixel-format and codec; 4 | // need to figure out how to pick a different codec in the factory. 5 | enum class VideoFrameFormat 6 | { 7 | RGBA32, 8 | BGRA32, 9 | ARGB32, 10 | ABGR32, 11 | CpuTexture, 12 | GpuTextureD3D11 13 | }; 14 | 15 | // Definitions of callback functions. 16 | typedef void(*IncomingVideoFrameCallback)( 17 | const void* texture, 18 | const uint8_t* data_y, 19 | const uint8_t* data_u, 20 | const uint8_t* data_v, 21 | const uint8_t* data_a, 22 | int stride_y, 23 | int stride_u, 24 | int stride_v, 25 | int stride_a, 26 | uint32_t width, 27 | uint32_t height, 28 | uint64_t timeStampUS); 29 | 30 | typedef void(*LocalDataChannelReadyCallback)(const char* label); 31 | 32 | typedef void(*DataAvailableCallback)(const char* label, const uint8_t* data, int length, bool is_binary); 33 | 34 | typedef void(*FailureCallback)(const char* msg); 35 | 36 | typedef void(*LocalSdpReadyToSendCallback)(const char* type, const char* sdp); 37 | 38 | typedef void(*IceCandidateReadyToSendCallback)(const char* candidate, 39 | const int sdp_mline_index, 40 | const char* sdp_mid); 41 | 42 | typedef void(*AudioBusReadyCallback)(const void* audio_data, 43 | int bits_per_sample, 44 | int sample_rate, 45 | int number_of_channels, 46 | int number_of_frames); 47 | 48 | typedef void(*StateChangedCallback)(int state); 49 | 50 | typedef void(*VideoFrameProcessedCallback)(int video_track_id, const void *pixels, bool is_encoded); 51 | 52 | typedef void(*RemoteTrackChangedCallback)(const char* track_id, int media_kind, int change_kind); 53 | 54 | typedef void(*LogSink)(const char* message, int severity); 55 | 56 | -------------------------------------------------------------------------------- /webrtc-native/NativeVideoBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "NativeVideoBuffer.h" 3 | 4 | namespace webrtc 5 | { 6 | NativeVideoBuffer::NativeVideoBuffer( 7 | int track_id, 8 | VideoFrameFormat format, 9 | int width, 10 | int height, 11 | const void* texture, 12 | VideoFrameEvents* events) 13 | : track_id_(track_id) 14 | , format_(format) 15 | , width_(width) 16 | , height_(height) 17 | , texture_(texture) 18 | , events_(events) 19 | , request_time_(std::chrono::high_resolution_clock::now()) 20 | , encoded_time_(std::chrono::microseconds::max()) 21 | { 22 | if (texture_ && format_ == VideoFrameFormat::GpuTextureD3D11) 23 | { 24 | // Make sure to keep the texture alive until we're done with it. 25 | auto texture3D11 = reinterpret_cast(const_cast(texture_)); 26 | texture3D11->AddRef(); 27 | } 28 | } 29 | 30 | NativeVideoBuffer::~NativeVideoBuffer() 31 | { 32 | if (events_ && texture_) 33 | { 34 | events_->OnFrameProcessed(track_id_, texture_, is_encoded_); 35 | } 36 | 37 | if (texture_ && format_ == VideoFrameFormat::GpuTextureD3D11) 38 | { 39 | // Release the D3D11 texture when we're done with it. 40 | auto texture3D11 = reinterpret_cast(const_cast(texture_)); 41 | texture3D11->Release(); 42 | } 43 | } 44 | 45 | VideoFrameBuffer::Type NativeVideoBuffer::type() const 46 | { 47 | return Type::kNative; 48 | } 49 | 50 | int NativeVideoBuffer::width() const 51 | { 52 | return width_; 53 | } 54 | 55 | int NativeVideoBuffer::height() const 56 | { 57 | return height_; 58 | } 59 | 60 | void NativeVideoBuffer::set_encoded(bool is_encoded) 61 | { 62 | is_encoded_ = is_encoded; 63 | encoded_time_ = std::chrono::high_resolution_clock::now(); 64 | 65 | //char buffer[100]; 66 | //sprintf_s(buffer, "webrtc-nvenc delay = %08lld microsec", request_encode_delay().count()); 67 | //SetConsoleTitleA(buffer); 68 | } 69 | 70 | rtc::scoped_refptr NativeVideoBuffer::ToI420() 71 | { 72 | throw std::runtime_error("Converting a native buffer to a CPU 420 buffer is not supported"); 73 | } 74 | } // namespace webrtc 75 | -------------------------------------------------------------------------------- /webrtc-native/NativeVideoBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "macros.h" 3 | #include "VideoFrameEvents.h" 4 | #include "VideoObserver.h" 5 | 6 | namespace webrtc 7 | { 8 | 9 | class NativeVideoBuffer : public VideoFrameBuffer 10 | { 11 | public: 12 | NativeVideoBuffer(int track_id, VideoFrameFormat format, int width, int height, const void* texture, VideoFrameEvents* events); 13 | ~NativeVideoBuffer() override; 14 | 15 | Type type() const override; 16 | int width() const override; 17 | int height() const override; 18 | const void *texture() const { return texture_; } 19 | VideoFrameFormat format() const { return format_; } 20 | 21 | bool is_encoded() const { return is_encoded_; } 22 | void set_encoded(bool is_encoded); 23 | 24 | // Delay between the request to encode the frame, and the actual encoding time point. 25 | std::chrono::microseconds request_encode_delay() const 26 | { 27 | return std::chrono::duration_cast(encoded_time_ - request_time_); 28 | } 29 | 30 | DISALLOW_COPY_MOVE_ASSIGN(NativeVideoBuffer); 31 | 32 | private: 33 | rtc::scoped_refptr ToI420() override; 34 | 35 | const int track_id_; 36 | VideoFrameFormat format_; 37 | const int width_; 38 | const int height_; 39 | const void* texture_; 40 | VideoFrameEvents* events_; 41 | bool is_encoded_ = false; 42 | std::chrono::time_point request_time_; 43 | std::chrono::time_point encoded_time_; 44 | }; 45 | } // namespace webrtc 46 | -------------------------------------------------------------------------------- /webrtc-native/NvEncoderH264.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "macros.h" 3 | 4 | class NvEncFacadeD3D11; 5 | 6 | namespace webrtc { 7 | 8 | class NvEncoderH264 final : public VideoEncoder { 9 | public: 10 | static bool IsAvailable(); 11 | 12 | explicit NvEncoderH264(); 13 | ~NvEncoderH264() override; 14 | 15 | DISALLOW_COPY_MOVE_ASSIGN(NvEncoderH264); 16 | 17 | // |max_payload_size| is ignored. 18 | // The following members of |codec_settings| are used. The rest are ignored. 19 | // - codecType (must be kVideoCodecH264) 20 | // - targetBitrate 21 | // - maxFramerate 22 | // - width 23 | // - height 24 | int32_t InitEncode(const VideoCodec* codec_settings, 25 | int32_t number_of_cores, 26 | size_t max_payload_size) override; 27 | int32_t Release() override; 28 | 29 | int32_t RegisterEncodeCompleteCallback( 30 | EncodedImageCallback* callback) override; 31 | int32_t SetRateAllocation(const VideoBitrateAllocation& bitrate_allocation, 32 | uint32_t framerate) override; 33 | 34 | // The result of encoding - an EncodedImage and RTPFragmentationHeader - are 35 | // passed to the encode complete callback. 36 | int32_t Encode(const VideoFrame& frame, 37 | const CodecSpecificInfo* codec_specific_info, 38 | const std::vector* frame_types) override; 39 | 40 | EncoderInfo GetEncoderInfo() const override; 41 | 42 | private: 43 | H264BitstreamParser h264_bitstream_parser_; 44 | 45 | // Reports statistics with histograms. 46 | void ReportInit(); 47 | void ReportError(); 48 | void SetStreamState(bool send_stream); 49 | 50 | NvEncFacadeD3D11* encoder; 51 | std::vector encoded_output_buffer_; 52 | EncodedImage encoded_image_; 53 | 54 | VideoCodec codec_; 55 | size_t max_payload_size_; 56 | EncodedImageCallback* encoded_image_callback_; 57 | 58 | bool has_reported_init_; 59 | bool has_reported_error_; 60 | 61 | bool is_sending_ = false; 62 | bool key_frame_request_ = false; 63 | 64 | FILE* debug_output_file_ = nullptr; 65 | }; 66 | 67 | } // namespace webrtc 68 | -------------------------------------------------------------------------------- /webrtc-native/PeerConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NativeInterface.h" 4 | #include "VideoObserver.h" 5 | #include "VideoFrameEvents.h" 6 | 7 | #undef HAS_LOCAL_VIDEO_OBSERVER 8 | #define HAS_REMOTE_VIDEO_OBSERVER 9 | 10 | class PeerConnection final 11 | : public webrtc::PeerConnectionObserver 12 | , public webrtc::CreateSessionDescriptionObserver 13 | , public webrtc::AudioTrackSinkInterface 14 | , public VideoFrameEvents 15 | { 16 | public: 17 | PeerConnection( 18 | webrtc::PeerConnectionFactoryInterface* factory, 19 | const char** ice_url_array, const int ice_url_count, 20 | const char* ice_username, const char* ice_password, 21 | bool can_receive_audio, bool can_receive_video, 22 | bool enable_dtls_srtp); 23 | 24 | ~PeerConnection() override; 25 | 26 | bool created() const { return peer_connection_; } 27 | 28 | rtc::scoped_refptr& factory() { return factory_; } 29 | 30 | // TODO: Allow the user to select the kind of stream (what camera, etc...) 31 | int AddVideoTrack(const std::string& label, int min_bps, int max_bps, int max_fps); 32 | bool SendVideoFrame(int video_track_id, const uint8_t* pixels, int stride, int width, int height, VideoFrameFormat format); 33 | 34 | bool CreateOffer(); 35 | bool CreateAnswer(); 36 | bool SetAudioControl(bool is_mute, bool is_record); 37 | 38 | bool AddDataChannel(const char* label, bool is_ordered, bool is_reliable); 39 | bool SendData(const char* label, const uint8_t* data, int length, bool is_binary); 40 | bool RemoveDataChannel(const char* label); 41 | 42 | 43 | // Register callback functions. 44 | void RegisterOnLocalI420FrameReady(IncomingVideoFrameCallback callback) const; 45 | void RegisterOnRemoteI420FrameReady(IncomingVideoFrameCallback callback) const; 46 | void RegisterOnLocalDataChannelReady(LocalDataChannelReadyCallback callback); 47 | void RegisterOnDataFromDataChannelReady(DataAvailableCallback callback); 48 | void RegisterOnFailure(FailureCallback callback); 49 | void RegisterOnAudioBusReady(AudioBusReadyCallback callback); 50 | void RegisterOnLocalSdpReadyToSend(LocalSdpReadyToSendCallback callback); 51 | void RegisterOnIceCandidateReadyToSend(IceCandidateReadyToSendCallback callback); 52 | void RegisterSignalingStateChanged(StateChangedCallback callback); 53 | void RegisterConnectionStateChanged(StateChangedCallback callback); 54 | void RegisterVideoFrameProcessed(VideoFrameProcessedCallback callback); 55 | void RegisterRemoteTrackChanged(RemoteTrackChangedCallback callback); 56 | 57 | bool SetRemoteDescription(const char* type, const char* sdp) const; 58 | bool AddIceCandidate(const char* sdp, const int sdp_mlineindex, const char* sdp_mid) const; 59 | 60 | void AddRef() const override; 61 | rtc::RefCountReleaseStatus Release() const override; 62 | 63 | protected: 64 | bool CreateTransceivers() const; 65 | bool SetAudioControl(); 66 | 67 | // PeerConnectionObserver implementation. 68 | void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override; 69 | 70 | void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override; 71 | 72 | 73 | void OnTrack(rtc::scoped_refptr transceiver) override; 74 | 75 | void OnRenegotiationNeeded() override; 76 | 77 | void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override; 78 | 79 | void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override; 80 | 81 | void OnDataChannel(rtc::scoped_refptr channel) override; 82 | 83 | void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; 84 | 85 | // CreateSessionDescriptionObserver implementation. 86 | void OnSuccess(webrtc::SessionDescriptionInterface* desc) override; 87 | 88 | void OnFailure(webrtc::RTCError error) override; 89 | 90 | // AudioTrackSinkInterface implementation. 91 | void OnData(const void* audio_data, 92 | int bits_per_sample, 93 | int sample_rate, 94 | size_t number_of_channels, 95 | size_t number_of_frames) override; 96 | 97 | void OnFrameProcessed(int video_track_id, const void* pixels, bool is_encoded) override; 98 | 99 | // Get remote audio tracks ssrcs. 100 | std::vector GetRemoteAudioTrackSynchronizationSources() const; 101 | 102 | private: 103 | rtc::scoped_refptr factory_; 104 | rtc::scoped_refptr peer_connection_; 105 | 106 | class DataChannelEntry : public webrtc::DataChannelObserver 107 | { 108 | public: 109 | DataChannelEntry(PeerConnection* connection, 110 | rtc::scoped_refptr data_channel); 111 | ~DataChannelEntry() override; 112 | 113 | // DataChannelObserver implementation. 114 | void OnStateChange() override; 115 | void OnMessage(const webrtc::DataBuffer& buffer) override; 116 | 117 | rtc::scoped_refptr channel; 118 | PeerConnection* connection; 119 | 120 | private: 121 | // disallow copy-and-assign 122 | DataChannelEntry(const DataChannelEntry&) = delete; 123 | DataChannelEntry& operator=(const DataChannelEntry&) = delete; 124 | }; 125 | 126 | int last_video_track_id_ = 0; 127 | 128 | // TODO: Also use an identifier of a data-channel. 129 | std::map> data_channels_; 130 | 131 | std::map> video_tracks_; 132 | 133 | #ifdef HAS_LOCAL_VIDEO_OBSERVER 134 | std::unique_ptr local_video_observer_; 135 | #endif 136 | 137 | #ifdef HAS_REMOTE_VIDEO_OBSERVER 138 | std::unique_ptr remote_video_observer_; 139 | #endif 140 | 141 | // rtc::scoped_refptr remote_stream_; 142 | 143 | webrtc::PeerConnectionInterface::RTCConfiguration config_; 144 | 145 | LocalDataChannelReadyCallback OnLocalDataChannelReady = nullptr; 146 | DataAvailableCallback OnDataFromDataChannelReady = nullptr; 147 | FailureCallback OnFailureMessage = nullptr; 148 | AudioBusReadyCallback OnAudioReady = nullptr; 149 | VideoFrameProcessedCallback OnVideoFrameProcessed = nullptr; 150 | 151 | LocalSdpReadyToSendCallback OnLocalSdpReadyToSend = nullptr; 152 | IceCandidateReadyToSendCallback OnIceCandidateReady = nullptr; 153 | StateChangedCallback OnSignalingStateChanged = nullptr; 154 | StateChangedCallback OnConnectionStateChanged = nullptr; 155 | RemoteTrackChangedCallback OnRemoteTrackChanged = nullptr; 156 | 157 | bool is_mute_audio_ = false; 158 | bool is_record_audio_ = false; 159 | bool can_receive_audio_ = false; 160 | bool can_receive_video_ = false; 161 | 162 | // disallow copy-and-assign 163 | PeerConnection(const PeerConnection&) = delete; 164 | PeerConnection& operator=(const PeerConnection&) = delete; 165 | }; 166 | -------------------------------------------------------------------------------- /webrtc-native/TestVideoCapturer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "TestVideoCapturer.h" 4 | 5 | TestVideoCapturer::TestVideoCapturer() = default; 6 | TestVideoCapturer::~TestVideoCapturer() = default; 7 | 8 | void TestVideoCapturer::OnFrame(const webrtc::VideoFrame& frame) 9 | { 10 | int cropped_width = 0; 11 | int cropped_height = 0; 12 | int out_width = 0; 13 | int out_height = 0; 14 | 15 | if (!video_adapter_.AdaptFrameResolution( 16 | frame.width(), frame.height(), frame.timestamp_us() * 1000, 17 | &cropped_width, &cropped_height, &out_width, &out_height)) 18 | { 19 | // Drop frame in order to respect frame rate constraint. 20 | return; 21 | } 22 | 23 | if (out_height != frame.height() || out_width != frame.width()) 24 | { 25 | // Video adapter has requested a down-scale. Allocate a new buffer and 26 | // return scaled version. 27 | rtc::scoped_refptr scaled_buffer = 28 | webrtc::I420Buffer::Create(out_width, out_height); 29 | scaled_buffer->ScaleFrom(*frame.video_frame_buffer()->ToI420()); 30 | broadcaster_.OnFrame(webrtc::VideoFrame::Builder() 31 | .set_video_frame_buffer(scaled_buffer) 32 | .set_rotation(webrtc::kVideoRotation_0) 33 | .set_timestamp_us(frame.timestamp_us()) 34 | .set_id(frame.id()) 35 | .build()); 36 | } 37 | else 38 | { 39 | // No adaptations needed, just return the frame as is. 40 | broadcaster_.OnFrame(frame); 41 | } 42 | } 43 | 44 | rtc::VideoSinkWants TestVideoCapturer::GetSinkWants() 45 | { 46 | return broadcaster_.wants(); 47 | } 48 | 49 | void TestVideoCapturer::AddOrUpdateSink( 50 | rtc::VideoSinkInterface* sink, 51 | const rtc::VideoSinkWants& wants) 52 | { 53 | broadcaster_.AddOrUpdateSink(sink, wants); 54 | UpdateVideoAdapter(); 55 | } 56 | 57 | void TestVideoCapturer::RemoveSink(rtc::VideoSinkInterface* sink) 58 | { 59 | broadcaster_.RemoveSink(sink); 60 | UpdateVideoAdapter(); 61 | } 62 | 63 | void TestVideoCapturer::UpdateVideoAdapter() 64 | { 65 | rtc::VideoSinkWants wants = broadcaster_.wants(); 66 | video_adapter_.OnResolutionFramerateRequest( 67 | wants.target_pixel_count, wants.max_pixel_count, wants.max_framerate_fps); 68 | } 69 | -------------------------------------------------------------------------------- /webrtc-native/TestVideoCapturer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class TestVideoCapturer : public rtc::VideoSourceInterface 4 | { 5 | public: 6 | TestVideoCapturer(); 7 | virtual ~TestVideoCapturer(); 8 | 9 | void AddOrUpdateSink(rtc::VideoSinkInterface* sink, 10 | const rtc::VideoSinkWants& wants) override; 11 | void RemoveSink(rtc::VideoSinkInterface* sink) override; 12 | 13 | protected: 14 | void OnFrame(const webrtc::VideoFrame& frame); 15 | rtc::VideoSinkWants GetSinkWants(); 16 | 17 | private: 18 | void UpdateVideoAdapter(); 19 | 20 | rtc::VideoBroadcaster broadcaster_; 21 | cricket::VideoAdapter video_adapter_; 22 | }; 23 | -------------------------------------------------------------------------------- /webrtc-native/VideoCameraCapturer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "VideoCameraCapturer.h" 3 | 4 | VideoCameraCapturer::VideoCameraCapturer() : vcm_(nullptr) 5 | { 6 | } 7 | 8 | bool VideoCameraCapturer::Init(size_t width, 9 | size_t height, 10 | size_t target_fps, 11 | size_t capture_device_index) 12 | { 13 | std::unique_ptr device_info( 14 | webrtc::VideoCaptureFactory::CreateDeviceInfo()); 15 | 16 | char device_name[256]; 17 | char unique_name[256]; 18 | if (device_info->GetDeviceName(static_cast(capture_device_index), 19 | device_name, sizeof(device_name), unique_name, 20 | sizeof(unique_name)) != 0) 21 | { 22 | Destroy(); 23 | return false; 24 | } 25 | 26 | vcm_ = webrtc::VideoCaptureFactory::Create(unique_name); 27 | vcm_->RegisterCaptureDataCallback(this); 28 | 29 | device_info->GetCapability(vcm_->CurrentDeviceName(), 0, capability_); 30 | 31 | capability_.width = static_cast(width); 32 | capability_.height = static_cast(height); 33 | capability_.maxFPS = static_cast(target_fps); 34 | capability_.videoType = webrtc::VideoType::kI420; 35 | 36 | if (vcm_->StartCapture(capability_) != 0) 37 | { 38 | Destroy(); 39 | return false; 40 | } 41 | 42 | RTC_CHECK(vcm_->CaptureStarted()); 43 | 44 | return true; 45 | } 46 | 47 | VideoCameraCapturer* VideoCameraCapturer::Create(size_t width, 48 | size_t height, 49 | size_t target_fps, 50 | size_t capture_device_index) 51 | { 52 | std::unique_ptr vcm_capturer(new VideoCameraCapturer()); 53 | if (!vcm_capturer->Init(width, height, target_fps, capture_device_index)) 54 | { 55 | RTC_LOG(LS_WARNING) << "Failed to create VideoCameraCapturer(w = " << width 56 | << ", h = " << height << ", fps = " << target_fps 57 | << ")"; 58 | return nullptr; 59 | } 60 | return vcm_capturer.release(); 61 | } 62 | 63 | void VideoCameraCapturer::Destroy() 64 | { 65 | if (!vcm_) 66 | return; 67 | 68 | vcm_->StopCapture(); 69 | vcm_->DeRegisterCaptureDataCallback(); 70 | // Release reference to VCM. 71 | vcm_ = nullptr; 72 | } 73 | 74 | VideoCameraCapturer::~VideoCameraCapturer() 75 | { 76 | Destroy(); 77 | } 78 | 79 | void VideoCameraCapturer::OnFrame(const webrtc::VideoFrame& frame) 80 | { 81 | TestVideoCapturer::OnFrame(frame); 82 | } 83 | -------------------------------------------------------------------------------- /webrtc-native/VideoCameraCapturer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "TestVideoCapturer.h" 3 | 4 | class VideoCameraCapturer : public TestVideoCapturer, 5 | public rtc::VideoSinkInterface { 6 | public: 7 | static VideoCameraCapturer* Create(size_t width, 8 | size_t height, 9 | size_t target_fps, 10 | size_t capture_device_index); 11 | virtual ~VideoCameraCapturer(); 12 | 13 | void OnFrame(const webrtc::VideoFrame& frame) override; 14 | 15 | private: 16 | VideoCameraCapturer(); 17 | bool Init(size_t width, 18 | size_t height, 19 | size_t target_fps, 20 | size_t capture_device_index); 21 | void Destroy(); 22 | 23 | rtc::scoped_refptr vcm_; 24 | webrtc::VideoCaptureCapability capability_; 25 | }; 26 | -------------------------------------------------------------------------------- /webrtc-native/VideoFrameEvents.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class VideoFrameEvents abstract 4 | { 5 | public: 6 | virtual ~VideoFrameEvents() = default; 7 | 8 | // Called when a video frame is processed, and is ready to be reused. It might not have been encoded, it could be skipped. 9 | virtual void OnFrameProcessed(int video_track_id, const void* pixels, bool was_encoded) = 0; 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /webrtc-native/VideoObserver.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "VideoObserver.h" 3 | #include "NativeVideoBuffer.h" 4 | 5 | void VideoObserver::SetVideoCallback(IncomingVideoFrameCallback callback) 6 | { 7 | std::lock_guard lock(mutex); 8 | OnIncomingVideoFrame = callback; 9 | } 10 | 11 | void VideoObserver::OnFrame(const webrtc::VideoFrame& frame) 12 | { 13 | std::unique_lock lock(mutex); 14 | if (!OnIncomingVideoFrame) 15 | return; 16 | 17 | rtc::scoped_refptr buffer( 18 | frame.video_frame_buffer()); 19 | 20 | switch (buffer->type()) 21 | { 22 | case webrtc::VideoFrameBuffer::Type::kI420A: 23 | { 24 | // The buffer has alpha channel. 25 | webrtc::I420ABufferInterface* i420a_buffer = buffer->GetI420A(); 26 | 27 | OnIncomingVideoFrame(nullptr, 28 | i420a_buffer->DataY(), i420a_buffer->DataU(), 29 | i420a_buffer->DataV(), i420a_buffer->DataA(), 30 | i420a_buffer->StrideY(), i420a_buffer->StrideU(), 31 | i420a_buffer->StrideV(), i420a_buffer->StrideA(), 32 | frame.width(), frame.height(), frame.timestamp_us()); 33 | } 34 | break; 35 | 36 | case webrtc::VideoFrameBuffer::Type::kNative: 37 | { 38 | const auto native_buffer = dynamic_cast(buffer.get()); 39 | if (native_buffer) 40 | { 41 | OnIncomingVideoFrame(native_buffer->texture(), 42 | nullptr, nullptr, nullptr, nullptr, 43 | 0, 0, 0, 0, 44 | frame.width(), frame.height(), frame.timestamp_us()); 45 | break; 46 | } 47 | // goto default: 48 | } 49 | 50 | default: 51 | { 52 | rtc::scoped_refptr i420_buffer = buffer->ToI420(); 53 | OnIncomingVideoFrame(nullptr, 54 | i420_buffer->DataY(), i420_buffer->DataU(), 55 | i420_buffer->DataV(), nullptr, i420_buffer->StrideY(), 56 | i420_buffer->StrideU(), i420_buffer->StrideV(), 0, 57 | frame.width(), frame.height(), frame.timestamp_us()); 58 | } 59 | break; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /webrtc-native/VideoObserver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NativeInterface.h" 4 | 5 | class VideoObserver final : public rtc::VideoSinkInterface 6 | { 7 | public: 8 | VideoObserver() = default; 9 | ~VideoObserver() = default; 10 | 11 | void SetVideoCallback(IncomingVideoFrameCallback callback); 12 | 13 | protected: 14 | // VideoSinkInterface implementation 15 | void OnFrame(const webrtc::VideoFrame& frame) override; 16 | 17 | private: 18 | IncomingVideoFrameCallback OnIncomingVideoFrame = nullptr; 19 | std::mutex mutex; 20 | }; 21 | -------------------------------------------------------------------------------- /webrtc-native/filesystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using namespace std::experimental; 6 | 7 | -------------------------------------------------------------------------------- /webrtc-native/libs.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #pragma comment(lib, "webrtc.lib") 4 | 5 | #ifdef _WIN32 6 | # pragma comment(lib, "secur32.lib") 7 | # pragma comment(lib, "winmm.lib") 8 | # pragma comment(lib, "dmoguids.lib") 9 | # pragma comment(lib, "wmcodecdspuuid.lib") 10 | # pragma comment(lib, "msdmo.lib") 11 | # pragma comment(lib, "Strmiids.lib") 12 | #endif 13 | -------------------------------------------------------------------------------- /webrtc-native/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A macro to disallow the copy/move constructor and operator= functions 4 | #define DISALLOW_COPY_MOVE_ASSIGN(TypeName) \ 5 | TypeName(const TypeName &) = delete; \ 6 | void operator=(const TypeName &) = delete; \ 7 | TypeName(TypeName &&) = delete; \ 8 | void operator=(TypeName &&) = delete; 9 | 10 | #define DEFAULT_COPY_MOVE_ASSIGN(TypeName) \ 11 | TypeName(const TypeName& other) = default; \ 12 | TypeName(TypeName&& other) noexcept = default; \ 13 | TypeName& operator=(const TypeName& other) = default; \ 14 | TypeName& operator=(TypeName&& other) noexcept = default; \ 15 | 16 | #define DEFAULT_COPY_MOVE_ASSIGN_DTOR(TypeName) \ 17 | ~TypeName() = default; \ 18 | DEFAULT_COPY_MOVE_ASSIGN(TypeName) \ 19 | 20 | #define DEFAULT_COPY_MOVE_ASSIGN_CTOR_DTOR(TypeName) \ 21 | TypeName() = default; \ 22 | DEFAULT_COPY_MOVE_ASSIGN_DTOR(TypeName) \ 23 | -------------------------------------------------------------------------------- /webrtc-native/main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "main.h" 3 | 4 | #if _WIN32 5 | 6 | HMODULE currentModuleHandle; 7 | 8 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 9 | { 10 | if (ul_reason_for_call == DLL_PROCESS_ATTACH) 11 | { 12 | currentModuleHandle = hModule; 13 | } 14 | 15 | return true; 16 | } 17 | #else 18 | # error "This library only works on Windows for now" 19 | #endif -------------------------------------------------------------------------------- /webrtc-native/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | extern HMODULE currentModuleHandle; 5 | #endif 6 | -------------------------------------------------------------------------------- /webrtc-native/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /webrtc-native/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma warning( push ) 4 | #pragma warning( disable : 4244 ) 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "api/scoped_refptr.h" 19 | #include "api/media_stream_interface.h" 20 | #include "api/data_channel_interface.h" 21 | #include "api/peer_connection_interface.h" 22 | #include "api/create_peerconnection_factory.h" 23 | #include "api/video_track_source_proxy.h" 24 | 25 | #include "api/video/video_sink_interface.h" 26 | #include "api/video/video_frame.h" 27 | #include "api/video/video_frame_buffer.h" 28 | #include "api/video/video_source_interface.h" 29 | #include "api/video/video_rotation.h" 30 | #include "api/video/i420_buffer.h" 31 | #include "api/video/i010_buffer.h" 32 | 33 | #include "api/audio_codecs/builtin_audio_decoder_factory.h" 34 | #include "api/audio_codecs/builtin_audio_encoder_factory.h" 35 | 36 | #include "common_types.h" // NOLINT(build/include) 37 | #include "common_video/include/video_frame_buffer.h" 38 | #include "common_video/libyuv/include/webrtc_libyuv.h" 39 | 40 | #include "media/base/video_adapter.h" 41 | #include "media/base/video_broadcaster.h" 42 | 43 | #include "media/engine/internal_decoder_factory.h" 44 | #include "media/engine/internal_encoder_factory.h" 45 | #include "media/engine/multiplex_codec_factory.h" 46 | #include "media/engine/fake_video_codec_factory.h" 47 | 48 | #include "modules/video_capture/video_capture_factory.h" 49 | #include "modules/video_capture/video_capture.h" 50 | 51 | #include "modules/video_coding/utility/simulcast_rate_allocator.h" 52 | #include "modules/video_coding/utility/simulcast_utility.h" 53 | #include 54 | 55 | #include "pc/video_track_source.h" 56 | 57 | #include "absl/memory/memory.h" 58 | #include "absl/strings/match.h" 59 | 60 | #include "rtc_base/checks.h" 61 | #include "rtc_base/logging.h" 62 | #include "rtc_base/bind.h" 63 | #include "rtc_base/checks.h" 64 | #include "rtc_base/keep_ref_until_done.h" 65 | #include "rtc_base/random.h" 66 | #include "rtc_base/critical_section.h" 67 | #include "rtc_base/task_queue.h" 68 | #include "rtc_base/task_utils/repeating_task.h" 69 | #include "rtc_base/ssl_adapter.h" 70 | 71 | #include "system_wrappers/include/clock.h" 72 | #include "system_wrappers/include/metrics.h" 73 | 74 | #include "libyuv/scale.h" 75 | #include "libyuv/convert.h" 76 | 77 | #include "common_video/h264/h264_bitstream_parser.h" 78 | #include "common_video/h264/h264_common.h" 79 | #include "modules/video_coding/include/video_codec_interface.h" 80 | #include "media/base/h264_profile_level_id.h" 81 | 82 | #ifdef _WIN32 83 | # include 84 | # include 85 | #endif 86 | 87 | #pragma warning( pop ) 88 | -------------------------------------------------------------------------------- /webrtc-native/webrtc-native.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {9d2fda62-8d75-4652-8c3f-dbe47743a76b} 6 | h;cpp;cc 7 | 8 | 9 | 10 | 11 | src 12 | 13 | 14 | src 15 | 16 | 17 | src 18 | 19 | 20 | src 21 | 22 | 23 | src 24 | 25 | 26 | src 27 | 28 | 29 | src 30 | 31 | 32 | src 33 | 34 | 35 | src 36 | 37 | 38 | src 39 | 40 | 41 | src 42 | 43 | 44 | src 45 | 46 | 47 | src 48 | 49 | 50 | src 51 | 52 | 53 | src 54 | 55 | 56 | 57 | 58 | src 59 | 60 | 61 | src 62 | 63 | 64 | src 65 | 66 | 67 | src 68 | 69 | 70 | src 71 | 72 | 73 | src 74 | 75 | 76 | src 77 | 78 | 79 | src 80 | 81 | 82 | src 83 | 84 | 85 | src 86 | 87 | 88 | src 89 | 90 | 91 | src 92 | 93 | 94 | src 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /windows_build_nvpipe.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iimachines/webrtc-dotnet-core/b5f0d3f4eb310ed885e77c60ef4547943afaa88e/windows_build_nvpipe.bat --------------------------------------------------------------------------------