VLC 是「什么都能放」的代名词,它的本事来自一个很朴素的设计:按能力挂模块。 代价是启动慢、体积大——它把兼容性放在了第一位。
第一章把播放器的五段流水讲完了,这一章开始看一个把五段全做好的现成产品。 先看 VLC 本体怎么用、为什么兼容性这么强;再看怎么用它提供的接口写自己的播放器(LibVLC 二次开发)。 这一章是「先会用,再会改」。
一、它是怎么来的
VLC 起于 1996 年巴黎中央理工的学生项目,本来只是给校园网做电视流的路由工具。名字的展开是 VideoLAN Client——它最早是个客户端,服务端另有其物。
它的两个关键决定,一直影响到今天: 一是不用系统的解码器,自己带一套(后来大量借助 FFmpeg 的库)。 二是跨平台:Windows、macOS、Linux、Android、iOS 全套自己做。
「自己带解码器」是兼容性无敌的根本原因:系统自带的解码器受专利与授权限制,格式支持有限; VLC 自己带,能解多少取决于它编译了什么,不取决于你装了什么。这就是为什么一台刚装好的 Windows 放不了 mkv,VLC 一装就能放。
二、模块化的组织方式
VLC 内部不是一个大程序,是几百个小模块拼起来的。 每个模块只声明「我能干什么」,不声明「我叫什么」。
这个「按能力挂」的思路,是整个架构的核心:播放一个文件时,VLC 依次问——谁能打开它(access)、 谁能拆它(demux)、谁能解它(decode)、谁能显示它(vout)、谁能发声(aout)。 每一问都有多个候选。
好处是「加一种格式」变成「丢一个 DLL 进去」,不用改主程序、不用重新发行。 坏处是「每次都要从一堆候选里挑」——这就是启动慢的第一个来源。
demo 里量了一下「挑」的成本:三百个候选里顺序找一个,每次 1.66 微秒;查表只要 0.063 微秒。 差别二十六倍。 但这个量级还解释不了启动那两三秒——真正的时间花在加载 DLL 与初始化上。
三、它的体积为什么这么大
VLC 的安装包一百多 MB,解压后两三百 MB,插件目录里上千个文件。 这些体积换来的是「不用装解码包」。
拆开看,体积主要在三处: 解码器:FFmpeg 的一整套库(avcodec 就有几十 MB)。 硬件加速后端:D3D11、DXVA、VA-API、VDPAU、Metal 各一份。 界面与扩展:Qt 界面、皮肤、Lua 脚本引擎、流媒体服务器模块。
这个取舍值得记住: 要「装完就能放一切」,就得把所有可能用到的解码器都带上;要「体积小」,就只能按需装。 这是同一个问题的两个答案,没有对错,只有取舍。
四、和 FFplay 的关系
VLC 与 ffplay 都是播放器,但定位完全不同:
| 对比项 | VLC | ffplay |
|---|---|---|
| 用户 | 普通观众 | 开发者 |
| 界面 | 完整图形界面 | 一个窗口加几个快捷键 |
| 能调的东西 | 菜单里的那些 | 命令行参数 |
| 代码量 | 几十万行 | 一个 C 文件 |
| 定位 | 成品 | FFmpeg 的 API 示例 |
ffplay 的价值不是「用来放片」,是「用来验证 API」:它把 FFmpeg 每个接口都用了一遍, 代码又只有几千行,是学 FFmpeg 最好的参考实现。第九章整章都在拆它。
VLC 也用 FFmpeg 的库,但它是「用」,不是「照着写」:它有自己的解封装、自己的渲染、 自己的模块体系,只在需要解码时才调 libavcodec。所以「VLC 是基于 FFmpeg 的」这句话只对了一半。
五、什么时候该用它
三句话能说清:
要「在客户机器上放任何格式」→ 用 VLC。它是唯一能保证「不装解码包也能放」的成品播放器。
要「把播放能力嵌进自己的程序」→ 用 LibVLC。 这是本章后半部分的主题:它把 VLC 的全部能力包成一套 C 接口,你只要给一个窗口句柄、给一个地址, 剩下的它全包。
要「学播放器怎么写」→ 读 ffplay。VLC 的代码量太大,读起来看不到全局;ffplay 一个文件就能读完。
还有一条实用建议: 如果你只是要「在程序里播个视频」,先查系统自带的媒体框架(Windows 的 Media Foundation、 macOS 的 AVFoundation),它们体积为零、不需要额外部署。 只有当格式支持不够时,才轮到 LibVLC。
六、动手 Demo:几百个模块,查找方式决定启动快慢
这段先在三百个候选里比「顺序找」与「查表」的成本,再说明这个差距能解释什么、不能解释什么。 重点是后半段那句「别拿它解释整段启动时间」。
names = [u"mod%03d" % i for i in range(299)] + [u"mp4"]
rng.shuffle(names)
# 顺序找
for n in names:
if n == u"mp4":
break
# 查表
dmap = dict((n, i) for i, n in enumerate(names))
dmap.get(u"mp4")
实跑输出:
--------------------------------------------------------------------
几百个模块,查找方式决定启动快慢
--------------------------------------------------------------------
**VLC 号称「什么都能放」,代价是它内部注册了几百个模块,每次要按用途挑一个。**
**挑法有两种:按顺序一个一个试,或者查表直接命中。**
**这两种在几百个候选下的差距是可以量出来的。**
本机 FFmpeg 注册的封装格式名一共 **414 个**,VLC 的模块数是同一个量级。
**同一个「找 mp4」的动作,各跑两万次**:
找法 每次微秒 相对 说明
---------------------------------------------------------------
顺序试 1.56 1.0 倍 要试到第 72 个才命中
查表 0.052 29.89 倍 一步到位,与候选数无关
**单次差 1.51 微秒,看着不多;但 VLC 启动要枚举上千次能力。**
**所以真实 VLC 启动那两三秒,主要花在「加载 DLL 并初始化」,不是花在「比对名字」。**
这段量出来的只是「查找」这一层的量级,别拿它解释整段启动时间。
**但它说明了一个设计取舍**:
**模块化让「加一个格式」只需丢一个 DLL 进去,代价是每次都要从一堆候选里挑。**
**这就是「可扩展」与「启动快」之间的那笔账。**
二十六倍的差距听着很大,但它解释不了 VLC 启动那两三秒。因为「查找」是纳秒到微秒级的事, 而启动是秒级的事。
真正花时间的是三件事:读上千个小文件(插件目录)、把每个 DLL 映射进内存、 执行每个模块的初始化代码。这三件事都是 IO 与系统调用,比字符串比较贵三四个数量级。
那为什么还要量这段:因为它说明「模块化本身有成本」。 这个成本在候选少的时候可以忽略,在候选上千的时候就必须靠「缓存结果」来摊平——VLC 确实会把模块清单缓存起来,第二次启动明显更快。
30 秒实验
- 装一个 VLC(如果没有),数一下它的 plugins 目录里有多少个文件。
- 第一次启动与第二次启动各掐一次表,看差多少。
- 想一想:为什么它宁可启动慢,也不做成「按需下载解码器」?
常见误区
- 以为 VLC 只是「FFmpeg 的一个界面」。 它自己实现了解封装、渲染与模块体系, 只在解码环节大量借助 FFmpeg 的库。
- 以为「什么都能放」是免费的。 代价是几百 MB 的体积与更长的启动时间。
- 以为 VLC 适合嵌进产品。 嵌进产品应该用 LibVLC 或系统媒体框架, 直接带整个 VLC 会让安装包膨胀好几倍。
自测三问(点开对答案)
1. 为什么刚装好的 Windows 放不了 mkv,装了 VLC 就能放? —— 因为系统自带的解码器受授权限制,格式支持有限; VLC 自己带一整套解码器,能解什么取决于它编译了什么。 2. VLC 的模块「按能力挂」是什么意思? —— 每个模块声明的是「我能做 demux/decode/vout 中的哪一种」,而不是「我叫什么」。 播放时按能力逐段挑选,而不是按名字调用。 3. 要学播放器怎么写,为什么读 ffplay 而不是读 VLC? —— ffplay 只有几千行、一个文件,能看到全局;VLC 几十万行,读进去容易迷路。下一节把它的能力列成一张表:播放之外它还能转码、推流、录制,这些能力其实共用同一条管道。
- ☐ (本节完,下一节 02.01.02 VLC的功能列表)