---
title: WebRTC
url: https://doc.liz6.com/ja/networking/10-real-time-communication/01-WebRTC
locale: ja
area: networking
tags:
- WebRTC
- signaling
- SDP
- ICE
- Trickle ICE
- STUN
- TURN
- DTLS-SRTP
- SCTP
- Data Channel
- GCC
- simulcast
- SVC
- SFU
- MCU
- mesh
- Opus
- networking
- real-time-communication
date: 2026-06-30
modified: 2026-07-19
description: ブラウザ間での直接オーディオ・ビデオ転送——WebRTCはSDPでメディアパラメータを交渉し、ICEでNATトラバーサルを行い、DTLS-SRTPで暗号化転送を行います。P2Pの「サーバーを経由しない」ことは性能上の利点であると同時に、デバッグの悪夢でもあります。
---

# WebRTC

> ブラウザ間での直接オーディオ・ビデオ転送——WebRTCはSDPでメディアパラメータを交渉し、ICEでNATトラバーサルを行い、DTLS-SRTPで暗号化転送を行います。P2Pの「サーバーを経由しない」ことは性能上の利点であると同時に、デバッグの悪夢でもあります。

## 概要

WebRTC（2011年）はブラウザに組み込まれたリアルタイム通信の標準規格であり、プラグインなしでビデオ通話、画面共有、P2Pデータ転送を実現します。複数のプロトコルを組み合わせています：SDPによるメディア交渉、ICEによるNAT透過、DTLS-SRTPによるメディア暗号化、SCTPによるデータチャネル。WebRTCの接続確立は非同期で行われ、candidateが一つずつ到着し、一つずつ検証されます。Trickle ICEにより接続時間が大幅に短縮されます。

## アーキテクチャスタック

<svg viewBox="0 0 720 370" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="WebRTC 架构栈:从浏览器 API 到底层传输协议">
  <defs><marker id="wrtcArchArrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="370" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">WebRTC アーキテクチャスタック: ブラウザAPIから下層トランスポートプロトコルへ</text>

  <rect x="130" y="46" width="460" height="42" rx="6" fill="#4f46e5"/>
  <text x="360" y="71" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">Browser JS API(RTCPeerConnection, RTCDataChannel)</text>
  <line x1="360" y1="88" x2="360" y2="106" stroke="#475569" stroke-width="1.7" marker-end="url(#wrtcArchArrow)"/>

  <rect x="130" y="106" width="460" height="40" rx="6" fill="#4f46e5"/>
  <text x="360" y="130" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">WebRTC C++ stack(libwebrtc, Google)</text>
  <line x1="360" y1="146" x2="360" y2="164" stroke="#475569" stroke-width="1.7" marker-end="url(#wrtcArchArrow)"/>

  <rect x="20" y="164" width="130" height="60" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="85" y="186" text-anchor="middle" font-size="11.5" font-weight="700" fill="#3730a3">SDP</text>
  <text x="85" y="202" text-anchor="middle" font-size="10" fill="#4f46e5">メディア交渉</text>
  <text x="85" y="215" text-anchor="middle" font-size="10" fill="#4f46e5">(offer/answer)</text>

  <rect x="158" y="164" width="130" height="60" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="223" y="186" text-anchor="middle" font-size="11.5" font-weight="700" fill="#115e59">ICE</text>
  <text x="223" y="202" text-anchor="middle" font-size="10" fill="#0f766e">NAT透過</text>
  <text x="223" y="215" text-anchor="middle" font-size="10" fill="#0f766e">(STUN + TURN)</text>

  <rect x="296" y="164" width="130" height="60" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="361" y="186" text-anchor="middle" font-size="11.5" font-weight="700" fill="#3730a3">DTLS</text>
  <text x="361" y="202" text-anchor="middle" font-size="10" fill="#4f46e5">鍵交換 →</text>
  <text x="361" y="215" text-anchor="middle" font-size="10" fill="#4f46e5">SRTP鍵を派生</text>

  <rect x="434" y="164" width="130" height="60" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="499" y="186" text-anchor="middle" font-size="11.5" font-weight="700" fill="#115e59">SRTP</text>
  <text x="499" y="202" text-anchor="middle" font-size="10" fill="#0f766e">暗号化された</text>
  <text x="499" y="215" text-anchor="middle" font-size="10" fill="#0f766e">RTPストリーム</text>

  <rect x="572" y="164" width="128" height="60" rx="6" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="636" y="186" text-anchor="middle" font-size="11.5" font-weight="700" fill="#334155">SCTP</text>
  <text x="636" y="202" text-anchor="middle" font-size="10" fill="#475569">データチャネル</text>

  <line x1="360" y1="224" x2="360" y2="246" stroke="#475569" stroke-width="1.7" marker-end="url(#wrtcArchArrow)"/>

  <rect x="210" y="246" width="135" height="40" rx="6" fill="#0d9488"/>
  <text x="277" y="270" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">UDP(優先)</text>
  <rect x="355" y="246" width="135" height="40" rx="6" fill="#f97316"/>
  <text x="422" y="270" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">TCP(フォールバック)</text>

  <rect x="60" y="304" width="600" height="50" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="325" font-size="12.5" fill="#3730a3">5つのプロトコルが「トランスポート層」を構成する——SDP/ICEは接続確立の交渉を担当し、DTLS/SRTPはメディアのセキュリティを管理し、</text>
  <text x="76" y="343" font-size="12.5" fill="#3730a3">SCTPはデータチャネルを管理する。すべてはUDP上で動作し、接続できない場合にのみTCPにフォールバックする。</text>
</svg>

## シグナリング: 意図的に空白とされた一环

WebRTC は**シグナリングを規定していません**——両端がSDPとICE candidateをどのように交換するかは標準では一切規定されず、WebSocket/HTTP/任意のチャネルを使って自分で実装する必要があります。これは意図的な設計です：メディアパスはP2Pである必要がありますが、「まだ接続していない2者が最初にどのように接触するか」には、双方から到達可能なサードパーティが必要です。

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="信令服务器牵线协商,媒体与数据走 P2P 直连">
  <defs>
    <marker id="wrtcSigArrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
    <marker id="wrtcSigBidir" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto-start-reverse"><path d="M0,0 L8,3 L0,6 Z" fill="#0d9488"/></marker>
  </defs>
  <rect width="720" height="300" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">シグナリングは紹介役を担当し、メディア/データはP2P直結で通信する</text>

  <rect x="250" y="55" width="220" height="58" rx="8" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="360" y="76" text-anchor="middle" font-size="12" font-weight="700" fill="#334155">シグナリングサーバー(自前構築、任意プロトコル)</text>
  <text x="360" y="97" text-anchor="middle" font-size="10.5" fill="#475569">SDP offer/answer + ICE candidateの交換</text>

  <rect x="40" y="150" width="140" height="54" rx="8" fill="#4f46e5"/>
  <text x="110" y="182" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Peer A</text>
  <rect x="540" y="150" width="140" height="54" rx="8" fill="#4f46e5"/>
  <text x="610" y="182" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Peer B</text>

  <line x1="115" y1="150" x2="262" y2="115" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcSigArrow)"/>
  <line x1="458" y1="115" x2="605" y2="150" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcSigArrow)"/>

  <line x1="180" y1="177" x2="540" y2="177" stroke="#0d9488" stroke-width="1.8" marker-start="url(#wrtcSigBidir)" marker-end="url(#wrtcSigBidir)"/>
  <text x="360" y="197" text-anchor="middle" font-size="11" font-weight="700" fill="#0f766e">(交渉完了後) SRTPメディア / SCTPデータ —— P2P直結</text>

  <rect x="40" y="225" width="640" height="52" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="56" y="246" font-size="12.5" fill="#115e59">シグナリングは「両端がどのように互いを見つけるか」だけを担い、プロトコルは限定されない(WebSocket/HTTPどちらでも可、標準は介入しない)。</text>
  <text x="56" y="264" font-size="12.5" fill="#115e59">一度offer/answerとcandidateの交換が完了すると、メディアとデータはサーバーを完全に迂回して直結する。</text>
</svg>

まずシグナリングによってoffer/answerが渡され、初めてICEが接続可能なcandidateを持てる——これがWebRTCにおける「鶏が先か卵が先か」の問題である。

## SDP 交渉

```
Caller → Callee (Offer):
  v=0
  o=- 1234567890 2 IN IP4 192.168.1.100
  s=-
  t=0 0
  a=group:BUNDLE audio video data     ← 同一トランスポートパスでのマルチプレクス
  m=audio 49170 UDP/TLS/RTP/SAVPF 111 103
  c=IN IP4 192.168.1.100
  a=rtpmap:111 opus/48000/2
  a=rtcp-mux                           ← RTPとRTCPを同一ポートで共有
  a=ice-ufrag:8sdf7g
  a=ice-pwd:asd88fg
  a=fingerprint:sha-256 AB:CD:...      ← DTLS証明書ハッシュ

Callee → Caller (Answer):
  m=audio 49180 UDP/TLS/RTP/SAVPF 111  ← 選択されたポート + コーデック
  ...

  → 双方の交渉完了 → ICEによる接続チェック開始
```

## ICE

各peerは3種類のcandidateを収集する：
1. **host**: ローカルIP:port
2. **srflx** (server reflexive): STUNサーバーから返されるパブリックIP:port
3. **relay** (relayed): TURNサーバーが割り当てる中継アドレス

各(local candidate + remote candidate)のペアに対して接続チェック(STUN binding)を実行し → 最適な利用可能なペアを選択する（優先度は host > srflx > relay、つまり「直結可能なら中継を使わない」）。

**STUNとTURNの役割分担**：STUNは単に「自分のパブリックマッピングを問い合わせているだけ」であり、メディアを転送せず、ほぼコストゼロです。しかし、両端が対称型NATの背後にあり、NATトラバーサルが失敗した場合、**TURN中継**にフォールバックする必要があります——すべてのメディアがそれを介して転送され、帯域幅のコストが高くなります。これはデプロイにおいて唯一避けられないサーバーコストです。本番環境での経験則：接続の約10〜20%が最終的にTURNに到達します。

**Trickle ICE**：すべてのcandidateの収集が完了するのを待ってからofferを送信する必要はありません。candidateは発見次第シグナリングを通じて相手側に送信され、接続チェックが行われます。これにより、接続確立時間が「最も遅いcandidateを待つ」状態から「最初の利用可能なペアが準備できるまで」に短縮され——これがWebRTCの高速接続確立の鍵です。

## DTLS-SRTP

DTLSハンドシェイク(TLS 1.2をUDP上で動作させるもの) → ハンドシェイク完了後、双方はDTLSからSRTP鍵を派生する(追加の交換は行わない)：

<svg viewBox="0 0 720 400" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="DTLS 握手流程:从 ClientHello 到派生 SRTP 密钥">
  <defs><marker id="wrtcDtlsArrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">DTLS ハンドシェイク: ClientHelloからSRTP鍵の派生へ</text>

  <rect x="30" y="55" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="105" y="83" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">ClientHello</text>
  <line x1="180" y1="78" x2="200" y2="78" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="200" y="55" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="275" y="83" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">ServerHello</text>
  <line x1="350" y1="78" x2="370" y2="78" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="370" y="55" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="445" y="83" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">Certificate</text>
  <line x1="520" y1="78" x2="540" y2="78" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="540" y="55" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="615" y="76" text-anchor="middle" font-size="10.5" font-weight="700" fill="#3730a3">ServerKey-</text>
  <text x="615" y="90" text-anchor="middle" font-size="10.5" font-weight="700" fill="#3730a3">Exchange</text>

  <polyline points="615,101 615,116 190,116 190,131" fill="none" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="115" y="131" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="190" y="152" text-anchor="middle" font-size="10.5" font-weight="700" fill="#3730a3">ClientKey-</text>
  <text x="190" y="166" text-anchor="middle" font-size="10.5" font-weight="700" fill="#3730a3">Exchange</text>
  <line x1="265" y1="154" x2="285" y2="154" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="285" y="131" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="152" text-anchor="middle" font-size="10.5" font-weight="700" fill="#3730a3">ChangeCipher-</text>
  <text x="360" y="166" text-anchor="middle" font-size="10.5" font-weight="700" fill="#3730a3">Spec</text>
  <line x1="435" y1="154" x2="455" y2="154" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="455" y="131" width="150" height="46" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="530" y="159" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">Finished</text>

  <polyline points="530,177 530,192 360,192 360,207" fill="none" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="160" y="207" width="400" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="234" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">DTLS-SRTP拡張: どのSRTP暗号スイートを使用するかを交渉</text>

  <line x1="360" y1="251" x2="360" y2="266" stroke="#475569" stroke-width="1.6" marker-end="url(#wrtcDtlsArrow)"/>

  <rect x="110" y="266" width="500" height="48" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="360" y="287" text-anchor="middle" font-size="12" font-weight="700" fill="#166534">双方はDTLS master_secretからSRTP鍵を派生する</text>
  <text x="360" y="304" text-anchor="middle" font-size="11" fill="#15803d">SRTP keys = SRTP_KDF(DTLS master_secret)</text>

  <rect x="40" y="330" width="640" height="54" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="56" y="351" font-size="12.5" fill="#3730a3">SRTP鍵は個別に転送されない——今回のDTLSハンドシェイクで生成されたmaster secretから直接派生され、</text>
  <text x="56" y="369" font-size="12.5" fill="#3730a3">このハンドシェイクと自然に紐付けられ、追加の鍵交換ラウンドを省略する。</text>
</svg>

## Data Channel

DTLS上に覆いかぶさるSCTP (Stream Control Transmission Protocol)：

```
TCPと比較して：
  - マルチストリーム (HOLブロッキングなし)
  - メッセージ境界を保持
  - 設定可能：信頼性あり (reliable) / 部分的信頼性 (timed reliability)

適した用途：ファイル転送、ゲームステート、チャット
```

## 輻輳制御とSimulcast

リアルタイムメディアはTCPのようにパケット損失と再送に頼ることができない——フレームのデッドラインはわずか十数ミリ秒であり、再送を待つと必ずタイムアウトする。WebRTCは**GCC (Google Congestion Control)**を使用する：RTPパケットの到着に基づく**レイテンシ勾配**で輻輳を予測する（キューが増加中 → 早期にビットレートを低下）、実際にパケットが損失するのを待つのではなく、送信側はこれに基づいてエンコードビットレートを動的に調整する。

**Simulcast / SVC** は、マルチユーザー会議における異種帯域幅の問題を解決する：同じカメラ入力から**複数の品質レベル**（例：180p/360p/720p）を同時にエンコードし、ミドルウェアサーバーが各受信者のネットワーク状況に応じて適切な品質レベルを個別に配信する——これが以下のSFUが「視聴者ごとに品質を選択できる」前提である。

## SFU / MCU / Mesh — マルチユーザートポロジー

WebRTCは本来P2Pであるが、マルチユーザーの場合P2Pはスケーラビリティに欠けるため、サーバーの導入が必須となる。3つのトポロジー（[ストリーミングプロトコル](/networking/10-real-time-communication/02-streaming-protocols.md)における配信のトレードオフと同根）：

<svg viewBox="0 0 720 355" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="多人拓扑三选一:Mesh、SFU、MCU 的取舍对比">
  <rect width="720" height="355" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">マルチユーザートポロジーの選択: Mesh / SFU / MCU</text>

  <rect x="40" y="55" width="90" height="60" rx="6" fill="#94a3b8"/>
  <text x="85" y="90" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">Mesh</text>
  <rect x="145" y="55" width="535" height="60" rx="6" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="160" y="80" font-size="11.5" fill="#334155">N人が両者間で直結 → 各ユーザーがN-1本のアップストリーム</text>
  <text x="160" y="100" font-size="11.5" font-weight="700" fill="#475569">3〜4人のみ利用可能、サーバー不要</text>

  <rect x="40" y="127" width="90" height="64" rx="6" fill="#0d9488"/>
  <text x="85" y="163" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">SFU</text>
  <rect x="145" y="127" width="535" height="64" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="160" y="150" font-size="11" fill="#0f766e">選択的転送: サーバーはデコードせず転送のみ、各ユーザーはアップストリーム1本、必要に応じて配信(simulcastと連携)</text>
  <text x="160" y="172" font-size="11.5" font-weight="700" fill="#115e59">→ 主流の解決策、スケーラブル、サーバーCPU負荷が軽い(トランスコード不要)</text>

  <rect x="40" y="203" width="90" height="60" rx="6" fill="#f97316"/>
  <text x="85" y="238" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">MCU</text>
  <rect x="145" y="203" width="535" height="60" rx="6" fill="#ffedd5" stroke="#fdba74"/>
  <text x="160" y="228" font-size="11.5" fill="#9a3412">ミキシング: サーバーがすべてのストリームをデコード + 1つのストリームに合成して配信</text>
  <text x="160" y="248" font-size="11.5" font-weight="700" fill="#c2410c">→ 受信側のダウンストリーム/計算リソースを節約できるが、サーバーCPUコストが極めて高い</text>

  <rect x="40" y="277" width="640" height="54" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="56" y="298" font-size="12.5" fill="#115e59">SFUは転送のみでデコードしないため、スケーラビリティとサーバーコストのバランスが取れており、マルチユーザー会議の主流ソリューションである。</text>
  <text x="56" y="316" font-size="12.5" fill="#115e59">Meshは超小規模な場合にのみ適しており、MCUはサーバーの計算リソースでクライアントの軽量さを引き換えるが、コストが最も高くなる。</text>
</svg>

**なぜWebRTCはHLSのようにCDNで展開しにくいのか**：各接続には状態がある（ICE/DTLS/SRTPのコンテキスト + リアルタイムの輻輳制御フィードバック）ため、キャッシュ可能なファイルではない。大規模展開はエッジキャッシュではなくSFUのカスケードによってのみ可能——これは「サブ秒のリアルタイム性」がもたらす根本的な代償である。

## 参考

- **webrtc.org**: webrtc.googlesource.com
- **ツール**: `chrome://webrtc-internals`

*Keywords: WebRTC, signaling, SDP, ICE, Trickle ICE, STUN, TURN, DTLS-SRTP, SCTP, Data Channel, GCC, simulcast, SVC, SFU, MCU, mesh, Opus*
