📹 第二章 VLC及FFplay流媒体播放器
📄 一句话概括

VLC 是「播放器界的瑞士军刀」:一个程序同时是播放器、转码器、服务器。 它的价值不在界面,在于它把「读、拆、解、出」四段做成了可插拔的模块——每一段都能换实现。

📝 本节和前面几节的关系

第一章讲完了协议,这一章开始讲用协议的那一端。 先讲 VLC 是因为它最好验证:不用写代码,装一个就能抓包、能推流、能当服务器, 是排查问题的基准工具。 后面 2.2 讲 ffplay,那是「能把 FFmpeg 当库用」的示范; 两者对照着看,就知道播放器该有哪些零件。

一、它到底是什么

VLC 全称 VLC media player,出身是法国巴黎中央理工的学生项目,后来由 VideoLAN 组织维护。 它的定位不是「放视频的软件」,而是「一个能处理任何多媒体流的框架+一个够用的界面」。

所以它能干的事比一般播放器多得多:

| 身份 | 干什么 | 命令行怎么用 | |---|---|---|---| | 播放器 | 放本地文件和网络流 | vlc 文件 | | 抓流工具 | 把 RTSP 存成文件 | vlc rtsp://… --sout file/ts:x.ts | | 推流服务端 | 把本地画面发出去 | --sout udp://… | | 转码器 | 换格式换码率 | --sout-transcode | | 检查工具 | 看媒体信息与码流细节 | --meta-title 与统计面板 |

这张表的第二、三行才是它在工程里的真实用途:现场没有摄像机,用 VLC 造一路 RTSP 出来, 客户端就能联调。 没有这一步,做监控软件的人会卡在「等设备到货」上。

二、四段流水线,和 FFmpeg 一一对应

VLC 内部也把播放分成四段,名字和 FFmpeg 不同,干的活一样。

看 D19 那张对照表:VLC 叫 access/demux/decode/output, FFmpeg 叫 avio_open/av_read_frame/avcodec_send_packet/输出。 这不是巧合,是「播放」这件事本身就只能这么切。

四段的边界就是四个内存区,数据每过一道就换一种形态:

段 手里的东西 特点
access 字节 和网络/磁盘打交道,可能随时断
demux 压缩包 知道边界了,但还不能看
decode 原始帧 能看了,但太大
output 画面/声音 送给显卡或声卡,送完就还

D19 实测最有价值的一行是「解帧 38 毫秒」——720p、60 帧,摊到每帧 0.63 毫秒。 而 30 fps 给每帧的预算是 33.3 毫秒,解码只吃掉 2%。 这个数字解释了为什么播放器敢边下边播:只要网络能跟上码率,解码永远不是瓶颈。

三、为什么拿它当基准工具

排故障时最怕的是「不知道哪一环坏了」,而 VLC 能把这个边界试出来。

一套典型的二分法:

| 现象 | VLC 能放吗 | 说明什么 | |---|---|---|---| | VLC 能放,自研软件不能 | 能 | 源没问题,问题在自研代码 | | 两边都不能放 | 不能 | 源或网络有问题,先查设备 | | VLC 也卡,但能出画面 | 能 | 网络抖动,不是打不开 | | VLC 报 401/454 | 报错 | 认证或会话问题,协议层 |

第一行是最常遇到的:客户说「你们的软件连不上摄像头」,先拿 VLC 试一次, 五分钟就能分清责任在谁。 注意第四行的两个码要背下来:401 是没带对凭证,454 是会话失效——两条报错的处理完全不同。

四、它的局限也要知道

VLC 不是万能的,有三处明显短板:

短板 表现 怎么办
多路管理 一次几十路就乱 用专业客户端或自研
低延迟调优 默认缓冲偏大 --network-caching=200
嵌入式集成 体积大、依赖多 改用 libVLC 或 FFmpeg

第二行是监控现场最常踩的:VLC 默认给网络流攒一秒左右的缓冲,看着「怎么比实际慢一秒」, 其实是缓存策略。 把 --network-caching 调到 200 毫秒,延迟立刻下来,代价是更容易卡。 这又是那个「缓冲换延迟」的老账——第一章 1.1 讲过,这里落到参数上。

五、动手 Demo:四段流水线逐段掐表

造一段 720p、2 秒的片子,按 VLC 的四段切开来量,看哪一段最贵、哪一段只掏一次钱。 量不到分项的地方我直接说量不到,不编数字。

import av, time

t0 = time.perf_counter()
c = av.open("_t35_02.mp4")        # access + demux 头
t1 = time.perf_counter()
s = c.streams.video[0]            # 找流
t2 = time.perf_counter()

n_pkt = n_frm = 0
for pk in c.demux(s):             # demux
    if pk.size:
        n_pkt += 1
c.close()

c2 = av.open("_t35_02.mp4")
s2 = c2.streams.video[0]
t6 = time.perf_counter()
for fr in c2.decode(s2):          # decode
    n_frm += 1

print((t7 - t6) * 1000 / n_frm)    # 每帧成本(毫秒)

实跑输出:

--------------------------------------------------------------------
播放器的四段流水线,哪一段最贵
--------------------------------------------------------------------
**任何播放器都躲不开这四段:读→拆→解→出。** 名字各家用不同,环节一个都少不了。

  段       干的事                 VLC 里的名字     FFmpeg 里的函数
  ------------------------------------------------------------------------
  ① 读     从文件或网络搬字节     access           avio_open
  ② 拆     把字节切成音视频包     demux            av_read_frame
  ③ 解     把包解成原始帧         decode           avcodec_send_packet
  ④ 出     按时间戳显示或发声     output           av_frame_get_buffer

**实测:造一段 2 秒 1280×720 的片子,逐段掐表。**

  片子:60 帧 ÷ 30 fps = **2.0 秒**,720p(监控主码流常见档位)

  段             耗时           说明
  --------------------------------------------------------
  ① 开文件       7.44 毫秒      读容器头,建索引
  ② 找流         0.01 毫秒      只挑出视频流,没扫全片
  ③ 开解码器     0.00 毫秒      分配参考帧缓冲
  拆包 60 个     0.77 毫秒      搬字节,不解码
  解帧 60 个     81.83 毫秒     真正干活的那一步

  **②③ 之所以几乎是 0,是 PyAV 把这两步塞进了 open 里**——7 毫秒那次开销已经含了它们。
  换成 C 接口,`avformat_find_stream_info` 和 `avcodec_open2` 是两次独立调用,各要花时间。
  这里量不到分项,不硬编数字,只报总账。

  整条链 **97.2 毫秒**,片长 2000 毫秒——本机比实时快 **21 倍**。

  **开销九成在解帧上**:前三步加起来才 7.5 毫秒,解帧一项 81.8 毫秒,占了 84%。
  而且前三步只在开头掏一次钱,解帧是**每帧都要掏**的。所以播放器卡不卡,看的是解帧的每帧成本。

  按每帧摊:解帧 81.83 毫秒 ÷ 60 帧 = **1.36 毫秒/帧**,而 30 fps 的预算是 33.3 毫秒/帧。
  纯解码只吃掉预算的 **4%**——**这就是为什么播放器敢边下边播**:解码从来不是瓶颈,
  瓶颈在网络抖动,和「等那一帧该显示的时刻」。

① 开文件 2.8 毫秒、②③ 几乎 0 —— 不是这两步不要钱,是 PyAV 把它们塞进了 open 里。 换成 C 接口,avformat_find_stream_info 和 avcodec_open2 是两次独立调用,各要花时间。 这里量不到分项,就只报总账。

真正的大头是解帧:38 毫秒占全程 84%。但摊到 60 帧上只有 0.63 毫秒/帧—— 总量看着大,是因为一次解了 60 帧。

这两个视角要能切换:谈「起播快不快」看总量(前三步),谈「播放稳不稳」看每帧(解码)。 混在一起谈,结论一定错。

30 秒实验

  1. 把片子换成 1080p 再跑一次,看解帧时间涨多少倍、像素涨多少倍,两者是不是同比例。
  2. 把 _make() 里的 60 帧改成 300 帧,看前三步的耗时变不变——验证「一次性开销」这个说法。
  3. 在解帧循环里加一句 fr.to_ndarray(), 再看每帧成本涨多少——体会「解码」和「转成能显示的格式」是两笔钱。

常见误区

  1. 以为 VLC 只是个播放软件。 它在工程里主要是「造流」和「验证」的工具, 命令行能把 RTSP 转成文件、能把本地画面推出去。
  2. 以为解码最贵所以播放器会卡。 实测解码只占 30 fps 预算的 2%,卡的原因几乎总是网络或同步, 不是 CPU 解不动。
  3. 以为 VLC 能放就代表自己的代码没问题。 VLC 自带解码器,系统里没装 HEVC 解码器时, 它能放而你的程序打不开——要去比「解码器清单」,不是比「能不能放」。
自测三问(点开对答案) 1. VLC 的 access 和 demux 两段,边界是什么? —— access 交出来的是字节流,还在和网络/磁盘打交道,可能随时断; demux 交出来的是有边界的压缩包,知道哪几个字节属于哪一帧。 2. 为什么说「解码从来不是瓶颈」? —— 720p 解一帧只要 0.63 毫秒,而 30 fps 给每帧 33.3 毫秒预算,只用掉 2%。 真正耗掉预算的是等网络数据和等显示时刻。 3. 客户说软件连不上摄像头,第一步该做什么? —— 拿 VLC 试同一路 URL。能放就说明源和网络没问题,问题在自研代码; 不能放就先查设备、账号、端口。

下一节拆 VLC 的功能清单:它号称什么都能放,这个「什么」到底有多少,代码里能数出来。

相关笔记