11-3. 視訊通話架構完整解析
深入理解 SIP+RTP+SDP 如何協同實現視訊通話系統
📹 視訊通話架構完整解析
🎯 視訊通話使用什麼協定?
💡 比喻:打電話 = 訊號系統 + 語音傳輸
視訊通話 = SIP(訊號) + RTP(媒體) + SDP(協商)完整答案:
視訊通話不是單一協定,而是多個協定協同工作:
| 協定層 | 協定 | 功能 | 比喻 |
|---|---|---|---|
| 訊號層 | SIP | 建立/終止通話 | 撥號系統 📞 |
| 描述層 | SDP | 協商媒體參數 | 通話規格書 📋 |
| 傳輸層 | RTP | 傳輸音視訊 | 實際通話 🎙️📹 |
| 控制層 | RTCP | 監控品質 | 通話品質監測 📊 |
面試標準答案:
視訊通話主要使用 SIP 協定作為訊號控制,配合 RTP/RTCP 傳輸音視訊數據,並透過 SDP 協商編碼器、解析度等參數。現代系統常結合 WebRTC 技術實現瀏覽器端的視訊通話。
🏗️ 完整架構圖
兩人視訊通話
┌─────────────────────────────────────────────────────┐
│ 視訊通話完整堆疊 │
└─────────────────────────────────────────────────────┘
Alice Bob
┌──────────┐ ┌──────────┐
│ 應用層 │ │ 應用層 │
│ (UI) │ │ (UI) │
└──────────┘ └──────────┘
↓ ↓
┌──────────┐ SIP Signaling Server ┌──────────┐
│ SIP │ ←─────────────────────────────→ │ SIP │
│ Client │ (INVITE, ACK, BYE) │ Client │
└──────────┘ └──────────┘
↓ ↓
┌──────────┐ Exchange SDP ┌──────────┐
│ SDP │ ←─────────────────────────────→ │ SDP │
└──────────┘ (協商編碼器、解析度) └──────────┘
↓ ↓
┌──────────┐ ┌──────────┐
│ RTP │ ←══════════════════════════════→ │ RTP │
│ (音視訊) │ Direct P2P Connection │ (音視訊) │
└──────────┘ └──────────┘
↓ ↓
┌──────────┐ ┌──────────┐
│ RTCP │ ←──────────────────────────────→ │ RTCP │
│ (品質監控)│ (Feedback, Statistics) │ (品質監控)│
└──────────┘ └──────────┘
↓ ↓
┌──────────┐ ┌──────────┐
│ UDP │ │ UDP │
└──────────┘ └──────────┘📨 協定詳解
1️⃣ SIP(訊號控制)
💡 功能:建立和終止通話
就像電話的「撥號」和「掛斷」流程:
Alice SIP Server Bob
│ │ │
├─ INVITE ───────────>│ │ Alice 撥號
│ (包含 SDP Offer) │ │
│ ├─ INVITE ───────────>│
│ │ (轉發) │
│ │ │
│ │<─ 200 OK ───────────┤ Bob 接聽
│<─ 200 OK ────────────┤ (包含 SDP Answer) │
│ │ │
├─ ACK ───────────────────────────────────────>│ 確認
│ │ │
│ [SIP 的任務完成] │
│ │ │
│<═══════════ RTP 直連(不經過 SIP Server)═══>│INVITE 訊息範例:
INVITE sip:bob@example.com SIP/2.0
Via: SIP/2.0/UDP alice.com:5060;branch=z9hG4bK776
To: Bob <sip:bob@example.com>
From: Alice <sip:alice@example.com>;tag=123
Call-ID: abc@alice.com
CSeq: 1 INVITE
Contact: <sip:alice@192.168.1.100:5060>
Content-Type: application/sdp
Content-Length: 245
v=0
o=alice 2890844526 2890844526 IN IP4 192.168.1.100
s=Video Call
c=IN IP4 192.168.1.100
t=0 0
m=audio 49170 RTP/AVP 0 8
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
m=video 51372 RTP/AVP 99
a=rtpmap:99 H264/90000
a=fmtp:99 profile-level-id=42e01f;level-asymmetry-allowed=12️⃣ SDP(媒體協商)
💡 功能:告訴對方「我支援什麼功能」
就像買手機前先看規格表SDP 內容:
v=0 # 版本
o=alice 2890844526 2890844526 IN IP4 192.168.1.100 # 發起者
s=Video Call # 會話名稱
c=IN IP4 192.168.1.100 # 連線資訊(IP)
t=0 0 # 時間(0 0 = 永久)
# 音訊媒體
m=audio 49170 RTP/AVP 0 8 # Port 49170, 支援 0(PCMU) 和 8(PCMA)
a=rtpmap:0 PCMU/8000 # 0 = PCMU, 8kHz 取樣
a=rtpmap:8 PCMA/8000 # 8 = PCMA, 8kHz 取樣
# 視訊媒體
m=video 51372 RTP/AVP 99 # Port 51372, 支援 99(H.264)
a=rtpmap:99 H264/90000 # 99 = H.264, 90kHz 時鐘頻率
a=fmtp:99 profile-level-id=42e01f # H.264 參數(Baseline Profile)
a=fmtp:99 packetization-mode=1 # 封包模式
a=framesize:99 1920-1080 # 解析度 1080p
a=framerate:30 # 幀率 30 fpsSDP Offer/Answer 協商:
Alice Offer:
m=video 51372 RTP/AVP 99 96
a=rtpmap:99 H264/90000
a=rtpmap:96 VP8/90000
(Alice 支援 H.264 和 VP8)
Bob Answer:
m=video 38060 RTP/AVP 99
a=rtpmap:99 H264/90000
(Bob 只支援 H.264)
→ 最終使用 H.2643️⃣ RTP(媒體傳輸)
💡 功能:實際傳輸音視訊封包
就像電話線傳送聲音RTP 封包格式:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC (Synchronization Source) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Data |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+欄位說明:
- PT(Payload Type):99 = H.264, 0 = PCMU, 8 = PCMA
- Sequence Number:封包序號(用於偵測丟包)
- Timestamp:時間戳(用於同步)
- SSRC:來源識別碼
Python 發送 RTP:
import socket
import struct
import time
class RTPSender:
def __init__(self, dest_ip, dest_port, payload_type):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.dest = (dest_ip, dest_port)
self.payload_type = payload_type
self.sequence = 0
self.ssrc = 12345 # 隨機 SSRC
def send_packet(self, payload):
# RTP Header (12 bytes)
version = 2
padding = 0
extension = 0
csrc_count = 0
marker = 0
# 組裝 Header
header = struct.pack(
'!BBHII',
(version << 6) | (padding << 5) | (extension << 4) | csrc_count,
(marker << 7) | self.payload_type,
self.sequence,
int(time.time() * 90000), # Timestamp(90kHz)
self.ssrc
)
# 發送封包
packet = header + payload
self.sock.sendto(packet, self.dest)
self.sequence = (self.sequence + 1) % 65536
# 使用
sender = RTPSender('192.168.1.200', 51372, payload_type=99)
# 發送 H.264 視訊幀
with open('video_frame.h264', 'rb') as f:
frame_data = f.read()
sender.send_packet(frame_data)4️⃣ RTCP(品質監控)
💡 功能:監控通話品質,回報統計資訊
就像網路速度測試RTCP 封包類型:
- SR(Sender Report):發送者報告
- RR(Receiver Report):接收者報告
- SDES(Source Description):來源描述
- BYE:離開會話
- APP:應用自訂
RTCP RR 範例(接收者報告):
class RTCPReceiver:
def send_receiver_report(self):
# 統計資訊
packets_lost = 10 # 丟包數
packets_received = 1000 # 已收到
jitter = 5 # 抖動(ms)
# RTCP RR 封包
report = {
'type': 'RR',
'ssrc': self.ssrc,
'fraction_lost': packets_lost / packets_received,
'cumulative_lost': packets_lost,
'highest_sequence': self.last_seq,
'jitter': jitter
}
# 發送 RTCP(Port = RTP Port + 1)
self.sock.sendto(
self.encode_rtcp(report),
(self.dest_ip, self.rtp_port + 1)
)應用:自適應位元率(ABR)
def on_rtcp_report(report):
packet_loss = report['fraction_lost']
if packet_loss > 0.1: # 丟包率 > 10%
# 降低視訊位元率
reduce_bitrate()
elif packet_loss < 0.01: # 丟包率 < 1%
# 提高視訊位元率
increase_bitrate()🏢 多人視訊會議架構
1️⃣ MCU(Multipoint Control Unit)混音架構
💡 比喻:電視台導播
把所有人的畫面混合成一個九宮格,再廣播給所有人┌──────────────────────────────────────┐
│ MCU Server │
│ ┌────────────────────────────────┐ │
│ │ Video Mixer(影像混合) │ │
│ │ ┌───┬───┬───┐ │ │
│ │ │ A │ B │ C │ → 合成九宮格 │ │
│ │ ├───┼───┼───┤ │ │
│ │ │ D │ E │ F │ │ │
│ │ └───┴───┴───┘ │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ Audio Mixer(音訊混合) │ │
│ │ A + B + C + D + E + F → 混音 │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
↓ ↓ ↓
Alice (1流) Bob (1流) Charlie (1流)
(下載 1 個合成流)優點:
- ✅ 客戶端負擔最低(只接收 1 個流)
- ✅ 適合弱網路環境
- ✅ 統一的視訊品質
缺點:
- ❌ 伺服器運算量極大(需要編碼/解碼)
- ❌ 延遲較高(混合需要時間)
- ❌ 無法自訂布局(固定九宮格)
架構圖:
Alice ──┐
Bob ────┼──> MCU Server
Charlie─┤ │
Dave ───┘ │ 1. 解碼所有輸入
│ 2. 混合成九宮格
│ 3. 重新編碼
│ 4. 廣播給所有人
↓
[A][B][C]
[D][E][F] ← 合成畫面
[ ][ ][ ]
↓
Alice, Bob, Charlie, Dave 都收到相同的合成畫面2️⃣ SFU(Selective Forwarding Unit)轉發架構
💡 比喻:郵局轉信
只轉發,不開拆(不編碼/解碼)┌──────────────────────────────────────┐
│ SFU Server │
│ ┌────────────────────────────────┐ │
│ │ 只轉發(不混合) │ │
│ │ │ │
│ │ Alice 的流 ───────→ Bob │ │
│ │ Alice 的流 ───────→ Charlie │ │
│ │ Bob 的流 ─────────→ Alice │ │
│ │ Bob 的流 ─────────→ Charlie │ │
│ │ ... │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
↑ ↑ ↑
Alice (上傳1個流, 下載N-1個流)
Bob (上傳1個流, 下載N-1個流)
Charlie (上傳1個流, 下載N-1個流)優點:
- ✅ 延遲極低(只轉發,不處理)
- ✅ 伺服器負擔低(不需編碼/解碼)
- ✅ 每個客戶端可自訂布局
- ✅ 支援 Simulcast(多碼率)
缺點:
- ❌ 客戶端負擔高(需解碼 N-1 個流)
- ❌ 頻寬需求高(下載 N-1 個流)
Simulcast 優化:
Alice 上傳 3 種解析度:
- High: 1080p @ 2 Mbps
- Medium: 720p @ 1 Mbps
- Low: 360p @ 300 Kbps
SFU 根據每個接收者的網路狀況選擇:
- Bob (好網路) ← 1080p
- Charlie (普通) ← 720p
- Dave (弱網路) ← 360p架構圖:
Alice (上傳 1080p/720p/360p)
↓
SFU Server
├─→ Bob (下載 1080p)
├─→ Charlie (下載 720p)
└─→ Dave (下載 360p)
好處:
- Alice 只上傳 1 次(3 種解析度)
- SFU 自動選擇適合的解析度發送
- 節省頻寬,優化體驗3️⃣ SFU vs MCU 比較
| 特性 | MCU | SFU |
|---|---|---|
| 延遲 | 高(100-300ms) | 低(10-50ms) |
| 伺服器 CPU | 極高 🔥🔥🔥 | 低 ✅ |
| 客戶端 CPU | 低 ✅ | 高 🔥🔥 |
| 頻寬(客戶端) | 低(1 個流) | 高(N-1 個流) |
| 彈性 | 低(固定布局) | 高(自訂布局) |
| 適用人數 | 大(100+) | 中(10-50) |
| 典型應用 | 大型網路研討會 | 團隊會議 |
實務建議:
10 人以下:SFU(低延遲、高品質)
10-50 人:SFU + Simulcast
50-100 人:MCU(減輕客戶端負擔)
100+ 人:MCU + 觀眾模式(只下載,不上傳)🔗 SIP + WebRTC 整合
💡 讓瀏覽器也能打 SIP 電話架構
Browser (WebRTC) Gateway SIP Client
│ │ │
├─ WebSocket ────────>│ │ WebSocket 訊號
│ (SIP over WS) │ │
│ ├─ SIP (UDP) ───────>│ 轉換為標準 SIP
│ │ │
│<────── SDP ──────────┼────── SDP ────────>│ 交換 SDP
│ │ │
│<══════ SRTP ═══════>│<═════ RTP ════════>│ 媒體流
│ (加密) │ (轉碼) │Gateway 功能:
- 協定轉換:WebSocket ↔ UDP
- 訊號轉換:SIP over WS ↔ 標準 SIP
- 媒體轉碼:SRTP(加密)↔ RTP
- NAT 穿透:處理 ICE/STUN/TURN
JsSIP + Asterisk 範例:
// 瀏覽器端(JsSIP)
const socket = new JsSIP.WebSocketInterface('wss://pbx.example.com:8089/ws');
const ua = new JsSIP.UA({
sockets: [socket],
uri: 'sip:1001@pbx.example.com',
password: 'secret',
session_timers: false
});
ua.start();
// 撥打 SIP 分機
const session = ua.call('sip:2002@pbx.example.com', {
mediaConstraints: { audio: true, video: true }
});
session.on('accepted', () => {
console.log('通話建立');
});; Asterisk 配置(/etc/asterisk/http.conf)
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
; WebSocket 支援
enablestatic=yes; /etc/asterisk/pjsip.conf
[transport-ws]
type=transport
protocol=ws
bind=0.0.0.0:8089
[1001]
type=endpoint
context=internal
disallow=all
allow=ulaw,alaw,vp8,h264
auth=1001
aors=1001
webrtc=yes ; 啟用 WebRTC
[1001]
type=auth
auth_type=userpass
password=secret
username=1001
[1001]
type=aor
max_contacts=5🎓 常見面試題
Q1:視訊通話使用什麼協定?(完整回答)
答案:
視訊通話是多協定協同工作的結果:
1. SIP(Session Initiation Protocol):
- 功能:訊號控制(建立、修改、終止通話)
- 傳輸:通常使用 UDP 5060(未加密)或 TCP 5061(TLS 加密)
- 類比:電話的撥號系統
2. SDP(Session Description Protocol):
- 功能:描述媒體參數(編碼器、解析度、IP/Port)
- 傳輸:嵌入在 SIP 訊息中(Content-Type: application/sdp)
- 類比:通話規格書
3. RTP(Real-time Transport Protocol):
- 功能:傳輸音視訊數據
- 傳輸:UDP(通常動態分配 Port)
- 類比:實際的通話內容
4. RTCP(RTP Control Protocol):
- 功能:監控品質、回報統計
- 傳輸:UDP(Port = RTP Port + 1)
- 類比:通話品質監測
完整流程:
1. SIP INVITE(包含 SDP Offer)
→ 協商編碼器、解析度
2. SIP 200 OK(包含 SDP Answer)
→ 確認使用 H.264, 1080p, Port 51372
3. SIP ACK
→ 確認收到
4. RTP 傳輸視訊(UDP Port 51372)
RTCP 監控品質(UDP Port 51373)
→ 實際視訊通話
5. SIP BYE
→ 結束通話面試加分:
- 提到 WebRTC 結合 SIP 的趨勢
- 提到 SRTP(加密的 RTP)
- 提到 ICE/STUN/TURN 處理 NAT 穿透
Q2:為什麼 RTP 使用 UDP 而不是 TCP?
答案:
💡 比喻:
UDP = 直播(寧可跳格,不要延遲)
TCP = 下載(寧可慢,也要完整)UDP 優勢(即時通訊):
- 低延遲:
TCP:封包遺失 → 重傳 → 等待 ACK → 延遲累積
UDP:封包遺失 → 直接播放下一個封包(容許跳格)
視訊通話:1 秒前的畫面重傳已無意義- 無重傳機制:
TCP 重傳:
時間 0s:發送封包 A
時間 1s:未收到 ACK,重傳封包 A
時間 2s:收到 ACK
→ 延遲 2 秒
UDP:
時間 0s:發送封包 A
時間 1s:封包 A 遺失,繼續發送封包 B
→ 延遲 0 秒(容許 A 遺失)- Head-of-Line Blocking:
TCP 問題:
封包順序:A B C D
如果 B 遺失:C 和 D 必須等待 B 重傳
→ 所有後續封包阻塞
UDP:
B 遺失 → C, D 照常播放
→ 不阻塞UDP 缺點與補救:
| 缺點 | 補救機制 |
|---|---|
| 無連線狀態 | RTCP 監控連線 |
| 封包可能亂序 | RTP Sequence Number 重排 |
| 封包可能遺失 | FEC(Forward Error Correction)冗余編碼 |
| 無流量控制 | RTCP Feedback 調整位元率 |
程式碼示範(處理亂序):
class RTPReceiver:
def __init__(self):
self.buffer = {} # 緩衝區
self.last_played = 0
def on_packet(self, packet):
seq = packet.sequence_number
# 放入緩衝區
self.buffer[seq] = packet
# 嘗試播放連續的封包
while (self.last_played + 1) in self.buffer:
self.play(self.buffer[self.last_played + 1])
del self.buffer[self.last_played + 1]
self.last_played += 1
# 超時處理(跳過遺失的封包)
if seq > self.last_played + 10:
print(f"封包 {self.last_played + 1} 遺失,跳過")
self.last_played = seq - 1Q3:如何優化視訊通話品質?
答案:
多層優化策略:
1. 網路層優化:
# QoS(Quality of Service)標記
import socket
# 建立 RTP Socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 設定 DSCP(Differentiated Services Code Point)
# EF (Expedited Forwarding) = 46,最高優先級
sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 46 << 2)
# 發送 RTP 封包(路由器會優先處理)
sock.sendto(rtp_packet, (dest_ip, dest_port))2. 自適應位元率(ABR):
// 根據 RTCP 報告調整位元率
peerConnection.getStats().then(stats => {
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
const packetsLost = report.packetsLost;
const packetsReceived = report.packetsReceived;
const lossRate = packetsLost / (packetsLost + packetsReceived);
if (lossRate > 0.05) { // 丟包率 > 5%
// 降低位元率
adjustBitrate('down');
} else if (lossRate < 0.01) { // 丟包率 < 1%
// 提高位元率
adjustBitrate('up');
}
}
});
}, 1000);
function adjustBitrate(direction) {
const sender = peerConnection.getSenders().find(s => s.track.kind === 'video');
const parameters = sender.getParameters();
if (direction === 'down') {
parameters.encodings[0].maxBitrate *= 0.8; // 降低 20%
} else {
parameters.encodings[0].maxBitrate *= 1.2; // 提高 20%
}
sender.setParameters(parameters);
}3. FEC(Forward Error Correction):
原理:發送冗余數據,即使部分封包遺失也能恢復
原始封包:A B C D
FEC 封包:E = A ⊕ B ⊕ C ⊕ D(XOR)
如果 B 遺失:
B = A ⊕ C ⊕ D ⊕ E(可恢復!)// 啟用 FEC(使用 libwebrtc)
const sender = peerConnection.addTransceiver('video', {
direction: 'sendonly',
sendEncodings: [{
fec: {
ssrc: 123456,
mechanism: 'red+ulpfec' // RED + ULP FEC
}
}]
}).sender;4. Jitter Buffer(抖動緩衝):
class JitterBuffer:
def __init__(self, buffer_size=50): # 50ms 緩衝
self.buffer = []
self.buffer_size = buffer_size
def add_packet(self, packet):
self.buffer.append(packet)
self.buffer.sort(key=lambda p: p.timestamp)
# 當緩衝區滿時,開始播放
if len(self.buffer) >= self.buffer_size:
packet_to_play = self.buffer.pop(0)
self.play(packet_to_play)5. Simulcast(多碼率):
// 發送 3 種解析度
const sender = peerConnection.addTransceiver('video', {
sendEncodings: [
{ rid: 'h', maxBitrate: 900000, scaleResolutionDownBy: 1 }, // 1080p
{ rid: 'm', maxBitrate: 300000, scaleResolutionDownBy: 2 }, // 540p
{ rid: 'l', maxBitrate: 100000, scaleResolutionDownBy: 4 } // 270p
]
}).sender;
// SFU 自動選擇適合的解析度發送給不同接收者
Q4:SRTP 和 RTP 有什麼不同?
答案:
RTP = Real-time Transport Protocol(未加密)
SRTP = Secure RTP(加密)差異:
| 特性 | RTP | SRTP |
|---|---|---|
| 加密 | ❌ 無 | ✅ AES 加密 |
| 認證 | ❌ 無 | ✅ HMAC-SHA1 |
| 防重放 | ❌ 無 | ✅ Sequence Number 檢查 |
| 適用場景 | 內網通話 | 公網通話 |
SRTP 封包格式:
RTP Packet:
┌────────┬─────────┐
│ Header │ Payload │
└────────┴─────────┘
SRTP Packet:
┌────────┬────────────────┬──────────┐
│ Header │ Encrypted │ Auth Tag │
│ │ Payload │ (HMAC) │
└────────┴────────────────┴──────────┘SRTP 密鑰交換(使用 DTLS-SRTP):
WebRTC 流程:
1. DTLS 握手(類似 TLS)
→ 交換憑證、協商加密演算法
2. 產生 SRTP Master Key
→ 雙方各自產生對稱金鑰
3. 使用 SRTP 傳輸音視訊
→ 所有 RTP 封包都加密JavaScript 啟用 SRTP(WebRTC 預設):
// WebRTC 自動使用 SRTP,無需額外設定
const peerConnection = new RTCPeerConnection();
// 檢查是否使用 SRTP
peerConnection.getStats().then(stats => {
stats.forEach(report => {
if (report.type === 'transport') {
console.log('DTLS State:', report.dtlsState); // "connected"
console.log('SRTP Cipher:', report.srtpCipher); // "AES_CM_128_HMAC_SHA1_80"
}
});
});Q5:如何實作斷線重連?
答案:
SIP 層重連:
class ReliableSIPUA {
constructor(config) {
this.ua = new JsSIP.UA(config);
this.setupReconnection();
}
setupReconnection() {
this.ua.on('disconnected', () => {
console.log('SIP 斷線,5 秒後重連');
setTimeout(() => {
this.ua.start();
}, 5000);
});
this.ua.on('connected', () => {
console.log('SIP 重新連線成功');
});
}
}RTP 層重連(ICE Restart):
session.on('iceconnectionstatechange', async () => {
const state = session.connection.iceConnectionState;
if (state === 'disconnected' || state === 'failed') {
console.log('RTP 斷線,執行 ICE Restart');
// ICE Restart
await session.renegotiate({
iceRestart: true // 重新進行 ICE 協商
});
}
});完整斷線處理:
class RobustVideoCall {
constructor() {
this.maxRetries = 3;
this.retryCount = 0;
}
async call(targetURI) {
try {
this.session = this.ua.call(targetURI, options);
this.session.on('failed', (event) => {
if (event.cause === 'Request Timeout' && this.retryCount < this.maxRetries) {
this.retryCount++;
console.log(`重試 ${this.retryCount}/${this.maxRetries}`);
setTimeout(() => {
this.call(targetURI); // 重試
}, 2000);
}
});
this.session.on('iceconnectionstatechange', () => {
this.handleICEState();
});
} catch (error) {
console.error('通話失敗:', error);
}
}
async handleICEState() {
const state = this.session.connection.iceConnectionState;
switch (state) {
case 'failed':
// 嘗試 ICE Restart
await this.session.renegotiate({ iceRestart: true });
break;
case 'disconnected':
// 等待 5 秒看是否自動恢復
setTimeout(async () => {
if (this.session.connection.iceConnectionState === 'disconnected') {
await this.session.renegotiate({ iceRestart: true });
}
}, 5000);
break;
case 'connected':
console.log('RTP 連線已恢復');
this.retryCount = 0; // 重置重試計數
break;
}
}
}📝 總結
視訊通話架構核心要點:
協定層:
- SIP:訊號控制(撥號、掛斷)📞
- SDP:媒體協商(編碼器、解析度)📋
- RTP:媒體傳輸(音視訊數據)🎙️📹
- RTCP:品質監控(統計、反饋)📊
架構選擇:
- 小型會議(<10 人):SFU ⚡
- 中型會議(10-50 人):SFU + Simulcast 🚀
- 大型會議(50+ 人):MCU 🏢
優化策略:
- QoS 標記優先級
- 自適應位元率(ABR)
- FEC 冗余編碼
- Jitter Buffer 抖動緩衝
- Simulcast 多碼率
安全性:
- SRTP 加密媒體
- DTLS-SRTP 密鑰交換
- TLS 加密訊號
🔗 延伸閱讀
- 上一篇:06-2. SIP 呼叫流程
- 下一章:07-1. DNS 域名解析
- RFC 3550(RTP):https://tools.ietf.org/html/rfc3550
- RFC 3711(SRTP):https://tools.ietf.org/html/rfc3711
- WebRTC 官方文件:https://webrtc.org/