🎬 第二章 VLC播放器及二次开发应用
📄 一句话概括

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

  1. 打开 VLC 的 plugins 目录,按前缀(libaccess、libdemux 等)数一下各类模块。
  2. 把 plugins 目录临时改名,启动 VLC,看它报什么错。
  3. 想一想:为什么 VLC 不把「挑模块」的结果显示给用户?

常见误区

  1. 以为每类多个模块是「冗余代码」。 它是降级链:快的不一定能用,能用的不一定快。
  2. 以为「没有可用的输入」是文件坏了。 多数时候是插件目录缺了对应的 demux 模块。
  3. 以为 FFmpeg 也管显示。 它只产出帧,显示要播放器自己实现。
自测三问(点开对答案) 1. VLC 的六类模块里,哪两类合起来对应「解封装」? —— access(打开输入源、取字节)与 demux(把字节拆成包)。 2. 为什么解码会有三个以上候选模块? —— 因为硬件加速接口有新旧之分、兼容性不同,再加一个「永远能用」的软解兜底。 3. 删掉 plugins 里的 mp4 模块,VLC 会报什么错? —— 「没有可用的输入」——不是「缺 mp4 模块」,所以这个错很误导。

模块体系看懂了,下一节进入接口层:LibVLC 就是把这套模块包成一套 C API。

相关笔记