Skip to content

Instantly share code, notes, and snippets.

@fredldotme
Created June 26, 2020 07:32
Show Gist options
  • Save fredldotme/9f33eaeb327f5d2e17c175ff7095281a to your computer and use it in GitHub Desktop.
Save fredldotme/9f33eaeb327f5d2e17c175ff7095281a to your computer and use it in GitHub Desktop.
//-----------------------------------------------------------------------------
// Filename: Program.cs
//
// Description: An abbreviated example program of how to use the SIPSorcery
// core library to place a SIP call. The example program depends on one audio
// input and one audio output being available.
//
// Author(s):
// Aaron Clauson (aaron@sipsorcery.com)
//
// History:
// 26 Oct 2019 Aaron Clauson Created, Dublin, Ireland.
// 26 Feb 2020 Aaron Clauson Switched RTP to use RtpAVSession.
//
// License:
// BSD 3-Clause "New" or "Revised" License, see included LICENSE.md file.
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Serilog;
using SIPSorcery.Media;
using SIPSorcery.Net;
using SIPSorcery.SIP;
using SIPSorcery.SIP.App;
using SIPSorcery.Sys;
namespace SIPSorcery
{
class Program
{
private static readonly string DEFAULT_DESTINATION_SIP_URI = "sip:time@sipsorcery.com"; // Talking Clock.
private static Microsoft.Extensions.Logging.ILogger Log = SIPSorcery.Sys.Log.Logger;
private static void OnRtpPacketReceived(SDPMediaTypesEnum mediaType, RTPPacket rtpPacket)
{
Console.WriteLine("OnRtpPacketReceived");
}
private const int DTMF_EVENT_PAYLOAD_ID = 101;
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 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;
var localOfferAddress = NetServices.GetLocalAddressForRemote(dstAddress);
// Initialise an RTP session to receive the RTP packets from the remote SIP server.
/*var audioOptions = new AudioOptions
{
AudioSource = AudioSourcesEnum.CaptureDevice,
AudioCodecs = new List<SDPMediaFormatsEnum> { SDPMediaFormatsEnum.PCMA, SDPMediaFormatsEnum.PCMU },
OutputDeviceIndex = AudioOptions.DEFAULT_OUTPUTDEVICE_INDEX
};*/
var pcmu = new SDPMediaFormat(SDPMediaFormatsEnum.PCMU);
var pcma = new SDPMediaFormat(SDPMediaFormatsEnum.PCMA);
int clockRate = pcmu.GetClockRate();
SDPMediaFormat rtpEventFormat = new SDPMediaFormat(DTMF_EVENT_PAYLOAD_ID);
rtpEventFormat.SetFormatAttribute($"{SDP.TELEPHONE_EVENT_ATTRIBUTE}/{clockRate}");
rtpEventFormat.SetFormatParameterAttribute("0-16");
var rtpSession = new RTPSession(/*isMediaMultiplexed*/ true, /*isRtcpMultiplexed*/ true, /*isSecure*/ false, /*bindAddress*/ null);
var audioCapabilities = new List<SDPMediaFormat> { pcmu, pcma, rtpEventFormat };
MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, audioCapabilities);
rtpSession.addTrack(audioTrack);
Console.WriteLine("Adding this mofucker");
rtpSession.OnRtpPacketReceived += OnRtpPacketReceived;
var offerSDP = rtpSession.CreateOffer(localOfferAddress);
// 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}.");
if (resp.Status == SIPResponseStatusCodesEnum.SessionProgress)
{
Console.WriteLine("rtpSession.Start();");
rtpSession.Start();
}
};
uac.CallFailed += (uac, err, resp) =>
{
Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
hasCallFailed = true;
};
uac.CallAnswered += (iuac, resp) =>
{
if (resp.Status == SIPResponseStatusCodesEnum.Ok)
{
Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
var result = rtpSession.SetRemoteDescription(SDP.ParseSDPDescription(resp.Body));
if(result == SetDescriptionResultEnum.OK)
{
rtpSession.Start();
}
else
{
Log.LogWarning($"Failed to set remote description {result}.");
uac.Hangup();
}
}
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 != null ? 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...");
Console.WriteLine("rtpSession.Close();");
rtpSession.Close(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();
}
}
/// <summary>
/// Adds a console logger. Can be omitted if internal SIPSorcery debug and warning messages are not required.
/// </summary>
private static void AddConsoleLogger()
{
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;
}
/// <summary>
/// Enable detailed SIP log messages.
/// </summary>
private static void EnableTraceLogs(SIPTransport sipTransport)
{
sipTransport.SIPRequestInTraceEvent += (localEP, remoteEP, req) =>
{
Log.LogDebug($"Request received: {localEP}<-{remoteEP}");
Log.LogDebug(req.ToString());
};
sipTransport.SIPRequestOutTraceEvent += (localEP, remoteEP, req) =>
{
Log.LogDebug($"Request sent: {localEP}->{remoteEP}");
Log.LogDebug(req.ToString());
};
sipTransport.SIPResponseInTraceEvent += (localEP, remoteEP, resp) =>
{
Log.LogDebug($"Response received: {localEP}<-{remoteEP}");
Log.LogDebug(resp.ToString());
};
sipTransport.SIPResponseOutTraceEvent += (localEP, remoteEP, resp) =>
{
Log.LogDebug($"Response sent: {localEP}->{remoteEP}");
Log.LogDebug(resp.ToString());
};
sipTransport.SIPRequestRetransmitTraceEvent += (tx, req, count) =>
{
Log.LogDebug($"Request retransmit {count} for request {req.StatusLine}, initial transmit {DateTime.Now.Subtract(tx.InitialTransmit).TotalSeconds.ToString("0.###")}s ago.");
};
sipTransport.SIPResponseRetransmitTraceEvent += (tx, resp, count) =>
{
Log.LogDebug($"Response retransmit {count} for response {resp.ShortDescription}, initial transmit {DateTime.Now.Subtract(tx.InitialTransmit).TotalSeconds.ToString("0.###")}s ago.");
};
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment