编者按:
音视频产品中的动效素材需求是源源不断的,例如贴纸花字,转场特效,照片和视频模板等,并且对它们的产量,上线速度,以及视觉效果都有比较高的要求。但在传统工作流中的交付成本却非常高,需要通过代码来手动还原设计师预设的动效内容。为了降低或消除动画相关的研发成本,腾讯内部历时 5 年研发了一套 PAG 动画工作流的解决方案,能够一键将 AE 动画内容导出并应用到几乎所有的主流平台。LiveVideoStackCon 2022北京站邀请到了腾讯媒资产品中心副总监陈仁健,为大家系统地分享在产品需求的驱动下,PAG遇到的技术挑战和实践经验细节。
1)诞生背景
动效内容在全行业的
各个场景里都已经被广泛使用,因为能够显著提升用户交互的体验。
但动效素材
的生产流程却一直是个很大的行业痛点,主要因为在传统的工作流下,依赖研发用代码还原动效的生产方式存在巨大的瓶颈。
在传统的动效工作流中,一般是设计师先使用 AE 设计好动效,然后导出一个视频Demo,研发再来根据 Demo 拆解动效组成并通过代码还原。这套工作流主要有以下三大核心痛点:
研发成本高:
每个动效都需要研发通过代码来还原,单独排期的特效以及手工配置还原的过程,都需要大量的研发人力持续投入。由于研发人力有限,也导致这个流程无法批量化生产素材。
生产周期长:
设计师和研发人员的联调成本高,效果还原度需要反复确认。之前生产一个典型的视频模板,平均需要研发投入5人天还原才能上线。这样很难跟上运营节奏,容易错过时事热点。
视觉动效弱:
AE里有很多复杂动效,使用纯代码还原起来非常困难,设计师只能不断简化效果以达到跟开发成本的平衡。导致最终上线的视觉效果都是打折过的,往往达不到预期。
要彻底解决这些核心痛点,我们需要一套
自动化的素材生产通路。于是腾讯从 2016 年就着手开始研发了一套 PAG 的动效解决方案:
PAG 的目标是降低或消除动效相关的研发成本,打通设计师创作到素材上线的自动化流程。
整套工作流主要分为左边的 AE 导出插件和桌面预览工具部分,以及右边的SDK 部分。AE 导出插件能够一键将设计师制作好的动效导出成PAG文件,经过桌面预览工具的确认,再上线到终端由PAG SDK渲染成动效内容。可以发现左边的部分实际上是不需要研发介入的,我们将之前需要研发介入的工作都变成了自动化的工具。整套工作流完美解决了传统工作流的三个核心痛点:
研发成本方面:
在PAG的动效工作流里,研发只有一次性接入 SDK的成本,在后续整个素材生产流程都无需研发人力介入。整套工作流不在受制于研发的人力瓶颈,就能够开放给更多的设计师使用,批量化的进行素材生产。
生产周期方面:
由于砍掉了研发成本,最耗时的研发和设计的联调环节也不存在了。设计师可以所见即所得地生产素材,极大地缩短了生产周期,视频模板平均生产耗时从一周可以降低到四个小时,能够快速响应运营热点。
视觉动效方面:
PAG的SDK已经完全还原了AE整个动效的渲染系统,接入一次,设计师就可以充分利用AE动效的原子能力,组合出无限的视觉动效,不用因为代码还原成本的问题而打折扣。
可能前面的介绍还不够直观,这里还有个模板素材上线的视频演示:
可以看到设计师在制作完模板后,只需要很简单的点击菜单里的导出选项即可得到一个PAG文件,不需要任何复杂的配置过程。导出的文件双击就能直接播放,而且预览工具里还提供文本和占位图的编辑能力,可以直接看到替换用户素材后的效果。在经过预览工具的确认之后,设计师就可以将 PAG 文件交付上线了。通过 PAG 的 SDK 可以一次性渲染到几乎所有的主流平台上。
2)应用场景
这是到目前为止 PAG 的 5 个典型应用场景:直播礼物,UI 动画,贴纸花字,视频模板,和游戏战报。
目前微信视频号的直播就在采用 PAG 方案实现所有的礼物动效,也用在了之前西城男孩的线上演唱会里。UI 动画这里演示的 Pick 按钮是一个 PAG 的可交互动效,支持编程控制进度、文本内容。关键是整个动效文件体积非常小,仅 2 KB左右。中间的贴纸花字和视频模板相信大家在短视频产品中都已经非常熟悉了。其中 PAG 的视频模板现在也大规模的应用在了广告视频的生成中。最后一个游戏战报场景,使用了 PAG 提供的图层组合能力,可以从多个PAG文件动态组合出一个自适应的模板,相当于活字印刷的功能。这块目前的已经在王者荣耀以及和平精英的高光时刻战报功能里得到了充分的应用。
3)同类方案对比
行业里跟 PAG 相似的动效解决方案还有 Lottie 和 SVGA:
这三个方案背后其实存在一个比较有意思的共同点:它们的作者都具有 Flash 相关的研发背景。Flash 是历史上把研发和设计师的工作流打通的最为完善的平台,但是从 PC 时代过渡到移动端后,这里就出现了彻底的断层。这三个方案其实都是在将原先繁荣的 Flash 工作流复刻到移动端上,一起在推动行业动效工作流的持续完善。除了诞生场景的不同,目前 PAG 相比其他方案在文件格式,渲染架构,AE特性支持,运行时编辑以及平台支持等各方面都更具有优势。
相信经过前面几部分的讲解,大家对 PAG 方案解决的具体问题和应用场景应该已经有一个大致的了解。在接下来的内容中,我们将带领大家深入到这个方案的技术细节中,展示如何从头打造一个 PAG 动效方案的全过程,以及其中涉及到的重点技术挑战。PAG 方案到目前为止已经迭代了 5 年多,共经历 4 个大版本:
1.0 - 高效文件格式
在 1.0 版本中,我们从最底层开始实现了一套类似游戏引擎的渲染架构,能够无缝地跟视频渲染管线整合,并通过三级缓存等架构的设计满足了极高的实时性需求。但我们花费最多的时间的部分还是在 PAG 文件格式的设计上,最终实现了相同动效内容,只有其他方案一半左右的大小,而且解码还快 12 倍。那具体是如何做到 PAG 文件大小的极致压缩呢?
我们首先采用了二进制的数据结构来存储动效内容。因为二进制数据结构能够非常方便的单文件集成任何资源,并且由于不需要像 JSON 一样处理字符串匹配问题,解码速度可以快几十倍。
而在压缩率方面,二进制数据结构可以跳过 Key 的内容,只存储 Value,这样也能节省大量空间。另外我们还引入了 TAG 数据块的结构,来解决二进制数据的向后兼容性问题。每个 PAG 文件都由无数个互相独立的 TAG 组成,扩展格式只需新增 TAG 即可。高版本 SDK 可以识别低版本所有的 TAG,低版本 SDK 遇到无法识别的 TAG 会跳过读取,如果不是关键的信息也可以正常解码不影响渲染。
但做到这个程度其实也只是对齐了行业里通用序列化方案的上限,而在压缩率方面我们还有很大的空间,原理就是根据动效文件本身的特点实现跨越字节边界的压缩。具体有两大压缩策略:
等于默认值可无需存储:
时间轴属性是AE动效的最基本组成单元,但通常情况下设计师并不会修改所有的属性,大部分时间轴属性都等于默认值。这里可以用一个标志位就跳过默认值的存储。例如一个Point类型的时间轴属性,默认值最少需要8个字节存储。而我们如果使用一个字节的标志位来跳过默存储,单个属性就可以节省7个字节的空间。这是非常可观的。
尽量聚合相似数据类型:
我们在文件的每个属性组里,都会尽可能地把相似的数据重新排列,让他们聚合到一起。这样就可以绕开字节对齐的问题,使用比特位来紧凑存储。例如中间这一组的属性,经过重新排列后,符号位的区域可从5字节降到5比特。内容的区域还可以利用右边的连续数组编码压缩,平均减少一半以上的文件大小。其原理主要是利用了表示屏幕坐标的数据值通常不会太大,在存储数组时我们可以按照最大的那个数需要的比特位来存储所有数据即可。
经过以上的压缩策略,我们就可以把一个动效文件尽可能地压缩到最小。这里是一些 PAG 和 Lottie 的动效文件大小的对比数据:
两者都进行了 zip 压缩之后再进行对比,可以看到相同的动效内容平均 PAG 只有Lottie 文件的 56% 大小。如果不进行 zip 压缩,差距还会更大。
2.0 - AE 全特性支持
我们在 2.0 版本里重点引入了 BMP 预合成的解决方案,并且比较创新的实现了矢量和序列帧的混合导出能力,从而在保留编辑性的前提下又实现了所有的 AE 特性的导出,从这个版本开始才真正意义上彻底释放了设计师的生产力。
矢量导出方式优势就是文件极小,并且可以运行时编辑动效的内容。但这种方式注定无法支持所有AE特性,因为有很多的AE效果在有桌面显卡的情况下,都要走一个进度条才能预览,在移动端是没有可能做到实时渲染的。这也导致在实际的生产过程中,设计师有很多的复杂动效,都无法用矢量模式导出出来,这样会极大限制设计师的创意发挥。
而传统的序列帧导出方式,运行时又无法编辑,文件也相对较大。
PAG方案在这里的创新点就是将两者的进行了完美整合,支持矢量和序列帧的混合导出。
设计师可以主动标记哪些图层使用序列帧导出,例如不需要编辑并且有复杂的动效,而需要编辑的图层继续用简单的矢量方式导出。
从而实现支持所有的AE特性又能保持运行时的编辑性。
在实现混合导出后,剩下的挑战就是怎么尽可能压缩序列帧的大小。我们在PAG内部设计了视频序列帧的格式,充分利用了视频的极限帧间压缩能力。另外视频的格式还可以在运行时利用硬件加速解码,从而获得更高的渲染性能。但它也有一个明显缺点,就是不支持透明通道。我们会在导出时,会将一张 RGBA 的截图扩展成两倍大小的不透明图片,左边放置 RGB 的内容,右边放置表示 Alpha 的灰度图。最后渲染时再合并回 RGBA 的图片,从而实现透明通道的支持。而在渲染的过程中,我们没有像常规做法一样先转换 YUV 到 RGB,再叠加 Alpha 通道的合并,而是直接实现了各种 YUV 硬解格式的一次性上屏,利用自定义 Shader 脚本,在一次绘制中同时完成 YUV 到 RGB的转换以及与Alpha通道的合并,让视频序列帧也实现了接近普通图片一样的绘制性能。
在解决了视频帧的导出和渲染后,我们还要考虑上层的数据封装格式。PAG并没有使用标准的MP4容器作为视频帧的封装,而是设计了一个简化的数据结构。主要还是出于性能优化的原因。
第一个方面是因为动效播放是存在随机性的,我们需要一次性拿到所有的关键帧列表,才可以做到精确的判断是否需要重置解码器,但要从标准的 MP4 容器里获取关键帧列表,耗时会非常高。
另外一方面,视频序列帧本质上还是代表了一个动效,因此也存在静态区间的概念。
大部分的动效素材,实际上并不是整个时间轴都在变化的,或多或少会存在一些画面静止的区间。
虽然这些静态区间数据不影响文件大小,但传给硬件解码器都会造成固定的解码器等待。
因此我们在导出时会利用自定义数据结构记录额外的静态区间信息。
这样在渲染时,就可以直接跳过不必要的解码器等待。
例如右边这个俄罗斯方块的视频模板,每个方块虽然都是一个静态图层,但是使用了矢量不支持的效果,因此就会导出一个全时长都是静态的视频序列帧,这种情况也是比较常见的。
而通过刚刚的优化,直接能够提升99%的性能。
最后可以看一下各种序列帧方案文件大小的对比。相比传统的图片序列帧,视频序列帧可以轻松压缩到百分之一点几的大小。
3.0 - 模板拼装组合
运行时编辑性在 PAG 里一直是项非常重要的能力,是让设计师的素材创意跟用户的个性化元素能够快速融合的关键。我们在 PAG 的前两个版本的迭代过程中,已经分别实现了文本编辑以及占位图的编辑能力,让业务可以轻松实现贴纸花字以及视频模板等功能。再到 3.0 版本的时,我们又引入了图层渲染树的编辑架构,让素材的最小控制单元由文件变成了图层。不仅很好满足了一键出片和游戏战报相关场景下模板动态拼装的需求,也让运行时编辑的灵活性提升到了一个新的高度。
在PAG 1.0版本时,我们的主要需求是在视频上叠加各种带动画的贴纸花字,因此需要文本的运行时编辑能力。
想象一个滚字的动画从屏幕旁边一路滚过来,我们在PAG里提供了接口可以修改文本内容,字体,颜色,字号等十几项属性,并保留设计师预设的动画效果。
基本原理就是运行时对占位的文本内容进行修改,但保留所有预设的动画属性再进行渲染。
到PAG 2.0时,我们同样基于方式这个又引入了占位图的概念来解决视频模板的需求。
核心原理就是运行时将视频逐帧替换到指定的占位图上,由PAG文件来控制视频的画面的动效和层级关系,输出完整的内容。
设计师在制作视频模板时,只要添加一个占位图并当成视频处理就行,对它应用的任何变换和特效最终都会作用到替换后的视频上。
一个 PAG 文件就是一个完整的模板,可以包含一个或多个占位图。
我们可以使用单个占位图来实现简单添加效果的视频模板,也可以用两个占位图实现视频片段切换的转场特效,或者多个占位图来实现画面复制的多格视频。
这样可以把模板的创意生产完全交给设计师发挥,最终让照片或视频模板等应用场景进入了工业化批量生产的时代。
而到 3.0 版本时,我们的编辑需求进入了智能模板的阶段。会根据用户传的视频内容,自动生成一个自适应的模板。3.0的智能模板存在无限种可能性,设计师没法靠穷举每种可能性去生产素材。最佳方式是生产一个个小的PAG效果组件,然后进行拼装组合。
于是我们在 3.0 里引入了图层渲染树的编辑架构,把素材的最小控制单元由文件下沉到了图层级别。一个文件就是一棵图层渲染树,内部每个图层都可以自由增删改。并且文件本身也是一个图层,因此可以把多个文件添加到一个空的 PAGComposition 里组合播放,并且通过 setMatrix 对文件的空间相对位置进行任意修改。而在时间轴的组合上。PAGFile 增加了时间伸缩的能力,提供循环,变速,定格等多种自适应模式,可以灵活适配用户的视频时长。每个图层又提供了起始时间的调整能力,能够自由组合每个图层在时间轴上的相对位置。
经过这些改造,新的接口不仅很好地满足了类似王者战报这样的需求场景,也为海量素材播放提供了新的优化可能,不再需要给每个动效创建独立的上下文,而是可以组合在一起共享同一个上下文渲染,实现性能的大幅提升。
3.0 版本引入的图层渲染树架构,确实让运行时编辑的灵活度提升了一个量级,但也对缓存系统以及线程安全提出了新的挑战。
因为 PAG 文件解码后的对象在 3.0 之前是允许复用的,假设你要画50个相同的星星动画到画布上,只需要解码一次并且只占一份文件内存。现在如果图层编辑是直接修改到真实的文件对象本身,要么是放弃文件复用,每次都完整拷贝一份大量基础数据,要么就需要对文件整体进行加锁,又无实现跨线程的并发渲染。
于是我们又在 3.0 版本里做了一个图层套壳的设计,将之前的 PAG 文件对象拆成了内外两个部分:外壳的 PAGFile 对象非常轻量,只记录用户可能修改的属性,并且一棵渲染树上会共享同一个线程锁来确保读写安全。而内部的 File 对象才存储实际占内存的那些基础数据,并且被设计为完全只读的,这样也就无需加锁。这样多个外壳 PAGFile 可以同时引用同一个内部 File 对象,既保障了编辑的独立性,也天然支持多线程并发访问。最终在保留文件复用机制的前提下,顺利引入了非常灵活的图层渲染树编辑架构。
4.0 - 全新渲染引擎
在性能和包体方面,到 4.0 版本时我们在上层能做的工作已经差不多到极限了,要继续突破只能深入到渲染引擎底层替换掉 Skia。因此我们花了将近一年半的时间从头实现了一套全新的纯 GPU 绘图引擎 TGFX,最终将 PAG 包体直线降低了 65% 左右,并将 PAG 的完整能力扩展到了 Web 端。
首先看一下为什么不继续使用 Skia?目前谷歌开源的 Skia 2D 绘图库是行业里事实标准,多年来几乎没有任何实质性的可替代方案出现,Chrome,Firefox,Flutter,Adobe 系列软件,乃至 Android 系统都在基于 Skia 做文本和矢量的绘制。但 Skia 本身是一个维护了近 20 年的方案,也存在很多的历史包袱,很难满足 PAG 对包体和性能的进一步优化需求。
在包体方面,我们虽然已经针对 Skia 做了非常多的定制和裁剪,但是它依然占据了 PAG 3.0 版本 80% 左右的包体,也无法再进一步进行裁剪。
而在性能方面,由于 Skia 需要兼容历史遗留的 CPU 绘制模式,在 API 上暴露会比较保守,很多针对现代 GPU 绘制管线可以进一步优化的接口都没暴露出来。为了彻底突破包体和性能的限制,我们花了将近一年半的时间自研实现了一套轻量的纯 GPU 绘图引擎 TGFX,完成了对 Skia 的替换。下面看一下 TGFX 具体做了哪些优化:
在包体方面,我们最终以 400K 左右的大小覆盖了 Skia 近 2M 包体的绝大部分功能。核心优化策略主要有两点:
彻底抛弃传统的 CPU 渲染管线:
因为现代的硬件已经几乎不存在没有 GPU 的设备了,即使像服务器端这种特殊的场景,通过 Swiftshader 来模拟 GPU 得到的性能也会让你很意外。但 Skia 由于历史原因一直同时包含了 CPU 和 GPU 的两条渲染管线,并且由于它的 GPU 渲染管线重度依赖 CPU 的部分,导致没法单独使用它的 GPU 渲染管线。我们在 TGFX 中彻底解决了这个耦合的问题,打造出了一个纯 GPU 的绘图引擎,这里就节省了大概一半的包体。
最大化的利用平台端内置的所有能力:
例如图片解码,字体解析,矢量栅格化等等,这些都会优先使用系统原生的接口替代内置第三方库的策略。以文本和矢量的栅格化为例,在 iOS 上我们直接使用了系统提供的 CoreGraphics,文本方面则利用起 CoreText 等。而在其他平台才嵌入了 Freetype。虽然增加了不同平台适配的工作量,但是包体确实也获得了极致的优化。
在易用性方面,TGFX 也做了不少改进,尤其是这个 Skia 没有的 Device & Window 系统。Skia 虽然提供了 GPU 的渲染管线,但要用起来门槛还是比较高的。主要存在两个问题:
第一点是 Skia 只实现了跨平台渲染的部分,
所有跟平台相关的视图桥接以及上下文的初始化都需要用户自己处理。
这会导致用户正常用起来 Skia 的 GPU 模式需要对每个平台写大量的适配代码。除了工作量大外这部分还是兼容性的重灾区,要处理很多类似 iOS 中退到后台执行 OpenGL 的特殊情况。
第二点是 Skia 并不帮你保证线程安全或者上下文状态的切换,
默认都由调用方自己处理。
但绝大部分刚刚接触 Skia 的用户并不清楚这里的坑点,Skia 也没有显式说明过,按普通的方式接入使用,很容易就造成大量的显存泄露以及难以排查的随机 Crash。
但 TGFX 提供了完善的 Device & Window 系统,可以帮你把这些问题一次性都彻底解决,只要按照统一的模式进行调用,所有平台相关的复杂度都可以不用关心,并且从 API 上限制了你必须以线程安全的方式进行调用。即使没有非常资深的 GPU 渲染经验也可以很容易上手使用。
-
我们默认开启了 HardwareBuffer 的支持,来全面加速纹理的提交。
这里重点是利用了运行时反射的机制彻底解决了设备兼容的问题,只要设备和系统存在这块能力我们就会自动开启。
-
在线程安全以及并发渲染能力方面,除了前面提到的 Device&Windows 系统,TGFX 还彻底改进了 Skia 的 GPU 对象管理模型,所有的 GPU 对象都可以在任意线程释放,等关联的上下文激活时才真正清理,避免了在 Skia 中经常出现的随机 Crash 和泄露问题。
-
我们在 TGFX 的接口设计上约束了图片解码完会尽可能只缓存 GPU 的纹理部分,这样理论上全局可以直接降低一半的内存占用,避免像 Skia 里一样图片总是重复占用双份内存,因为要兼容CPU绘制管线的读取。
-
TGFX 在全平台都实现了默认字体的读取能力
,包括读取浏览器的默认字体库。解决了 Skia 的 Web 版本在这块的缺陷,否则渲染中文都要下载上百 M 的字体包的问题,在 Web 上几乎没有可用性。
以上这些还只是 TGFX 做的优化的一部分,更多的细节欢迎大家到 Github(
https://github.com/Tencent/libpag
) 源代码中参考研究。
最后来看一下成果,在我们把 Skia 彻底替换为 TGFX 绘图引擎后,PAG 4.0版本的包体整体平均都下降了65%左右,并且矢量渲染性能平均还提升了 60% 左右。整体的优化非常显著。后续我们也正在推动 TGFX 作为一个独立仓库开源。我们会持续完善并把它打造为一个通用的 2D 绘图引擎,为行业提供 Skia 之外的另一个轻量化的选择。
1)方案价值
PAG方案诞生在最复杂的视频编辑下,也能够很好的满足其他各种场景下的动效需求。它提的供所见即所得的桌面工具和AE插件,能够一键将设计师的创意导出成PAG文件,并通过SDK快速渲染到几乎所有的主流平台上。
去研发成本:
素材生产环节无需研发介入,节省大量研发人力和调试返工成本。研发只需要接入一次SDK的成本,后续设计师可以独立完成素材的生产上线,也避免了最耗时的研发和设计的联调环节,最终将素材生产相关的研发成本大幅降低。
工业化生产:
由于不再受到研发人力瓶颈的限制,素材生产可以扩大到更多的设计师进行批量化生产。再加上桌面效率工具在效果预览和性能检测上的易用性,设计师可以所见即所得地生产素材,最终让视频模板平均生产耗时从一周降低到四个小时,实现快速响应运营热点。
无限AE动效:
PAG的SDK已经完全还原了AE的整个动效渲染系统,并支持矢量和序列帧混合导出,接入一次,设计师就可以复用PAG经过5年积累的AE动效原子能力,组合出无限的视觉动效,不用因为代码还原成本的问题而对效果打折扣。
2)开源成果
PAG 已于2022年 1 月 14 日正式开源至 Github,到目前已获得 2500+ star数,官网累计上线 46 篇文档教程,团队日常对接 2400+ 持续增长的设计师和研发用户群,SDK 也已接入服务了腾讯内外 600+ 产品业务,包括微信,手机 QQ,王者荣耀,等腾讯系头部 App,也有大量的外部产品,比如小红书,B站,京东,知乎等,稳定性经过了海量用户的持续验证。
3)未来展望
过去一年多,PAG 团队的主要精力都投入到了PAG 4.0 全新渲染引擎的升级完善上,目的也是为了后续整个 PAG 方案能有更高的天花板。接下来我们的重心会主要聚焦在以下四个方向上:
补全AE 特性:
前面虽然我们依靠BMP预合成能力已经能导出所有AE特效,但用那个方式导出无法运行时编辑,所以我们围绕可编辑的图层还是需要继续补全 AE 特性支持。
完善工具链:
重点会推出图层级别的性能分析工具,帮助设计师快速定位素材的性能热点,高效完成性能调优,以及提供移动端的真机性能分析 App。
更多渲染后端:
持续迭代完善 TGFX,增加更多渲染后端,适配Metal和 Vulkan等硬件图形接口,最大化发挥现代 GPU 硬件的渲染性能。
可交互能力:
目前市面上几个同类型的解决方案,包括 PAG 在内都还处在 Flash 当时的第一个发展阶段,也就是简单的动效内容还原,接下来在 PAG 5.0 版本里,我们将会把 PAG 推动到第二个发展阶段,也就是集成可交互图形的能力。这样可以进一步简化业务场景中仍然需要编码实现的大量动态化需求。
感兴趣的朋友可以通过以下方式进一步了解PAG:
LiveVideoStackCon 2023上海讲师招募中
LiveVideoStackCon是每个人的舞台,如果你在团队、公司中独当一面,在某一领域或技术拥有多年实践,并热衷于技术交流,欢迎申请成为LiveVideoStackCon的讲师。请提交演讲内容至邮箱:speaker@livevideostack.com。
本文分享自微信公众号 - LiveVideoStack(livevideostack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。