VLC 的模块可以按「能力」分成六类,每一类都有多个候选在竞争。 理解这张分类表,就理解了「为什么同一个片在这台机器上换了个解码器」。
上一节讲完 VLC 作为成品的使用,这一节开始往内部看。 这一节是 LibVLC 二次开发的地基:你要用它的接口,就得知道接口背后是哪几类模块在干活。
一、六类能力
demo 里用 FFmpeg 的数量类比了 VLC 的模块规模,这里给分类。
| 能力 | 干什么 | 典型模块 |
|---|---|---|
| access | 打开输入源 | file、http、rtsp、screen |
| demux | 把字节流拆成包 | mp4、ts、mkv、avi |
| decode | 把包还原成帧 | avcodec、dav1d、DXVA |
| filter | 处理数据(去隔行、调色) | deinterlace、equalizer |
| vout | 把画面显示出来 | direct3d、opengl、wingdi |
| aout | 把声音送出去 | directsound、mmdevice |
六类串起来正好是第一章那条五段流水,只是把「解封装」拆成了 access 与 demux 两段, 又多了一个 filter。
access 与 demux 分开是有道理的:前者管「从哪儿拿字节」,后者管「这些字节是什么格式」。 所以「从 HTTP 读一个 MP4」这件事,是 http 模块提供字节、mp4 模块负责拆包,两个模块协作。
二、为什么每类都有多个模块
这不是冗余,是「降级链」:
以解码为例,VLC 里可能有这么几个候选: D3D11 硬解:最快,但要求显卡支持这个编码与这个档次。 DXVA2 硬解:老一点的接口,兼容性更广。 avcodec 软解:永远能用,但最慢。
挑的原则是「能用 + 优先级高」,而不是「谁快」。 所以有时你会发现它宁可用慢的也不开硬解——因为硬解模块自己声明「这个参数我接不了」。
这种设计带来一个用户体验上的好处:用户永远不用管「我这个片该用什么解码器」, 播放器自己挨个试。也带来一个调试上的麻烦:出问题时看不出它用了谁,得开详细日志才知道。
三、ffmpeg 里的对应物
demo 里数了两个量:封装格式名 414 个、编解码器名 557 个。 这两个数字的量级就是 VLC 的模块规模。
对照关系:
| VLC 的类 | FFmpeg 的对应 | 本机数量 |
|---|---|---|
| demux/mux | 格式名 | 414 |
| decode/encode | 编解码器名 | 557 |
| vout/aout | avdevice 的输出设备 | 很少 |
| filter | 滤镜名 | 几百个 |
注意第三行那个「很少」:FFmpeg 不太管「怎么显示」,它只负责「产出帧」。 显示是播放器自己的事——VLC 自己做 vout,ffplay 用 SDL,你写播放器就得自己选。 这也是为什么第六章要专门讲 SDL。
第四行「几百个滤镜」是 31 课的主题:libavfilter 里的滤镜比播放器用到的多得多, 因为转码与特效比播放需要更多处理。
四、模块怎么被发现
VLC 启动时会扫 plugins 目录,把每个 DLL 读一遍,问它「你能干什么」。
这个「问」的机制是一对宏:vlc_module_begin 与 vlc_module_end。
每个模块在里面必须声明三样东西:
描述(set_description):给人看的名字,日志里会打印。
能力与优先级(set_capability):比如「我是 demux,分数 10」。
回调(set_callbacks):真干活的打开与关闭两个函数。
那个分数值很关键:分数高的先试,试不通再降。 所以「挑哪个模块」是「分数 + 能不能用」两个因素共同决定的。
这也解释了「插件目录为什么不能乱删」:删掉 mp4 的 demux 模块,VLC 就打不开 mp4, 而且报的错是「没有可用的输入」,不是「缺 mp4 模块」——这个错很误导人。
五、动手 Demo:模块不是概念,是能数出来的东西
这段数本机的封装格式与编解码器,再把 VLC 的六类能力对上号。 重点看第二张表:每一类在 FFmpeg 里对应什么、数量差多少。
fmts = av.formats_available
codecs = av.codecs_available
TH([u"能力类别", u"本机数量", u"对应 VLC 的哪类模块"], [0, 0, 0])
T([u"封装格式名", u"%d" % len(fmts), u"demux/mux 模块"], [0, 0, 0])
T([u"编解码器名", u"%d" % len(codecs), u"decoder/encoder 模块"],
[0, 0, 0])
实跑输出:
--------------------------------------------------------------------
模块不是概念,是能数出来的东西
--------------------------------------------------------------------
**VLC 的「模块」听起来抽象,其实可以拆成几类能力来数。**
**本机 FFmpeg 的这几张表,就是同一套东西的另一种组织形式。**
能力类别 本机数量 对应 VLC 的哪类模块
-----------------------------------------------------
封装格式名 414 demux/mux 模块
编解码器名 557 decoder/encoder 模块
**VLC 的组织方式是「按能力挂模块」**:每个 DLL 声明的不是「我叫什么」,而是「我能干什么」。
能力名 干什么 一个具体模块
------------------------------------------------------------
demux 把字节流拆成包 mp4、ts、mkv 各一个
decode 把包还原成帧 avcodec、dav1d、硬件解码
vout 把帧显示出来 direct3d、opengl、直写窗口
aout 把声音送出去 directsound、mmdevice
access 打开输入源 file、http、rtsp、dvd
filter 处理数据 deinterlace、equalizer
**「按能力挂」带来一个很实际的后果**:**同一个能力可以有多个模块竞争,VLC 按优先级挑。**
比如解码,它可能同时有「FFmpeg 软解」「DXVA 硬解」「D3D11 硬解」三个模块。
挑谁取决于「能不能用」和「优先级分数」,
**不是「谁更快」**——**这是设计目标决定的:宁可稳,不要最快。**
**这也解释了一个常见现象**:同一台机器上,VLC 播某个片用软解、播另一个片用硬解。
**不是它随机,是「这个片的编码格式与参数,硬解模块声明自己接不了」。**
414 与 557,这两个数字说明「格式支持」是一件很占地方的事。 每多支持一种格式,就多一段代码、多一个模块。
但真正被用到的很少:日常遇到的格式不超过二十种,剩下几百个是「长尾」——老旧格式、专业格式、 区域性格式。这正好解释了 VLC 的体积:它把长尾全带上了。
最后一行值得注意:VLC 的 vout/aout 模块在 FFmpeg 里没有对应物,因为 FFmpeg 只负责产出帧, 不管显示。「怎么显示」是播放器自己的事,这是第六章 SDL 的伏笔。
30 秒实验
- 打开 VLC 的 plugins 目录,按前缀(libaccess、libdemux 等)数一下各类模块。
- 把 plugins 目录临时改名,启动 VLC,看它报什么错。
- 想一想:为什么 VLC 不把「挑模块」的结果显示给用户?
常见误区
- 以为每类多个模块是「冗余代码」。 它是降级链:快的不一定能用,能用的不一定快。
- 以为「没有可用的输入」是文件坏了。 多数时候是插件目录缺了对应的 demux 模块。
- 以为 FFmpeg 也管显示。 它只产出帧,显示要播放器自己实现。
自测三问(点开对答案)
1. VLC 的六类模块里,哪两类合起来对应「解封装」? —— access(打开输入源、取字节)与 demux(把字节拆成包)。 2. 为什么解码会有三个以上候选模块? —— 因为硬件加速接口有新旧之分、兼容性不同,再加一个「永远能用」的软解兜底。 3. 删掉 plugins 里的 mp4 模块,VLC 会报什么错? —— 「没有可用的输入」——不是「缺 mp4 模块」,所以这个错很误导。模块体系看懂了,下一节进入接口层:LibVLC 就是把这套模块包成一套 C API。
- ☐ (本节完,下一节 02.03.02 LibVLC简介)