PAG是腾讯在好几年前发布的一种动画文件格式,但是以前一直没开源,用的人也很少,也就一直没有关注。不过他们终于在今年1月份开源了,我也是这个时候开始去研究PAG,发现里面有不少值得学习的东西。
做应用开发的我们都知道,可以利用系统提供的动画API实现各种动效,但是这种办法效率很低,并且无法移植,多端都需要自己开发一遍。并且开发出来的效果和设计师需要的多多少少会有一些差异,一开始的方案是使用帧动画或者gif/webp的动图来播放,但是由于图片占用资源过大,所以没有广泛使用。16年起业界开始出现了诸如AirBnb的Lottie,Facebook的KeyFrames(已废弃)的解决方案。它们的实现原理类似,都是通过AE插件,导出动画的动画信息,描述信息为JSON文件。然后在目标平台解析后,通过平台的绘图函数(Canvas)渲染,其中Lottie是使用最广泛的动画格式。
PAG的诞生就是为视频编辑的场景发起的,在设计上就很大程度考虑了作为视频编辑素材的通用格式。因为pag很适合用于作为视频编辑素材格式。PAG的渲染部分完全和平台无关,并且都在异步线程内渲染,通过视频素材支持了AE全特性;此外还提供了各种层级素材的动态替换,矩阵变换的API。这种极大的灵活性为视频编辑领域提供了极大的便利。我从目前看到的PAG的优势在于:
正是由于这些特性,让Pag能够应用的领域十分广泛。
打开PAG官网: https://pag.io/ 后,我们可以直接下载官方的Pag文件查看器:PagViewer,如图所示,打开之后是一个播放器的样子(同时会提醒是否安装AE插件,根据自己情况选择即可)。
然后我们可以去 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
Pag提供的图片图层替换功能就十分适合在特效渲染这一部分使用。我们可以在生成Pag文件的时候就放一张Camera的占位图,然后在摄像头预览的时候,把摄像头的数据直接动态替换到这个占位图里面即可十分简单的实现特效模板里面添加摄像头的功能。此外Pag提供的矩阵变换功能也能够十分简单的实现图层的2D/3D变换,我们可以直接通过设置贴纸图层的矩阵来实现诸如人脸识别道具类的功能。
我这里简单的导出了一个Pag素材如下:
我们把图层1替换为相机的图像,图层0跟随人脸即可很简单的实现一个猫耳朵的特效。这里由于时间关系,我没有去做道具跟随人脸的功能,猫耳朵只是一张静态图片。只是简单的把道具做了一个旋转变化,达到一个能让大家对图层变化有个认识的目的。
我这里简单的实现了一个摄像头预览的功能,Activity代码在PngGLRender.java我们实现这个功能的核心流程仅需如下三部:我们实现这个功能的核心流程仅需如下三部:
...
pngTextureId = initRenderTarget();
PAGSurface pagSurface = PAGSurface.FromTexture(pngTextureId, mWidth, mHeight);
...
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();
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渲染后编码即可简单的实现录制。
目前视频编辑领域的特效主要分为视频转场,模板两大功能。其中模板是一个实现的难点功能。目前业内的实现方式有如下几种:
Pag内置了很多内置的矢量动画:AE功能支持列表;此外还内置实现了一些AE的特效:AE内置特效支持 ,除了这些以外其他诸如粒子效果等可以通过预合成PNG序列帧或者透明视频的方式导出素材即可。
我这里直接用了官方的nodegroup_clip1 来实现了一个简单的视频模板功能。代码在PagMVActivity.java也就只需要使用Pag的图层替换功能,然后放到MediaCodec的OpenGL环境里渲染即可。
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"));
}
if (pagPlayer == null) {
PAGSurface pagSurface = PAGSurface.FromSurface(mEncoder.createInputSurface());
pagPlayer = new PAGPlayer();
pagPlayer.setSurface(pagSurface);
pagPlayer.setComposition(exportPagFile);
pagPlayer.setProgress(0);
}
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我认为的问题
整体上说Pag是一套十分优秀的动画框架,官方也说明这个项目初衷就是用于视频编辑场景的,只是后来扩展到了UI动画领域。如果是用来做视频编辑场景的动画或者特效框架我觉得十分合适,做UI动画框架可以先实验性的使用,不要直接就替换掉Lottie来用,Pag用作UI动画的话还是有一定的局限性的。