全部文章

滤镜是一张查找表

从滤镜下载走进逐帧渲染:理解视频管线、帧缓冲、3D LUT,以及为什么草稿应保存特效标识符而不是文件路径。

从一个下载问题开始

刚进入编辑器团队时,我负责滤镜下载。它看起来只是基础设施:服务器放资源,客户端按需获取。直到遇到一个问题——下载和安装都成功,同一滤镜在某台设备正常,另一台却显示灰色矩形。

要解释这个问题,必须知道下载之后滤镜是什么、引擎如何处理一帧,以及“帧”在内存里究竟意味着什么。理解这条链路后,下载模块的许多设计才有了理由。

视频编辑的五个阶段

解封装、解码、渲染、编码与封装媒体层处理文件与编解码,效果引擎接收帧和参数。预览与导出共享主要处理阶段。

解封装读取容器中的视频、音频流与索引,把压缩样本交给解码器,此时还不是图像。

解码把压缩样本变成帧。媒体层封装 iOS 的 VideoToolbox、Android 的 MediaCodec 等平台能力,优先利用硬件路径,不支持的格式或 profile 再考虑软件处理。

渲染由效果引擎接收帧与滤镜、文字、转场、动画等描述,在 GPU 上合成新的帧。媒体层不需要理解每个滤镜,效果引擎也不负责打开媒体文件。

编码与封装再把渲染帧压缩,并与音频写入输出容器。预览要赶上播放时钟,导出则不受同样的实时显示截止时间约束。

30 fps 的帧间隔约 33.3 毫秒,60 Hz 显示刷新间隔约 16.7 毫秒。流水线可能重叠执行各阶段,但吞吐与显示调度必须满足播放节奏,否则就会掉帧或卡顿。

帧有多大,为什么要留在 GPU 附近

一帧是固定分辨率的像素网格。1080p 有 1920 × 1080 个像素,RGBA 每通道一字节时约 8.3 MB,30 帧/秒就是约 249 MB/s 的原始像素量。NV12 约每像素 1.5 字节,同样分辨率约 3.1 MB。

这些量级解释了为什么应避免无谓的 CPU/GPU 往返复制。解码缓冲、纹理和帧缓冲之间尽可能直接共享或转换,比“先拷到 CPU 看一下”更符合实时处理需求。

渲染程序根据输入纹理与参数计算输出像素,结果写入帧缓冲,再交给显示或编码。预览通过缓冲轮换协调显示与下一帧处理,具体缓冲数量和同步方式由实现决定。

颜色滤镜可以表示为 3D LUT

这里讨论的是按像素做颜色映射的滤镜,不是所有空间或 AI 效果。它把输入 RGB 映射到输出 RGB,适合采样成三维查找表。

每个轴取 64 个点,就有 64 × 64 × 64 个条目。引擎复用同一个 LUT shader,具体滤镜作为数据提供。

三维 LUT 的采样与强度混合64 个颜色切片铺成 512 × 512 图像,采样后与原色按强度混合。

64 张 64 × 64 切片按 8 × 8 排列,可以装进一张 512 × 512 PNG。输入颜色通常落在离散格点之间,shader 用邻近值插值。这个表示适合平滑颜色变换,但维度、插值规则、色彩空间和精度都属于渲染契约,不能仅凭“相同数学公式”就断言各平台逐像素完全一致。

开头的问题来自资源在重编码时应用了颜色配置,支持与忽略配置的设备读取到不同表值。下载校验因此不仅检查文件存在,还检查维度、位深与按约定解码后的像素哈希。

强度计算可以写成 mix(original, lut(original), intensity)。0 表示原图,1 表示完整应用滤镜;这个标量也是后续关键帧动画的入口。

下载设计由渲染契约反推

滤镜包从目录到安装与解析资源先下载到临时位置,验证后原子安装,registry 再把 ID 与版本解析为本地包。

滤镜包包含 LUT、缩略图与 manifest,声明 ID、版本、显示名、默认强度、像素哈希和最低引擎版本。目录保持轻量,资源按需下载,默认类别可在合适网络环境下预取,不支持的引擎版本提前过滤。

安装先在临时目录解压、解析并校验,再在同一文件系统内原子重命名到 <id>/<version>/,避免调用者看到半安装状态。损坏资源不进入可用缓存。

相同包的并发请求合并,在途数量受限;网络错误退避重试,确定不存在或不匹配的包进入明确失败状态。缓存按使用情况淘汰,但本机草稿引用的资源保留,固定集合从草稿引用推导,避免手工维护漂移。

Registry 只回答一个问题:给定 ID 和版本,是否有可用的已安装资源?引擎与草稿加载器都通过这个入口,不直接拼磁盘路径。

关键帧把时间接入参数

滤镜强度从第一个关键帧的 0 变化到两秒后的 1,中间值由共享时间线求值器计算,再与帧一起传给渲染器。渲染器接收最终标量,不重新应用一次缓动。

这样各平台可以共享时钟、插值和默认值语义。仍然需要数值容差与跨平台测试,但至少不会由不同层各自解释同一条曲线。

草稿为什么保存 ID,而不是路径

{
  "type": "clip",
  "filter": { "id": "fx.warm_film", "version": 3, "intensity": 0.8 }
}

路径属于当前设备,换一台手机就失效;ID 与版本表达资源身份。打开草稿时,registry 未命中便请求对应包,完成后继续渲染;已安装且被草稿固定的包则无需再次下载。

云草稿需要文档中的每个引用在陌生设备上仍有意义。素材可以用内容哈希命名,滤镜从一开始就有稳定 ID,原来的本地解析流程因此可以延伸为远端补齐流程。

这项工作的收获是:基础设施也需要理解自己搬运的内容。帧很大,所以避免复制;滤镜是结构化数据,所以校验解码语义;动画是逐帧参数,所以明确求值归属;草稿会跨设备,所以保存身份而不是位置。