WebRTC 的三个关键技术(理论强化篇)
网络传输中,数据包的到达时间不均匀,这种现象称为抖动(Jitter)。发送时间: |──10ms──|──10ms──|──10ms──|──10ms──|到达时间: |──8ms──|──15ms──|──5ms──|──12ms──|抖动 = 到达间隔的变化WebRTC 使用算法进行带宽估计和拥塞控制。│ GCC 算法架构 ││ ││ │ 发送端 BWE │ ││ │ │ ││ │ │ 延迟梯
WebRTC 的三个关键技术(理论强化篇)
本文是 WebRTC 系列专栏的第四篇,将深入剖析 WebRTC 背后的三大核心技术:NAT 穿透、音视频实时传输协议、以及音频处理与带宽控制。理解这些技术原理,将帮助你更好地优化 WebRTC 应用。
目录
1. NAT 穿透
1.1 为什么需要 NAT 穿透?
NAT 的背景
NAT(Network Address Translation,网络地址转换) 是解决 IPv4 地址枯竭问题的关键技术。它允许多个设备共享一个公网 IP 地址。
┌─────────────────────────────────────────────────────────────┐
│ 互联网 │
│ 公网 IP: 203.0.113.1 │
└─────────────────────────────────────────────────────────────┘
│
│
┌─────────┴─────────┐
│ NAT 路由器 │
│ 公网: 203.0.113.1 │
│ 私网: 192.168.1.1 │
└─────────┬─────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 设备 A │ │ 设备 B │ │ 设备 C │
│192.168. │ │192.168. │ │192.168. │
│ 1.100 │ │ 1.101 │ │ 1.102 │
└─────────┘ └─────────┘ └─────────┘
P2P 通信的挑战
当两个位于 NAT 后面的设备想要直接通信时,会遇到问题:
设备 A (192.168.1.100) 设备 B (10.0.0.50)
│ │
│ NAT A NAT B │
│ (203.0.113.1) (198.51.100.1) │
│ │
└────────────── ??? ─────────────────────┘
问题:A 不知道 B 的公网地址和端口
B 不知道 A 的公网地址和端口
NAT 会阻止未经请求的入站连接
1.2 NAT 的类型
根据 RFC 3489,NAT 可分为四种类型:
1. Full Cone NAT(完全锥形)
内部地址 192.168.1.100:5000
↓ NAT 映射
外部地址 203.0.113.1:8000
特点:任何外部主机都可以通过 203.0.113.1:8000 访问内部设备
穿透难度:★☆☆☆☆ (最容易)
2. Restricted Cone NAT(受限锥形)
内部地址 192.168.1.100:5000
↓ NAT 映射
外部地址 203.0.113.1:8000
特点:只有内部设备曾经发送过数据的外部 IP 才能回复
限制:IP 地址限制
穿透难度:★★☆☆☆
3. Port Restricted Cone NAT(端口受限锥形)
内部地址 192.168.1.100:5000
↓ NAT 映射
外部地址 203.0.113.1:8000
特点:只有内部设备曾经发送过数据的外部 IP:Port 才能回复
限制:IP 地址 + 端口限制
穿透难度:★★★☆☆
4. Symmetric NAT(对称型)
内部地址 192.168.1.100:5000
↓ 发送到不同目标,映射不同
发送到 Server1 → 203.0.113.1:8000
发送到 Server2 → 203.0.113.1:8001
特点:每个目标地址使用不同的外部端口
穿透难度:★★★★★ (最难)
NAT 类型穿透成功率
| NAT A \ NAT B | Full Cone | Restricted | Port Restricted | Symmetric |
|---|---|---|---|---|
| Full Cone | ✅ 100% | ✅ 100% | ✅ 100% | ✅ 100% |
| Restricted | ✅ 100% | ✅ 95% | ✅ 90% | ⚠️ 70% |
| Port Restricted | ✅ 100% | ✅ 90% | ✅ 85% | ⚠️ 50% |
| Symmetric | ✅ 100% | ⚠️ 70% | ⚠️ 50% | ❌ 10% |
1.3 ICE 框架
ICE(Interactive Connectivity Establishment) 是 WebRTC 用于 NAT 穿透的核心框架,定义在 RFC 8445。
ICE 工作流程
┌─────────────────────────────────────────────────────────────────┐
│ ICE 工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 候选收集 (Candidate Gathering) │
│ ├── Host 候选:本地 IP 地址 │
│ ├── Server Reflexive 候选:通过 STUN 获取的公网地址 │
│ └── Relay 候选:TURN 服务器分配的中继地址 │
│ │
│ 2. 候选交换 (Candidate Exchange) │
│ └── 通过信令服务器交换所有候选 │
│ │
│ 3. 连通性检查 (Connectivity Checks) │
│ └── 对所有候选对进行 STUN Binding 请求测试 │
│ │
│ 4. 候选对排序 (Candidate Pair Prioritization) │
│ └── 根据优先级选择最佳路径 │
│ │
│ 5. 连接建立 (Connection Establishment) │
│ └── 使用最优候选对建立连接 │
│ │
└─────────────────────────────────────────────────────────────────┘
ICE 候选类型
// ICE 候选示例
{
// Host 候选 - 本地地址
candidate: "candidate:1 1 UDP 2122252543 192.168.1.100 54321 typ host",
// Server Reflexive 候选 - STUN 获取的公网地址
candidate: "candidate:2 1 UDP 1686052863 203.0.113.1 12345 typ srflx raddr 192.168.1.100 rport 54321",
// Relay 候选 - TURN 中继地址
candidate: "candidate:3 1 UDP 41885439 198.51.100.1 3478 typ relay raddr 203.0.113.1 rport 12345"
}
候选优先级
ICE 按以下顺序尝试连接:
| 优先级 | 候选类型 | 说明 |
|---|---|---|
| 1 (最高) | Host | 直接使用本地 IP(局域网内最快) |
| 2 | Server Reflexive | 通过 STUN 获取的公网地址 |
| 3 | Peer Reflexive | 连通性检查中发现的地址 |
| 4 (最低) | Relay | TURN 中继(保底方案) |
1.4 STUN 协议
STUN(Session Traversal Utilities for NAT) 用于发现公网地址和 NAT 类型。
STUN 工作原理
┌──────────────┐ ┌──────────────┐
│ Client │ │ STUN Server │
│ 192.168.1.100│ │ 203.0.113.50 │
└──────┬───────┘ └──────┬───────┘
│ │
│ 1. Binding Request │
│ (源: 192.168.1.100:5000) │
│ ─────────────────────────────────────────> │
│ │
│ NAT 转换 │
│ (源变为: 203.0.113.1:8000) │
│ │
│ 2. Binding Response │
│ (你的公网地址是 203.0.113.1:8000) │
│ <───────────────────────────────────────── │
│ │
STUN 消息格式
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic Cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transaction ID (96 bits) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Attributes... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
常用 STUN 服务器
const iceServers = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
{ urls: 'stun:stun4.l.google.com:19302' },
{ urls: 'stun:stun.stunprotocol.org:3478' }
];
1.5 TURN 协议
TURN(Traversal Using Relays around NAT) 是 STUN 的扩展,当 P2P 穿透失败时提供中继服务。
TURN 工作原理
┌──────────────┐ ┌──────────────┐
│ Client A │ │ Client B │
│ 192.168.1.100│ │ 10.0.0.50 │
└──────┬───────┘ └──────┬───────┘
│ │
│ ┌──────────────────┐ │
│ │ TURN Server │ │
│ │ 198.51.100.1 │ │
│ └────────┬─────────┘ │
│ │ │
│ 1. Allocate │ │
│ ───────────────> │ │
│ │ │
│ 2. 分配中继地址 │ │
│ 198.51.100.1: │ │
│ 49152 │ │
│ <─────────────── │ │
│ │ │
│ 3. 媒体数据 │ 4. 转发媒体数据 │
│ ═══════════════> │ ═══════════════════════> │
│ │ │
│ <═══════════════ │ <═══════════════════════ │
│ 6. 转发媒体数据 │ 5. 媒体数据 │
│ │ │
TURN 配置示例
const iceServers = [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'password'
},
{
urls: 'turn:turn.example.com:443?transport=tcp',
username: 'user',
credential: 'password'
},
{
urls: 'turns:turn.example.com:443', // TURN over TLS
username: 'user',
credential: 'password'
}
];
TURN 服务器选型
| 开源方案 | 特点 |
|---|---|
| coturn | 最流行,功能完整,支持 STUN/TURN/ICE |
| Pion TURN | Go 语言实现,轻量级 |
| eturnal | Erlang 实现,高并发 |
| 商业服务 | 特点 |
|---|---|
| Twilio | 全球节点,按量计费 |
| Xirsys | 专注 WebRTC,易于集成 |
| Google Cloud | 与 GCP 集成 |
1.6 ICE 状态机
┌─────────────────────────────────────────────────────────────────┐
│ ICE 连接状态 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ new │ │
│ └────┬────┘ │
│ │ 开始收集候选 │
│ ▼ │
│ ┌─────────┐ │
│ │checking │ ←─────────────┐ │
│ └────┬────┘ │ │
│ │ │ 重新检查 │
│ ┌──────────────┼──────────────┐ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ │ │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ │ │
│ │connected │ │ completed │ │ failed │─┘ │
│ └────┬─────┘ └─────┬─────┘ └──────────┘ │
│ │ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │disconnected │ │ │
│ └──────┬──────┘ │ │
│ │ │ │
│ └──────────────┴───────────────┐ │
│ ▼ │
│ ┌──────────┐ │
│ │ closed │ │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
状态说明
| 状态 | 说明 |
|---|---|
new |
初始状态,ICE 代理刚创建 |
checking |
正在进行连通性检查 |
connected |
至少找到一个可用的候选对 |
completed |
所有候选对检查完成,已选择最优路径 |
failed |
所有候选对检查失败 |
disconnected |
连接暂时中断 |
closed |
ICE 代理已关闭 |
2. 音视频实时传输协议
2.1 协议栈概览
┌─────────────────────────────────────────────────────────────────┐
│ WebRTC 协议栈 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ 音视频数据 / DataChannel 数据 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ SRTP │ │ SCTP │ │
│ │ (加密媒体传输) │ │ (数据通道传输) │ │
│ └───────────┬─────────────┘ └───────────┬─────────────┘ │
│ │ │ │
│ └───────────────┬───────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DTLS │ │
│ │ (密钥交换与加密) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ICE │ │
│ │ (NAT 穿透) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ UDP / TCP │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 RTP(Real-time Transport Protocol)
RTP 是实时音视频传输的基础协议,定义在 RFC 3550。
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Synchronization Source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| Contributing Source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Payload |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
RTP 头部字段说明
| 字段 | 位数 | 说明 |
|---|---|---|
| V (Version) | 2 | RTP 版本,固定为 2 |
| P (Padding) | 1 | 是否有填充 |
| X (Extension) | 1 | 是否有扩展头 |
| CC (CSRC Count) | 4 | CSRC 标识符数量 |
| M (Marker) | 1 | 标记位,如视频帧结束 |
| PT (Payload Type) | 7 | 负载类型(编解码器) |
| Sequence Number | 16 | 序列号,用于检测丢包和排序 |
| Timestamp | 32 | 时间戳,用于同步 |
| SSRC | 32 | 同步源标识符 |
常见 Payload Type
| PT | 编解码器 | 媒体类型 | 采样率 |
|---|---|---|---|
| 0 | PCMU | Audio | 8000 Hz |
| 8 | PCMA | Audio | 8000 Hz |
| 96-127 | 动态分配 | - | - |
WebRTC 常用动态 PT:
- 111: Opus (音频)
- 96: VP8 (视频)
- 98: VP9 (视频)
- 102: H.264 (视频)
2.3 RTCP(RTP Control Protocol)
RTCP 用于传输控制信息,与 RTP 配合使用。
RTCP 包类型
| 类型 | 名称 | 说明 |
|---|---|---|
| 200 | SR (Sender Report) | 发送端报告 |
| 201 | RR (Receiver Report) | 接收端报告 |
| 202 | SDES (Source Description) | 源描述 |
| 203 | BYE | 结束通知 |
| 204 | APP | 应用自定义 |
| 205 | RTPFB (Transport Layer FB) | 传输层反馈 |
| 206 | PSFB (Payload-specific FB) | 负载特定反馈 |
Sender Report (SR) 格式
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| RC | PT=SR=200 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of sender |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| NTP timestamp, most significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, least significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's packet count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's octet count |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| Report Block(s)... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
重要的 RTCP 反馈消息
NACK(Negative Acknowledgement)
用于请求重传丢失的包:
┌──────────────┐ ┌──────────────┐
│ Sender │ │ Receiver │
└──────┬───────┘ └──────┬───────┘
│ │
│ RTP #1, #2, #3, #5, #6 │
│ ─────────────────────────────────────────> │
│ │
│ 检测到 #4 丢失 │
│ │
│ RTCP NACK (请求重传 #4) │
│ <───────────────────────────────────────── │
│ │
│ RTP #4 (重传) │
│ ─────────────────────────────────────────> │
│ │
PLI(Picture Loss Indication)
请求发送关键帧:
// 当检测到视频解码问题时
// 接收端发送 PLI 请求关键帧
REMB(Receiver Estimated Maximum Bitrate)
接收端带宽估计:
接收端估计可用带宽 → 发送 REMB → 发送端调整码率
2.4 SRTP(Secure RTP)
SRTP 是 RTP 的加密版本,WebRTC 强制使用。
SRTP 加密流程
┌─────────────────────────────────────────────────────────────────┐
│ SRTP 加密流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ RTP 包 │ │
│ │ (明文) │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ SRTP 加密 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ 密钥派生 │→ │ AES-CM 加密 │→ │ HMAC-SHA1 认证 │ │ │
│ │ │ (DTLS 协商) │ │ (负载加密) │ │ (完整性保护) │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ SRTP 包 │ │
│ │ (密文) │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
SRTP 包格式
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header (12 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Encrypted Payload |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Authentication Tag |
| (10 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.5 DTLS(Datagram TLS)
DTLS 用于在 UDP 上提供 TLS 安全性,负责 SRTP 密钥交换。
DTLS 握手流程
┌──────────────┐ ┌──────────────┐
│ Client │ │ Server │
└──────┬───────┘ └──────┬───────┘
│ │
│ ClientHello │
│ (支持的加密套件、随机数) │
│ ─────────────────────────────────────────> │
│ │
│ ServerHello │
│ HelloVerifyRequest (防止 DoS) │
│ <───────────────────────────────────────── │
│ │
│ ClientHello (带 Cookie) │
│ ─────────────────────────────────────────> │
│ │
│ ServerHello, Certificate, │
│ ServerKeyExchange, CertificateRequest, │
│ ServerHelloDone │
│ <───────────────────────────────────────── │
│ │
│ Certificate, ClientKeyExchange, │
│ CertificateVerify, ChangeCipherSpec, │
│ Finished │
│ ─────────────────────────────────────────> │
│ │
│ ChangeCipherSpec, Finished │
│ <───────────────────────────────────────── │
│ │
│ ═══════ 安全通道建立,导出 SRTP 密钥 ═══════ │
│ │
DTLS-SRTP 密钥导出
DTLS 主密钥
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 密钥导出函数 (KDF) │
│ │
│ PRF(master_secret, "EXTRACTOR-dtls_srtp", │
│ client_random + server_random) │
│ │
└─────────────────────────────────────────────────────────────┘
│
├── SRTP 加密密钥 (Client)
├── SRTP 加密密钥 (Server)
├── SRTP 认证密钥 (Client)
├── SRTP 认证密钥 (Server)
├── SRTP 盐值 (Client)
└── SRTP 盐值 (Server)
3. 回声消除、抗抖动与带宽控制
3.1 回声消除(AEC)
回声产生原因
┌─────────────────────────────────────────────────────────────────┐
│ 回声产生示意图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 用户 A 用户 B │
│ ┌─────────┐ ┌─────────┐ │
│ │ 麦克风 │ │ 扬声器 │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ │ 1. A 说话 │ │
│ │ ─────────────────────────────────────> │ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────┐ │
│ │ │ B 的扬声器播放 │ │
│ │ │ A 的声音 │ │
│ │ └────────┬────────┘ │
│ │ │ │
│ │ ┌────────▼────────┐ │
│ │ │ B 的麦克风采集 │ │
│ │ │ 扬声器的声音 │ │
│ │ └────────┬────────┘ │
│ │ │ │
│ │ 2. 回声传回 A │ │
│ │ <───────────────────────────────────── │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────┐ │ │
│ │ A 听到 │ │ │
│ │ 自己的 │ │ │
│ │ 回声! │ │ │
│ └─────────┘ │ │
│ │
└─────────────────────────────────────────────────────────────────┘
AEC 工作原理
┌─────────────────────────────────────────────────────────────────┐
│ AEC 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 远端信号 (扬声器播放) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 自适应滤波器 │ │
│ │ │ │
│ │ 估计房间的声学特性(回声路径) │ │
│ │ 生成回声估计信号 │ │
│ │ │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ 回声估计 │
│ ┌─────────┐ │
│ 麦克风信号 ────────> │ - │ ────────> 输出信号 │
│ (语音 + 回声) │ 减法器 │ (仅语音) │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
WebRTC AEC3 特点
WebRTC 使用第三代回声消除算法(AEC3):
| 特性 | 说明 |
|---|---|
| 自适应滤波器 | 基于 NLMS(归一化最小均方)算法 |
| 延迟估计 | 自动检测扬声器到麦克风的延迟 |
| 非线性处理 | 处理扬声器失真产生的非线性回声 |
| 双讲检测 | 检测双方同时说话的情况 |
启用 AEC
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true, // 启用回声消除
echoCancellationType: 'system' // 或 'browser'
}
});
3.2 噪声抑制(NS)
噪声类型
| 类型 | 示例 |
|---|---|
| 稳态噪声 | 空调声、风扇声、电流声 |
| 非稳态噪声 | 键盘声、咳嗽声、门铃声 |
| 背景人声 | 其他人的说话声 |
NS 工作原理
┌─────────────────────────────────────────────────────────────────┐
│ 噪声抑制流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 输入信号 (语音 + 噪声) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 频谱分析 │ │
│ │ (FFT 变换) │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 噪声估计 │ │
│ │ │ │
│ │ • 语音活动检测 (VAD) │ │
│ │ • 在静音段估计噪声频谱 │ │
│ │ • 持续更新噪声模型 │ │
│ │ │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 频谱减法 │ │
│ │ │ │
│ │ 输出频谱 = 输入频谱 - α × 噪声频谱 │ │
│ │ │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 频谱合成 │ │
│ │ (IFFT 变换) │ │
│ └────────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 输出信号 (语音) │
│ │
└─────────────────────────────────────────────────────────────────┘
启用噪声抑制
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
noiseSuppression: true, // 启用噪声抑制
autoGainControl: true // 自动增益控制
}
});
3.3 抖动缓冲(Jitter Buffer)
什么是抖动?
网络传输中,数据包的到达时间不均匀,这种现象称为抖动(Jitter)。
发送时间: |──10ms──|──10ms──|──10ms──|──10ms──|
P1 P2 P3 P4 P5
到达时间: |──8ms──|──15ms──|──5ms──|──12ms──|
P1 P2 P3 P4 P5
抖动 = 到达间隔的变化
抖动缓冲工作原理
┌─────────────────────────────────────────────────────────────────┐
│ 抖动缓冲原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 网络接收 抖动缓冲 播放输出 │
│ │
│ P1 ──┐ ┌─────────────┐ │
│ │ │ ┌───┬───┬───┤ │
│ P3 ──┼──────────────────>│ │P1 │P2 │P3 │──────────> 均匀播放 │
│ │ │ └───┴───┴───┤ │
│ P2 ──┘ │ 缓冲区 │ │
│ └─────────────┘ │
│ │
│ 乱序到达 重新排序 平滑输出 │
│ 不均匀间隔 缓冲延迟 均匀间隔 │
│ │
└─────────────────────────────────────────────────────────────────┘
自适应抖动缓冲
WebRTC 使用自适应抖动缓冲,根据网络状况动态调整缓冲大小:
| 网络状况 | 缓冲大小 | 延迟 |
|---|---|---|
| 稳定 | 小 | 低 |
| 抖动大 | 大 | 高 |
| 丢包多 | 大 | 高 |
抖动小 → 缓冲小 → 延迟低
↑ ↓
└─────────┘
动态调整
抖动大 → 缓冲大 → 延迟高
↑ ↓
└─────────┘
动态调整
3.4 带宽控制(BWE)
GCC 算法概述
WebRTC 使用 GCC(Google Congestion Control) 算法进行带宽估计和拥塞控制。
┌─────────────────────────────────────────────────────────────────┐
│ GCC 算法架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 发送端 BWE │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 延迟梯度 │ │ 丢包率 │ │ 带宽估计 │ │ │
│ │ │ 检测器 │───>│ 检测器 │───>│ 融合器 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Pacer │ │
│ │ (发送节奏控制) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 编码器码率控制 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
延迟梯度检测
基于包间延迟变化检测拥塞:
发送间隔: Δs = t_send(i) - t_send(i-1)
接收间隔: Δr = t_recv(i) - t_recv(i-1)
延迟梯度: d = Δr - Δs
d > 0 → 队列增长 → 拥塞
d < 0 → 队列减少 → 空闲
d ≈ 0 → 稳定
带宽调整策略
┌─────────────────────────────────────────────────────────────────┐
│ 带宽调整状态机 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ │
│ ┌────────>│ Increase │<────────┐ │
│ │ │ (增加) │ │ │
│ │ └─────┬─────┘ │ │
│ │ │ │ │
│ 空闲检测 拥塞检测 空闲检测 │
│ │ │ │ │
│ │ ▼ │ │
│ ┌────┴────┐ ┌───────────┐ ┌───┴─────┐ │
│ │ Hold │<───│ Decrease │───>│ Hold │ │
│ │ (保持) │ │ (减少) │ │ (保持) │ │
│ └─────────┘ └───────────┘ └─────────┘ │
│ │
│ Increase: 带宽 = 带宽 × 1.08 (每秒) │
│ Decrease: 带宽 = 带宽 × 0.85 (立即) │
│ Hold: 带宽保持不变 │
│ │
└─────────────────────────────────────────────────────────────────┘
Transport-wide Congestion Control
WebRTC 使用 Transport-wide CC 扩展进行更精确的带宽估计:
发送端:
每个 RTP 包添加 transport-wide 序列号
接收端:
定期发送 RTCP Transport Feedback
包含每个包的接收时间
发送端:
根据反馈计算延迟梯度
估计可用带宽
获取带宽统计
// 获取连接统计信息
const stats = await peerConnection.getStats();
stats.forEach(report => {
if (report.type === 'outbound-rtp' && report.kind === 'video') {
console.log('视频发送统计:', {
bytesSent: report.bytesSent,
packetsSent: report.packetsSent,
targetBitrate: report.targetBitrate,
// 计算实际码率
bitrate: (report.bytesSent * 8) / (report.timestamp / 1000)
});
}
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
console.log('连接统计:', {
availableOutgoingBitrate: report.availableOutgoingBitrate,
currentRoundTripTime: report.currentRoundTripTime
});
}
});
3.5 前向纠错(FEC)
FEC 通过发送冗余数据来恢复丢失的包,无需重传。
FEC 工作原理
┌─────────────────────────────────────────────────────────────────┐
│ FEC 工作原理 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 发送端: │
│ ┌─────┬─────┬─────┬─────┐ │
│ │ P1 │ P2 │ P3 │ P4 │ 原始数据包 │
│ └──┬──┴──┬──┴──┬──┴──┬──┘ │
│ │ │ │ │ │
│ └─────┴─────┴─────┘ │
│ │ │
│ ▼ XOR │
│ ┌─────────┐ │
│ │ FEC │ 冗余包 = P1 ⊕ P2 ⊕ P3 ⊕ P4 │
│ └─────────┘ │
│ │
│ 传输: P1, P2, P3, P4, FEC │
│ │
│ 接收端 (假设 P3 丢失): │
│ ┌─────┬─────┬─────┬─────┐ │
│ │ P1 │ P2 │ ? │ P4 │ │
│ └──┬──┴──┬──┴─────┴──┬──┘ │
│ │ │ │ │
│ └─────┴───────────┘ │
│ │ │
│ ▼ XOR with FEC │
│ ┌─────────┐ │
│ │ P3 │ P3 = P1 ⊕ P2 ⊕ P4 ⊕ FEC │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
WebRTC 中的 FEC
WebRTC 支持多种 FEC 方案:
| 方案 | 适用场景 | 开销 |
|---|---|---|
| Opus FEC | 音频 | 低 |
| FlexFEC | 视频 | 可配置 |
| RED (Redundant Encoding) | 音频 | 中 |
4. 总结
核心技术回顾
| 技术领域 | 关键技术 | 作用 |
|---|---|---|
| NAT 穿透 | ICE/STUN/TURN | 建立 P2P 连接 |
| 媒体传输 | RTP/RTCP/SRTP | 实时传输与加密 |
| 音频处理 | AEC/NS/AGC | 提升音频质量 |
| 网络适应 | 抖动缓冲/BWE/FEC | 适应网络变化 |
技术选型建议
┌─────────────────────────────────────────────────────────────────┐
│ 技术选型决策树 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ NAT 穿透方案: │
│ ├── 企业内网 → 仅 STUN │
│ ├── 公网用户 → STUN + TURN │
│ └── 高可用要求 → 多 TURN 服务器 │
│ │
│ 音频处理: │
│ ├── 普通场景 → 启用 AEC + NS + AGC │
│ ├── 音乐场景 → 关闭 AEC,保留 AGC │
│ └── 专业设备 → 可关闭所有处理 │
│ │
│ 带宽控制: │
│ ├── 稳定网络 → 固定码率 │
│ ├── 移动网络 → 自适应码率 │
│ └── 弱网环境 → 启用 FEC + 降低分辨率 │
│ │
└─────────────────────────────────────────────────────────────────┘
下一篇预告
在下一篇文章中,我们将全面梳理 WebRTC 的 API 全景图,包括:
- getUserMedia 详解
- RTCPeerConnection 完整 API
- RTCRtpSender / Receiver
- RTCDataChannel 高级用法
参考资料
- RFC 8445 - Interactive Connectivity Establishment (ICE)
- RFC 5389 - Session Traversal Utilities for NAT (STUN)
- RFC 5766 - Traversal Using Relays around NAT (TURN)
- RFC 3550 - RTP: A Transport Protocol for Real-Time Applications
- RFC 3711 - The Secure Real-time Transport Protocol (SRTP)
- WebRTC for the Curious - Media Communication
- Google Congestion Control Algorithm
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)