🎬 第七章 流媒体开源库简介
📄 一句话概括

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 秒实验

  1. 把帧数从 50 改成 250,看容器开销占比——降到一成以下,因为索引是固定开销。
  2. 把 crf 从 30 改成 18,看比值——会变小,高质量编码下帧大小更接近。
  3. 加一条音频流,看 demux 出的包数——翻倍多,因为音视频各走一条流。

常见误区

  1. 「VLC 只是一个播放器」——它还能转码、串流、当服务器。
  2. 「五段必须按顺序串行」——顺序固定,但可以并行流水。
  3. 「播放卡就是网络问题」——先看 CPU,高就是解不动。
自测三问(点开对答案) 1. VLC 的五段链路是什么? —— **access、demux、decode、filter、output。** 2. 怎么快速判断卡在哪一段? —— **看 CPU**:高是 decode,低是 access。 3. 它为什么功能多但启动快? —— **模块按需加载**,用不到就不进内存。

相关笔记