腾讯Libpag研究1(扫盲和介绍)

Pag介绍

PAG是腾讯在好几年前发布的一种动画文件格式,但是以前一直没开源,用的人也很少,也就一直没有关注。不过他们终于在今年1月份开源了,我也是这个时候开始去研究PAG,发现里面有不少值得学习的东西。

动画库扫盲

做应用开发的我们都知道,可以利用系统提供的动画API实现各种动效,但是这种办法效率很低,并且无法移植,多端都需要自己开发一遍。并且开发出来的效果和设计师需要的多多少少会有一些差异,一开始的方案是使用帧动画或者gif/webp的动图来播放,但是由于图片占用资源过大,所以没有广泛使用。16年起业界开始出现了诸如AirBnb的Lottie,Facebook的KeyFrames(已废弃)的解决方案。它们的实现原理类似,都是通过AE插件,导出动画的动画信息,描述信息为JSON文件。然后在目标平台解析后,通过平台的绘图函数(Canvas)渲染,其中Lottie是使用最广泛的动画格式。

为什么讲PAG?

PAG的诞生就是为视频编辑的场景发起的,在设计上就很大程度考虑了作为视频编辑素材的通用格式。因为pag很适合用于作为视频编辑素材格式。PAG的渲染部分完全和平台无关,并且都在异步线程内渲染,通过视频素材支持了AE全特性;此外还提供了各种层级素材的动态替换,矩阵变换的API。这种极大的灵活性为视频编辑领域提供了极大的便利。我从目前看到的PAG的优势在于:

  • 跨全平台(Android/IOS,Mac,Linux,Windows,Web)
  • 使用灵活:文字可编辑、图片可替换、图层可编辑或变换位置、可多Pag组合使用
  • AE全特性支持(复杂特效通过视频或序列帧实现)
  • 支持声音(只支持存储数据,不支持播放)
  • 有足够量级的线上应用正在使用

正是由于这些特性,让Pag能够应用的领域十分广泛。

尝鲜

打开PAG官网: https://pag.io/ 后,我们可以直接下载官方的Pag文件查看器:PagViewer,如图所示,打开之后是一个播放器的样子(同时会提醒是否安装AE插件,根据自己情况选择即可)。
腾讯Libpag研究1(扫盲和介绍)_第1张图片
然后我们可以去 https://github.com/Tencent/libpag/tree/main/assets 里下载一些官方提供的示例pag文件拖动到PagViewer里面即可播放。比如这里我下载了这个:nodegroup_clip1 拖动到PagViewer里面效果如下:

然后我们可以试试图层替换功能。这个功能是Pag的亮点功能,也是Pag适合在视频编辑场景应用的核心功能:

如何使用

Pag的使用方式比较简单,我这里以Android为例:
一、创建PagPlayer,设置数据源(PagComposition),绑定画布

方式1. 手动绑定Surface
PAGFile pagFile =  PAGFile.Load(getContext().getAssets(), "xxx.pag");
PAGSurface pagSurface = PAGSurface.FromSurface(mEncoder.createInputSurface());
pagPlayer = new PAGPlayer();
pagPlayer.setSurface(pagSurface);
pagPlayer.setComposition(pagFile);
方式2. 直接使用PagView
pagView.setComposition(pagFile);

二、放到渲染里渲染

(pagPlayer/pagView).setProgress();
(pagPlayer/pagView).flush();

这里可以看到pag的播放方式比较特别,用的是0.0-1.0的浮点数来播放。我理解设计者应该是为了省略一些动画长度,帧率等细节,让大家只关注播放进度本身吧。
我们可以直接看官方的Demo自己跑一下。

iOS DEMO下载: https://github.com/libpag/pag-ios.git
Android DEMO下载: https://github.com/libpag/pag-android.git

更多信息大家可以去Pag开发者看看

如何用到音视频领域

我这里用视频拍摄和视频模板这两种核心场景来展示了PAG在音视频场景下的使用方式: https://github.com/tbruceyu/pag_camera-mv_sample

视频拍摄

目前短视频领域的视频拍摄的主要流程是:
腾讯Libpag研究1(扫盲和介绍)_第2张图片

Pag提供的图片图层替换功能就十分适合在特效渲染这一部分使用。我们可以在生成Pag文件的时候就放一张Camera的占位图,然后在摄像头预览的时候,把摄像头的数据直接动态替换到这个占位图里面即可十分简单的实现特效模板里面添加摄像头的功能。此外Pag提供的矩阵变换功能也能够十分简单的实现图层的2D/3D变换,我们可以直接通过设置贴纸图层的矩阵来实现诸如人脸识别道具类的功能。
我这里简单的导出了一个Pag素材如下:
腾讯Libpag研究1(扫盲和介绍)_第3张图片

我们把图层1替换为相机的图像,图层0跟随人脸即可很简单的实现一个猫耳朵的特效。这里由于时间关系,我没有去做道具跟随人脸的功能,猫耳朵只是一张静态图片。只是简单的把道具做了一个旋转变化,达到一个能让大家对图层变化有个认识的目的。

我这里简单的实现了一个摄像头预览的功能,Activity代码在PngGLRender.java我们实现这个功能的核心流程仅需如下三部:我们实现这个功能的核心流程仅需如下三部:

  1. 创建渲染Surface为目标纹理(pngTextureId)
...
pngTextureId = initRenderTarget();
PAGSurface pagSurface = PAGSurface.FromTexture(pngTextureId, mWidth, mHeight);
...
  1. 替换图层为Camera纹理,变换贴纸图层位置后渲染到目标纹理
pagFile.replaceImage(1, PAGImage.FromTexture(cameraTex, GLES20GL_TEXTURE_2D, 720, 1280));
GLES20.glViewport(0, 0, viewWidth, viewHeight);
long elapse = System.currentTimeMillis() - timestamp;
long playTime = elapse * 1000;
float rotation = (elapse * 1.0f % 3000L / 3000L) * 360;
camera.save();
camera.rotateX(rotation);
camera.rotateY(rotation);
camera.getMatrix(transform);
camera.restore();
pagFile.getLayerAt(1).setMatrix(transform);
pagPlayer.setProgress(playTime % duration * 1.0f / duration);
pagPlayer.flush();
  1. 目标纹理上屏渲染
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, pngTextureId);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20GL_DEPTH_BUFFER_BIT);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

由于时间关系,我这里没有实现录制的功能,但是直接通过在编码线程内用pag输出的Texture渲染后编码即可简单的实现录制。

视频编辑特效

目前视频编辑领域的特效主要分为视频转场,模板两大功能。其中模板是一个实现的难点功能。目前业内的实现方式有如下几种:

  1. 通过Texture占桩下发视频或图片素材,下发Shader和配置文件实现不同的特效
  2. 通过解析BodyMovie的JSON协议,自己用OpenGL实现部分效果和模板视频替换
  3. 自研协议,预设实现转场,通过自制JSON和特定透明视频素材叠加实现

Pag内置了很多内置的矢量动画:AE功能支持列表;此外还内置实现了一些AE的特效:AE内置特效支持 ,除了这些以外其他诸如粒子效果等可以通过预合成PNG序列帧或者透明视频的方式导出素材即可。
我这里直接用了官方的nodegroup_clip1 来实现了一个简单的视频模板功能。代码在PagMVActivity.java也就只需要使用Pag的图层替换功能,然后放到MediaCodec的OpenGL环境里渲染即可。

  1. 设置图片替换
private void replaceImages(PAGFile pagFile) {
    pagFile.replaceImage(0, PAGImage.FromAssets(getAssets(), "1.jpg"));
    pagFile.replaceImage(1, PAGImage.FromAssets(getAssets(), "2.jpg"));
    pagFile.replaceImage(2, PAGImage.FromAssets(getAssets(), "1.jpg"));
    pagFile.replaceImage(3, PAGImage.FromAssets(getAssets(), "2.jpg"));
    pagFile.replaceImage(4, PAGImage.FromAssets(getAssets(), "1.jpg"));
}
  1. 创建Pag Surface
if (pagPlayer == null) {
    PAGSurface pagSurface = PAGSurface.FromSurface(mEncoder.createInputSurface());
    pagPlayer = new PAGPlayer();
    pagPlayer.setSurface(pagSurface);
    pagPlayer.setComposition(exportPagFile);
    pagPlayer.setProgress(0);
}
  1. 渲染Pag
private void generateSurfaceFrame(int frameIndex) {
    int totalFrames = (int)(exportPagFile.duration() * exportPagFile.frameRate() / 1000000);
    float progress = frameIndex % totalFrames * 1.0f / totalFrames;
    pagPlayer.setProgress(progress);
    pagPlayer.flush();
}

此外贴纸也是视频编辑的常见功能,我们也可以通过Pag的文字修改功能简单的实现贴纸功能的渲染部分,不过业务拖拽,贴纸层次,贴纸选中之类的逻辑是很复杂的,需要自己实现。

问题

在研究过程中,我也发现了一些关于Pag我认为的问题

  • 为了通用性,透明素材是用的双视频叠加实现的,占用内存比较大。在视频编辑场景的素材通常用黑色背景视频Blend方式实现,并不需要这个透明通道。
  • pag导出工具不开源,无法通过自定义格式来实现新增内置特效(官方希望文件格式统一)
  • 预合成素材过多容易导致严重的性能问题
  • 只支持硬件渲染,不支持软件渲染(PagView不是一个普通的View),在多个Surface上渲染Pag图片会有严重性能问题,需要共用一个PagSurface。用作UI动画框架可能不够灵活
  • 图层替换功能需要有占位图,没有看到预留位置的功能。会浪费一些存储空间(不会浪费多少内存,因为Pag不会没播放的时候就解码)
  • 包大小增加1.5M,noffavc版本1.1M左右(已经优化得比较极致了)

总结

整体上说Pag是一套十分优秀的动画框架,官方也说明这个项目初衷就是用于视频编辑场景的,只是后来扩展到了UI动画领域。如果是用来做视频编辑场景的动画或者特效框架我觉得十分合适,做UI动画框架可以先实验性的使用,不要直接就替换掉Lottie来用,Pag用作UI动画的话还是有一定的局限性的。

你可能感兴趣的:(多媒体开发,Android移动开发,动画,前端,css3)