sgcWebRTC: um mecanismo de mídia WebRTC nativo para Delphi e C++ Builder

· Componentes
sgcWebRTC, um mecanismo de mídia WebRTC nativo para Delphi e C++ Builder

sgcWebRTC é um novo pacote para Delphi e C++ Builder que adiciona um mecanismo WebRTC completo à sua aplicação: sinalização real de oferta/resposta SDP, conectividade ICE com STUN e TURN, transporte criptografado DTLS-SRTP, canais de dados SCTP e trilhas de áudio e vídeo com Opus, G.711, VP8 e H.264.

Ele roda como Object Pascal puro. Não há Chromium embutido, nenhum controle TWebBrowser ou TEdgeBrowser e nenhuma ponte JavaScript no processo. Essa é a diferença em relação à maioria das outras formas de fazer WebRTC a partir do Delphi, que funcionam hospedando um mecanismo de navegador e conduzindo a pilha JavaScript dele a partir do Pascal. Aqui a conexão entre pares é código compilado chamando o sistema operacional diretamente, por isso ela também roda no Android e no iOS, e dentro de um serviço do Windows sem interface gráfica ou de um dispositivo de quiosque sem nenhum navegador instalado.

O sgcWebRTC é um complemento do sgcWebSockets Enterprise e está incluído no pacote All-Access.

Um componente, a conexão entre pares inteira

O sgcWebRTC não coloca novos componentes na paleta. Ele libera o restante da superfície no formato do W3C na mesma classe TsgcRTCPeerConnection que já vem no sgcWebSockets Enterprise. A edição Enterprise entrega esse componente para conectividade ICE e TURN e para sinalização por um relay WebSocket. O sgcWebRTC acrescenta a máquina de estados SDP, a associação SCTP, as sessões RTP e a criptografia SRTP sobre ele, de modo que um único objeto é dono da sinalização, da conectividade, da criptografia, dos canais de dados e da mídia.

Uma conexão entre pares é construída em quatro etapas. Primeiro a sinalização, em que os dois pares trocam descrições de sessão por um canal que você já tem. Depois a conexão, em que o ICE descobre qual endereço e porta conseguem realmente alcançar o outro lado. Depois a proteção, em que um handshake DTLS sobre o par de candidatos escolhido deriva as chaves SRTP. E, por fim, a comunicação, em que dados e mídia fluem diretamente entre os pares, sem nenhum servidor no meio.

Sinalização: SDP de verdade, para que o outro par possa ser um navegador

CreateOffer, CreateAnswer, SetLocalDescription, SetRemoteDescription e AddIceCandidate montam e consomem SDP padrão seguindo a máquina de estados JSEP da RFC 8829. O par da outra ponta pode ser outra aplicação Delphi, um aplicativo móvel ou uma aba de navegador. Você transporta a descrição e os candidatos pelo canal de sinalização que preferir, um WebSocket, um endpoint HTTP ou uma fila de mensagens, exatamente como uma aplicação de navegador os transporta pelo próprio servidor de sinalização dela.

Este é o lado que faz a oferta. Defina um servidor ICE, associe os dois eventos de sinalização, abra um canal de dados e então crie a oferta.

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;

O lado que responde consome o que chega e responde. Nada mais muda.

// 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);

Renegociação, reinício do ICE e a regra de glare da negociação perfeita do W3C já vêm prontos. Adicione uma trilha a uma sessão estabelecida e NegotiationNeeded passa a True, OnNegotiationNeeded dispara e a próxima oferta carrega a nova linha m. Se os dois pares ofertarem ao mesmo tempo, aquele cuja propriedade Polite é True desfaz a própria oferta e responde à do outro, de modo que a sessão sobrevive à colisão em vez de travar.

Conexão: ICE, STUN e TURN

A coleta de candidatos segue a RFC 8445. Os candidatos de host vêm das interfaces locais, os candidatos server-reflexive vêm de uma requisição de binding STUN e os candidatos retransmitidos vêm de uma alocação TURN quando os dois pares estão atrás de NATs que não os deixam se alcançar diretamente. O ICE então emparelha cada candidato local com cada candidato remoto e testa os pares até que um funcione.

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

OnConnectionStateChange informa o estado do transporte conforme ele passa por coleta, conexão e conectado, e SelectedLocalCandidate e SelectedRemoteCandidate dizem qual par venceu, que é a forma mais rápida de ver se uma chamada foi direta ou passou pelo 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;

Duas opções importam quando o outro par é um navegador. TrickleICE publica os candidatos à medida que são encontrados, em vez de esperar o fim da coleta, e TrickleICEAuto a ativa automaticamente quando a descrição remota a anuncia. Todo navegador faz trickle, então a resposta a uma oferta de navegador sai imediatamente, em vez de sair depois do tempo limite da coleta.

Proteção: DTLS-SRTP, não opcional

A criptografia de mídia é obrigatória, o mesmo modelo que um navegador impõe. Um handshake DTLS roda sobre o par ICE escolhido, as chaves SRTP são derivadas dele e cada pacote RTP, RTCP e SCTP da conexão é criptografado desde o primeiro pacote. A ponta remota é autenticada pela impressão digital do certificado transportada no SDP, e é por isso que o WebRTC não precisa de uma autoridade certificadora para isso.

Canais de dados sobre SCTP

CreateDataChannel abre um RTCDataChannel sobre SCTP-over-DTLS, com o handshake de abertura DCEP da RFC 8832. O canal pode ser ordenado ou não ordenado, totalmente confiável, ou parcialmente confiável com um limite de retransmissões ou um limite de tempo de vida, que é o que você quer para telemetria ou estado de jogo, onde um pacote atrasado vale menos do que um pacote recente.

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 envia texto e SendBytes envia cargas binárias. BufferedAmount informa quanto ainda está na fila, para que uma transferência de arquivos possa regular o próprio ritmo em vez de inundar a associação.

Áudio, vídeo e compartilhamento de tela

AddTrack anexa uma trilha de mídia à conexão e a devolve. A trilha é dona do seu codificador e do seu decodificador, então você empurra amostras ou quadros brutos para dentro e sai o RTP codificado e criptografado. O que chega do par remoto volta decodificado pelos eventos da trilha.

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;

Uma trilha adicionada pelo par remoto, em vez de criada por você, chega por OnTrack com os mesmos eventos já ligados ao codec negociado.

O áudio é Opus, ou G.711 nas variantes u-law e A-law quando é preciso interoperar com telefonia. O vídeo é VP8 por meio de um binding da libvpx, H.264 por meio do codificador de hardware que a plataforma oferece, Media Foundation no Windows, VideoToolbox no macOS e no iOS, MediaCodec no Android, e Motion JPEG no Windows para os casos em que um caminho simples, só com quadros intra, já basta.

A captura de microfone e a reprodução no alto-falante estão disponíveis para Windows, Linux, macOS, iOS e Android. A captura de câmera, a captura de desktop e a renderização de vídeo estão disponíveis para Windows, onde TsgcScreenCapture_Win captura o desktop inteiro, um monitor, uma única janela ou uma região, com ou sem o cursor. Nas demais plataformas os codecs e o transporte funcionam da mesma forma, e você alimenta os quadros por SendVideoFrame e desenha o que chega de OnVideoFrame com o que a plataforma oferecer.

Aguentando uma rede real

Uma conexão entre pares que só funciona em uma LAN tranquila não serve para muita coisa. O sgcWebRTC traz a mesma caixa de ferramentas de resiliência que um navegador usa, e cada peça é uma chave em RTCOptions.Media, negociada apenas quando a descrição remota também a apresenta.

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

Um estimador de largura de banda baseado em atraso, no estilo do Google Congestion Control, observa o enlace e conduz o bitrate alvo do codificador de vídeo, de modo que a imagem se degrada de forma suave quando a conexão estreita, em vez de travar. O feedback RTCP NACK e PLI, a retransmissão da RFC 4588 e RED/ULPFEC cobrem a perda de pacotes por baixo disso.

Os servidores também vêm junto

Uma conexão entre pares precisa de um canal de sinalização para apresentar os dois pares, e normalmente de um servidor STUN, e de um servidor TURN para os casos em que nada mais passa. Os três vêm como componentes Delphi no sgcWebSockets Enterprise, então você pode rodar a pilha inteira por conta própria em vez de alugar a infraestrutura: TsgcWSPServer_WebRTC como relay de sinalização, TsgcSTUNServer e TsgcTURNServer para a conectividade. Nada impede que você aponte o cliente para um servidor STUN público ou para um serviço TURN hospedado, de um jeito ou de outro o protocolo é o padrão.

Padrões

O sgcWebRTC é um mecanismo baseado em padrões, não um transporte proprietário. Ele implementa a RFC 8445 e a RFC 8489 para ICE e STUN, a RFC 8656 para TURN, a RFC 8827 e a RFC 5764 para DTLS e DTLS-SRTP, a RFC 3550 e a RFC 3711 para RTP e SRTP, a RFC 8831 e a RFC 8832 para canais de dados, a RFC 8829 para a máquina de estados de oferta/resposta JSEP, e a RFC 4588 para retransmissão. É isso que permite que um par Delphi converse com um par em navegador sem uma camada de tradução no meio.

Demos

Quatro demos acompanham o pacote, cada uma com o código-fonte completo. Demos\35.P2P\05.RTCPeerConnection conecta dois pares e inclui um pequeno servidor de sinalização WebSocket que você pode executar localmente. Demos\35.P2P\06.DataChannel abre um canal de dados SCTP e envia por ele cargas de texto e binárias. Demos\30.WebRTC_Protocol\03.AudioCall e Demos\30.WebRTC_Protocol\02.VideoCall são as demos de mídia, do microfone ao alto-falante e da câmera à tela.

Disponibilidade

O sgcWebRTC é um complemento do sgcWebSockets Enterprise. Não está disponível para as edições Standard ou Professional, e está incluído no pacote All-Access. Há licenças Single, Team e Site, todas com código-fonte completo e um ano de atualizações.

Ele oferece suporte do Delphi 7 ao Delphi 13 Florence e às versões correspondentes do C++ Builder, no Windows, Linux, macOS, iOS e Android. Não há download separado, o instalador de avaliação da sua versão da IDE já o contém.

O mecanismo de mídia é exclusivo de Delphi e C++ Builder. A versão .NET do sgcWebSockets inclui TsgcWSProtocol_WebRTC_Server, um relay de sinalização WebSocket para o cenário clássico de navegador para navegador, mas não existe equivalente .NET de TsgcRTCPeerConnection.

Página do produto · Detalhamento de recursos · Baixar a versão de avaliação · Preços

Dúvidas ou comentários? Fale conosco, você receberá uma resposta das pessoas que escreveram o código.