sgcWebRTC: a Native WebRTC Media Engine for Delphi and C++ Builder

· Components
sgcWebRTC, a native WebRTC media engine for Delphi and C++ Builder

sgcWebRTC is a new package for Delphi and C++ Builder that adds a complete WebRTC engine to your application: real SDP offer/answer signalling, ICE connectivity with STUN and TURN, DTLS-SRTP encrypted transport, SCTP data channels, and audio and video tracks with Opus, G.711, VP8 and H.264.

It runs as pure Object Pascal. There is no embedded Chromium, no TWebBrowser or TEdgeBrowser control, and no JavaScript bridge in the process. That is the difference from most other ways of doing WebRTC from Delphi, which work by hosting a browser engine and driving its JavaScript stack from Pascal. Here the peer connection is compiled code calling the operating system directly, so it also runs on Android and iOS, and inside a headless Windows service or a kiosk device with no browser installed at all.

sgcWebRTC is an add-on to sgcWebSockets Enterprise, and it is included in the All-Access bundle.

One Component, the Whole Peer Connection

sgcWebRTC does not put new components on the palette. It unlocks the rest of the W3C-shaped surface on the same TsgcRTCPeerConnection class that already ships in sgcWebSockets Enterprise. Enterprise gives you that component for ICE and TURN connectivity and for signalling over a WebSocket relay. sgcWebRTC adds the SDP state machine, the SCTP association, the RTP sessions and SRTP encryption on top of it, so one object owns signalling, connectivity, encryption, data channels and media.

A peer connection is built in four steps. First signalling, where the two peers exchange session descriptions over a channel you already have. Then connecting, where ICE works out which address and port can actually reach the other side. Then securing, where a DTLS handshake over the nominated candidate pair derives the SRTP keys. And finally communicating, where data and media flow directly between the peers with no server in the middle.

Signalling: Real SDP, So the Other Peer Can Be a Browser

CreateOffer, CreateAnswer, SetLocalDescription, SetRemoteDescription and AddIceCandidate build and consume standard SDP following the JSEP state machine of RFC 8829. The peer at the other end can be another Delphi application, a mobile app or a browser tab. You carry the description and the candidates over whatever signalling channel you like, a WebSocket, an HTTP endpoint or a message queue, exactly as a browser application carries them through its own signalling server.

This is the offering side. Set an ICE server, hook the two signalling events, open a data channel, then create the offer.

uses
  sgcP2P;

var
  oRTC: TsgcRTCPeerConnection;
begin
  oRTC := TsgcRTCPeerConnection.Create(nil);

  oRTC.RTCOptions.ICEServers.AddURL('stun:stun.l.google.com:19302');

  oRTC.OnLocalDescription := OnLocalDescriptionHandler;
  oRTC.OnICECandidate := OnICECandidateHandler;
  oRTC.OnConnectionStateChange := OnConnectionStateChangeHandler;
  oRTC.OnDataChannel := OnDataChannelHandler;

  oRTC.TrickleICE := True;

  oRTC.CreateDataChannel('chat'); // forces RTCOptions.DTLS on
  oRTC.CreateOffer;               // gathers candidates and builds the SDP offer
end;

procedure TForm1.OnLocalDescriptionHandler(Sender: TObject;
  const aType, aSDP: string);
begin
  // aType is 'offer' or 'answer'. Send both fields to the remote peer
  // over your own signalling channel.
  MySignalling.SendDescription(aType, aSDP);
end;

procedure TForm1.OnICECandidateHandler(Sender: TObject;
  const aCandidate, aSdpMid: string; aSdpMLineIndex: Integer);
begin
  // With TrickleICE on, candidates arrive one by one while the offer
  // is already on its way to the other peer.
  MySignalling.SendCandidate(aCandidate, aSdpMid, aSdpMLineIndex);
end;

The answering side consumes what arrives and replies. Nothing else changes.

// the remote description arrived over your signalling channel
oRTC.SetRemoteDescription('offer', vSDP);
oRTC.CreateAnswer;          // raises OnLocalDescription with 'answer'

// and every remote candidate as it arrives
oRTC.AddIceCandidate(vCandidate, vSdpMid, vSdpMLineIndex);

Renegotiation, ICE restart and the W3C perfect-negotiation glare rule are built in. Add a track to an established session and NegotiationNeeded goes true, OnNegotiationNeeded fires and the next offer carries the new m-line. If both peers offer at the same time, the one whose Polite property is True rolls its own offer back and answers the remote one, so the session survives the collision instead of deadlocking.

Connecting: ICE, STUN and TURN

Candidate gathering follows RFC 8445. Host candidates come from the local interfaces, server-reflexive candidates come from a STUN binding request, and relayed candidates come from a TURN allocation when the two peers sit behind NATs that will not let them reach each other directly. ICE then pairs every local candidate with every remote one and tests the pairs until one works.

oRTC.RTCOptions.ICEServers.AddURL('stun:stun.l.google.com:19302');
oRTC.RTCOptions.ICEServers.AddURL('turn:turn.example.com:3478',
  'username', 'credential');

OnConnectionStateChange reports the transport as it moves through gathering, connecting and connected, and SelectedLocalCandidate and SelectedRemoteCandidate tell you which pair won, which is the quickest way to see whether a call went direct or through the relay.

procedure TForm1.OnConnectionStateChangeHandler(Sender: TObject;
  aState: TsgcRTCConnectionState);
begin
  case aState of
    rtccsGathering:    DoLog('gathering candidates');
    rtccsConnecting:   DoLog('checking candidate pairs');
    rtccsConnected:    DoLog('connected: ' + oRTC.SelectedRemoteCandidate);
    rtccsDisconnected: DoLog('disconnected');
    rtccsFailed:       DoLog('failed, try RestartIce');
  end;
end;

Two options matter when the other peer is a browser. TrickleICE publishes candidates as they are found instead of waiting for gathering to end, and TrickleICEAuto turns it on automatically when the remote description advertises it. Every browser trickles, so an answer to a browser offer goes out immediately rather than after the gathering timeout.

Securing: DTLS-SRTP, Not Optional

Media encryption is mandatory, the same model a browser enforces. A DTLS handshake runs over the nominated ICE pair, the SRTP keys are derived from it, and every RTP, RTCP and SCTP packet on the connection is encrypted from the first packet. The remote end is authenticated by the certificate fingerprint carried in the SDP, which is why WebRTC does not need a certificate authority for this.

Data Channels Over SCTP

CreateDataChannel opens an RTCDataChannel over SCTP-over-DTLS, with the DCEP open handshake of RFC 8832. The channel can be ordered or unordered, fully reliable, or partially reliable with a retransmit limit or a lifetime limit, which is what you want for telemetry or game state where a late packet is worth less than a fresh one.

uses
  sgcP2P, sgcP2P_DataChannel;

var
  vChat, vTelemetry: TsgcRTCDataChannel;
begin
  // ordered and reliable, the default
  vChat := oRTC.CreateDataChannel('chat');
  vChat.OnOpen := OnChannelOpen;
  vChat.OnMessage := OnChannelMessage;

  // unordered, no retransmissions: drop it rather than deliver it late
  vTelemetry := oRTC.CreateDataChannel('telemetry', False, 0);
end;

procedure TForm1.OnChannelOpen(Sender: TObject);
begin
  TsgcRTCDataChannel(Sender).Send('hello');
end;

procedure TForm1.OnChannelMessage(Sender: TObject; const aText: string);
begin
  DoLog('remote said: ' + aText);
end;

// a channel the remote peer opened arrives here
procedure TForm1.OnDataChannelHandler(Sender: TObject;
  aChannel: TsgcRTCDataChannel);
begin
  aChannel.OnMessage := OnChannelMessage;
  aChannel.OnMessageBinary := OnChannelMessageBinary;
end;

Send moves text and SendBytes moves binary payloads. BufferedAmount tells you how much is still queued, so a file transfer can pace itself instead of flooding the association.

Audio, Video and Screen Sharing

AddTrack attaches a media track to the connection and returns it. The track owns its encoder and decoder, so you push raw samples or frames in and the encoded, encrypted RTP goes out. What arrives from the remote peer comes back decoded through the track events.

uses
  sgcP2P, sgcP2P_RTC_Media, sgcP2P_Codec_Types;

var
  vAudio, vVideo: TsgcRTCTrack;
begin
  vAudio := oRTC.AddTrack(rtctkAudio, cctAudioOpus);
  vVideo := oRTC.AddTrack(rtctkVideo, cctVideoH264);

  // what the remote peer sends, already decoded
  vAudio.OnAudio := OnRemoteAudio;
  vVideo.OnVideoFrame := OnRemoteVideoFrame;

  oRTC.CreateOffer;
end;

// push captured microphone samples into the audio track
procedure TForm1.OnMicrophoneCapture(Sender: TObject; const aPCM: TBytes;
  aSamplesPerChannel: Integer);
begin
  vAudio.SendPCM(aPCM, aSamplesPerChannel);
end;

// push captured camera or desktop frames into the video track
procedure TForm1.OnCameraCapture(Sender: TObject;
  const aFrame: TsgcVideoFrame);
begin
  vVideo.SendVideoFrame(aFrame);
end;

// draw what the remote peer sends
procedure TForm1.OnRemoteVideoFrame(Sender: TObject;
  const aFrame: TsgcVideoFrame);
begin
  MyRenderer.Render(aFrame);
end;

A track the remote peer added, rather than one you created, arrives through OnTrack with the same events already wired to the negotiated codec.

Audio is Opus, or G.711 in its u-law and A-law flavours when you have to interoperate with telephony. Video is VP8 through a libvpx binding, H.264 through the hardware encoder the platform provides, Media Foundation on Windows, VideoToolbox on macOS and iOS, MediaCodec on Android, and Motion JPEG on Windows for the cases where a simple intra-only path is enough.

Microphone capture and speaker playback ship for Windows, Linux, macOS, iOS and Android. Camera capture, desktop capture and video rendering ship for Windows, where TsgcScreenCapture_Win captures the whole desktop, one monitor, a single window or a region, with or without the cursor. On the other platforms the codecs and the transport work the same way, and you feed frames in through SendVideoFrame and draw what arrives from OnVideoFrame with whatever the platform gives you.

Holding Up on a Real Network

A peer connection that only works on a quiet LAN is not much use. sgcWebRTC carries the same resilience toolbox a browser uses, and each piece is a switch on RTCOptions.Media, negotiated only when the remote description shows it too.

oRTC.RTCOptions.Media.CongestionControl := True; // transport-cc, REMB fallback
oRTC.RTCOptions.Media.Pacing := True;            // smooth the outgoing packets
oRTC.RTCOptions.Media.RTX := True;               // RFC 4588 retransmission on NACK
oRTC.RTCOptions.Media.FEC := True;               // RED / ULPFEC forward error correction

A delay-based bandwidth estimator in the style of Google Congestion Control watches the link and drives the target bitrate of the video encoder, so the picture degrades gracefully when the connection narrows instead of stalling. RTCP NACK and PLI feedback, RFC 4588 retransmission and RED/ULPFEC cover the packet loss underneath it.

The Servers Ship Too

A peer connection needs a signalling channel to introduce the two peers, and usually a STUN server, and a TURN server for the cases where nothing else gets through. All three ship as Delphi components in sgcWebSockets Enterprise, so you can run the whole stack yourself instead of renting the infrastructure: TsgcWSPServer_WebRTC as the signalling relay, TsgcSTUNServer and TsgcTURNServer for connectivity. Nothing stops you pointing the client at a public STUN server or a hosted TURN service instead, it is the standard protocol either way.

Standards

sgcWebRTC is a standards-based engine, not a proprietary transport. It implements RFC 8445 and RFC 8489 for ICE and STUN, RFC 8656 for TURN, RFC 8827 and RFC 5764 for DTLS and DTLS-SRTP, RFC 3550 and RFC 3711 for RTP and SRTP, RFC 8831 and RFC 8832 for data channels, RFC 8829 for the JSEP offer/answer state machine, and RFC 4588 for retransmission. That is what lets a Delphi peer talk to a browser peer without a translation layer in between.

Demos

Four demos ship with the package, each with full source. Demos\35.P2P\05.RTCPeerConnection connects two peers and includes a small WebSocket signalling server you can run locally. Demos\35.P2P\06.DataChannel opens an SCTP data channel and sends text and binary payloads over it. Demos\30.WebRTC_Protocol\03.AudioCall and Demos\30.WebRTC_Protocol\02.VideoCall are the media demos, microphone to speaker and camera to screen.

Availability

sgcWebRTC is an add-on to sgcWebSockets Enterprise. It is not available for the Standard or Professional editions, and it is included in the All-Access bundle. Single, Team and Site licenses are available, all with full source code and one year of updates.

It supports Delphi 7 through Delphi 13 Florence and the matching C++ Builder versions, on Windows, Linux, macOS, iOS and Android. There is no separate download, the trial installer for your IDE version already contains it.

The media engine is Delphi and C++ Builder only. The sgcWebSockets .NET port includes TsgcWSProtocol_WebRTC_Server, a WebSocket signalling relay for the classic browser-to-browser scenario, but there is no .NET equivalent of TsgcRTCPeerConnection.

Product page · Feature breakdown · Download the trial · Pricing

Questions or feedback? Get in touch, you will get a reply from the people who wrote the code.