public void JsonRoundtripUnitTest() { RTCPeerConnection pcSrc = new RTCPeerConnection(null); var videoTrackSrc = new MediaStreamTrack(SDPMediaTypesEnum.video, false, new List <SDPAudioVideoMediaFormat> { new SDPAudioVideoMediaFormat(SDPMediaTypesEnum.video, 96, "VP8", 90000) }); pcSrc.addTrack(videoTrackSrc); var offer = pcSrc.createOffer(new RTCOfferOptions()); Assert.NotNull(offer.toJSON()); logger.LogDebug($"offer: {offer.toJSON()}"); var parseResult = RTCSessionDescriptionInit.TryParse(offer.toJSON(), out var init); Assert.True(parseResult); Assert.Equal(RTCSdpType.offer, init.type); Assert.NotNull(init.sdp); SDP sdp = SDP.ParseSDPDescription(init.sdp); Assert.Equal(0, sdp.Version); }
private void AddActionsToSignalingWebSocket() { signaling.OnMessage += (sender, message) => { logger.LogDebug($"Received message: {message.Data}"); if (message.Data == "{\"data\":{\"getRemoteMedia\":true}}") { Negotiate(); return; } if (message.Data == "{\"data\":{\"candidate\":null}}") { return; } string correct_message = ConvertString(message.Data); logger.LogDebug($"After nesting:{correct_message}"); if (RTCIceCandidateInit.TryParse(correct_message, out var IceCandidate)) { logger.LogDebug($"Got remote candidate: {correct_message}"); pc.addIceCandidate(IceCandidate); } else if (RTCSessionDescriptionInit.TryParse(correct_message, out var SDP)) { logger.LogDebug($"Setting SDP: {correct_message}"); var result = pc.setRemoteDescription(SDP); if (result != SetDescriptionResultEnum.OK) { logger.LogWarning($"Failed to set remote description, {result}."); pc.Close("failed to set remote description"); } } }; }
public void ParseJavascriptJsonTest() { string json = "{\"type\":\"answer\",\"sdp\":\"v=0\r\no=- 3619871509827895381 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=msid-semantic: WMS\r\nm=video 9 UDP/TLS/RTP/SAVP 100\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:XqpN\r\na=ice-pwd:VOXKGB0AIf10Kqtv+zcQKgLF\r\na=ice-options:trickle\r\na=fingerprint:sha-256 D4:6E:F7:FD:B3:4F:4D:3D:3A:B3:92:2C:CC:F6:4E:46:88:B7:01:E9:B7:E1:77:03:8E:BB:AA:DC:26:1B:9D:2E\r\na=setup:active\r\na=mid:0\r\na=recvonly\r\na=rtcp-mux\r\na=rtpmap:100 VP8/90000\r\n\"}"; var parseResult = RTCSessionDescriptionInit.TryParse(json, out var init); Assert.True(parseResult); Assert.Equal(RTCSdpType.answer, init.type); Assert.NotNull(init.sdp); }
private void DoProcessMessage(Message message) { switch (message.type) { case "Offer": // var offer = JsonUtility.FromJson<RTCSessionDescriptionInit>(message.args); if (RTCSessionDescriptionInit.TryParse(message.args, out RTCSessionDescriptionInit offer)) { Debug.Log($"Got remote SDP, type {offer.type}"); var result = rtcPeerConnection.setRemoteDescription(offer); if (result != SetDescriptionResultEnum.OK) { Debug.Log($"Failed to set remote description, {result}."); rtcPeerConnection.Close("Failed to set remote description"); } else { if (rtcPeerConnection.signalingState == RTCSignalingState.have_remote_offer) { var answerSdp = rtcPeerConnection.createAnswer(); rtcPeerConnection.setLocalDescription(answerSdp); Debug.Log($"Sending SDP answer"); Send("Offer", answerSdp.toJSON()); } } } break; case "IceCandidate": if (RTCIceCandidateInit.TryParse(message.args, out RTCIceCandidateInit candidate)) { Debug.Log($"Got remote Ice Candidate, uri {candidate.candidate}"); rtcPeerConnection.addIceCandidate(candidate); } break; } }
static async Task Main() { Console.WriteLine("WebRTC Echo Test Client"); logger = AddConsoleLogger(); CancellationTokenSource cts = new CancellationTokenSource(); #region Set up a simple Windows Form with two picture boxes. _form = new Form(); _form.AutoSize = true; _form.BackgroundImageLayout = ImageLayout.Center; _sourceVideoPicBox = new PictureBox { Size = new Size(VIDEO_FRAME_WIDTH, VIDEO_FRAME_HEIGHT), Location = new Point(0, 0), Visible = true }; _echoVideoPicBox = new PictureBox { Size = new Size(VIDEO_FRAME_WIDTH, VIDEO_FRAME_HEIGHT), Location = new Point(0, VIDEO_FRAME_HEIGHT), Visible = true }; _form.Controls.Add(_sourceVideoPicBox); _form.Controls.Add(_echoVideoPicBox); Application.EnableVisualStyles(); ThreadPool.QueueUserWorkItem(delegate { Application.Run(_form); }); _form.FormClosing += (sender, e) => _isFormActivated = false; _form.Activated += (sender, e) => _isFormActivated = true; //_form.FormClosed += (sender, e) => // TODO. #endregion // Video sink and source to generate and consume VP8 video streams. var testPattern = new VideoTestPatternSource(new VpxVideoEncoder()); var vp8VideoSink = new VideoEncoderEndPoint(); #region Connect the video frames generated from the sink and source to the Windows form. testPattern.OnVideoSourceRawSample += (uint durationMilliseconds, int width, int height, byte[] sample, VideoPixelFormatsEnum pixelFormat) => { if (_isFormActivated) { _form?.BeginInvoke(new Action(() => { if (_form.Handle != IntPtr.Zero) { unsafe { fixed(byte *s = sample) { var bmpImage = new Bitmap(width, height, width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, (IntPtr)s); _sourceVideoPicBox.Image = bmpImage; } } } })); } }; vp8VideoSink.OnVideoSinkDecodedSample += (byte[] bmp, uint width, uint height, int stride, VideoPixelFormatsEnum pixelFormat) => { if (_isFormActivated) { _form?.BeginInvoke(new Action(() => { if (_form.Handle != IntPtr.Zero) { unsafe { fixed(byte *s = bmp) { var bmpImage = new Bitmap((int)width, (int)height, stride, PixelFormat.Format24bppRgb, (IntPtr)s); _echoVideoPicBox.Image = bmpImage; } } } })); } }; #endregion await testPattern.StartVideo().ConfigureAwait(false); var pc = await CreatePeerConnection(testPattern, vp8VideoSink).ConfigureAwait(false); Console.WriteLine($"Sending offer to {SIGNALING_SERVER}."); var signaler = new HttpClient(); var offer = pc.createOffer(null); await pc.setLocalDescription(offer).ConfigureAwait(false); var content = new StringContent(offer.toJSON(), Encoding.UTF8, "application/json"); var response = await signaler.PostAsync($"{SIGNALING_SERVER}", content).ConfigureAwait(false); var answerStr = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (RTCSessionDescriptionInit.TryParse(answerStr, out var answerInit)) { var setAnswerResult = pc.setRemoteDescription(answerInit); if (setAnswerResult != SetDescriptionResultEnum.OK) { Console.WriteLine($"Set remote description failed {setAnswerResult}."); } } else { Console.WriteLine("Failed to parse SDP answer from signaling server."); } Console.WriteLine("Press any key to exit."); Console.ReadLine(); }
static async Task <int> Main(string[] args) { Console.WriteLine("Starting webrtc echo test client."); string echoServerUrl = DEFAULT_ECHO_SERVER_URL; if (args?.Length > 0) { echoServerUrl = args[0]; } logger = AddConsoleLogger(LogEventLevel.Verbose); var pc = CreatePeerConnection(); var offer = pc.createOffer(null); await pc.setLocalDescription(offer); bool didConnect = false; TaskCompletionSource <int> connectResult = new TaskCompletionSource <int>(); pc.onconnectionstatechange += (state) => { logger.LogInformation($"Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed) { pc.Close("remote disconnection"); } else if (state == RTCPeerConnectionState.connected) { didConnect = true; pc.Close("normal"); } else if (state == RTCPeerConnectionState.closed) { connectResult.SetResult(didConnect ? SUCCESS_RESULT : FAILURE_RESULT); } }; logger.LogInformation($"Posting offer to {echoServerUrl}."); var httpClient = new HttpClient(); var content = new StringContent(offer.toJSON(), Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(echoServerUrl, content); var answerStr = await response.Content.ReadAsStringAsync(); if (RTCSessionDescriptionInit.TryParse(answerStr, out var answerInit)) { var setAnswerResult = pc.setRemoteDescription(answerInit); if (setAnswerResult != SetDescriptionResultEnum.OK) { logger.LogWarning($"Set remote description failed {setAnswerResult}."); } } else { logger.LogWarning("Failed to parse SDP answer from echo server."); } var result = await connectResult.Task; logger.LogInformation($"Connection result {result}."); return(result); }
private static async Task RunCommand(Options options, bool noOptions) { // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. //ManualResetEvent exitMre = new ManualResetEvent(false); logger = AddConsoleLogger(); // Start MDNS server. var mdnsServer = new ServiceDiscovery(); if (options.StunServer != null) { string[] fields = options.StunServer.Split(';'); _stunServer = new RTCIceServer { urls = fields[0], username = fields.Length > 1 ? fields[1] : null, credential = fields.Length > 2 ? fields[2] : null, credentialType = RTCIceCredentialType.password }; } _relayOnly = options.RelayOnly; if (!string.IsNullOrEmpty(options.IceTypes)) { options.IceTypes.Split().ToList().ForEach(x => { if (Enum.TryParse <RTCIceCandidateType>(x, out var iceType)) { _iceTypes.Add(iceType); } }); if (!_iceTypes.Any(x => x == RTCIceCandidateType.host)) { _offerOptions = new RTCOfferOptions { X_ExcludeIceCandidates = true }; _answerOptions = new RTCAnswerOptions { X_ExcludeIceCandidates = true }; } } if (!string.IsNullOrEmpty(options.AcceptIceTypes)) { options.AcceptIceTypes.Split().ToList().ForEach(x => { if (Enum.TryParse <RTCIceCandidateType>(x, out var iceType)) { _acceptIceTypes.Add(iceType); } }); } if (options.UseWebSocket || options.UseSecureWebSocket || noOptions) { // Start web socket. Console.WriteLine("Starting web socket server..."); _webSocketServer = new WebSocketServer(IPAddress.Any, WEBSOCKET_PORT, options.UseSecureWebSocket); if (options.UseSecureWebSocket) { _webSocketServer.SslConfiguration.ServerCertificate = new X509Certificate2(LOCALHOST_CERTIFICATE_PATH); _webSocketServer.SslConfiguration.CheckCertificateRevocation = false; } _webSocketServer.AddWebSocketService <WebRTCWebSocketPeer>("/", (peer) => { peer.OfferOptions = _offerOptions; if (_acceptIceTypes != null && _acceptIceTypes.Count > 0) { peer.FilterRemoteICECandidates = (init) => _acceptIceTypes.Any(x => x == RTCIceCandidate.Parse(init.candidate).type); } peer.CreatePeerConnection = CreatePeerConnection; }); _webSocketServer.Start(); Console.WriteLine($"Waiting for browser web socket connection to {_webSocketServer.Address}:{_webSocketServer.Port}..."); } else if (!string.IsNullOrWhiteSpace(options.WebSocketServer)) { // We are the client for a web socket server. The JSON signalling exchange still occurs the same way as when the web socket // server option is used except that as the web socket client we receive the SDP offer from the server. WebRTCWebSocketClient wsockClient = new WebRTCWebSocketClient(options.WebSocketServer, CreatePeerConnection); await wsockClient.Start(exitCts.Token); Console.WriteLine("web socket client started."); } else if (options.CreateJsonOffer) { var pc = await Createpc(null, _stunServer, _relayOnly); var offerSdp = pc.createOffer(null); await pc.setLocalDescription(offerSdp); Console.WriteLine(offerSdp.sdp); var offerJson = JsonConvert.SerializeObject(offerSdp, new Newtonsoft.Json.Converters.StringEnumConverter()); var offerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(offerJson)); Console.WriteLine(offerBase64); string remoteAnswerB64 = null; while (string.IsNullOrWhiteSpace(remoteAnswerB64)) { Console.Write("Remote Answer => "); remoteAnswerB64 = Console.ReadLine(); } string remoteAnswer = Encoding.UTF8.GetString(Convert.FromBase64String(remoteAnswerB64)); Console.WriteLine(remoteAnswer); RTCSessionDescriptionInit answerInit = JsonConvert.DeserializeObject <RTCSessionDescriptionInit>(remoteAnswer); Console.WriteLine($"Remote answer: {answerInit.sdp}"); var res = pc.setRemoteDescription(answerInit); if (res != SetDescriptionResultEnum.OK) { // No point continuing. Something will need to change and then try again. pc.Close("failed to set remote sdp"); } } else if (options.RestServer != null) { string[] fields = options.RestServer.Split(';'); if (fields.Length < 3) { throw new ArgumentException("The 'rest' option must contain 3 semi-colon separated fields, e.g. --rest=https://localhost:5001/api/webrtcsignal;myid;theirid."); } var webrtcRestPeer = new WebRTCRestSignalingPeer(fields[0], fields[1], fields[2], CreatePeerConnection); webrtcRestPeer.OfferOptions = _offerOptions; webrtcRestPeer.AnswerOptions = _answerOptions; if (_acceptIceTypes != null && _acceptIceTypes.Count > 0) { webrtcRestPeer.FilterRemoteICECandidates = (init) => _acceptIceTypes.Any(x => x == RTCIceCandidate.Parse(init.candidate).type); } await webrtcRestPeer.Start(exitCts); } else if (options.EchoServer != null) { // Create offer and send to echo server. var pc = await Createpc(null, _stunServer, _relayOnly); var signaler = new HttpClient(); var offer = pc.createOffer(null); await pc.setLocalDescription(offer); var content = new StringContent(offer.toJSON(), Encoding.UTF8, "application/json"); var response = await signaler.PostAsync($"{options.EchoServer}", content); var answerStr = await response.Content.ReadAsStringAsync(); if (RTCSessionDescriptionInit.TryParse(answerStr, out var answerInit)) { var setAnswerResult = pc.setRemoteDescription(answerInit); if (setAnswerResult != SetDescriptionResultEnum.OK) { Console.WriteLine($"Set remote description failed {setAnswerResult}."); } } else { Console.WriteLine("Failed to parse SDP answer from echo server."); } } _ = Task.Run(() => ProcessInput(exitCts)); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); Console.WriteLine(); Console.WriteLine("Exiting..."); _peerConnection?.Close("application exit"); _webSocketServer?.Stop(); Task.Delay(1000).Wait(); }
static async Task <int> Main(string[] args) { Console.WriteLine("Starting webrtc echo test client."); WebRTCTestTypes testType = WebRTCTestTypes.PeerConnection; string echoServerUrl = Options.DEFAULT_ECHO_SERVER_URL; LogEventLevel verbosity = Options.DEFAULT_VERBOSITY; int pcTimeout = Options.TEST_TIMEOUT_SECONDS; if (args?.Length == 1) { echoServerUrl = args[0]; } else if (args?.Length > 1) { Options opts = null; var parseResult = Parser.Default.ParseArguments <Options>(args) .WithParsed(o => opts = o); testType = opts != null ? opts.TestType : testType; echoServerUrl = opts != null && !string.IsNullOrEmpty(opts.ServerUrl) ? opts.ServerUrl : echoServerUrl; verbosity = opts != null ? opts.Verbosity : verbosity; pcTimeout = opts != null ? opts.TestTimeoutSeconds : pcTimeout; } logger = AddConsoleLogger(verbosity); logger.LogDebug($"Performing test {testType} to {echoServerUrl}."); var pc = await CreatePeerConnection(); var offer = pc.createOffer(); await pc.setLocalDescription(offer); bool success = false; ManualResetEventSlim testComplete = new ManualResetEventSlim(); var dc = pc.DataChannels.FirstOrDefault(); if (dc != null) { var pseudo = Crypto.GetRandomString(5); dc.onopen += () => { logger.LogInformation($"Data channel {dc.label}, stream ID {dc.id} opened."); dc.send(pseudo); }; dc.onmessage += (dc, proto, data) => { string echoMsg = Encoding.UTF8.GetString(data); logger.LogDebug($"data channel onmessage {proto}: {echoMsg}."); if (echoMsg == pseudo) { logger.LogInformation($"Data channel echo test success."); if (testType == WebRTCTestTypes.DataChannelEcho) { success = true; testComplete.Set(); } } else { logger.LogWarning($"Data channel echo test failed, echoed message of {echoMsg} did not match original of {pseudo}."); } }; } pc.onconnectionstatechange += (state) => { logger.LogInformation($"Peer connection state changed to {state}."); if (state == RTCPeerConnectionState.disconnected || state == RTCPeerConnectionState.failed) { pc.Close("remote disconnection"); } else if (state == RTCPeerConnectionState.connected && testType == WebRTCTestTypes.PeerConnection) { success = true; testComplete.Set(); } }; logger.LogInformation($"Posting offer to {echoServerUrl}."); var httpClient = new HttpClient(); var content = new StringContent(offer.toJSON(), Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync(echoServerUrl, content); var answerStr = await response.Content.ReadAsStringAsync(); if (RTCSessionDescriptionInit.TryParse(answerStr, out var answerInit)) { logger.LogDebug($"SDP answer: {answerInit.sdp}"); var setAnswerResult = pc.setRemoteDescription(answerInit); if (setAnswerResult != SetDescriptionResultEnum.OK) { logger.LogWarning($"Set remote description failed {setAnswerResult}."); } } else { logger.LogWarning($"Failed to parse SDP answer from echo server: {answerStr}."); } pc.OnClosed += testComplete.Set; logger.LogDebug($"Test timeout is {pcTimeout} seconds."); testComplete.Wait(pcTimeout * 1000); if (!pc.IsClosed) { pc.close(); await Task.Delay(1000); } logger.LogInformation($"{testType} test success {success }."); return((success) ? SUCCESS_RESULT : FAILURE_RESULT); }