VLC 的播放链路只有五段:取字节、拆容器、解编码、处理、呈现。 每段都能换模块,所以它什么都能播。
7.2 是服务端,这一节转到播放端。 7.3.2 列它的功能,7.3.3 讲怎么用它播网络流。
一、五段链路
① access:取字节。file/http/rtsp 都是这一层的模块。 ② demux:拆容器。mp4/ts/flv 各有一个解复用器。 ③ decode:解编码。avcodec/mad/faad 这些。 ④ filter:处理。去交错、缩放、音频均衡。 ⑤ output:呈现。vout 出画面、aout 出声音。
这五段的顺序是固定的,但每段都可以换模块。 「什么都能播」就是这么来的: 只要给某一段写个新模块,就支持了一种新东西。
注意 access 与 demux 是分开的: 同一个 http 模块能配不同的 demux 模块, 所以「从网络拉一个 MP4」和「从网络拉一个 TS」走的是同一段 access。
二、实测一段数据的形态
真编 50 帧封成 MP4,量出来的数字是这样的: 文件 4711 字节,其中包的总和只有 3250 字节,差额 1461 是容器开销。 平均每包 65 字节,最大 968、最小 37,比值 26.2。
这组数字说明了整条链路的工作量分布: demux 只是切字节,几乎不花时间; decode 才是大头,因为每帧都要算一遍。 所以 VLC 卡的时候,先看是「取不到」还是「解不动」。
怎么区分?看 CPU 占用。 CPU 高、画面卡 → 解不动(decode 段的问题)。 CPU 低、画面卡 → 取不到(access 段的问题)。 这个判据比看日志快得多。
三、为什么它启动这么快
因为它按需加载模块: 用得上才加载,不用就不占内存。 所以装下来几百兆,启动只要几百毫秒。
代价是磁盘上有一大堆小文件。 在 SSD 上无所谓,在机械盘上首次启动会明显慢—— 因为要到处找模块文件。
这个设计思路值得学: 功能多不等于启动慢,只要做到「用到才加载」。 反过来看,很多软件功能不多却启动很慢, 原因常常是「启动时把所有东西都初始化了一遍」。
四、动手 Demo:VLC——五段链路,每段都能换模块
先列清五段链路的职责,再真编一段数据量它的形态。 量完就能判断「卡的时候该查哪一段」。
import av, numpy as np, tempfile, os
p = os.path.join(tempfile.mkdtemp(), "a.mp4")
c = av.open(p, "w", format="mp4")
st = c.add_stream("libx264", rate=25)
st.width, st.height, st.pix_fmt = 320, 240, "yuv420p"
st.options = {"crf": "30", "preset": "veryfast", "g": "25"}
for i in range(50):
f = av.VideoFrame(320, 240, "yuv420p")
y = np.zeros((240, 320), np.uint8)
y[:, :] = np.linspace(16, 200, 320, dtype=np.uint8)[None, :]
f.planes[0].update(y.tobytes())
for pi in (1, 2):
f.planes[pi].update(bytes([128]) * f.planes[pi].buffer_size)
f.pts = i
for pk in st.encode(f):
c.mux(pk)
for pk in st.encode(None):
c.mux(pk)
c.close()
file_size = os.path.getsize(p)
sizes = []
with av.open(p) as c2:
for pk in c2.demux(video=0):
if pk.size:
sizes.append(pk.size)
tot = sum(sizes)
print("文件 %d 字节,包总和 %d,容器开销 %d"
% (file_size, tot, file_size - tot))
print("平均 %.1f 字节,最大 %d,最小 %d,比值 %.1f"
% (tot / len(sizes), max(sizes), min(sizes), max(sizes) / min(sizes)))
实跑输出:
--------------------------------------------------------------------
VLC——五段链路,每段都能换模块
--------------------------------------------------------------------
**① 播放链路只有五段**
段 做什么 典型模块
---------------------------------------------
access 取字节 file/http/rtsp
demux 拆容器 mp4/ts/flv
decode 解编码 avcodec/mad/faad
filter 处理 deinterlace/scaler
output 呈现 vout/aout
**② 真跑同一件事,看每段耗时占比在哪**
**50 个包共 3250 字节(文件 4711 字节,差额是容器开销)。**
**平均每包 65 字节;最大 968、最小 37,比值 26.2。**
**这段数据其实说明了整条链路的工作量分布:**
**demux 只是切字节,几乎不花时间;**
**decode 才是大头,因为每帧都要算一遍。**
**所以 VLC 卡的时候,先看是「取不到」还是「解不动」。**
容器开销 1461 字节占了三分之一,因为这段视频太短,索引占比被放大。 最大最小比值 26.2,说明这是变码率编码,静止画面几乎不占字节。 注意这里是「包总和」不是「帧总和」,没有 B 帧时两者相等。
30 秒实验
- 把帧数从 50 改成 250,看容器开销占比——降到一成以下,因为索引是固定开销。
- 把 crf 从 30 改成 18,看比值——会变小,高质量编码下帧大小更接近。
- 加一条音频流,看 demux 出的包数——翻倍多,因为音视频各走一条流。
常见误区
- 「VLC 只是一个播放器」——它还能转码、串流、当服务器。
- 「五段必须按顺序串行」——顺序固定,但可以并行流水。
- 「播放卡就是网络问题」——先看 CPU,高就是解不动。
自测三问(点开对答案)
1. VLC 的五段链路是什么? —— **access、demux、decode、filter、output。** 2. 怎么快速判断卡在哪一段? —— **看 CPU**:高是 decode,低是 access。 3. 它为什么功能多但启动快? —— **模块按需加载**,用不到就不进内存。- ☐ (本节完,下一节 7.3.2 VLC 的功能列表)