WebRTC + 直播技术学习路线

目标:理解 WebRTC 原理 + 跑通 Android 推拉流 Demo + 了解移动端直播架构
节奏:工作日 30-45 分钟(看理论),周末集中写代码
整理时间:2026-06


一、背景知识:WebRTC vs 传统直播

1.1 WebRTC 不只针对 Web

WebRTC 有完整的 Android / iOS 原生库,移动端支持非常成熟:

  • Android:org.webrtc:google-webrtcio.getstream:stream-webrtc-android
  • iOS:内置 WebRTC.framework
  • Web:浏览器原生支持

1.2 三种运行模式

模式 架构 适用场景 延迟
P2P 端到端直连 1v1 通话 最低
SFU 服务器转发,不解码 多人会议、连麦
MCU 服务器解码+混流 大会议、弱网端 较高

腾讯会议、Zoom 均采用 SFU + MCU 混合模式,根据参与人数和网络状况动态切换。

1.3 传统直播 vs WebRTC 详细对比

架构对比

1
2
3
4
5
6
# 传统直播
主播 → RTMP推流 → 流媒体服务器 → CDN分发 → 观众拉流(HLS/FLV)

# WebRTC
端A <── ICE协商 ──> 端B
<── 媒体流(SRTP) ──>

延迟对比

协议 延迟 原因
HLS 5-30秒 切片缓冲,每片2-10秒
HTTP-FLV 1-3秒 流式传输,有缓冲区
RTMP 1-3秒 TCP,有重传队列
WebRTC 50-500ms UDP,丢包直接丢弃不等重传

WebRTC 延迟低的根本原因:宁可丢帧,不等重传

弱网表现

WebRTC 内置三重弱网保障:

  • NACK:发现丢包主动请求重传
  • FEC(前向纠错):提前发冗余包,丢了也能还原
  • GCC(Google Congestion Control):实时估算带宽,自动降码率保流畅

成本对比

传统直播 WebRTC
分发成本 低(CDN按流量) 高(SFU服务器)
开源生态 FFmpeg/ijkplayer/SRS WebRTC原生/Mediasoup/Livekit
延迟 秒级 毫秒级

1.4 移动端直播架构全貌

1
2
3
主播手机
├── RTMP/SRT 推流 ──> CDN ──> HLS/FLV ──> 普通观众(高并发)
└── WebRTC ──────────> SFU ──> WebRTC ──> 连麦观众(低延迟)

二、Week 1-2:理论打底

工作日 30-45 分钟,通勤/睡前均可。不写代码,先把原理搞透。

Week 1:WebRTC 核心原理

必须理解的 5 个核心概念:

① 信令(Signaling)

  • WebRTC 本身不定义信令协议,需要自己实现
  • 本质:交换 SDP 和 ICE Candidate 的通道
  • 常见实现:WebSocket / HTTP 长轮询 / 任意方式均可

② SDP(Session Description Protocol)

  • 描述「我能用什么编解码器、什么格式通信」
  • 使用 Offer/Answer 模型:A 发 Offer,B 回 Answer

③ ICE / STUN / TURN

1
2
3
4
5
6
STUN Server:告诉你「你的公网 IP 是 x.x.x.x:port」
ICE:枚举所有可能的连接路径,选最优的一条
- host:本机局域网地址(最优先)
- srflx:通过 STUN 获取的公网地址
- relay:通过 TURN 中继的地址(兜底)
TURN Server:P2P 穿透失败时,所有流量通过服务器中继

④ DTLS + SRTP

  • 所有 WebRTC 媒体流强制加密,无法关闭
  • DTLS:基于 UDP 的 TLS,握手交换密钥
  • SRTP:加密后的 RTP,传输音视频数据

⑤ 拥塞控制(GCC)

  • 实时估算当前可用带宽
  • 带宽充足 → 提升码率/分辨率;带宽不足 → 降低码率

推荐阅读:WebRTC for the Curious(必读,免费电子书)

Week 2:音视频基础

视频编解码器对比

编解码器 特点 WebRTC 中的角色
H.264 硬件支持最广,兼容性最好 Android 默认首选
H.265/HEVC 压缩率更高,但专利费问题 部分场景使用
VP8 Google 开源,无专利 WebRTC 早期默认
VP9 VP8 升级版,压缩率好 Chrome 常用
AV1 新一代,压缩率最高 逐步普及中

关键参数关系

1
2
码率 = 分辨率 × 帧率 × 压缩率(近似)
弱网时:优先降码率 → 其次降分辨率 → 最后降帧率

帧类型

  • I帧(关键帧):完整图像,可独立解码,体积大
  • P帧(预测帧):相对前一帧的差异,体积小
  • B帧(双向预测帧):WebRTC 通常不用(增加延迟)

推荐阅读:Digital Video Introduction


三、Week 3-4:Android WebRTC 第一个 Demo

Week 3:Loopback Demo

添加依赖:

1
2
// app/build.gradle
implementation 'io.getstream:stream-webrtc-android:1.1.3'

核心 API 学习顺序:

1
2
3
4
5
6
7
8
9
PeerConnectionFactory(全局初始化)

AudioSource / VideoSource(媒体源)

AudioTrack / VideoTrack(媒体轨道)

PeerConnection(连接管理核心)

SurfaceViewRenderer(视频渲染)

Loopback Demo 核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 本机两个 PeerConnection 互连,不需要服务器
private fun startNegotiation() {
val constraints = MediaConstraints().apply {
mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"))
}
pcA.createOffer(object : SdpObserver {
override fun onCreateSuccess(sdp: SessionDescription) {
pcA.setLocalDescription(SimpleSdpObserver(), sdp)
pcB.setRemoteDescription(SimpleSdpObserver(), sdp)
pcB.createAnswer(object : SdpObserver {
override fun onCreateSuccess(answer: SessionDescription) {
pcB.setLocalDescription(SimpleSdpObserver(), answer)
pcA.setRemoteDescription(SimpleSdpObserver(), answer)
}
override fun onSetSuccess() {}
override fun onCreateFailure(error: String) {}
override fun onSetFailure(error: String) {}
}, constraints)
}
override fun onSetSuccess() {}
override fun onCreateFailure(error: String) {}
override fun onSetFailure(error: String) {}
}, constraints)
}

// ICE Candidate 交换
inner class PeerAObserver : PeerConnection.Observer {
override fun onIceCandidate(candidate: IceCandidate) {
pcB.addIceCandidate(candidate) // A 的 candidate 加给 B
}
// ...
}

验收标准: 本机摄像头画面出现在两个 SurfaceViewRenderer 里。

Week 4:信令服务器 + 1v1 通话

最简信令服务器(Node.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 8080 })
const rooms = {}

wss.on('connection', (ws) => {
ws.on('message', (data) => {
const msg = JSON.parse(data)
if (!rooms[msg.roomId]) rooms[msg.roomId] = []
if (!rooms[msg.roomId].includes(ws)) rooms[msg.roomId].push(ws)
// 转发给房间内其他人
rooms[msg.roomId].forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data)
}
})
})
})

两台手机通话时序:

1
2
3
4
5
6
7
8
9
设备A                信令服务器              设备B
├── 加入房间 ───────→ │ │
│ │ ←── 加入房间 ──────────┤
├── createOffer ─────→ │ │
│ │ ──── offer ────────────→ │
│ │ ←─── createAnswer ────── │
├── setRemoteDesc ←── │ │
├── IceCandidate ────→ │ ──── candidate ────── → │
└──────────── P2P 媒体流建立 ─────────────────────┘

验收标准: 两台手机互相看到对方摄像头,听到对方声音。


四、Week 5-6:SFU + 多人推拉流

Week 5:接入 Livekit

搭建服务器:

1
2
3
docker run --rm \
-p 7880:7880 -p 7881:7881 -p 7882:7882/udp \
livekit/livekit-server --dev --bind 0.0.0.0

Android 接入核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
implementation 'io.livekit:livekit-android:2.5.0'

// 连接并推流
room = LiveKit.create(applicationContext)
room.connect(url = "ws://your-server:7880", token = token)
room.localParticipant.setCameraEnabled(true)
room.localParticipant.setMicrophoneEnabled(true)

// 拉流(订阅其他参与者)
room.events.collect { event ->
if (event is RoomEvent.TrackSubscribed && event.track is VideoTrack) {
(event.track as VideoTrack).addRenderer(remoteRenderer)
}
}

验收标准: 3台手机进同一房间,互相能看到听到。

Week 6:传统推拉流(RTMP + HLS)

搭建 SRS 服务器:

1
docker run -p 1935:1935 -p 8080:8080 ossrs/srs:5

Android RTMP 推流(腾讯 LiteAV SDK):

1
2
3
4
val pusher = V2TXLivePusher(this, V2TXLiveMode.TXLiveMode_RTMP)
pusher.startCamera(true)
pusher.startMicrophone()
pusher.startPush("rtmp://your-server/live/stream1")

Android HLS 拉流(ExoPlayer):

1
2
3
4
val player = ExoPlayer.Builder(this).build()
player.setMediaItem(MediaItem.fromUri("http://your-server:8080/live/stream1.m3u8"))
player.prepare()
player.play()

验收标准: 一台手机推 RTMP,另一台 ExoPlayer 拉 HLS 播放。


五、Week 7-8:融会贯通

Week 7:自测问题

  1. 为什么 WebRTC 的延迟比 HLS 低 10-100 倍?
  2. ICE 协商失败会怎样?TURN 服务器的作用是什么?
  3. GCC 是怎么估算带宽的?丢包和延迟增大各触发什么策略?
  4. SFU 和 MCU 的 CPU 消耗为什么差这么多?
  5. 1000 人观看的直播,WebRTC 和 HLS 的服务器成本差多少?

Week 8:Wireshark 抓包

1
2
3
4
5
brew install wireshark

# 过滤条件
dtls # DTLS 握手
stun # STUN 协议

重点观察:

  • STUN Binding Request/Response:ICE 发现公网地址
  • DTLS ClientHello/ServerHello:加密握手过程
  • RTCP REMB 包:带宽估算反馈信号

六、配套资源

资源 类型 链接
WebRTC for the Curious 必读电子书 https://webrtcforthecurious.com/
Digital Video Introduction 音视频基础 https://github.com/leandromoreira/digital_video_introduction
stream-webrtc-android Android WebRTC 库 https://github.com/GetStream/webrtc-android
Livekit Android SDK SFU 接入 https://github.com/livekit/client-sdk-android
SRS 流媒体服务器 传统直播服务器 https://github.com/ossrs/srs
AppRTCMobile Google 官方示例 https://chromium.googlesource.com/external/webrtc/

七、每周验收 Checklist

Week 1

  • 能解释 SDP Offer/Answer 交换过程
  • 能画出 ICE 连接建立的流程图
  • 理解 STUN 和 TURN 的区别

Week 2

  • 理解 I/P/B 帧区别,知道 WebRTC 为什么不用 B 帧
  • 能解释码率/帧率/分辨率三者关系

Week 3

  • Loopback Demo 跑通
  • 理解 SDP 协商在代码里如何触发

Week 4

  • 信令服务器搭起来
  • 两台手机 1v1 通话成功

Week 5

  • Livekit 3人房间跑通
  • 理解 SFU 模式下每个客户端的上下行

Week 6

  • RTMP 推流 + HLS 拉流跑通
  • 能描述「直播+连麦」混合架构

Week 7

  • 能回答全部 5 个自测问题

Week 8

  • 抓到 DTLS 握手包并能解释
  • 找到 RTCP REMB 包,理解其作用

附录一:服务端用 Docker,客户端跑真机

为什么推荐 Docker 跑服务端

跑 Demo 时服务端有三个组件:信令服务器、Livekit SFU、SRS 流媒体服务器。这三个用 Docker 有明显优势:

  • 一行命令启动,不污染本机环境
  • 随时销毁重建,环境始终干净
  • 官方镜像开箱即用,无需手动编译配置
1
2
3
4
5
6
7
8
9
10
11
# 信令服务器(自己写的 Node.js)
docker build -t signaling-server .
docker run -p 8080:8080 signaling-server

# Livekit SFU
docker run --rm \
-p 7880:7880 -p 7881:7881 -p 7882:7882/udp \
livekit/livekit-server --dev --bind 0.0.0.0

# SRS 流媒体服务器
docker run -p 1935:1935 -p 8080:8080 ossrs/srs:5

不适合用 Docker 的部分

  • Android App:必须跑在真机或 Android 模拟器上,Docker 里没有摄像头/麦克风
  • Wireshark 抓包:Docker 有网络 NAT 层,抓包会变复杂,建议直接在宿主机抓

推荐组合

1
2
3
4
服务端(Docker)          客户端(真机)
信令服务器 ──────────────→ Android 手机A
Livekit SFU ─────────────→ Android 手机B
SRS 流媒体 ───────────────→ Android 手机C

局域网内手机和 Mac 互通,your-server 填 Mac 的局域网 IP 即可。


附录二:常见问题排查

ICE 连接失败

  1. 检查 STUN 服务器是否可达
  2. 检查防火墙是否放行 UDP
  3. 检查 ICE candidate 是否正确交换
  4. 添加 TURN 服务器作为兜底

画面卡顿/花屏

  1. 查看丢包率(>5% 明显影响质量)
  2. 确认硬件编码已启用
  3. 检查码率是否超过实际带宽

Android 音频回声

  1. 确认 AEC(回声消除)已启用
  2. 检查 AudioManager 模式是否为 MODE_IN_COMMUNICATION

最后更新:2026-06