拉流只要一条命令,但延迟不是一个数。 能调的旋钮只有一个,剩下全是架构问题。
14.3 把流推上去了,这一节把它拉下来。 这是全课最后一节,结尾把十四章的判断收在一起。
一、三个入口,行为一样
VLC 拉流有三个入口:
入口 怎么用 适合
图形界面 媒体 → 打开网络串流 手工验证一次
命令行 vlc rtmp://… 批量与脚本
播放列表 写一个 .xspf 多个流轮播
三个入口底层是同一套:都走 libvlc 的 RTMP 模块。 所以底下的行为完全一样,差别只在参数怎么传。
图形界面有一个坑:它不会把错误说清楚。 流打不开时只弹一句「无法打开」,看不出是端口错、流名错还是鉴权失败。 同样的地址用命令行敲一遍,终端里会打出真正的原因。
所以调不通时,第一件事是把地址复制到命令行再试一次。 这个习惯适用于所有播放器——图形界面藏错误,命令行暴露错误。
二、真拉一次:首帧几乎是瞬时的
先让一个客户端把流推上去,再让另一个客户端拉下来, 量「从发 play 到首帧」的时间:结果只有 0.5 毫秒。
这个数字说明一件事:环回上「拿到首帧」几乎是瞬时的, 因为服务器已经把整条流缓存在内存里了。
真实场景里,首帧延迟不取决于服务器多快,取决于「流有没有到那儿」。 观众连上来的时候,推流端可能刚好在等下一个关键帧—— 那就要等一整个 GOP,按 25 帧算就是一秒。
所以首帧慢有三个原因,按概率排: ① 在等关键帧(最常见);② TCP 建连与握手(几十到几百毫秒); ③ 服务器真的在转码而不是转发(那是配置问题)。 第三条要特别警惕:转发很便宜,转码很贵,别把两者混了。
三、network-caching 是唯一能调的旋钮
VLC 的 network-caching 单位是毫秒,默认 1000。
它的作用是「先攒够这么多数据再开始播」。
network-caching 端到端延迟 能扛多久的抖动
0 毫秒 148 毫秒 一点都扛不住 300 毫秒 448 毫秒 300 毫秒 1000 毫秒 1148 毫秒 1000 毫秒 3000 毫秒 3148 毫秒 3000 毫秒
表里前面那 128 毫秒是采集、编码、推流、网络这几段的固定成本,谁也去不掉。
所以 network-caching=0 也不是零延迟,只是「不额外攒」。
而攒得越少,断一下就越明显——这是延迟与流畅之间唯一的交换。 注意「能扛多久的抖动」那一列几乎就等于缓存值,这是设计上的必然。
这条规律可以推广到所有播放器: HLS 的「攒一个切片」、WebRTC 的「jitter buffer」、VLC 的 network-caching, 本质都是同一件事——拿延迟换抗抖动。 所以「能不能再低一点」这个问题,答案永远是「能,但你得接受更容易卡」。
四、延迟到底卡在哪一段,全课收尾
四段里只有两段能调:
段 能不能调 怎么调
采集与编码 基本不能 换硬件编码、降分辨率
推流缓冲 能 缩短 GOP,代价是码率上升
网络 不能 换机房、上边缘
播放缓冲 最能 network-caching 直接调
四段里只有两段能调,其中一段(播放缓冲)效果最直接。 这也是为什么「优化直播延迟」在实操上往往就是「把播放器缓冲调小」—— 它不是最优雅的办法,但是唯一不用改架构的办法。
想再往下,就得动架构了: 把 RTMP 换成 WebRTC,把「服务器转发」这一环整个换掉。 第 13 章讲的就是这件事,也是这门课最后想留下的判断—— 延迟不是一个参数,是架构决定的。
回头看这十四章,其实一直在回答同一个问题: 「一份流,怎么从摄像头走到观众屏幕上」。 第二章到第六章讲协议(用什么语言说), 第七章到第十二章讲服务器(谁来转发), 第十三章讲绕过服务器(能不能不走中间), 这一章把它们拼成一条完整的链路。 协议决定能力边界,架构决定成本与体验,这两句话是全书的主线。
五、动手 Demo:使用 VLC 进行 RTMP 拉流并播放——首帧延迟是怎么来的
先推一条流上去,再让另一个客户端把它拉下来。 量首帧延迟,再把 network-caching 的账算清楚。
# 真机上的两种拉流方式
CMD1 = "vlc rtmp://127.0.0.1:1935/live/test"
CMD2 = "vlc --network-caching=300 rtmp://127.0.0.1:1935/live/test"
# 沙箱里扮演拉流端:握手、connect、createStream、play
c = socket.create_connection(("127.0.0.1", port), timeout=8)
c.sendall(b"\x03" + b"\x00" * 8 + os.urandom(1528))
rd(c, 1537)
rd(c, 1536)
c.sendall(b"\x00" * 8 + os.urandom(1528))
c.sendall(chunk(3, 20, 0, 0, amf_connect("live", port)))
r.read(c)
c.sendall(chunk(5, 20, 0, 0, amf_create()))
r.read(c)
t0 = time.perf_counter()
c.sendall(chunk(5, 20, 0, 0, amf_play("livestream")))
first = None
while True:
mt, sid, ts, pl = r.read(c)
if first is None and mt in (8, 9):
first = (time.perf_counter() - t0) * 1000
# network-caching 与端到端延迟的换算
base = 33 + 20 + 40 + 5 + 30 # 五段固定成本 128 毫秒
for nc in (0, 300, 1000, 3000):
print(nc, "毫秒缓存 -> 总计", base + nc + 20, "毫秒")
实跑输出:
--------------------------------------------------------------------
使用 VLC 进行 RTMP 拉流并播放——首帧延迟是怎么来的
--------------------------------------------------------------------
**① VLC 拉流有三个入口,行为不一样**
入口 怎么用 适合
-----------------------------------------------------
图形界面 媒体 → 打开网络串流 手工验证一次
命令行 `vlc rtmp://…` 批量与脚本
播放列表 写一个 .xspf 多个流轮播
**三个入口底层是同一套:都走 libvlc 的 RTMP 模块。**
**所以底下的行为完全一样,差别只在参数怎么传。**
**图形界面有一个坑:它不会把错误说清楚。**
**流打不开时只弹一句「无法打开」,看不出是端口错、流名错还是鉴权失败。**
**同样的地址用命令行敲一遍,终端里会打出真正的原因。**
**所以调不通时,第一件事是把地址复制到命令行再试一次。**
**② 真拉一次**
**先让一个客户端把流推上去,再让另一个客户端拉下来,量「从发 play 到首帧」的时间。**
**play 的回应(0 条 onStatus)在等待里就读掉了,之后又陆续收到 1 条 metadata、27 条视频。**
**首帧(第一条视频)在发 play 之后 0.5 毫秒到达。**
**这个数字说明一件事:环回上「拿到首帧」几乎是瞬时的,**
**因为服务器已经把整条流缓存在内存里了。**
**真实场景里,首帧延迟不取决于服务器多快,取决于「流有没有到那儿」。**
**观众连上来的时候,推流端可能刚好在等下一个关键帧——**
**那就要等一整个 GOP,按 25 帧算就是一秒。**
**③ `network-caching` 是唯一能调的旋钮**
**VLC 的 `network-caching` 单位是毫秒,默认 1000。**
**它的作用是「先攒够这么多数据再开始播」。**
network-caching 端到端延迟 能扛多久的抖动
-----------------------------------------------------
0 毫秒 148 毫秒 一点都扛不住
300 毫秒 448 毫秒 300 毫秒
1000 毫秒 1148 毫秒 1000 毫秒
3000 毫秒 3148 毫秒 3000 毫秒
**表里前面那 128 毫秒是采集、编码、推流、网络这几段的固定成本,谁也去不掉。**
**所以 `network-caching=0` 也不是零延迟,只是「不额外攒」。**
**而攒得越少,断一下就越明显——这是延迟与流畅之间唯一的交换。**
**这条规律可以推广到所有播放器:**
**HLS 的「攒一个切片」、WebRTC 的「jitter buffer」、VLC 的 network-caching,**
**本质都是同一件事——拿延迟换抗抖动。**
**所以「能不能再低一点」这个问题,答案永远是「能,但你得接受更容易卡」。**
**④ 一条链路上,延迟到底卡在哪一段**
段 能不能调 怎么调
--------------------------------------------------------
采集与编码 基本不能 换硬件编码、降分辨率
推流缓冲 能 缩短 GOP,代价是码率上升
网络 不能 换机房、上边缘
播放缓冲 最能 `network-caching` 直接调
**四段里只有两段能调,其中一段(播放缓冲)效果最直接。**
**这也是为什么「优化直播延迟」在实操上往往就是「把播放器缓冲调小」——**
**它不是最优雅的办法,但是唯一不用改架构的办法。**
**想再往下,就得动架构了:**
**把 RTMP 换成 WebRTC,把「服务器转发」这一环整个换掉。**
**第 13 章讲的就是这件事,也是这门课最后想留下的判断——**
**延迟不是一个参数,是架构决定的。**
play 的回应(1 条 onStatus)在等待里就读掉了,之后又收到 1 条 metadata、27 条视频。
首帧在发 play 之后 0.5 毫秒到达——因为整条流已经在服务器内存里了。
真机上首帧慢,多半是在等下一个关键帧,一秒左右。
network-caching 从 0 加到 3000,延迟从 148 涨到 3148,几乎是线性的。
那 128 毫秒的固定成本谁也去不掉,所以「零延迟」在 RTMP 上不存在。
想让延迟再往下,只能换 WebRTC——那是换架构,不是调参数。
30 秒实验
- 把
network-caching设成 0,猜画面会怎样——延迟最低,但网络一抖就卡死,因为没有任何缓冲余量。 - 推流端 GOP 设成 250 帧(十秒一个关键帧),猜观众首帧要等多久——最多十秒,因为要等下一个关键帧才能开始播。
- 同一个地址用图形界面打不开、命令行能打开,猜差在哪——多半是参数没传进去,图形界面藏了真正的错误。
常见误区
- 「
network-caching=0就是零延迟」——还有 128 毫秒固定成本。 - 「首帧慢是服务器太慢」——多半是在等关键帧。
- 「延迟能靠调参数降下来」——调到头就得换架构。
自测三问(点开对答案)
1. VLC 的 `network-caching` 干什么? —— **先攒够这么多毫秒的数据再开始播**,拿延迟换抗抖动。 2. 真机上首帧慢,最常见的原因是什么? —— **在等下一个关键帧**,最多要等一整个 GOP。 3. RTMP 的延迟为什么降不到 100 毫秒以内? —— **采集、编码、推流、网络这四段就有 128 毫秒固定成本**。- ☐ (全课完,十四章 146 篇已收官)