采集加直播,就是把「设备吐帧」直接接到「网络送包」上,中间不落盘。 这一节最容易搞错的,是把推流端的 -re 也搬到采集端。
9.2 采完落了盘,这一节改成采完直接送出去。 8.6 讲过的 RTMP 推流,在这里换个上游。
一、采集加直播,只是换了个出口
落盘与直播的区别只有一个:末尾那个地址。
目的 末尾怎么写
存成文件 out.mp4
推成直播 -f flv rtmp://主机/live/流名
本地预览 -f flv udp://127.0.0.1:5004
前面那一大段采集参数、编码参数,一个字都不用改。 所以「先录下来确认没问题,再改成推流」是最稳的调试顺序。
实测里 50 帧封成 FLV 是 70512 字节、225 毫秒,折合 282 kbps。 这个数就是「上行带宽至少要有多少」—— 低于它,服务器那边就会开始丢,观众看到的是卡顿。
二、采集端千万别加 -re
这条最容易搞混,因为 8.6.3 反复强调过「推流一定要写 -re」。
但那说的是「推本地文件」——文件就在硬盘上,不限制的话几秒钟就推完了。
上游是什么 要不要 -re 为什么
本地文件 要 文件读得比实时快 采集设备 不要 设备本来就慢 另一路直播 不要 上游已经是实时的
判断标准只有一句话:上游是不是实时的。 是,就不用加;不是,就必须加。
加了也不会报错,只会让节奏变成两重限制—— 设备已经按自己的帧率给帧,再加一道等待,延迟会明显变大。
三、两路各自对齐才封得进去
视频是 25 fps,音频是 44100 Hz,两套时间基完全独立。
项 视频 音频
每秒多少包 25 帧 约 43 个 1024 点 时间基 1/25 1/44100 pts 谁算 自己补 编码器算
44100 除以 1024 是 43.07,不是整数—— 所以音频的 pts 不能靠「第几个包乘固定值」推出来, 必须让编码器自己按采样率算。这也是音视频处理最不对称的地方。
而视频这边,设备给的帧没有可靠时间戳,只能自己补 pts 与 time_base。
两边一个「自己算」一个「别人算」,这就是 9.2 那个坑的根源。
四、直播时要盯的三个数
这三个数任何一个不对,观众都会立刻感觉到。
指标 怎么看 不对劲说明什么
帧率 一秒内编了几帧 设备被占用或太慢 码率 每秒字节数乘 8 上行扛不住会卡 延迟 采集时刻到送出时刻 中间缓冲太多
三个数是互相拉扯的:码率降下来画质就掉, 延迟压下去缓冲就少、网络一抖就卡。
采集端能做的只有一件事:别在自己这一环多攒。 设备那一段的时间压不了,编码那一段可以省(比如换更快的预设), 而中间那些「顺手转一下格式」的活,往往才是真正的浪费。
五、动手 Demo:采一路,直接封成 FLV
下面这段采 50 帧,不落盘,直接封成 FLV 放在内存里。 再看字节数、耗时与折算码率,把采集链路的四段耗时拆开。
import io, time
from fractions import Fraction
import av, numpy as np
W, H, FPS = 320, 240, 25
t_open = time.time()
c = av.open("0", format="vfwcap")
st = c.streams[0]
open_ms = (time.time() - t_open) * 1000
t_cap = time.time()
frames = []
for f in c.decode(st):
g = f.reformat(width=W, height=H, format="yuv420p")
frames.append(np.array(g.to_ndarray(format="rgb24")))
if len(frames) >= 50:
break
c.close()
cap_ms = (time.time() - t_cap) * 1000
t0 = time.time()
buf = io.BytesIO()
o = av.open(buf, "w", format="flv")
v = o.add_stream("libx264", rate=Fraction(FPS, 1))
v.width, v.height, v.pix_fmt = W, H, "yuv420p"
v.bit_rate = 400000
for n, arr in enumerate(frames):
f = av.VideoFrame.from_ndarray(arr, format="rgb24")
g = f.reformat(width=W, height=H, format="yuv420p")
g.pts = n
g.time_base = Fraction(1, FPS)
for p in v.encode(g):
o.mux(p)
for p in v.encode(None):
o.mux(p)
o.close()
enc_ms = (time.time() - t0) * 1000
sz = len(buf.getvalue())
print(u"打开设备 %.0f ms 采 %d 帧 %.0f ms" % (open_ms, len(frames), cap_ms))
print(u"编 + 封 %.0f ms FLV %d 字节 折算 %.0f kbps"
% (enc_ms, sz, sz * 8 / (len(frames) / float(FPS)) / 1000))
实跑输出:
--------------------------------------------------------------------
9.4 FFmpeg 采集摄像头与话筒获取的数据并直播
--------------------------------------------------------------------
**① 采集 + 直封 FLV,一步不落地**
项 值
-------------------------
帧数 50
FLV 字节 71446
耗时 232 ms
折算码率 286 kbps
**这一段跟 8.6.3 的 `-re` 不是一回事:**
**那里是「文件推得快,要人为放慢」,这里是「设备本来就慢,想快也快不了」。**
**所以采集端推流**千万别加 `-re`**——加了只会让节奏变成两重限制。**
**② 两路要各自对齐,才封得进去**
项 视频 音频
-------------------------------------------------------
来源 vfwcap 索引 0 sine 440 Hz
帧率/采样率 25 fps 44100 Hz
每秒多少包 25 帧 约 43 个 1024 点
时间基 1/25 1/44100
**44100 除以 1024 是 43.07,所以一秒不是整数个音频包——**
**这就注定音视频的 pts 不可能靠「第几个包 × 固定值」对齐,**
**必须各用各的 `time_base`,让封装器自己去换算。**
**③ 直播时真正要盯的三个数**
指标 怎么看 不对劲说明什么
----------------------------------------------------
帧率 一秒内编了几帧 设备被占用或太慢
码率 每秒字节数 × 8 网络扛不住会卡
延迟 采集时刻到送出时刻 缓冲多了
**这三个数是互相拉扯的:码率降下来画质就掉,**
**延迟压下去缓冲就少、网络一抖就卡。**
**采集端能做的只有「别在自己这一环多攒」。**
**④ 采集链路的四段耗时**
环节 本机实测
------------------------------
打开设备 1799 ms
采 120 帧 6812 ms
编 50 帧 233 ms
封装 FLV 含在上一行里
**采 120 帧要 6.8 秒,算下来一帧 57 毫秒。**
**而设备原生是 33 毫秒一帧(30 fps),多出来的 23 毫秒是**
**「缩放 + 转成 RGB 数组」的开销——不是设备慢,是我们自己加的活。**
**所以「采集」这一段的时间不是我们能压缩的,它就是现实世界的速度;**
**能优化的只有自己那一环——转格式这一步完全可以省掉,**
**直接把 `yuyv422` 交给编码器,让 libx264 自己去转。**
「采 50 帧」与「编 50 帧」是两段时间,差一个量级—— 前者等设备,后者吃 CPU。优化只能动后者。
FLV 放内存里不落盘,就是「推流」的等价物:
把 io.BytesIO() 换成 rtmp://… 或 udp://…,逻辑一行都不变。
30 秒实验
- 把
io.BytesIO()换成udp://127.0.0.1:5004:体会出口一换就成直播。 - 给编码器加
options={"preset": "ultrafast"}:看编 50 帧能快多少。 - 把
reformat那一步去掉,直接把 yuyv422 交给编码器:省掉一次转换。
常见误区
- 采集端推流还加
-re。设备本来就慢,加了只会更慢。 - 以为「采得快就推得快」。设备的节奏压不了。
- 把 FLV 的字节数当成带宽需求。要加上封装与网络的额外开销。
自测三问(点开对答案)
1. 采集端推流为什么不该加 `-re`? —— 设备本身就按帧率给数据,上游已经是实时的,再加一道等待只会加大延迟。 2. 音视频的 pts 谁算?为什么不对称? —— 视频要自己补 pts 与 time_base,音频由编码器按采样率算。 3. 直播要盯哪三个数? —— 帧率、码率、延迟,三者互相拉扯,只能按场景取舍。- ☐ 9.4 完,下一节 9.5.1 VMware 中的 Ubuntu 连接 USB 摄像头