sgcWebRTC: Delphi と C++ Builder 向けのネイティブ WebRTC メディアエンジン

· コンポーネント
sgcWebRTC、Delphi と C++ Builder 向けのネイティブ WebRTC メディアエンジン

sgcWebRTC は Delphi と C++ Builder 向けの新しいパッケージで、アプリケーションに完全な WebRTC エンジンを追加します。本物の SDP オファー/アンサーによるシグナリング、STUN と TURN を使う ICE 接続、DTLS-SRTP で暗号化されたトランスポート、SCTP データチャネル、そして Opus、G.711、VP8、H.264 による音声トラックと映像トラックを備えています。

動作するのは純粋な Object Pascal です。組み込みの Chromium も、TWebBrowserTEdgeBrowser コントロールも、プロセス内の JavaScript ブリッジもありません。そこが、Delphi から WebRTC を扱う他の多くの方法との違いです。それらはブラウザエンジンをホストして、その JavaScript スタックを Pascal から操作する仕組みです。ここではピア接続はオペレーティングシステムを直接呼び出すコンパイル済みコードなので、Android と iOS でも動作しますし、ブラウザがまったくインストールされていないヘッドレスの Windows サービスやキオスク端末の中でも動きます。

sgcWebRTC は sgcWebSockets Enterprise のアドオンで、All-Access バンドルにも含まれています。

1 つのコンポーネントでピア接続のすべてを

sgcWebRTC はパレットに新しいコンポーネントを追加しません。sgcWebSockets Enterprise にすでに同梱されている同じ TsgcRTCPeerConnection クラス上で、W3C に沿った残りの API 面を解放します。Enterprise では、このコンポーネントを ICE と TURN による接続、そして WebSocket リレー経由のシグナリングに使えます。sgcWebRTC はその上に SDP の状態マシン、SCTP アソシエーション、RTP セッション、SRTP 暗号化を追加するので、1 つのオブジェクトがシグナリング、接続、暗号化、データチャネル、メディアのすべてを担います。

ピア接続は 4 つのステップで構築されます。まずシグナリングで、2 つのピアがすでに持っているチャネルを通じてセッション記述を交換します。次に接続で、どのアドレスとポートなら実際に相手側へ到達できるかを ICE が突き止めます。次にセキュリティの確立で、選ばれた候補ペア上の DTLS ハンドシェイクが SRTP の鍵を導出します。そして最後に通信で、間にサーバーを挟まずにデータとメディアがピア同士のあいだを直接流れます。

シグナリング: 本物の SDP、だから相手のピアはブラウザでもよい

CreateOfferCreateAnswerSetLocalDescriptionSetRemoteDescriptionAddIceCandidate が、RFC 8829 の JSEP 状態マシンに従って標準の SDP を組み立て、解釈します。相手側のピアは別の Delphi アプリケーションでも、モバイルアプリでも、ブラウザのタブでもかまいません。ブラウザアプリケーションが自前のシグナリングサーバーを通してやり取りするのとまったく同じように、記述と候補は WebSocket、HTTP エンドポイント、メッセージキューなど、好きなシグナリングチャネルで運びます。

こちらはオファーを出す側です。ICE サーバーを設定し、2 つのシグナリングイベントをフックし、データチャネルを開いてから、オファーを作成します。

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

再ネゴシエーション、ICE リスタート、そして W3C の perfect negotiation による衝突ルールが組み込まれています。確立済みのセッションにトラックを追加すると NegotiationNeeded が True になり、OnNegotiationNeeded が発生し、次のオファーが新しい m-line を運びます。両方のピアが同時にオファーを出した場合は、Polite プロパティが True のほうが自分のオファーをロールバックして相手のオファーに応答するので、セッションはデッドロックせずに衝突を乗り切ります。

接続: ICE、STUN、TURN

候補の収集は RFC 8445 に従います。ホスト候補はローカルのインターフェイスから、サーバーリフレクシブ候補は STUN のバインディングリクエストから得られ、リレー候補は、2 つのピアが互いに直接到達できない NAT の背後にいるときに TURN のアロケーションから得られます。そのうえで ICE は、すべてのローカル候補をすべてのリモート候補と組み合わせ、どれかがつながるまでペアをテストします。

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

OnConnectionStateChange は、トランスポートが gathering、connecting、connected と遷移していく様子を通知します。SelectedLocalCandidateSelectedRemoteCandidate はどのペアが選ばれたかを教えてくれるので、通話が直接つながったのかリレー経由なのかを確認する一番手早い方法になります。

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;

相手のピアがブラウザのときに重要なオプションが 2 つあります。TrickleICE は、収集が終わるのを待たずに、見つかった候補をその都度公開します。TrickleICEAuto は、リモートの記述がそれを示しているときに自動で有効にします。ブラウザはどれも trickle を使うので、ブラウザからのオファーへのアンサーは、収集のタイムアウトを待つことなくすぐに送り出されます。

セキュリティの確立: DTLS-SRTP は必須

メディアの暗号化は必須で、ブラウザが強制するのと同じモデルです。選ばれた ICE ペア上で DTLS ハンドシェイクが実行され、そこから SRTP の鍵が導出されます。接続上のすべての RTP、RTCP、SCTP パケットは最初のパケットから暗号化されます。リモート側は SDP に含まれる証明書のフィンガープリントで認証されます。WebRTC がこの用途に認証局を必要としないのは、そのためです。

SCTP 上のデータチャネル

CreateDataChannel は、RFC 8832 の DCEP オープンハンドシェイクを使って、SCTP over DTLS 上に RTCDataChannel を開きます。チャネルは順序保証あり、順序保証なし、完全に信頼性あり、あるいは再送回数の上限や生存時間の上限による部分的な信頼性のいずれかにできます。最後の形は、遅れて届くパケットより新しいパケットのほうが価値のあるテレメトリやゲームの状態にちょうど向いています。

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 はテキストを、SendBytes はバイナリのペイロードを送ります。BufferedAmount はまだキューに残っている量を示すので、ファイル転送はアソシエーションをあふれさせずに自分でペースを調整できます。

音声、映像、画面共有

AddTrack は接続にメディアトラックを取り付け、そのトラックを返します。トラックはエンコーダーとデコーダーを自分で持つので、生のサンプルやフレームを流し込めば、エンコードされ暗号化された RTP が出ていきます。リモートのピアから届いたものは、トラックのイベントを通じてデコード済みの状態で戻ってきます。

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;

自分で作成したものではなく、リモートのピアが追加したトラックは OnTrack を通じて届き、ネゴシエートされたコーデックに合わせて同じイベントがすでに接続された状態になっています。

音声は Opus、あるいは電話網と相互接続する必要がある場合は u-law と A-law の G.711 です。映像は libvpx バインディング経由の VP8、プラットフォームが提供するハードウェアエンコーダー経由の H.264 (Windows では Media Foundation、macOS と iOS では VideoToolbox、Android では MediaCodec)、そして単純なイントラのみの経路で十分な場合のために Windows では Motion JPEG が使えます。

マイクのキャプチャとスピーカーの再生は Windows、Linux、macOS、iOS、Android に同梱されています。カメラのキャプチャ、デスクトップのキャプチャ、映像の描画は Windows に同梱され、TsgcScreenCapture_Win がデスクトップ全体、特定のモニター、単一のウィンドウ、あるいは任意の領域を、カーソルあり/なしでキャプチャします。ほかのプラットフォームでもコーデックとトランスポートは同じように動くので、SendVideoFrame でフレームを流し込み、OnVideoFrame から届いたものをそのプラットフォームの手段で描画します。

実ネットワークで持ちこたえる

静かな LAN でしか動かないピア接続では、たいして役に立ちません。sgcWebRTC はブラウザが使うのと同じ耐性のための道具一式を備えており、それぞれが RTCOptions.Media のスイッチになっていて、リモートの記述も対応を示しているときにだけネゴシエートされます。

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

Google Congestion Control 方式の遅延ベース帯域推定器がリンクを監視し、映像エンコーダーの目標ビットレートを制御するので、回線が細くなっても映像は止まらず、緩やかに品質を落としていきます。その下では、RTCP の NACK と PLI のフィードバック、RFC 4588 の再送、RED/ULPFEC がパケットロスをカバーします。

サーバーも同梱

ピア接続には、2 つのピアを引き合わせるシグナリングチャネルが必要で、たいていは STUN サーバーも、そしてほかに何も通らない場合のために TURN サーバーも必要になります。この 3 つはすべて sgcWebSockets Enterprise に Delphi コンポーネントとして同梱されているので、インフラを借りる代わりにスタック全体を自分で運用できます。シグナリングリレーが TsgcWSPServer_WebRTC、接続用が TsgcSTUNServerTsgcTURNServer です。もちろん、代わりにクライアントを公開の STUN サーバーやホスティング型の TURN サービスに向けてもかまいません。どちらにしても標準のプロトコルです。

標準仕様

sgcWebRTC は独自のトランスポートではなく、標準仕様に基づいたエンジンです。ICE と STUN には RFC 8445 と RFC 8489、TURN には RFC 8656、DTLS と DTLS-SRTP には RFC 8827 と RFC 5764、RTP と SRTP には RFC 3550 と RFC 3711、データチャネルには RFC 8831 と RFC 8832、JSEP のオファー/アンサー状態マシンには RFC 8829、再送には RFC 4588 を実装しています。だからこそ Delphi のピアは、あいだに変換レイヤーを挟むことなくブラウザのピアと通信できます。

デモ

パッケージには 4 つのデモが、いずれも完全なソース付きで同梱されています。Demos\35.P2P\05.RTCPeerConnection は 2 つのピアを接続するもので、ローカルで実行できる小さな WebSocket シグナリングサーバーが付いています。Demos\35.P2P\06.DataChannel は SCTP のデータチャネルを開き、その上でテキストとバイナリのペイロードを送ります。Demos\30.WebRTC_Protocol\03.AudioCallDemos\30.WebRTC_Protocol\02.VideoCall はメディアのデモで、マイクからスピーカーへ、カメラから画面へという内容です。

提供について

sgcWebRTC は sgcWebSockets Enterprise のアドオンです。Standard エディションと Professional エディションでは利用できず、All-Access バンドルには含まれています。Single、Team、Site の各ライセンスがあり、いずれも完全なソースコードと 1 年間のアップデートが付きます。

Delphi 7 から Delphi 13 Florence まで、および対応する C++ Builder のバージョンをサポートし、Windows、Linux、macOS、iOS、Android で動作します。別途ダウンロードする必要はありません。お使いの IDE バージョン向けの体験版インストーラーにすでに含まれています。

メディアエンジンは Delphi と C++ Builder のみです。sgcWebSockets の .NET 版には、従来のブラウザ間シナリオ向けの WebSocket シグナリングリレーである TsgcWSProtocol_WebRTC_Server が含まれていますが、TsgcRTCPeerConnection に相当する .NET のクラスはありません。

製品ページ · 機能の詳細 · 体験版をダウンロード · 価格

ご質問やご意見はありませんか。お問い合わせください。コードを書いた本人から返信いたします。