---
title: WebRTC
url: https://doc.liz6.com/en/networking/10-real-time-communication/01-WebRTC
locale: en
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-16
description: Direct audio/video transmission between browsers—WebRTC uses SDP to negotiate media parameters, ICE to punch through NATs, and DTLS-SRTP for encrypted transmission. P2P’s "no server" nature is a performance advantage and a debugging nightmare.
---

# WebRTC

> Direct audio/video transmission between browsers—WebRTC uses SDP to negotiate media parameters, ICE to punch through NATs, and DTLS-SRTP for encrypted transmission. P2P’s "no server" nature is a performance advantage and a debugging nightmare.

## Overview

WebRTC (2011) is a browser-native real-time communication standard that enables video calls, screen sharing, and P2P data transfer without plugins. It combines several protocols: SDP for media negotiation, ICE for NAT traversal, DTLS-SRTP for media encryption, and SCTP for data channels. WebRTC connection establishment is fully asynchronous—candidates arrive and are validated one by one—while Trickle ICE significantly reduces connection time.

## Architecture Stack

<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 Architecture Stack: From Browser API to Underlying Transport Protocol">
  <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 Architecture Stack: From Browser API to Underlying Transport</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">Media Negotiation</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 Traversal</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">Key Exchange →</text>
  <text x="361" y="215" text-anchor="middle" font-size="10" fill="#4f46e5">Derive SRTP Keys</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">Encrypted</text>
  <text x="499" y="215" text-anchor="middle" font-size="10" fill="#0f766e">RTP Stream</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">Data Channel</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 (Preferred)</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 (Fallback)</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">These five protocols form the "transport layer"—SDP/ICE handles connection negotiation, DTLS/SRTP manages media security,</text>
  <text x="76" y="343" font-size="12.5" fill="#3730a3">and SCTP handles data channels; all run over UDP, falling back to TCP only if UDP fails.</text>
</svg>

## Signaling: The Intentionally Blank Spot

WebRTC **does not specify signaling**—how endpoints exchange SDP and ICE candidates is entirely up to you to implement via WebSocket/HTTP/any channel. This is intentional: the media path must be P2P, but "how two people who aren't connected yet meet" inherently requires a third party reachable by both.

<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="Signaling Server Facilitates Negotiation, Media and Data Go Direct 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">Signaling Facilitates Connection, Media/Data Goes Direct 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">Signaling Server (Self-hosted, Any Protocol)</text>
  <text x="360" y="97" text-anchor="middle" font-size="10.5" fill="#475569">Exchange 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">(After Negotiation) SRTP Media / SCTP Data —— Direct 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">Signaling only handles "how endpoints find each other"; the protocol is not specified (WebSocket/HTTP both work, standard doesn't care);</text>
  <text x="56" y="264" font-size="12.5" fill="#115e59">Once offer/answer and candidates are exchanged, media and data bypass the server entirely for direct connection.</text>
</svg>

Signaling must deliver the offer/answer first before ICE has candidates to connect to—this is the "chicken-and-egg" problem of WebRTC.

## SDP Negotiation

```
Caller → Callee (Offer):
  v=0
  o=- 1234567890 2 IN IP4 192.168.1.100
  s=-
  t=0 0
  a=group:BUNDLE audio video data     ← Multiplex multiple streams on one transport
  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 and RTCP on the same port
  a=ice-ufrag:8sdf7g
  a=ice-pwd:asd88fg
  a=fingerprint:sha-256 AB:CD:...      ← DTLS certificate hash

Callee → Caller (Answer):
  m=audio 49180 UDP/TLS/RTP/SAVPF 111  ← Selected port + codec
  ...

  → Negotiation Complete → ICE Connectivity Checks Begin
```

## ICE

Each peer collects three types of candidates:
1. **host**: Local IP:port
2. **srflx** (server reflexive): Public IP:port returned by STUN server
3. **relay** (relayed): Relay address allocated by TURN server

For each pair (local candidate + remote candidate), perform a connectivity check (STUN binding) → Select the best available pair (priority: host > srflx > relay, i.e., "prefer direct connection over relay").

**STUN vs TURN Division of Labor**: STUN simply "asks for your public mapping," does not relay media, and has near-zero cost; but when both ends are behind symmetric NATs and hole punching fails, you must fall back to a **TURN relay**—all media is forwarded through it, incurring high bandwidth costs. This is the only unavoidable server cost in deployment. Production experience: about 10-20% of connections ultimately fall back to TURN.

**Trickle ICE**: No need to wait for all candidates to be collected before sending an offer. Candidates are sent to the peer via signaling as they are discovered, and connectivity checks are performed concurrently, compressing connection time from "waiting for the slowest candidate" to "first available pair ready"—this is the key to WebRTC's fast connection establishment.

## DTLS-SRTP

DTLS handshake (TLS 1.2-like over UDP) → After handshake completion, both parties derive SRTP keys from DTLS (no additional exchange):

<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 Handshake Flow: From ClientHello to Deriving SRTP Keys">
  <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 Handshake: From ClientHello to Deriving SRTP Keys</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 Extension: Negotiate which SRTP cipher suite to use</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">Both parties derive SRTP keys from DTLS master_secret</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 keys are no longer transmitted separately—derived directly from the master secret generated by this DTLS handshake,</text>
  <text x="56" y="369" font-size="12.5" fill="#3730a3">naturally bound to this handshake, saving an extra round of key exchange.</text>
</svg>

## Data Channel

SCTP (Stream Control Transmission Protocol) layered over DTLS:

```
Compared to TCP:
  - Multiple streams (no HOL blocking)
  - Preserves message boundaries
  - Configurable: reliable (reliable) / partially reliable (timed reliability)

Suitable for: File transfer, game state, chat
```

## Congestion Control and Simulcast

Real-time media cannot rely on packet loss + retransmission like TCP—the deadline for a single frame is only tens of milliseconds; waiting for retransmission will inevitably miss it. WebRTC uses **GCC (Google Congestion Control)**: it predicts congestion based on the **delay gradient** of RTP packet arrivals (queue growing → reduce bitrate early), rather than waiting for actual packet loss, and the sender dynamically adjusts the encoding bitrate accordingly.

**Simulcast / SVC** solves heterogeneous bandwidth issues in multi-person conferences: the same camera stream is **encoded in multiple tiers simultaneously** (e.g., 180p/360p/720p), and a middle-tier server delivers the appropriate tier to each receiver based on their network—this is the prerequisite for the SFU below to "select tiers per user."

## SFU / MCU / Mesh — Multi-person Topology

WebRTC is inherently P2P, but P2P does not scale for multi-person scenarios, requiring server introduction. Three topologies (with the same distribution trade-offs as in [Streaming Protocols](/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="Multi-person Topology Trade-offs: Mesh, SFU, MCU Comparison">
  <rect width="720" height="355" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Multi-person Topology Trade-offs: 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 people connect pairwise → Each person uploads N-1 streams</text>
  <text x="160" y="100" font-size="11.5" font-weight="700" fill="#475569">Only usable for 3-4 people, no server</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">Selective forwarding: Server only forwards, does not decode; each person uploads 1 stream, downloads as needed (with simulcast)</text>
  <text x="160" y="172" font-size="11.5" font-weight="700" fill="#115e59">→ Mainstream solution, scalable, low server CPU (no transcoding)</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">Mixing: Server decodes all streams + composes one stream to send</text>
  <text x="160" y="248" font-size="11.5" font-weight="700" fill="#c2410c">→ Saves receiver downstream/compute, but server CPU is very expensive</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 only forwards, does not decode; it is the balance point between scalability and server cost, the mainstream solution for multi-person conferences;</text>
  <text x="56" y="316" font-size="12.5" fill="#115e59">Mesh is only suitable for very small scales, MCU uses server compute to replace client lightness but has the highest cost.</text>
</svg>

**Why WebRTC is hard to deploy on CDN like HLS**: Each connection is stateful (ICE/DTLS/SRTP context + real-time congestion feedback), not a cacheable file. Scaling can only be done via SFU cascading, not edge caching—this is the fundamental cost paid for "sub-second real-time."

## References

- **webrtc.org**: webrtc.googlesource.com
- **Tools**: `chrome://webrtc-internals`

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