/// <summary> /// Sends the sounds of silence. If the destination is on the other side of a NAT this is useful to open /// a pinhole and hopefully get the remote RTP stream through. /// </summary> /// <param name="rtpSocket">The socket we're using to send from.</param> /// <param name="rtpSendSession">Our RTP sending session.</param> /// <param name="cts">Cancellation token to stop the call.</param> private static async void SendRtp(Socket rtpSocket, RTPSession rtpSendSession, CancellationTokenSource cts) { int samplingFrequency = RTPPayloadTypes.GetSamplingFrequency((RTPPayloadTypesEnum)rtpSendSession.PayloadType); uint rtpTimestampStep = (uint)(samplingFrequency * SILENCE_SAMPLE_PERIOD / 1000); uint bufferSize = (uint)SILENCE_SAMPLE_PERIOD; uint rtpSendTimestamp = 0; uint packetSentCount = 0; uint bytesSentCount = 0; while (cts.IsCancellationRequested == false) { if (_remoteRtpEndPoint != null) { if (!_dtmfEvents.IsEmpty) { // Check if there are any DTMF events to send. _dtmfEvents.TryDequeue(out var rtpEvent); if (rtpEvent != null) { await rtpSendSession.SendDtmfEvent(rtpSocket, _remoteRtpEndPoint, rtpEvent, rtpSendTimestamp, (ushort)SILENCE_SAMPLE_PERIOD, (ushort)rtpTimestampStep, cts); } rtpSendTimestamp += rtpEvent.TotalDuration + rtpTimestampStep; } else { // If there are no DTMF events to send we'll send silence. byte[] sample = new byte[bufferSize / 2]; int sampleIndex = 0; for (int index = 0; index < bufferSize; index += 2) { sample[sampleIndex] = PCMU_SILENCE_BYTE_ZERO; sample[sampleIndex + 1] = PCMU_SILENCE_BYTE_ONE; } rtpSendSession.SendAudioFrame(rtpSocket, _remoteRtpEndPoint, rtpSendTimestamp, sample); rtpSendTimestamp += rtpTimestampStep; packetSentCount++; bytesSentCount += (uint)sample.Length; } } await Task.Delay(SILENCE_SAMPLE_PERIOD); } }
static void Main() { 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; 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))); // Un/comment this line to see/hide each SIP message sent and received. EnableTraceLogs(sipTransport); // Note this relies on the callURI host being an IP address. If it's a hostname a DNS lookup is required. IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(callUri.ToSIPEndPoint().Address); // Initialise an RTP session to receive the RTP packets from the remote SIP server. var rtpSession = new RTPSession((int)SDPMediaFormatsEnum.PCMU, null, null, true, localIPAddress.AddressFamily); var offerSDP = rtpSession.GetSDP(localIPAddress); // 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.DestinationEndPoint = SDP.GetSDPRTPEndPoint(resp.Body); Log.LogDebug($"Remote RTP socket {rtpSession.DestinationEndPoint}."); } 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; rtpCts.Cancel(); } } }; // Wire up the RTP receive session to the default speaker. 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); } }; // Send audio packets (in this case silence) to the callee. Task.Run(() => SendSilence(rtpSession, 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, offerSDP.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(); }; // Give the call some time to answer. Task.Delay(3000).Wait(); // Send some DTMF key presses via RTP events. var dtmf5 = new RTPEvent(0x05, false, RTPEvent.DEFAULT_VOLUME, 1200, RTPSession.DTMF_EVENT_PAYLOAD_ID); rtpSession.SendDtmfEvent(dtmf5, rtpCts.Token).Wait(); Task.Delay(2000, rtpCts.Token).Wait(); var dtmf9 = new RTPEvent(0x09, false, RTPEvent.DEFAULT_VOLUME, 1200, RTPSession.DTMF_EVENT_PAYLOAD_ID); rtpSession.SendDtmfEvent(dtmf9, rtpCts.Token).Wait(); Task.Delay(2000, rtpCts.Token).Wait(); var dtmf2 = new RTPEvent(0x02, false, RTPEvent.DEFAULT_VOLUME, 1200, RTPSession.DTMF_EVENT_PAYLOAD_ID); rtpSession.SendDtmfEvent(dtmf2, rtpCts.Token).Wait(); Task.Delay(2000, rtpCts.Token).ContinueWith((task) => { }).Wait(); // Don't care about the exception if the cancellation token is set. Log.LogInformation("Exiting..."); rtpCts.Cancel(); 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(); } }