microsoft / MixedReality-WebRTC

MixedReality-WebRTC is a collection of components to help mixed reality app developers integrate audio and video real-time communication into their application and improve their collaborative experience
https://microsoft.github.io/MixedReality-WebRTC/
MIT License
910 stars 283 forks source link

Argb32FrameReady event does not trigger. #520

Open mothermetabot opened 4 years ago

mothermetabot commented 4 years ago

I'm running into a problem and reading the source code has not helped.

I'm trying to share Video with a remote host and I was able to Connect, AFAIK (PeerConnection.Connected event fired, PeerConnection.IceGatheringState fired - State is Complete).

I have added an audio and video track to the PeerConnection, exactly as in the Documentation Page, and it does finds my devices correctly. Yet, the TrackAdded event never fires. My PeerConnection has no RemoteVideoTrack after connection is established. I noticed that, even if I subscribe to the LocalVideoTrack.ARGB32FrameReady event, it does not fire, even though there is a LocalVideoTrack attached to the PeerConnection.

I'm unsure how to even tackle this issue, any help would be appreciated.

djee-ms commented 4 years ago

Hi @mothermetabot,

Which version are you using? In the latest versions tracks are not added to the PeerConnection, they're added to transceivers. What's the complete setup? Are you using Unity or the C# library? What are the SDP offer and answer messages? What signaler are you using? What platform/architecture/device?

Can you please use the Bug Report template next time? This asks all the info we need to help diagnose.

mothermetabot commented 4 years ago

Thanks for the reply and excuse my lack of relevant information.

I am using the C# Lib on a Windows 10 64 bit Enterprise. The Application is built in 86x. The setup is very bare-bones. Both clients add the media tracks (as shown in the Documentation, exactly), signaling is done through a SignalR server and works as expected (messages are exchanged). I subscribe to the TrackAdded event, which never triggers. I tried iterating over the transceivers and subscribing to their ARGB32FrameReadyevent, but not even on my Local Media tracks the event fires, for some reason. Other than that the PeerConnectionis also set up as described in the documentation, using the google stun server.

This is the Sdp Message:

v=0
o=- 7798826082529914821 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:PZLN
a=ice-pwd:jpEh2bVOBw6hzBe2DZJRkmNV
a=ice-options:trickle
a=fingerprint:sha-256 0C:95:53:3E:BC:1A:AB:3A:51:D2:46:4B:31:51:13:7E:AE:9C:EB:1A:AD:5D:70:40:9E:77:96:8D:22:A4:3F:51
a=setup:actpass
a=mid:0
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=sendrecv
a=msid:- 6a7f30be-8c22-4889-ae02-72cd683c64d2
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 x-google-profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 multiplex/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 acn=VP9;x-google-profile-id=0
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 red/90000
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=127
a=rtpmap:125 ulpfec/90000
a=ssrc-group:FID 3504025078 2045163521
a=ssrc:3504025078 cname:J1mYA8AY/ZrwYZ/O
a=ssrc:3504025078 msid: 6a7f30be-8c22-4889-ae02-72cd683c64d2
a=ssrc:3504025078 mslabel:
a=ssrc:3504025078 label:6a7f30be-8c22-4889-ae02-72cd683c64d2
a=ssrc:2045163521 cname:J1mYA8AY/ZrwYZ/O
a=ssrc:2045163521 msid: 6a7f30be-8c22-4889-ae02-72cd683c64d2
a=ssrc:2045163521 mslabel:
a=ssrc:2045163521 label:6a7f30be-8c22-4889-ae02-72cd683c64d2
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:PZLN
a=ice-pwd:jpEh2bVOBw6hzBe2DZJRkmNV
a=ice-options:trickle
a=fingerprint:sha-256 0C:95:53:3E:BC:1A:AB:3A:51:D2:46:4B:31:51:13:7E:AE:9C:EB:1A:AD:5D:70:40:9E:77:96:8D:22:A4:3F:51
a=setup:actpass
a=mid:1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=sendrecv
a=msid:- c311241c-ad7f-4e09-b171-a03997edeab8
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:102 ILBC/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:2608459999 cname:J1mYA8AY/ZrwYZ/O
a=ssrc:2608459999 msid: c311241c-ad7f-4e09-b171-a03997edeab8
a=ssrc:2608459999 mslabel:
a=ssrc:2608459999 label:c311241c-ad7f-4e09-b171-a03997edeab8

PS: I will use the bug report template next time!

djee-ms commented 4 years ago

Which version are you using? In the latest versions tracks are not added to the PeerConnection, they're added to transceivers.

I really need that information please. The API is widely different between 1.0 and 2.0/master, and things do not work the same at all. Are you using a NuGet package (and which version)? Or are you building from sources (which branch/commit)?

The SDP message you pasted looks correct, assuming this is the offer one? Both audio and video have a a=sendrecv so are trying to negotiate a two-way streaming. Audio is asking for OPUS, and video for VP8, which are both supported on all platforms so this is likely not a codec issue. Can you please paste the SDP answer too, to confirm it actually accepts this?

djee-ms commented 4 years ago

Both clients add the media tracks (as shown in the Documentation, exactly),

Can you please link the page you are looking at too? It may be the wrong documentation version, or maybe we have some bug in the docs.

mothermetabot commented 4 years ago

I'm using version 2.0. I built the native dll from source, because I was having issues with the nuget package. But I have the Nuget package installed for the C# library.

This is the Sdp Answer:

v=0
o=- 6684045260706282497 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS

Also these are the links: PeerConnection setup. Media tracks setup. I tried to replicate this, basically.

In the output console I see these messages from time to time:

(stunport.cc:541): sendto : [0x00002743] A socket operation was attempted to an unreachable network.
(stunport.cc:541): sendto : [0x00002743] A socket operation was attempted to an unreachable network.
(stunport.cc:541): sendto : [0x00002743] A socket operation was attempted to an unreachable network.
(stunport.cc:541): sendto : [0x00002743] A socket operation was attempted to an unreachable network.
(stunport.cc:541): sendto : [0x00002743] A socket operation was attempted to an unreachable network.

Im unsure what they mean, but they do eventually stop.

Other than that I see these kinds of outputs: (port.cc:831): Port[161e14c0:0:1:0:local:Net[Realtek:192.168.178.52/32:Wifi:id=4]]: Sent STUN ping response, to=192.168.178.32:58182, id=626563547334796b48584f57

Also, if I wait for a while and set a breakpoint to watch the PeerConnectionobject, I can only see the TransceiversI have added on the local side. These only contain the local media tracks. However, if I subscribe to the Argb32FrameReady event of the existing LocalVideoTrack in this Transceiver, for some reason, the event doesn't trigger.

djee-ms commented 4 years ago

This is the Sdp Answer:

The SDP answer should be as big as the offer roughly. Here it's 5 lines only; this is not a valid SDP message. If this is what you have only, then the signaling is truncating it and not delivering it correctly, which is why nothing works.

mothermetabot commented 4 years ago

@djee-ms I fixed it for the most part by adding the media tracks after the call to PeerConnection.InitializeAsync() but I am now experiencing the same issue as #519.

Description: After the connection is established, on the side of the host that creates the offer(Caller) the TrackAdded event never triggers.

Furthermore, when I set a breakpoint to watch the PeerConnection object I observed the following:

On the Caller side: I can see 2 Transceivers with the NegotiatedDirection property set to SendOnly, even though the DesiredDirection property has a value of SendReceive.

On the Callee side: I can see 4 Transceivers. 2 of them have the NegotiatedDirection property set to ReceiveOnly and for some reason the DesiredDirection property is also set to ReceiveOnly. Even though I give it the value SendReceive (See code below). The other 2 Transceiver objects have SendReceive value in both NegotiatedDirection DesiredDirection properties.

In the SdpAnswer I see the following value (See Sdp Answer below for reference):

a=recvonly

Which would explain the behavior. This is very weird, because, I use the same code for both clients and set DesiredDirection to SendReceive on both, so I would expect the same results.

If I manually replace a=recvonly with a=sendrecv in the SDP Answer before I send it to the Caller, the event TrackAdded does trigger, like it should. But immediately afterwards I get an error message and the program crashes, which is understandable. This is what I see in the output:

# Fatal error in: c:\mr-webrtc\mixedreality-webrtc\libs\mrwebrtc\src\peer_connection.cpp, line 941
# last system error: 0
# Check failed: stats.size() == 1 (0 vs. 1)
# (probe_bitrate_estimator.cc:149): Probing successful [cluster id: 2] [send: 55440 bytes / 5 ms = 11088 kb/s] [receive: 55344 bytes / 14 ms = 3953.14 kb/s]
(probe_bitrate_estimator.cc:149): Probing successful [cluster id: 2] [send: 62400 bytes / 5 ms = 12480 kb/s] [receive: 62304 bytes / 19 ms = 3279.16 kb/s]
(probe_bitrate_estimator.cc:149): Probing successful [cluster id: 2] [send: 69280 bytes / 8 ms = 8660 kb/s] [receive: 69184 bytes / 19 ms = 3641.26 kb/s]
(probe_bitrate_estimator.cc:149): Probing successful [cluster id: 2] [send: 76160 bytes / 17 ms = 4480 kb/s] [receive: 76144 bytes / 20 ms = 3807.2 kb/s]

Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in mscorlib.dll
Exception thrown: 'System.TimeoutException' in WindowsBase.dll
(global_factory.cpp:289): Force-shutting down the global MixedReality-WebRTC factory while it still has 7 references. This will likely deadlock when dispatching the peer connection factory destructor to the signaling thread.
(global_factory.cpp:325): mr-webrtc alive objects report for 7 objects:
(global_factory.cpp:329): [0] (PeerConnection)  [~1 ref(s)]
(global_factory.cpp:329): [1] (LocalVideoTrack) webcam_track [~1 ref(s)]
(global_factory.cpp:329): [2] (LocalAudioTrack) microphone_track [~1 ref(s)]
(global_factory.cpp:329): [3] (VideoTransceiver) 7789100e-1d28-4bc6-b95d-e9b8b6c68dc2 [~1 ref(s)]
(global_factory.cpp:329): [4] (AudioTransceiver) 3d33580d-7305-4d76-ab45-b800a686e957 [~1 ref(s)]
(global_factory.cpp:329): [5] (RemoteVideoTrack) 8e2ae9c5-df09-4883-8b10-e034629e6526 [~1 ref(s)]
(global_factory.cpp:329): [6] (RemoteAudioTrack) b0a534bb-50a6-40e1-b57d-b9bd137f6d50 [~2 ref(s)]

#
# Fatal error in: ../../rtc_base/physicalsocketserver.cc, line 1207
# last system error: 0
# Check failed: dispatchers_.empty()

This is the Sdp Offer, followed by the Sdp Answer, followed by the exact code I used for both my clients:

Offer:

v=0
o=- 3451815082695993445 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:ghMJ
a=ice-pwd:q6dw1ytCQMyiBdJKTGd/QZlv
a=ice-options:trickle
a=fingerprint:sha-256 14:DB:D3:C7:3D:19:A9:51:CC:35:CC:2D:71:B4:1C:BD:BE:F6:C4:0F:B9:23:64:B5:B1:3C:87:0D:0A:40:9E:C2
a=setup:actpass
a=mid:0
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=sendrecv
a=msid:- a613200f-a3d0-48bf-a976-155d617aea21
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 x-google-profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 multiplex/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 acn=VP9;x-google-profile-id=0
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 red/90000
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=127
a=rtpmap:125 ulpfec/90000
a=ssrc-group:FID 2094064422 3637672892
a=ssrc:2094064422 cname:OoSNOOOvwxLRZU7D
a=ssrc:2094064422 msid: a613200f-a3d0-48bf-a976-155d617aea21
a=ssrc:2094064422 mslabel:
a=ssrc:2094064422 label:a613200f-a3d0-48bf-a976-155d617aea21
a=ssrc:3637672892 cname:OoSNOOOvwxLRZU7D
a=ssrc:3637672892 msid: a613200f-a3d0-48bf-a976-155d617aea21
a=ssrc:3637672892 mslabel:
a=ssrc:3637672892 label:a613200f-a3d0-48bf-a976-155d617aea21
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:ghMJ
a=ice-pwd:q6dw1ytCQMyiBdJKTGd/QZlv
a=ice-options:trickle
a=fingerprint:sha-256 14:DB:D3:C7:3D:19:A9:51:CC:35:CC:2D:71:B4:1C:BD:BE:F6:C4:0F:B9:23:64:B5:B1:3C:87:0D:0A:40:9E:C2
a=setup:actpass
a=mid:1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=sendrecv
a=msid:- fa9e6a56-553e-4a4c-977e-f04abbc9742e
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:102 ILBC/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:146763088 cname:OoSNOOOvwxLRZU7D
a=ssrc:146763088 msid: fa9e6a56-553e-4a4c-977e-f04abbc9742e
a=ssrc:146763088 mslabel:
a=ssrc:146763088 label:fa9e6a56-553e-4a4c-977e-f04abbc9742e

Answer:

v=0
o=- 7577414982413481514 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:MdLe
a=ice-pwd:4ZBuo6s0uWZP69SwcYk9mfjL
a=ice-options:trickle
a=fingerprint:sha-256 C4:3D:F1:FB:16:A4:EC:7C:F0:44:74:07:EB:A1:6E:5A:B2:AB:DD:A8:9E:9E:46:09:6C:12:E0:27:33:AF:95:16
a=setup:active
a=mid:0
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 x-google-profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 multiplex/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 acn=VP9;x-google-profile-id=0
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 red/90000
a=rtpmap:124 rtx/90000
a=fmtp:124 apt=127
a=rtpmap:125 ulpfec/90000
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:MdLe
a=ice-pwd:4ZBuo6s0uWZP69SwcYk9mfjL
a=ice-options:trickle
a=fingerprint:sha-256 C4:3D:F1:FB:16:A4:EC:7C:F0:44:74:07:EB:A1:6E:5A:B2:AB:DD:A8:9E:9E:46:09:6C:12:E0:27:33:AF:95:16
a=setup:active
a=mid:1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:102 ILBC/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000

Client code:

            //start peer connection
            pc = new PeerConnection();
            var config = new PeerConnectionConfiguration
            {
                IceServers = new List<IceServer> {
                    new IceServer{ Urls = { "stun:stun.l.google.com:19302" } }
                }
            };
            await pc.InitializeAsync(config);

            //Initialize media tracks
            AudioTrackSource microphoneSource = null;
            VideoTrackSource webcamSource = null;
            Transceiver audioTransceiver = null;
            Transceiver videoTransceiver = null;
            LocalAudioTrack localAudioTrack = null;
            LocalVideoTrack localVideoTrack = null;

            webcamSource = await DeviceVideoTrackSource.CreateAsync();

            var videoTrackConfig = new LocalVideoTrackInitConfig
            {
                trackName = "webcam_track"
            };
            localVideoTrack = LocalVideoTrack.CreateFromSource(webcamSource, videoTrackConfig);

            microphoneSource = await DeviceAudioTrackSource.CreateAsync();
            var audioTrackConfig = new LocalAudioTrackInitConfig
            {
                trackName = "microphone_track"
            };
            localAudioTrack = LocalAudioTrack.CreateFromSource(microphoneSource, audioTrackConfig);

            videoTransceiver = pc.AddTransceiver(MediaKind.Video);
            videoTransceiver.LocalVideoTrack = localVideoTrack;
            videoTransceiver.DesiredDirection = Transceiver.Direction.SendReceive;

            audioTransceiver = pc.AddTransceiver(MediaKind.Audio);
            audioTransceiver.LocalAudioTrack = localAudioTrack;
            audioTransceiver.DesiredDirection = Transceiver.Direction.SendReceive;

            //check if caller or callee
            if (signaler.IsCaller)
            {
                pc.LocalSdpReadytoSend += SendSdp;
                pc.IceCandidateReadytoSend +=  SendIce;

                pc.CreateOffer();
            }

            pc.VideoTrackAdded += track => {
                track.Argb32VideoFrameReady += RenderRemoteVideoFrame;
                pc.LocalVideoTracks.First().Argb32VideoFrameReady += RenderLocalVideoFrame;
            };

The signaling solution then calls this code on the Callee side through Remote Procedure Call:

            if (signal.SdpType == SdpMessageType.Offer)
            {
                await pc.SetRemoteDescriptionAsync(signal.ToSdpMessage());

                pc.LocalSdpReadytoSend += SendSdp(sdp);
                pc.IceCandidateReadytoSend += SendIce(ice);

                pc.CreateAnswer();
            }
            else
            {
                await pc.SetRemoteDescriptionAsync(signal.ToSdpMessage());
            }

Sorry for the wall of text, but I tried to be as thorough as possible. Thanks in advance!