static void Main(string[] args) { Console.WriteLine("SIPSorcery client user agent example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource rtpCts = new CancellationTokenSource(); // Cancellation token to stop the RTP stream. bool isCallHungup = false; bool hasCallFailed = false; // Logging configuration. Can be ommitted if internal SIPSorcery debug and warning messages are not required. var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory(); var loggerConfig = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug) .WriteTo.Console() .CreateLogger(); loggerFactory.AddSerilog(loggerConfig); SIPSorcery.Sys.Log.LoggerFactory = loggerFactory; SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI); if (args != null && args.Length > 0) { if (!SIPURI.TryParse(args[0])) { Log.LogWarning($"Command line argument could not be parsed as a SIP URI {args[0]}"); } else { callUri = SIPURI.ParseSIPURIRelaxed(args[0]); } } Log.LogInformation($"Call destination {callUri}."); // Set up a default SIP transport. var sipTransport = new SIPTransport(); int port = SIPConstants.DEFAULT_SIP_PORT + 1000; IPAddress localAddress = sipTransport.GetLocalAddress(IPAddress.Parse("8.8.8.8")); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(localAddress, port))); //sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, port))); //sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.IPv6Any, port))); //EnableTraceLogs(sipTransport); // Select the IP address to use for RTP based on the destination SIP URI. var endPointForCall = callUri.ToSIPEndPoint() == null?sipTransport.GetDefaultSIPEndPoint(callUri.Protocol) : sipTransport.GetDefaultSIPEndPoint(callUri.ToSIPEndPoint()); // Initialise an RTP session to receive the RTP packets from the remote SIP server. Socket rtpSocket = null; Socket controlSocket = null; // TODO (find something better): If the SIP endpoint is using 0.0.0.0 for SIP use loopback for RTP. IPAddress rtpAddress = localAddress; NetServices.CreateRtpSocket(rtpAddress, 49000, 49100, false, out rtpSocket, out controlSocket); var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null); // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var uac = new SIPClientUserAgent(sipTransport); uac.CallTrying += (uac, resp) => { Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}."); }; uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}."); uac.CallFailed += (uac, err) => { Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}"); hasCallFailed = true; }; uac.CallAnswered += (uac, resp) => { if (resp.Status == SIPResponseStatusCodesEnum.Ok) { Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); IPEndPoint remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body); Log.LogDebug($"Sending initial RTP packet to remote RTP socket {remoteRtpEndPoint}."); // Send a dummy packet to open the NAT session on the RTP path. rtpSendSession.SendAudioFrame(rtpSocket, remoteRtpEndPoint, 0, new byte[] { 0x00 }); } else { Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); } }; // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call. sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) => { if (sipRequest.Method == SIPMethodsEnum.BYE) { SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null); SIPResponse byeResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); byeTransaction.SendFinalResponse(byeResponse); if (uac.IsUACAnswered) { Log.LogInformation("Call was hungup by remote server."); isCallHungup = true; rtpCts.Cancel(); } } }; // It's a good idea to start the RTP receiving socket before the call request is sent. // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and // being ready to receive will stop any ICMP error response being generated. Task.Run(() => SendRecvRtp(rtpSocket, rtpSendSession, rtpCts)); // Start the thread that places the call. SIPCallDescriptor callDescriptor = new SIPCallDescriptor( SIPConstants.SIP_DEFAULT_USERNAME, null, callUri.ToString(), SIPConstants.SIP_DEFAULT_FROMURI, null, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, GetSDP(rtpSocket.LocalEndPoint as IPEndPoint).ToString(), null); uac.Call(callDescriptor); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; rtpCts.Cancel(); }; // At this point the call has been initiated and everything will be handled in an event handler or on the RTP // receive task. The code below is to gracefully exit. // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. rtpCts.Token.WaitHandle.WaitOne(); Log.LogInformation("Exiting..."); rtpSocket?.Close(); controlSocket?.Close(); if (!isCallHungup && uac != null) { if (uac.IsUACAnswered) { Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}."); uac.Hangup(); } else if (!hasCallFailed) { Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}."); uac.Cancel(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } SIPSorcery.Net.DNSManager.Stop(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } }