/// <summary> /// Places an outgoing SIP call. /// </summary> /// <param name="destination">The SIP URI to place a call to. The destination can be a full SIP URI in which case the all will /// be placed anonymously directly to that URI. Alternatively it can be just the user portion of a URI in which case it will /// be sent to the configured SIP server.</param> public async Task Call(string destination) { // Determine if this is a direct anonymous call or whether it should be placed using the pre-configured SIP server account. SIPURI callURI = null; string sipUsername = null; string sipPassword = null; string fromHeader = null; if (destination.Contains("@") || m_sipServer == null) { // Anonymous call direct to SIP server specified in the URI. callURI = SIPURI.ParseSIPURIRelaxed(destination); fromHeader = (new SIPFromHeader(m_sipFromName, SIPURI.ParseSIPURI(SIPFromHeader.DEFAULT_FROM_URI), null)).ToString(); } else { // This call will use the pre-configured SIP account. callURI = SIPURI.ParseSIPURIRelaxed(destination + "@" + m_sipServer); sipUsername = m_sipUsername; sipPassword = m_sipPassword; fromHeader = (new SIPFromHeader(m_sipFromName, new SIPURI(m_sipUsername, m_sipServer, null), null)).ToString(); } StatusMessage(this, $"Starting call to {callURI}."); var lookupResult = await Task.Run(() => { return(SIPDNSManager.ResolveSIPService(callURI, false)); }); if (lookupResult == null || lookupResult.LookupError != null) { StatusMessage(this, $"Call failed, could not resolve {callURI}."); } else { var dstEndpoint = lookupResult.GetSIPEndPoint(); StatusMessage(this, $"Call progressing, resolved {callURI} to {dstEndpoint}."); System.Diagnostics.Debug.WriteLine($"DNS lookup result for {callURI}: {dstEndpoint}."); SIPCallDescriptor callDescriptor = new SIPCallDescriptor(sipUsername, sipPassword, callURI.ToString(), fromHeader, null, null, null, null, SIPCallDirection.Out, _sdpMimeContentType, null, null); var audioSrcOpts = new AudioOptions { AudioSource = AudioSourcesEnum.Microphone, OutputDeviceIndex = m_audioOutDeviceIndex }; var videoSrcOpts = new VideoOptions { VideoSource = VideoSourcesEnum.TestPattern, SourceFile = RtpAVSession.VIDEO_TESTPATTERN, SourceFramesPerSecond = VIDEO_LIVE_FRAMES_PER_SECOND }; MediaSession = new RtpAVSession(audioSrcOpts, videoSrcOpts); m_userAgent.RemotePutOnHold += OnRemotePutOnHold; m_userAgent.RemoteTookOffHold += OnRemoteTookOffHold; await m_userAgent.InitiateCallAsync(callDescriptor, MediaSession); } }
/// <summary> /// Parses the destination command line option into: /// - A SIPEndPoint, which is an IP end point and transport (udp, tcp or tls), /// - A SIP URI. /// The SIPEndPoint determines the remote network destination to send the request to. /// The SIP URI is the URI that will be set on the request. /// </summary> /// <param name="dstn">The destination string to parse.</param> /// <returns>The SIPEndPoint and SIPURI parsed from the destination string.</returns> private static (SIPEndPoint, SIPURI) ParseDestination(string dst) { var dstEp = SIPEndPoint.ParseSIPEndPoint(dst); SIPURI dstUri = null; // Don't attempt a SIP URI parse for serialised SIPEndPoints. if (Regex.IsMatch(dst, "^(udp|tcp|tls|ws|wss)") == false && SIPURI.TryParse(dst, out var argUri)) { dstUri = argUri; } else { dstUri = new SIPURI(dstEp.Scheme, dstEp); } if (dstEp == null) { logger.LogDebug($"Could not extract IP end point from destination host of {dstUri.Host}."); var result = SIPDNSManager.ResolveSIPService(dstUri, false); if (result != null) { logger.LogDebug($"Resolved SIP URI {dstUri} to {result.GetSIPEndPoint()}."); dstEp = result.GetSIPEndPoint(); } } return(dstEp, dstUri); }
public void ResolveSIPServiceTest() { try { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); SIPDNSManager.UseNAPTRLookups = true; var result = SIPDNSManager.ResolveSIPService(SIPURI.ParseSIPURIRelaxed("sip:reg.sip-trunk.telekom.de;transport=tcp"), false); SIPEndPoint resultEP = result.GetSIPEndPoint(); Assert.NotNull(resultEP); logger.LogDebug($"resolved to SIP end point {resultEP}"); Assert.NotEmpty(result.SIPNAPTRResults); Assert.NotEmpty(result.SIPSRVResults); Assert.NotEmpty(result.EndPointResults); result = SIPDNSManager.ResolveSIPService(SIPURI.ParseSIPURIRelaxed("sip:tel.t-online.de"), false); Assert.NotNull(resultEP); result = SIPDNSManager.ResolveSIPService(SIPURI.ParseSIPURIRelaxed("sips:hpbxsec.deutschland-lan.de:5061;transport=tls"), false); Assert.NotNull(resultEP); } finally { SIPDNSManager.UseNAPTRLookups = false; } }
public SIPDNSLookupResult Resolve(SIPRequest sipRequest) { if (sipRequest.Header.Routes != null && sipRequest.Header.Routes.Length > 0 && !sipRequest.Header.Routes.TopRoute.IsStrictRouter) { return(SIPDNSManager.ResolveSIPService(sipRequest.Header.Routes.TopRoute.URI, true)); } else { return(SIPDNSManager.ResolveSIPService(sipRequest.URI, true)); } }
public void ResolveHostFromServiceTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); var result = SIPDNSManager.ResolveSIPService(SIPURI.ParseSIPURIRelaxed("sipsorcery.com"), false); SIPEndPoint resultEP = result.GetSIPEndPoint(); Assert.NotNull(resultEP); logger.LogDebug($"resolved to SIP end point {resultEP}"); }
public async Task ResolveSIPServiceAsyncTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); var result = await SIPDNSManager.ResolveAsync(SIPURI.ParseSIPURIRelaxed("sip:reg.sip-trunk.telekom.de;transport=tcp")); SIPEndPoint resultEP = result.GetSIPEndPoint(); Assert.NotNull(resultEP); logger.LogDebug($"resolved to SIP end point {resultEP}"); }
public void LookupLocalHostnameTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); string hostname = System.Net.Dns.GetHostName(); var result = SIPDNSManager.ResolveSIPService(SIPURI.ParseSIPURIRelaxed(hostname), false); SIPEndPoint resultEP = result.GetSIPEndPoint(); Assert.NotNull(resultEP); logger.LogDebug($"resolved to SIP end point {resultEP}"); }
/// <summary> /// Places an outgoing SIP call. /// </summary> /// <param name="destination">The SIP URI to place a call to. The destination can be a full SIP URI in which case the all will /// be placed anonymously directly to that URI. Alternatively it can be just the user portion of a URI in which case it will /// be sent to the configured SIP server.</param> public async Task Call(string destination) { // Determine if this is a direct anonymous call or whether it should be placed using the pre-configured SIP server account. SIPURI callURI = null; string sipUsername = null; string sipPassword = null; string fromHeader = null; if (destination.Contains("@") || m_sipServer == null) { // Anonymous call direct to SIP server specified in the URI. callURI = SIPURI.ParseSIPURIRelaxed(destination); fromHeader = (new SIPFromHeader(m_sipFromName, SIPURI.ParseSIPURI(SIPFromHeader.DEFAULT_FROM_URI), null)).ToString(); } else { // This call will use the pre-configured SIP account. callURI = SIPURI.ParseSIPURIRelaxed(destination + "@" + m_sipServer); sipUsername = m_sipUsername; sipPassword = m_sipPassword; fromHeader = (new SIPFromHeader(m_sipFromName, new SIPURI(m_sipUsername, m_sipServer, null), null)).ToString(); } StatusMessage(this, $"Starting call to {callURI}."); var lookupResult = await Task.Run(() => { return(SIPDNSManager.ResolveSIPService(callURI, false)); }); if (lookupResult == null || lookupResult.LookupError != null) { StatusMessage(this, $"Call failed, could not resolve {callURI}."); } else { var dstEndpoint = lookupResult.GetSIPEndPoint(); StatusMessage(this, $"Call progressing, resolved {callURI} to {dstEndpoint}."); System.Diagnostics.Debug.WriteLine($"DNS lookup result for {callURI}: {dstEndpoint}."); SIPCallDescriptor callDescriptor = new SIPCallDescriptor(sipUsername, sipPassword, callURI.ToString(), fromHeader, null, null, null, null, SIPCallDirection.Out, _sdpMimeContentType, null, null); m_rtpMediaSessionManager.Create(dstEndpoint.Address.AddressFamily); m_rtpMediaSessionManager.RTPMediaSession.RemotePutOnHold += OnRemotePutOnHold; m_rtpMediaSessionManager.RTPMediaSession.RemoteTookOffHold += OnRemoteTookOffHold; await m_userAgent.InitiateCall(callDescriptor, m_rtpMediaSessionManager.RTPMediaSession); } }
public void ResolveSIPServiceFromCacheTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); SIPURI lookupURI = SIPURI.ParseSIPURIRelaxed("sip:tel.t-online.de"); var result = SIPDNSManager.ResolveSIPService(lookupURI, false); Assert.NotNull(result); SIPEndPoint resultEP = result.GetSIPEndPoint(); Assert.NotNull(resultEP); logger.LogDebug($"resolved to SIP end point {resultEP}"); Assert.NotEmpty(result.SIPSRVResults); Assert.NotEmpty(result.EndPointResults); // Do the same look up again immediately to check the result when it comes from the in-memory cache. var resultCache = SIPDNSManager.ResolveSIPService(lookupURI, false); Assert.NotNull(resultCache); Assert.NotNull(resultCache.GetSIPEndPoint()); logger.LogDebug($"cache resolved to SIP end point {resultCache.GetSIPEndPoint()}"); }
public void LookupLocalHostnameTest() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); string hostname = System.Net.Dns.GetHostName(); if (hostname.EndsWith(SIPDNSManager.MDNS_TLD)) { // TODO: Look into why DNS calls on macos cannot resolve domains ending in ".local" // RFC6762 domains. logger.LogWarning("Skipping unit test LookupLocalHostnameTest due to RFC6762 domain."); } else { var result = SIPDNSManager.ResolveSIPService(SIPURI.ParseSIPURIRelaxed(hostname), false); SIPEndPoint resultEP = result.GetSIPEndPoint(); Assert.NotNull(resultEP); logger.LogDebug($"resolved to SIP end point {resultEP}"); } }
static void Main(string[] args) { Console.WriteLine("SIPSorcery call hold example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. bool isCallHungup = false; bool hasCallFailed = false; AddConsoleLogger(); // Check whether an override desination has been entered on the command line. 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. _sipTransport = new SIPTransport(); _sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, 0))); EnableTraceLogs(_sipTransport); var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false); Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}."); var dstAddress = lookupResult.GetSIPEndPoint().Address; IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(dstAddress); // Initialise an RTP session to receive the RTP packets from the remote SIP server. _ourRtpSocket = null; Socket controlSocket = null; NetServices.CreateRtpSocket(localIPAddress, 48000, 48100, false, out _ourRtpSocket, out controlSocket); var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null); var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null); _ourSDP = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDRECV); // Create a client/server user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var userAgent = new SIPUserAgent(_sipTransport, null); userAgent.ClientCallTrying += (uac, resp) => { Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}."); }; userAgent.ClientCallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}."); userAgent.ClientCallFailed += (uac, err) => { Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}"); hasCallFailed = true; exitCts.Cancel(); }; userAgent.ClientCallAnswered += (uac, resp) => { if (resp.Status == SIPResponseStatusCodesEnum.Ok) { Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); // Only set the remote RTP end point if there hasn't already been a packet received on it. if (_remoteRtpEndPoint == null) { _remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body); Log.LogDebug($"Remote RTP socket {_remoteRtpEndPoint}."); } } else { Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); } }; userAgent.CallHungup += () => { Log.LogInformation($"Call hungup by remote party."); exitCts.Cancel(); }; userAgent.OnReinviteRequest += ReinviteRequestReceived; // The only incoming requests that need to be explicitly in this example program are in-dialog // re-INVITE requests that are being used to place the call on/off hold. _sipTransport.SIPTransportRequestReceived += (localSIPEndPoint, remoteEndPoint, sipRequest) => { try { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { userAgent.InDialogRequestReceivedAsync(sipRequest).Wait(); } else if (sipRequest.Method == SIPMethodsEnum.OPTIONS) { SIPResponse optionsResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); _sipTransport.SendResponse(optionsResponse); } } catch (Exception excp) { Log.LogError($"Exception processing request. {excp.Message}"); } }; // 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(() => RecvRtp(_ourRtpSocket, rtpRecvSession, exitCts)); Task.Run(() => SendRtp(_ourRtpSocket, rtpSendSession, exitCts)); // Start the thread that places the call. SIPCallDescriptor callDescriptor = new SIPCallDescriptor( SIP_USERNAME, SIP_PASSWORD, callUri.ToString(), $"sip:{SIP_USERNAME}@localhost", callUri.CanonicalAddress, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, _ourSDP.ToString(), null); userAgent.Call(callDescriptor); // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'h') { // Place call on/off hold. if (userAgent.IsAnswered) { if (_holdStatus == HoldStatus.None) { Log.LogInformation("Placing the remote call party on hold."); _holdStatus = HoldStatus.WePutOnHold; _ourSDP = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDONLY); userAgent.SendReInviteRequest(_ourSDP); } else if (_holdStatus == HoldStatus.WePutOnHold) { Log.LogInformation("Removing the remote call party from hold."); _holdStatus = HoldStatus.None; _ourSDP = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDRECV); userAgent.SendReInviteRequest(_ourSDP); } else { Log.LogInformation("Sorry we're already on hold by the remote call party."); } } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // 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(); #region Cleanup. Log.LogInformation("Exiting..."); _ourRtpSocket?.Close(); controlSocket?.Close(); if (!isCallHungup && userAgent != null) { if (userAgent.IsAnswered) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } else if (!hasCallFailed) { Log.LogInformation($"Cancelling call to {userAgent?.CallDescriptor?.To}."); userAgent.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(); } #endregion }
static async Task Main(string[] args) { Console.WriteLine("SIPSorcery client user agent example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. ManualResetEvent exitMre = new ManualResetEvent(false); bool isCallHungup = false; bool hasCallFailed = false; AddConsoleLogger(); SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI); if (args != null && args.Length > 0) { if (!SIPURI.TryParse(args[0], out callUri)) { Log.LogWarning($"Command line argument could not be parsed as a SIP URI {args[0]}"); } } Log.LogInformation($"Call destination {callUri}."); // Set up a default SIP transport. var sipTransport = new SIPTransport(); EnableTraceLogs(sipTransport); // Get the IP address the RTP will be sent from. While we can listen on IPAddress.Any | IPv6Any // we can't put 0.0.0.0 or [::0] in the SDP or the callee will treat our RTP stream as inactive. var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false); Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}."); var dstAddress = lookupResult.GetSIPEndPoint().Address; // Initialise an RTP session to receive the RTP packets from the remote SIP server. var rtpSession = new RtpAVSession(dstAddress.AddressFamily, new AudioOptions { AudioSource = AudioSourcesEnum.Microphone }, null); var offerSDP = await rtpSession.createOffer(new RTCOfferOptions { RemoteSignallingAddress = dstAddress }); // 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}."); rtpSession.setRemoteDescription(new RTCSessionDescription { type = RTCSdpType.answer, sdp = SDP.ParseSDPDescription(resp.Body) }); rtpSession.Start(); } 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 += async(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) => { if (sipRequest.Method == SIPMethodsEnum.BYE) { SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await sipTransport.SendResponseAsync(okResponse); if (uac.IsUACAnswered) { Log.LogInformation("Call was hungup by remote server."); isCallHungup = true; exitMre.Set(); } } }; // Start the thread that places the call. SIPCallDescriptor callDescriptor = new SIPCallDescriptor( SIPConstants.SIP_DEFAULT_USERNAME, null, callUri.ToString(), SIPConstants.SIP_DEFAULT_FROMURI, callUri.CanonicalAddress, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, offerSDP.ToString(), null); uac.Call(callDescriptor); uac.ServerTransaction.TransactionTraceMessage += (tx, msg) => Log.LogInformation($"UAC tx trace message. {msg}"); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitMre.Set(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitMre.WaitOne(); Log.LogInformation("Exiting..."); rtpSession.CloseSession(null); 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(); } }
public SIPDNSLookupResult Resolve(SIPURI sipURI) { return(SIPDNSManager.ResolveSIPService(sipURI, true)); }
static void Main() { Console.WriteLine("SIPSorcery call hold example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP trnasport and RTP stream. bool isCallHungup = false; bool hasCallFailed = false; AddConsoleLogger(); SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI); Log.LogInformation($"Call destination {callUri}."); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, 0))); EnableTraceLogs(sipTransport); var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false); Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}."); var dstAddress = lookupResult.GetSIPEndPoint().Address; IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(dstAddress); // Initialise an RTP session to receive the RTP packets from the remote SIP server. Socket rtpSocket = null; Socket controlSocket = null; NetServices.CreateRtpSocket(localIPAddress, 48000, 48100, false, out rtpSocket, out controlSocket); var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null); 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}."); // Only set the remote RTP end point if there hasn't already been a packet received on it. if (_remoteRtpEndPoint == null) { _remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body); Log.LogDebug($"Remote RTP socket {_remoteRtpEndPoint}."); } } 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, 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; exitCts.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(() => RecvRtp(rtpSocket, rtpRecvSession, exitCts)); Task.Run(() => SendRtp(rtpSocket, rtpSendSession, exitCts)); // Start the thread that places the call. SIPCallDescriptor callDescriptor = new SIPCallDescriptor( SIP_USERNAME, SIP_PASSWORD, callUri.ToString(), $"sip:{SIP_USERNAME}@localhost", callUri.CanonicalAddress, 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; exitCts.Cancel(); }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'h') { } else if (keyProps.KeyChar == 'q') { Console.WriteLine(); Console.WriteLine("Hangup requested by user..."); uac.Hangup(); exitCts.Cancel(); rtpSocket?.Close(); controlSocket?.Close(); SIPSorcery.Sys.Log.Logger.LogInformation("Quitting..."); if (sipTransport != null) { SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.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(); } }
private static int INPUT_SAMPLE_PERIOD_MILLISECONDS = 20; // This sets the frequency of the RTP packets. 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. ManualResetEvent exitMre = new ManualResetEvent(false); bool isCallHungup = false; bool hasCallFailed = false; AddConsoleLogger(); SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI); if (args != null && args.Length > 0) { if (!SIPURI.TryParse(args[0], out callUri)) { Log.LogWarning($"Command line argument could not be parsed as a SIP URI {args[0]}"); } } Log.LogInformation($"Call destination {callUri}."); // Set up a default SIP transport. var sipTransport = new SIPTransport(); EnableTraceLogs(sipTransport); // Get the IP address the RTP will be sent from. While we can listen on IPAddress.Any | IPv6Any // we can't put 0.0.0.0 or [::0] in the SDP or the callee will ignore us. var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false); Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}."); var dstAddress = lookupResult.GetSIPEndPoint().Address; IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(dstAddress); // Initialise an RTP session to receive the RTP packets from the remote SIP server. var rtpSession = new RTPMediaSession((int)SDPMediaFormatsEnum.PCMU, localIPAddress.AddressFamily); var offerSDP = rtpSession.GetSDP(localIPAddress); // Get the audio input device. WaveInEvent waveInEvent = GetAudioInputDevice(); // 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}."); // Only set the remote RTP end point if there hasn't already been a packet received on it. if (rtpSession.DestinationEndPoint == null) { rtpSession.DestinationEndPoint = SDP.GetSDPRTPEndPoint(resp.Body); Log.LogDebug($"Remote RTP socket {rtpSession.DestinationEndPoint}."); } rtpSession.SetRemoteSDP(SDP.ParseSDPDescription(resp.Body)); waveInEvent.StartRecording(); } 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 += async(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) => { if (sipRequest.Method == SIPMethodsEnum.BYE) { SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await sipTransport.SendResponseAsync(okResponse); if (uac.IsUACAnswered) { Log.LogInformation("Call was hungup by remote server."); isCallHungup = true; exitMre.Set(); } } }; // Wire up the RTP receive session to the audio output device. var(audioOutEvent, audioOutProvider) = GetAudioOutputDevice(); rtpSession.OnReceivedSampleReady += (sample) => { for (int index = 0; index < sample.Length; index++) { short pcm = NAudio.Codecs.MuLawDecoder.MuLawToLinearSample(sample[index]); byte[] pcmSample = new byte[] { (byte)(pcm & 0xFF), (byte)(pcm >> 8) }; audioOutProvider.AddSamples(pcmSample, 0, 2); } }; // Wire up the RTP send session to the audio input device. uint rtpSendTimestamp = 0; waveInEvent.DataAvailable += (object sender, WaveInEventArgs args) => { byte[] sample = new byte[args.Buffer.Length / 2]; int sampleIndex = 0; for (int index = 0; index < args.BytesRecorded; index += 2) { var ulawByte = NAudio.Codecs.MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(args.Buffer, index)); sample[sampleIndex++] = ulawByte; } if (rtpSession.DestinationEndPoint != null) { rtpSession.SendAudioFrame(rtpSendTimestamp, sample); rtpSendTimestamp += (uint)(8000 / waveInEvent.BufferMilliseconds); } }; // Start the thread that places the call. SIPCallDescriptor callDescriptor = new SIPCallDescriptor( SIPConstants.SIP_DEFAULT_USERNAME, null, callUri.ToString(), SIPConstants.SIP_DEFAULT_FROMURI, callUri.CanonicalAddress, null, null, null, SIPCallDirection.Out, SDP.SDP_MIME_CONTENTTYPE, offerSDP.ToString(), null); uac.Call(callDescriptor); uac.ServerTransaction.TransactionTraceMessage += (tx, msg) => Log.LogInformation($"UAC tx trace message. {msg}"); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitMre.Set(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitMre.WaitOne(); Log.LogInformation("Exiting..."); waveInEvent?.StopRecording(); audioOutEvent?.Stop(); rtpSession.CloseSession(null); 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(); } }