WebRTC — Kiến trúc Video Call P2P trực tiếp trên trình duyệt

Posted on: 4/27/2026 10:18:36 AM

Mỗi ngày, hàng tỷ phút video call diễn ra trên Google Meet, Zoom, Discord và hàng trăm ứng dụng khác — tất cả đều chạy trên cùng một nền tảng: WebRTC. Đây là bộ API trình duyệt cho phép truyền audio, video và dữ liệu trực tiếp giữa các peer mà không cần plugin, không cần Flash, không cần cài đặt thêm bất kỳ thứ gì. Bài viết này đi sâu vào kiến trúc WebRTC từ giao thức mạng đến triển khai production với SFU, giúp bạn hiểu rõ cách xây dựng hệ thống real-time communication quy mô lớn.

7.7 tỷ+ Phút WebRTC mỗi tuần trên toàn cầu
<200ms Latency trung bình P2P với STUN
98% Trình duyệt hỗ trợ WebRTC (2026)
85% Kết nối thành công qua STUN, không cần TURN

1. WebRTC là gì và hoạt động như thế nào?

WebRTC (Web Real-Time Communication) là tập hợp các API và giao thức chuẩn W3C/IETF cho phép trình duyệt và ứng dụng native thiết lập kết nối peer-to-peer để truyền media và dữ liệu. Khác với mô hình client-server truyền thống, WebRTC cho phép hai thiết bị giao tiếp trực tiếp — giảm latency, tiết kiệm bandwidth server và đơn giản hóa kiến trúc cho các use case real-time.

Ba API cốt lõi của WebRTC trong trình duyệt:

  • MediaStream (getUserMedia) — Truy cập camera, microphone và screen capture
  • RTCPeerConnection — Thiết lập kết nối P2P, xử lý codec, mã hóa SRTP và quản lý ICE candidate
  • RTCDataChannel — Kênh truyền dữ liệu tùy ý (text, file, game state) qua SCTP với reliable hoặc unreliable mode
graph TD
    A["getUserMedia()
Camera + Mic"] --> B["MediaStream
Audio/Video Tracks"] B --> C["RTCPeerConnection
Mã hóa + ICE + DTLS"] C --> D{"NAT Traversal"} D -->|STUN thành công| E["P2P Direct
~85% trường hợp"] D -->|STUN thất bại| F["TURN Relay
~15% trường hợp"] E --> G["Remote Peer
Nhận stream"] F --> G C --> H["RTCDataChannel
Dữ liệu tùy ý"] H --> G style A fill:#e94560,stroke:#fff,color:#fff style C fill:#2c3e50,stroke:#fff,color:#fff style G fill:#4CAF50,stroke:#fff,color:#fff style D fill:#ff9800,stroke:#fff,color:#fff

Kiến trúc tổng quan WebRTC — từ MediaStream đến kết nối P2P

2. Signaling — Bước đầu tiên mà WebRTC không định nghĩa

WebRTC cố tình không quy định giao thức signaling. Đây là thiết kế có chủ đích — cho phép developer chọn bất kỳ kênh truyền nào phù hợp: WebSocket, HTTP long-polling, Firebase Realtime Database, hoặc thậm chí email. Signaling chỉ làm một việc: trao đổi thông tin cần thiết để hai peer tìm thấy nhau và thỏa thuận codec.

Quy trình signaling bao gồm 3 bước chính:

sequenceDiagram
    participant A as Peer A (Caller)
    participant S as Signaling Server
    participant B as Peer B (Callee)

    A->>S: 1. Tạo Offer (SDP)
    S->>B: Forward Offer
    B->>S: 2. Tạo Answer (SDP)
    S->>A: Forward Answer
    A->>S: 3. Gửi ICE Candidates
    S->>B: Forward ICE Candidates
    B->>S: Gửi ICE Candidates
    S->>A: Forward ICE Candidates
    A-->>B: Kết nối P2P thiết lập!

Quy trình Signaling — trao đổi SDP và ICE Candidates qua server trung gian

SDP (Session Description Protocol) là định dạng văn bản mô tả khả năng media của mỗi peer: codec hỗ trợ (VP9, H.264, Opus), bandwidth, địa chỉ IP/port. Khi Peer A tạo offer và Peer B trả answer, hai bên đã thỏa thuận xong codec và encryption parameters.

Mẹo triển khai Signaling Server

Với ứng dụng nhỏ (<1000 concurrent users), một WebSocket server đơn giản trên Node.js hoặc ASP.NET Core SignalR là đủ. Khi scale lên, hãy dùng Redis Pub/Sub làm message broker giữa các signaling node để đảm bảo mọi peer đều nhận được ICE candidates đúng lúc.

3. NAT Traversal — STUN, TURN và ICE Framework

Thách thức lớn nhất của P2P là NAT (Network Address Translation). Hầu hết thiết bị nằm sau router NAT, không có IP public trực tiếp. WebRTC giải quyết bằng ICE (Interactive Connectivity Establishment) — framework thử tất cả đường đi có thể và chọn đường tốt nhất.

3.1 STUN — Khám phá IP public

STUN (Session Traversal Utilities for NAT) server giúp client biết IP public và port mapping của mình. Client gửi request đến STUN server, server trả lại địa chỉ public mà nó thấy. Quá trình này nhẹ và nhanh — chỉ vài gói UDP. Google cung cấp STUN server miễn phí tại stun:stun.l.google.com:19302.

STUN hoạt động với khoảng 85% cấu hình NAT thông thường (Full Cone, Restricted Cone, Port Restricted Cone). Tuy nhiên, Symmetric NAT — thường gặp trong mạng doanh nghiệp — sẽ chặn STUN vì mỗi destination khác nhau được NAT sang port khác nhau.

3.2 TURN — Relay khi P2P thất bại

TURN (Traversal Using Relays around NAT) là phương án dự phòng: tất cả media đi qua TURN server làm trung gian. Điều này tốn bandwidth server đáng kể — mỗi stream video 720p tiêu thụ ~1.5 Mbps, nhân đôi qua relay — nên TURN chỉ dùng khi STUN thất bại.

Chi phí TURN không hề rẻ

Một TURN server xử lý 500 cuộc gọi video 1-1 đồng thời cần ~1.5 Gbps bandwidth. Với cloud pricing trung bình $0.08/GB, chi phí bandwidth có thể lên tới $500–800/ngày. Hãy luôn ưu tiên STUN và chỉ fallback sang TURN khi cần. Dùng coturn (open-source) và deploy gần user để giảm latency.

3.3 ICE — Tìm đường tốt nhất

ICE framework thu thập tất cả ICE candidates (địa chỉ có thể kết nối) từ 3 nguồn: host candidates (IP local), server reflexive candidates (từ STUN) và relay candidates (từ TURN). Sau đó ICE thực hiện connectivity checks theo thứ tự ưu tiên — ưu tiên P2P trực tiếp, fallback qua TURN nếu cần.

graph LR
    A["ICE Agent"] --> B["Host Candidate
IP local: 192.168.1.5:4532"] A --> C["Server Reflexive
STUN: 203.0.113.5:6789"] A --> D["Relay Candidate
TURN: 198.51.100.2:3478"] B --> E{"Connectivity
Check"} C --> E D --> E E -->|Ưu tiên 1| F["Direct P2P"] E -->|Ưu tiên 2| G["STUN-assisted P2P"] E -->|Ưu tiên 3| H["TURN Relay"] style A fill:#e94560,stroke:#fff,color:#fff style E fill:#ff9800,stroke:#fff,color:#fff style F fill:#4CAF50,stroke:#fff,color:#fff

ICE Framework — thu thập candidates và chọn đường kết nối tối ưu

4. Media Pipeline — Codec, Encryption và Adaptive Bitrate

4.1 Audio & Video Codec

WebRTC bắt buộc hỗ trợ các codec sau:

Loại Codec bắt buộc Codec tùy chọn (phổ biến) Đặc điểm
Audio Opus G.711, iSAC Opus: 6–510 kbps, adaptive bitrate, 48kHz. Tốt nhất cho voice + music
Video VP8, H.264 VP9, AV1, H.265 VP9 tiết kiệm 30-50% bandwidth so với VP8. AV1 mới nhất nhưng tốn CPU encode

4.2 Encryption bắt buộc

Mọi kết nối WebRTC đều bắt buộc mã hóa — không có tùy chọn tắt. Stack mã hóa gồm:

  • DTLS (Datagram Transport Layer Security) — Handshake và trao đổi khóa, tương tự TLS nhưng cho UDP
  • SRTP (Secure Real-time Transport Protocol) — Mã hóa payload audio/video bằng AES-128
  • SCTP over DTLS — Mã hóa dữ liệu trên RTCDataChannel

4.3 Adaptive Bitrate & Congestion Control

WebRTC sử dụng thuật toán GCC (Google Congestion Control) để tự động điều chỉnh bitrate dựa trên điều kiện mạng. Khi phát hiện packet loss hoặc tăng latency, encoder sẽ giảm resolution/framerate/bitrate. Khi mạng cải thiện, chất lượng tự động tăng trở lại. Đây là lý do video call thường "mờ" vài giây rồi rõ lại — GCC đang hoạt động.

Từ năm 2025, các trình duyệt bắt đầu hỗ trợ Simulcast — gửi cùng lúc nhiều layer chất lượng (ví dụ: 1080p + 720p + 360p). Receiver hoặc SFU chọn layer phù hợp với bandwidth hiện tại, tránh việc re-encode tốn CPU.

5. Kiến trúc Production — P2P, SFU và MCU

Kết nối P2P thuần túy chỉ hoạt động tốt cho cuộc gọi 1-1. Khi có 3+ participants, mô hình full mesh (mỗi peer kết nối với mọi peer khác) không scale được — N participants cần N×(N-1)/2 kết nối. Với 10 người, mỗi thiết bị phải encode và gửi 9 stream riêng biệt.

graph TD
    subgraph "P2P Mesh — Tối đa 4-5 người"
        P1["Peer 1"] <--> P2["Peer 2"]
        P1 <--> P3["Peer 3"]
        P2 <--> P3
    end

    subgraph "SFU — Hàng trăm người"
        S1["Peer 1"] --> SFU["SFU Server
Forward streams"] S2["Peer 2"] --> SFU S3["Peer 3"] --> SFU S4["Peer N"] --> SFU SFU --> S1 SFU --> S2 SFU --> S3 SFU --> S4 end subgraph "MCU — Bandwidth thấp" M1["Peer 1"] --> MCU["MCU Server
Mix + Re-encode"] M2["Peer 2"] --> MCU M3["Peer 3"] --> MCU MCU --> M1 MCU --> M2 MCU --> M3 end style SFU fill:#e94560,stroke:#fff,color:#fff style MCU fill:#2c3e50,stroke:#fff,color:#fff

Ba kiến trúc WebRTC: Mesh (P2P), SFU (Selective Forwarding) và MCU (Mixing)

5.1 SFU — Lựa chọn #1 cho Production 2026

SFU (Selective Forwarding Unit) là kiến trúc thống trị cho WebRTC production. Mỗi participant gửi 1 stream lên SFU, SFU forward stream đến tất cả participant còn lại — không decode, không re-encode. Ưu điểm:

  • CPU thấp trên server — chỉ forward packet, không xử lý media
  • Latency thấp — không có bước decode/encode giữa chừng
  • Scale tốt — mỗi SFU node xử lý 500-1000 concurrent streams
  • Simulcast tương thích — SFU chọn layer phù hợp cho từng receiver

5.2 MCU — Khi bandwidth client là vấn đề

MCU (Multipoint Control Unit) decode tất cả incoming streams, mix thành một layout duy nhất, re-encode và gửi cho mỗi participant. Client chỉ cần nhận 1 stream — tiết kiệm bandwidth downstream. Nhưng MCU tốn CPU server cực lớn và thêm 200-500ms latency do decode/encode. MCU phù hợp cho: thiết bị IoT yếu, kết nối mobile 3G, hoặc recording/broadcasting.

5.3 So sánh chi tiết SFU vs MCU

Tiêu chí SFU MCU
CPU Server Thấp (forward only) Rất cao (decode + mix + encode)
Latency thêm ~10-50ms ~200-500ms
Bandwidth Client (downstream) Cao (nhận N-1 streams) Thấp (nhận 1 stream)
Scale Tốt — 500-1000 streams/node Hạn chế — 50-100 participants/node
Chất lượng video Gốc (không re-encode) Giảm (qua re-encoding)
Simulcast Hỗ trợ native Không cần (đã mix)
Use case phù hợp Video conference, live streaming IoT, legacy device, recording

6. Open-Source SFU — LiveKit, mediasoup và Janus

Ba SFU phổ biến nhất trong cộng đồng open-source, mỗi cái phù hợp với ngữ cảnh khác nhau:

6.1 LiveKit — SFU hiện đại viết bằng Go

LiveKit nổi lên như lựa chọn hàng đầu cho team muốn ship nhanh. Viết bằng Go, tận dụng goroutine cho concurrent connections. Có sẵn SDK cho JavaScript, React, Swift, Kotlin, Flutter, Unity và cả server-side SDK cho Node.js, Python, Go, .NET. LiveKit cung cấp luôn signaling, room management và recording — không cần tự build.

// LiveKit JavaScript Client — kết nối room
import { Room, RoomEvent } from 'livekit-client';

const room = new Room();
await room.connect('wss://your-livekit-server.com', token);

room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
  const element = track.attach();
  document.getElementById('remote-video').appendChild(element);
});

// Publish local camera
const localTracks = await room.localParticipant.enableCameraAndMicrophone();

6.2 mediasoup — SFU hiệu năng cao với C++ core

mediasoup có core viết bằng C++ để tối ưu hiệu năng xử lý media, signaling layer bằng Node.js. Kiến trúc Worker-based: mỗi CPU core chạy một Worker process, xử lý media routing cho nhiều room. mediasoup cho phép kiểm soát chi tiết từng transport, producer, consumer — phù hợp cho team muốn customize sâu.

6.3 Janus Gateway — Đa năng với plugin architecture

Janus viết bằng C, ra đời từ 2014, là SFU lâu đời nhất và đa năng nhất. Kiến trúc plugin cho phép mở rộng: VideoRoom (SFU), AudioBridge (audio mixing), Streaming (one-to-many), SIP Gateway, Record/Play. Janus phù hợp khi cần tích hợp với hệ thống VoIP/SIP legacy.

Tiêu chí LiveKit mediasoup Janus
Ngôn ngữ Go C++ (core) + Node.js C
Khởi tạo Nhanh — SDK all-in-one Trung bình — cần build signaling Trung bình — cần chọn plugin
Customization Trung bình Rất cao Cao (plugin system)
Scalability Built-in multi-node Tự triển khai Tự triển khai
.NET SDK Có (server-side) Không chính thức Không
Recording Built-in (Egress) Tự implement Plugin Record/Play
Phù hợp Startup, ship nhanh Custom platform, scale lớn SIP/VoIP, legacy integration

7. Encoded Transform — End-to-End Encryption thực sự

Mặc định, WebRTC mã hóa hop-by-hop bằng DTLS-SRTP — nghĩa là SFU server có thể thấy media dạng rõ (plaintext) khi forward. Với ứng dụng nhạy cảm (y tế, tài chính), điều này không đủ.

WebRTC Encoded Transform API (W3C Working Draft, cập nhật 02/2026) cho phép chèn một bước xử lý vào pipeline giữa encoder và packetizer. Developer có thể mã hóa payload bằng khóa riêng trước khi gửi lên SFU — SFU chỉ forward được encrypted payload, không đọc được nội dung. Đây là true E2EE (End-to-End Encryption).

// Encoded Transform — mã hóa frame trước khi gửi
const sender = peerConnection.getSenders()[0];
const senderStreams = sender.createEncodedStreams();
const transformStream = new TransformStream({
  transform(encodedFrame, controller) {
    // Mã hóa payload bằng AES-GCM
    const encryptedData = encryptFrame(encodedFrame.data, sharedKey);
    encodedFrame.data = encryptedData;
    controller.enqueue(encodedFrame);
  }
});
senderStreams.readable
  .pipeThrough(transformStream)
  .pipeTo(senderStreams.writable);

Hỗ trợ trình duyệt Encoded Transform (04/2026)

Chrome/Edge: hỗ trợ đầy đủ từ Chrome 86+. Firefox: đang implement RTCRtpScriptTransform (spec mới nhất). Safari: chưa hỗ trợ — cần polyfill hoặc fallback. Nếu target audience chủ yếu dùng Chrome/Edge (>75% thị phần), có thể triển khai E2EE ngay.

8. Triển khai Production — Kiến trúc tham khảo

Dưới đây là kiến trúc production cho hệ thống video conference hỗ trợ 10,000+ concurrent users:

graph TD
    Client["Client App
Vue.js + LiveKit SDK"] -->|WebSocket| LB["Load Balancer
Geographic DNS"] LB --> SIG["Signaling Cluster
3+ nodes"] SIG -->|Redis Pub/Sub| SIG SIG --> SFU1["SFU Node 1
Region: Asia"] SIG --> SFU2["SFU Node 2
Region: EU"] SIG --> SFU3["SFU Node 3
Region: US"] SFU1 --> TURN1["TURN Server
coturn — Asia"] SFU2 --> TURN2["TURN Server
coturn — EU"] SFU3 --> TURN3["TURN Server
coturn — US"] SFU1 --> REC["Recording Service
Egress to S3/R2"] SFU1 --> MON["Monitoring
Prometheus + Grafana"] style Client fill:#e94560,stroke:#fff,color:#fff style SIG fill:#2c3e50,stroke:#fff,color:#fff style SFU1 fill:#16213e,stroke:#fff,color:#fff style SFU2 fill:#16213e,stroke:#fff,color:#fff style SFU3 fill:#16213e,stroke:#fff,color:#fff style MON fill:#4CAF50,stroke:#fff,color:#fff

Kiến trúc production multi-region cho WebRTC — SFU cluster với geographic routing

8.1 Checklist triển khai

  • TURN server — Deploy coturn ở mỗi region, cấu hình TLS (port 443) để bypass corporate firewall
  • Bandwidth planning — Mỗi participant video 720p: ~1.5 Mbps up + 1.5×(N-1) Mbps down (SFU mode). Với simulcast: down giảm 40-60%
  • Monitoring — Track metrics: ICE connection time, packet loss rate, bitrate adaptation, TURN usage percentage
  • Fallback strategy — Khi SFU node quá tải, redirect participant sang node khác. LiveKit có built-in load balancing
  • Recording — Dùng composite recording (MCU-style) cho archive, hoặc individual track recording cho post-processing

8.2 Code mẫu — Signaling Server với ASP.NET Core

// SignalingHub.cs — WebRTC signaling qua SignalR
public class SignalingHub : Hub
{
    public async Task JoinRoom(string roomId)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomId);
        await Clients.OthersInGroup(roomId).SendAsync("user-joined", Context.ConnectionId);
    }

    public async Task SendOffer(string targetId, string sdp)
    {
        await Clients.Client(targetId).SendAsync("offer", Context.ConnectionId, sdp);
    }

    public async Task SendAnswer(string targetId, string sdp)
    {
        await Clients.Client(targetId).SendAsync("answer", Context.ConnectionId, sdp);
    }

    public async Task SendIceCandidate(string targetId, string candidate)
    {
        await Clients.Client(targetId).SendAsync("ice-candidate", Context.ConnectionId, candidate);
    }
}
// Client — Vue.js + WebRTC
const pc = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'turn:turn.example.com:443', username: 'user', credential: 'pass' }
  ]
});

const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach(track => pc.addTrack(track, stream));

pc.onicecandidate = ({ candidate }) => {
  if (candidate) signalR.invoke('SendIceCandidate', targetId, JSON.stringify(candidate));
};

// Tạo offer và gửi qua signaling
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signalR.invoke('SendOffer', targetId, JSON.stringify(offer));

9. Tối ưu hiệu năng WebRTC

9.1 Simulcast & SVC

Gửi nhiều layer chất lượng cùng lúc, SFU chọn layer phù hợp cho từng receiver. Cấu hình simulcast trong WebRTC:

const sender = pc.addTrack(videoTrack, stream);
const params = sender.getParameters();
params.encodings = [
  { rid: 'low', maxBitrate: 200000, scaleResolutionDownBy: 4 },
  { rid: 'mid', maxBitrate: 700000, scaleResolutionDownBy: 2 },
  { rid: 'high', maxBitrate: 2500000 }
];
await sender.setParameters(params);

9.2 Bandwidth Estimation

Sử dụng RTCPeerConnection.getStats() để monitor real-time:

setInterval(async () => {
  const stats = await pc.getStats();
  stats.forEach(report => {
    if (report.type === 'outbound-rtp' && report.kind === 'video') {
      console.log(`Bitrate: ${report.bytesSent}, Frames: ${report.framesEncoded}`);
      console.log(`Quality limit: ${report.qualityLimitationReason}`);
    }
  });
}, 2000);

9.3 Network Quality Indicator

Đo packet loss và round-trip time để hiển thị quality indicator cho user:

  • Tốt: packet loss < 1%, RTT < 150ms
  • Trung bình: packet loss 1-5%, RTT 150-300ms
  • Kém: packet loss > 5%, RTT > 300ms → tự động giảm resolution

10. Use case thực tế ngoài Video Call

WebRTC không chỉ dành cho video conference:

  • Screen SharinggetDisplayMedia() API cho phép capture màn hình, cửa sổ hoặc tab cụ thể
  • File Transfer P2P — RTCDataChannel cho phép gửi file trực tiếp giữa browser, không qua server. Tốc độ có thể đạt 100+ Mbps trên mạng LAN
  • Cloud Gaming — Stream gameplay từ server, nhận input từ client qua DataChannel. Google Stadia (đã đóng) và Xbox Cloud Gaming đều dùng WebRTC
  • IoT & Robotics — Điều khiển robot/drone qua DataChannel, nhận video feed qua media stream
  • Live Streaming — WHIP (WebRTC HTTP Ingestion Protocol) cho phép publish live stream lên CDN qua WebRTC, thay thế RTMP truyền thống

WHIP & WHEP — Chuẩn mới cho Live Streaming

WHIP (WebRTC HTTP Ingestion Protocol) chuẩn hóa cách publish stream lên server. WHEP (WebRTC HTTP Egress Protocol) chuẩn hóa cách viewer subscribe stream. Cả hai đã được Cloudflare Stream, AWS IVS và nhiều CDN hỗ trợ. Đây là tương lai thay thế RTMP cho live streaming với latency sub-second.

Kết luận

WebRTC đã trưởng thành từ một thí nghiệm của Google thành nền tảng chuẩn cho mọi ứng dụng real-time communication trên web. Với kiến trúc SFU, bạn có thể xây dựng hệ thống video conference quy mô hàng nghìn người dùng đồng thời. Encoded Transform API mở ra khả năng end-to-end encryption thực sự. Và với WHIP/WHEP, WebRTC đang mở rộng sang cả lĩnh vực live streaming.

Điểm mấu chốt khi triển khai: bắt đầu với LiveKit nếu cần ship nhanh, mediasoup nếu cần customize sâu, và Janus nếu cần tích hợp VoIP legacy. Luôn deploy TURN server ở mỗi region và monitor ICE connection metrics để đảm bảo trải nghiệm tốt nhất cho user.

Tham khảo:
WebRTC Official — webrtc.org
MDN Web Docs — WebRTC API
W3C WebRTC Specification
W3C WebRTC Encoded Transform
LiveKit Documentation
mediasoup Documentation
Janus Gateway Documentation
BlogGeek.me — WebRTC Open Source Media Servers
V100.ai — Fastest WebRTC Server in 2026