ToC
前言
在字幕渲染领域,libass 和 xy-VSFilter 可以说是两架马车。libass 使用 C 语言编写,根据 Google Code 存档的说法,在大多数情况下 libass 的效率比 xy-VSFilter 快 50%。从支持的角度来看,ffmpeg 支持 libass 而非 xy-VSFilter,而 Linux 版本的 Aegisub 也只有 libass 可以选用。
最初对 libass 源码的兴趣源于 vsfiltermod 中的 \fsc 标签。根据描述,其功能近似于同时设置 \fscx 和 \fscy,而在实验中,我发现 libass 也会解析 \fsc,但其只会将字体大小复位,和 vsfiltermod 的效果不同。由此,我开始了对 libass 源码的探索之旅。
本系列使用的 libass 源码的 commit hash 为 `d149636`[1],对应 libass 版本号为正式版 0.15.0。
基本流程
我们的样例是 test/test.c。这个样例的功能是调用 libass,读取字幕文件并输出某一秒的渲染结果为 png 图片。通过梳理源码,我们可以整理出简单的程序执行流程,如图 1 所示:

图中实心箭头表示这个样例中主干部分的执行流程,而空心箭头则表示了详细的内部调用过程。这里我们只列出了大致的调用流程,省略了错误处理等情况。
简单梳理一下,图 1 中相对重要的部分有:**font_provider**、**ass_library**、**ass_renderer**、**ass_read_file** 和 **ass_render_frame**,分别对应了 libass 中的字体、**ASS Library**、**ASS Renderer**、**ASS Track** 和实际渲染部分。暂时忽略实际渲染,我们来看一看剩下的四个概念。
字体
字幕字幕,核心是字。既然有字那就必然存在字体。libass 中字体相关的架构如图 2 所示:

可以看到,字体的来源有两种:内嵌字体(Embbed Font)和外部字体。内嵌字体位于 ASS 文件的字体行中;而外部字体则和不同的操作系统有着密切的联系,libass 把对这部分的抽象命名为 Font Provider。
不同的操作系统对外部字体的支持方式不尽相同。如 Apple 的 Core Text[2],Win32 的 DirectWrite[3],Linux 的 fontconfig[4] 等,各自都是互不相同的字体实现方案。
样例通过 ass_get_available_font_providers 函数获取了系统中可用的字体实现方案。
ASS Library
从功能角度而言,ASS_Library 的职责并不单一:主要负责内嵌字体和样式相关的内容,同时也兼任消息回调的执行。
TODO: 将代码修改为图表形式
struct ass_library { char *fonts_dir; int extract_fonts; char **style_overrides;
ASS_Fontdata *fontdata; int num_fontdata; void (*msg_callback)(int, const char *, va_list, void *); void *msg_callback_data;};
typedef struct ass_library ASS_Library;在 libass 中,ASS_Library 还算常见。当你遇到它但又不记得具体功能时,回来看看就行了。
消息回调(msg_callback)
libass 通过消息回调向外界输出日志。为使这个过程更加灵活,libass 使用了消息回调的方式,调用用户定义的 msg_callback 函数进行输出。样例在渲染米粒垃圾 393 回 omake 时的日志输出如图 3 所示:

ASS Renderer
作为字幕渲染库,libass 的本质工作终究还是渲染。ASS_Renderer 就是 libass 中负责渲染的核心组件。
ASS Renderer 中记录了渲染所需的全部信息。由于这个部件过于复杂,我们之后单独论述。
Ass Track
正确读入 ass 字幕文件是渲染的前提,libass 中通过 ASS_Track 存储字幕,其基本结构如图 4 所示:

可以看到,ASS Track 包含了一个字幕文件的全部信息,而 ASS ParserPriv 则包含一些解析时会用到的非公开信息。
小结
通过 test/test.c 的示例,我们简单梳理了 libass 中的一些基本概念。本文也是继技术型博客行文迷思(1)之后的第一篇博客,期待各位在可读性、流畅性等方面提出宝贵意见(笑)。