Skip to content
Avatar
💭
Bitcoin, SIP & WebRTC
💭
Bitcoin, SIP & WebRTC

Highlights

Organizations

@bitcoin

Sponsors

@americanhodl @tylerspalding @jlopp @modl21 @martin-cermak

Sponsoring

@JeremyRubin @Sjors @luke-jr
sipsorcery/README.md
Target SIPSorcery Examples
(Windows Only)
Softphone
(Windows Only)
net461 Build status
netstandard2.0
dotnetcore3.1
Windows
MacOS
Ubuntu

Examples build status
Softphone build status

What Is It?

This fully C# library can be used to add Real-time Communications, typically audio and video calls, to .NET Core applications.

The diagram below is a high level overview of a Real-time audio and video call between Alice and Bob. It illustrates where the SIPSorcery library can help.

Real-time Communications Overview

Supports both VoIP (get started) and WebRTC (get started).

Some of the protocols supported:

  • Session Initiation Protocol (SIP),
  • Real-time Transport Protocol (RTP),
  • Web Real-time Communications (WebRTC),
  • Interactive Connectivity Establishment (ICE),
  • And more.

Media End Points - Audio/Video Sinks and Sources:

  • This library does not provide access to audio and video devices or native codecs. Providing cross platform access on top of .NET Core is a large undertaking. A number of efforts in separate libraries are currently in progress.

    • SIPSorceryMedia.Windows: Windows specific library that provides audio capture and playback. Also provides VP8 encoding and decoding functions. The examples in this repository use it.
    • SIPSorceryMedia.FFmpeg: A in-progress effort to provide cross platform audio, video and codec functions using PInvoke and FFmpeg.
    • Others: Contributions welcome. Frequently requested are Xamarin Forms on Android/iOS and Unix (Linux and/or Mac). New implementations need to implement one or more of the Audio Sink/Source and/or Video Sink/Source interfaces from SIPSorceryMedia.Abstractions.
  • This library provides only a small number of audio and video codecs (G711, G722 and MJPEG). Additional codecs, particularly video ones, require C++ libraries.

Installation

The library is compliant with .NET Standard 2.0 (encompassing .NET Core 2.0+) and .NET Framework 4.6.1 (theoretically also encompassed by netstandard2.0 but set as an explicit target due to compatibility issues between the two). It is available via NuGet.

For .NET Core:

dotnet add package SIPSorcery -v 4.0.71-pre

With Visual Studio Package Manager Console (or search for SIPSorcery on NuGet):

Install-Package SIPSorcery -v 4.0.71-pre

Documentation

Class reference documentation and articles explaining common usage are available at https://sipsorcery.github.io/sipsorcery/.

Getting Started VoIP

The simplest possible example to place an audio-only SIP call is shown below. This example relies on the Windows specific SIPSorceryMedia library to play the received audio and only works on Windows (due to lack of .NET Core audio device support on non-Windows platforms).

dotnet new console --name SIPGetStarted -f netcoreapp3.1
cd SIPGetStarted
dotnet add package SIPSorcery -v 4.0.71-pre
dotnet add package SIPSorceryMedia.Windows -v 0.0.18-pre
code . # If you have Visual Studio Code https://code.visualstudio.com installed.
# edit Program.cs and paste in the contents below.
dotnet run
# if successful you will hear the current time read out.
ctrl-c
using System;
using System.Threading.Tasks;
using SIPSorcery.SIP.App;
using SIPSorcery.Media;
using SIPSorceryMedia.Windows;

namespace SIPGetStarted
{
    class Program
    {
         private static string DESTINATION = "time@sipsorcery.com";
        
        static async Task Main()
        {
            Console.WriteLine("SIP Get Started");
			
            var userAgent = new SIPUserAgent();
            var winAudio = new WindowsAudioEndPoint(new AudioEncoder());
            var voipMediaSession = new VoIPMediaSession(winAudio.ToMediaEndPoints());

            // Place the call and wait for the result.
            bool callResult = await userAgent.Call(DESTINATION, null, null, voipMediaSession);
            Console.WriteLine($"Call result {((callResult) ? "success" : "failure")}.");

            Console.WriteLine("Press any key to hangup and exit.");
            Console.ReadLine();
        }
    }
}

The GetStarted example contains the full source and project file for the example above.

The three key classes in the above example are described in dedicated articles:

The examples folder contains sample code to demonstrate other common SIP/VoIP cases.

Getting Started WebRTC

The WebRTC specifications do not include directions about how signaling should be done (for VoIP the signaling protocol is SIP; WebRTC has no equivalent). The example below uses a simple JSON message exchange over web sockets for signaling. Part of the reason the Getting Started WebRTC is over 5 times as long as the Getting Started VoIP is the need for custom signaling.

The example requires two steps:

  • Run the dotnet console application,
  • Open an HTML page in a browser on the same machine.

The full project file and code are available at WebRTC Get Started.

The example relies on the Windows specific SIPSorceryMedia.Windows package. Hopefully in the future there will be equivalent packages for other platforms.

Step 1:

dotnet new console --name WebRTCGetStarted -f netcoreapp3.1
cd WebRTCGetStarted
dotnet add package SIPSorcery -v 4.0.71-pre
dotnet add package SIPSorceryMedia.Windows -v 0.0.18-pre
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Extensions.Logging
code . # If you have Visual Studio Code (https://code.visualstudio.com) installed
# edit Program.cs and paste in the contents below.
dotnet run
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Serilog;
using SIPSorcery.Net;
using SIPSorceryMedia.Windows;
using WebSocketSharp.Server;
using SIPSorcery.Media;
using Serilog.Extensions.Logging;

namespace demo
{
    class Program
    {
        private const int WEBSOCKET_PORT = 8081;
        private const string STUN_URL = "stun:stun.sipsorcery.com";

        private static Microsoft.Extensions.Logging.ILogger logger = NullLogger.Instance;

        static void Main()
        {
            Console.WriteLine("WebRTC Get Started");

            logger = AddConsoleLogger();

            // Start web socket.
            Console.WriteLine("Starting web socket server...");
            var webSocketServer = new WebSocketServer(IPAddress.Any, WEBSOCKET_PORT);
            webSocketServer.AddWebSocketService<WebRTCWebSocketPeer>("/", (peer) => peer.CreatePeerConnection = CreatePeerConnection);
            webSocketServer.Start();

            Console.WriteLine($"Waiting for web socket connections on {webSocketServer.Address}:{webSocketServer.Port}...");
            Console.WriteLine("Press ctrl-c to exit.");

            // Ctrl-c will gracefully exit the call at any point.
            ManualResetEvent exitMre = new ManualResetEvent(false);
            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();
        }

        private static RTCPeerConnection CreatePeerConnection()
        {
            RTCConfiguration config = new RTCConfiguration
            {
                iceServers = new List<RTCIceServer> { new RTCIceServer { urls = STUN_URL } }
            };
            var pc = new RTCPeerConnection(config);

            var testPatternSource = new VideoTestPatternSource();
            WindowsVideoEndPoint windowsVideoEndPoint = new WindowsVideoEndPoint(true);
            var audioSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions { AudioSource = AudioSourcesEnum.Music });

            MediaStreamTrack videoTrack = new MediaStreamTrack(windowsVideoEndPoint.GetVideoSourceFormats(), MediaStreamStatusEnum.SendRecv);
            pc.addTrack(videoTrack);
            MediaStreamTrack audioTrack = new MediaStreamTrack(audioSource.GetAudioSourceFormats(), MediaStreamStatusEnum.SendRecv);
            pc.addTrack(audioTrack);

            testPatternSource.OnVideoSourceRawSample += windowsVideoEndPoint.ExternalVideoSourceRawSample;
            windowsVideoEndPoint.OnVideoSourceEncodedSample += pc.SendVideo;
            audioSource.OnAudioSourceEncodedSample += pc.SendAudio;
            pc.OnVideoFormatsNegotiated += (sdpFormat) =>
                windowsVideoEndPoint.SetVideoSourceFormat(SDPMediaFormatInfo.GetVideoCodecForSdpFormat(sdpFormat.First().FormatCodec));
            pc.onconnectionstatechange += async (state) =>
            {
                logger.LogDebug($"Peer connection state change to {state}.");

                if (state == RTCPeerConnectionState.connected)
                {
                    await audioSource.StartAudio();
                    await windowsVideoEndPoint.StartVideo();
                    await testPatternSource.StartVideo();
                }
                else if (state == RTCPeerConnectionState.failed)
                {
                    pc.Close("ice disconnection");
                }
                else if (state == RTCPeerConnectionState.closed)
                {
                    await testPatternSource.CloseVideo();
                    await windowsVideoEndPoint.CloseVideo();
                    await audioSource.CloseAudio();
                }
            };

            // Diagnostics.
            pc.OnReceiveReport += (re, media, rr) => logger.LogDebug($"RTCP Receive for {media} from {re}\n{rr.GetDebugSummary()}");
            pc.OnSendReport += (media, sr) => logger.LogDebug($"RTCP Send for {media}\n{sr.GetDebugSummary()}");
            pc.GetRtpChannel().OnStunMessageReceived += (msg, ep, isRelay) => logger.LogDebug($"STUN {msg.Header.MessageType} received from {ep}.");
            pc.oniceconnectionstatechange += (state) => logger.LogDebug($"ICE connection state change to {state}.");

            return pc;
        }

        /// <summary>
        ///  Adds a console logger. Can be omitted if internal SIPSorcery debug and warning messages are not required.
        /// </summary>
        private static Microsoft.Extensions.Logging.ILogger AddConsoleLogger()
        {
            var seriLogger = new LoggerConfiguration()
                .Enrich.FromLogContext()
                .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
                .WriteTo.Console()
                .CreateLogger();
            var factory = new SerilogLoggerFactory(seriLogger);
            SIPSorcery.LogFactory.Set(factory);
            return factory.CreateLogger<Program>();
        }
    }
}

Step 2:

Create an HTML file, paste the contents below into it, open it in a browser that supports WebRTC and finally press the start button.

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">

    <script type="text/javascript">

        const STUN_URL = "stun:stun.sipsorcery.com";
        const WEBSOCKET_URL = "ws://127.0.0.1:8081/"

        var pc, ws;

        async function start() {
            pc = new RTCPeerConnection({ iceServers: [{ urls: STUN_URL }] });

            pc.ontrack = evt => document.querySelector('#videoCtl').srcObject = evt.streams[0];
            pc.onicecandidate = evt => evt.candidate && ws.send(JSON.stringify(evt.candidate));
            await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
                .then(stm => stm.getTracks().forEach(track => pc.addTrack(track, stm)));

            ws = new WebSocket(document.querySelector('#websockurl').value, []);
            ws.onmessage = async function (evt) {
                if (/^[\{"'\s]*candidate/.test(evt.data)) {
                    pc.addIceCandidate(JSON.parse(evt.data));
                }
                else {
                    await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(evt.data)));
                    pc.createAnswer()
                        .then((answer) => pc.setLocalDescription(answer))
                        .then(() => ws.send(JSON.stringify(pc.localDescription)));
                }
            };
        };

        async function closePeer() {
            pc.getSenders().forEach(sender => {
                sender.track.stop();
                pc.removeTrack(sender);
            });
            await pc.close();
            await ws.close();
        };

    </script>
</head>
<body>
    <video controls autoplay="autoplay" id="videoCtl" width="640" height="480"></video>
    <div>
        <input type="text" id="websockurl" size="40" />
        <button type="button" class="btn btn-success" onclick="start();">Start</button>
        <button type="button" class="btn btn-success" onclick="closePeer();">Close</button>
    </div>
</body>

<script>
    document.querySelector('#websockurl').value = WEBSOCKET_URL;
</script>

Result:

If successful the browser should display a test pattern image and play a music sample. The dotnet console should display a steady stream of RTCP reports.

...
[19:40:25 DBG] STUN BindingRequest received from 192.168.0.50:57681.
[19:40:26 DBG] RTCP Receive for video from 192.168.11.50:57681
SDES: SSRC=3458092865, CNAME=5+ksoe4uBNfyl5u5
Sender: SSRC=3458092865, PKTS=18, BYTES=16392
[19:40:26 DBG] RTCP Receive for video from 192.168.11.50:57681
Receiver: SSRC=3458092865
 RR: SSRC=852075017, LOST=0, JITTER=390
[19:40:26 DBG] STUN BindingRequest received from 192.168.11.50:57681.
[19:40:27 DBG] RTCP Receive for video from 192.168.11.50:57681
SDES: SSRC=3458092865, CNAME=5+ksoe4uBNfyl5u5
Sender: SSRC=3458092865, PKTS=46, BYTES=39676
[19:40:27 DBG] RTCP Receive for video from 192.168.11.50:57681
Receiver: SSRC=3458092865
 RR: SSRC=852075017, LOST=0, JITTER=368
[19:40:27 DBG] STUN BindingRequest received from 192.168.11.50:57681.
[19:40:27 DBG] RTCP Receive for audio from 192.168.11.50:57681
SDES: SSRC=1049319500, CNAME=5+ksoe4uBNfyl5u5
Sender: SSRC=1049319500, PKTS=106, BYTES=16960
[19:40:27 DBG] STUN BindingRequest received from 192.168.0.50:57681.
[19:40:27 DBG] RTCP Receive for video from 192.168.11.50:57681
Receiver: SSRC=3458092865
 RR: SSRC=852075017, LOST=0, JITTER=419
 ...

The examples folder contains sample code to demonstrate other common WebRTC cases.

Pinned

  1. A real-time communications, cross platform, C# .NET Core library for all your SIP, VoIP and WebRTC needs!

    C# 332 128

  2. A set of unofficial minimal sample apps that demonstrate how to use certain parts of Microsoft's Windows Media Foundation API.

    C++ 106 56

  3. A simple HTML/Javascript approach for charting skill usage over time. Integrates well with Linkedin.

    JavaScript 1 1

4,077 contributions in the last year

Oct Nov Dec Jan Feb Mar Apr May Jun Jul Aug Sep Oct Mon Wed Fri

Contribution activity

October 2020

Created a pull request in bitcoin/bitcoin that received 10 comments

CI: Bump vcpkg commit ID to get new msys mirror list

This fixes the appveyor CI job, see #20066. Currently the job fails because some of the vcpkg dependencies need to install msys2 and the hardcoded …

+1 −1 10 comments

Created an issue in bitcoin/bitcoin that received 16 comments

CI: Appveyor builds failing

Appveyor builds started failing about 20 hours ago for no reason I can discern. I cannot replicate the failures on my Windows 10 machine. I am able…

16 comments
1 contribution in private repositories Oct 19

Seeing something unexpected? Take a look at the GitHub profile guide.

You can’t perform that action at this time.