WebRTC 不能「连上就播」,要先交换一份 SDP 把参数谈好。 谈完还没完,还要 ICE 打通、DTLS 握手,视频才真正开始走。
11.5.2 用 RTMP 推流,这一节用 WebRTC 播同一路流。 下一章换 ZLMediaKit,把这套东西再用一遍。
一、它不能连上就播
RTMP 是「连上就发」,WebRTC 是「先商量再发」。 商量的内容只有三样:编码、加密、往哪儿发。
① 编码:写在 a=rtpmap: 里,两边要有交集。
没有共同编码,谈崩。
② 加密:写在 a=fingerprint: 里,是证书的指纹。
指纹对不上,DTLS 握手失败。
③ 地址:写在 a=candidate: 里,是可达的 IP 与端口。
一个可达的都没有,就一直连不上。
这三样里,前两样不通过会立刻报错,第三样不通过就是一直转圈, 没有任何提示。 所以「WebRTC 连不上」的排查顺序永远是:先看候选地址,再看指纹。
二、一次 SDP 交换
SRS 的 WebRTC 播放接口是 POST /rtc/v1/play。
请求体里带 streamurl 与一份 offer,响应里给一份 answer。
实测这一次交换:请求 658 字节,响应 734 字节,环回上一来一回十几毫秒。 注意这一步没有任何视频数据——它只谈参数。
谈完之后还有两步,都不在这一个请求里: ① ICE 打通:双方按候选地址互相发包,找到一条能通的路径。 ② DTLS 握手:在已经打通的路径上建立加密通道,之后才走 SRTP。 这两步走完,第一帧视频才开始传。
三、两份 SDP 摊开对比
把 offer 与 answer 逐项对照,能看出三条规律:
① 编码是「多选一」:offer 列了三种,answer 只挑中一种。
规则是取交集里优先级最高的那个。
② setup 决定谁主动:offer 说 actpass(我都可以),
answer 说 active(我来发起 DTLS)。
所以是服务器先发,浏览器等它。
③ 候选数量不对称很正常:offer 里可能是零个。
因为它写了 a=ice-options:trickle,候选不写进正文,
后面探到一个就单独发一条。
这三条里,第二条最能说明 WebRTC 的角色关系: 不是「浏览器连服务器」,而是「两边对等谈,谈完服务器先动手」。 这也是它和 RTMP 最根本的区别——RTMP 里服务端永远是被动的。
四、WebRTC 快在哪
四种方式的延迟量级差得很远: RTMP 与 HTTP-FLV 是 1 到 3 秒,HLS 是 6 到 30 秒,WebRTC 是 0.2 到 0.5 秒。
差在哪?两件事: ① 走 UDP,丢了就丢,不等重传; ② 不攒 GOP,收到关键帧就能出画。 HLS 慢是因为要等一整片写完,RTMP 慢是因为要攒一个 GOP。
但代价也很实在: 要额外编译、要开一堆 UDP 端口、还要 STUN 与 TURN 帮它穿 NAT。 所以直播平台的常见组合是「推流用 RTMP、大众观看用 HLS、 连麦和低延迟场景才上 WebRTC」。 不是技术先进就该全用,是每种协议各有一段最合适的场景。
五、动手 Demo:WebRTC 播放——SDP 一来一回
起一个 HTTP 服务,收 offer、回 answer, 再把两份 SDP 的关键字段摊开对比。
# 1. 发请求:POST /rtc/v1/play,body 里带 offer
req = ("{\"api\":\"/rtc/v1/play/\","
"\"streamurl\":\"webrtc://127.0.0.1/live/livestream\","
"\"sdp\":\"" + OFFER.replace("\n", "\\n") + "\"}")
c.sendall(head + req.encode())
# 2. 收 answer,从 JSON 里把 SDP 抠出来
resp = 全部收完
answer = json.loads(body)["sdp"]
# 3. 抠关键字段
for line in sdp.splitlines():
if line.startswith("m="):
media.append(line)
elif line.startswith("a=rtpmap:"):
pt = line[9:].split()[0]
rtpmap[pt] = line[9:].split(None, 1)[1]
elif line.startswith("a=ice-ufrag:"):
ufrag = line[12:]
elif line.startswith("a=candidate:"):
cands.append(line[12:].split())
实跑输出:
--------------------------------------------------------------------
WebRTC 播放——SDP 一来一回
--------------------------------------------------------------------
**① 它不能「连上就播」,得先商量**
**RTMP 是连上就发,WebRTC 要先交换一份 SDP 把参数谈好。**
**商量的内容有三样:编码、加密、往哪儿发。**
谈什么 写在 SDP 的哪一行 不通会怎样
-------------------------------------------------------------
编码 `a=rtpmap:` 两边没有共同编码,谈崩
加密 `a=fingerprint:` 指纹对不上,DTLS 失败
地址 `a=candidate:` 没有可达候选,一直连不上
**② 真起一个 HTTP 服务,做一次交换**
**SRS 的 WebRTC 播放接口是 `POST /rtc/v1/play`,**
**请求体里带 offer,响应里给 answer。这一步真跑。**
方向 字节 内容
------------------------------------------------
请求 658 streamurl 加一份 offer SDP
响应 734 code 加一份 answer SDP
**一次交换来回 15.8 毫秒,658 字节请求换回 734 字节响应。**
**这一步只谈了参数,还没有一个字节的视频。**
**视频要等 ICE 打通、DTLS 握手完成之后才开始走。**
**③ 把两份 SDP 摊开对比**
项目 offer(浏览器给) answer(SRS 回)
---------------------------------------------------------
媒体行数 1 1
候选编码数 3 1
编码名 H264/H265/opus H264
ice-ufrag 7hF2 mZ8q
setup actpass active
候选地址数 0 2
**offer 列了 3 种编码,answer 只挑中 1 种。**
**挑的规则是「取交集里优先级最高的那个」——SRS 选了 H264。**
**`setup` 那一栏最能说明谁主动:offer 说 actpass(我都可以),**
**answer 说 active(我来发起 DTLS 握手)。**
**所以是服务器先发 DTLS,浏览器等它。**
**候选地址那一栏,offer 是 0 个,answer 是 2 个,这不对称很正常。**
**offer 里写了 `a=ice-options:trickle`,意思是候选不写进 SDP,**
**后面探到一个就单独发一条,所以正文里是零个。**
**answer 里那 2 个:一个 host 是内网地址,一个 srflx 是 STUN 看到的外网地址。**
**两个都是同一个端口 8000,说明媒体复用一个端口(`rtcp-mux`)。**
**④ WebRTC 快在哪儿**
方式 首屏延迟量级 慢在哪
--------------------------------------------------------------
RTMP 1~3 秒 要攒够一个 GOP 才能出画
HTTP-FLV 1~3 秒 同上,还得走 HTTP 头
HLS 6~30 秒 等一个切片写完,再等索引刷新
WebRTC 0.2~0.5 秒 走 UDP,不攒 GOP
**这张表是行业口径,不是本机实测**——沙箱里没有真网络,也跑不起浏览器。
**但它和前面几节测出来的东西是自洽的:**
**HLS 的延迟下限等于切片长度(第 126 号 Demo 已经算过),**
**RTMP 要攒 GOP,WebRTC 用 UDP 又有前向纠错,所以最短。**
**代价是 WebRTC 最贵:**
**要额外编译、要开一堆 UDP 端口、还要 STUN/TURN 帮它穿 NAT。**
**这就是为什么直播平台通常是「推流用 RTMP、看的人多用 HLS,**
**连麦和低延迟场景才上 WebRTC」——不是技术先进就该全用。**
请求 658 字节、响应 734 字节,环回十几毫秒。
offer 列了三种编码,answer 只挑一种,取交集最高优先级。
offer 的候选数是零,因为它用 trickle 方式单独发。
setup 从 actpass 变成 active,说明服务器主动发 DTLS。
30 秒实验
- 把 offer 里的 H264 那一行删掉,猜会怎样——没有共同编码,SRS 直接拒绝。
- 把
a=setup改成passive,猜谁先动手——反过来,浏览器先发 DTLS。 - 把候选地址里的 srflx 删掉只留 host,猜能不能连——同网段能,跨网段不行。
常见误区
- 「WebRTC 是连上就播」——要先交换 SDP,再 ICE,再 DTLS。
- 「answer 里的编码越多越好」——只挑一个,多了没意义。
- 「延迟低就一定该用它」——要编译、要开 UDP、要 STUN/TURN。
自测三问(点开对答案)
1. WebRTC 播放的第一个接口是什么? —— **`POST /rtc/v1/play`**,请求带 offer,响应给 answer。 2. `setup active` 是什么意思? —— **我来发起 DTLS 握手**,这里是服务器主动。 3. WebRTC 为什么比 HLS 快那么多? —— **走 UDP 不攒 GOP,也不用等一整片切片写完。**- ☐ (本章完,下一章 12.1 Windows 编译配置 ZLMediaKit)