从一个下载问题开始
刚进入编辑器团队时,我负责滤镜下载。它看起来只是基础设施:服务器放资源,客户端按需获取。直到遇到一个问题——下载和安装都成功,同一滤镜在某台设备正常,另一台却显示灰色矩形。
要解释这个问题,必须知道下载之后滤镜是什么、引擎如何处理一帧,以及“帧”在内存里究竟意味着什么。理解这条链路后,下载模块的许多设计才有了理由。
视频编辑的五个阶段
媒体层处理文件与编解码,效果引擎接收帧和参数。预览与导出共享主要处理阶段。
解封装读取容器中的视频、音频流与索引,把压缩样本交给解码器,此时还不是图像。
解码把压缩样本变成帧。媒体层封装 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,具体滤镜作为数据提供。
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,原来的本地解析流程因此可以延伸为远端补齐流程。
这项工作的收获是:基础设施也需要理解自己搬运的内容。帧很大,所以避免复制;滤镜是结构化数据,所以校验解码语义;动画是逐帧参数,所以明确求值归属;草稿会跨设备,所以保存身份而不是位置。