“抖音”式的酷炫短视频开发进阶

“抖音”式的酷炫短视频开发进阶

原文链接

开始前先跟大家分享一个视频,这个Demo是基于iOS平台实现的,我们今天的分享也将聚焦在视频中多种特效的实现方法和经验总结。

应该如何实现

在实现Demo中特效前,我借鉴了funimate产品,利用它提供的功能生成视频,对它进行逐帧分析,并从中找出可能的实现方法。在视频分析中,使用了FFmpeg将视频分解成一帧一帧的图片从而进行分析。观察具体某一帧或者某几帧使用了怎样的特效。

ffmpeg –i output.mp4 –r 0.25 frames_%04d.png

具体到技术实现手段,第一种实现方式是把视频每一帧解码出的YUV,利用libyuv库来操作,甚至可以用RGBA来操作,这是通过CPU操作转换YUV来实现;第二种实现方法性能会更好,但开发成本可能也会相对较高,就是在GPU上操作纹理来实现。

由于我们需要在移动平台实现,而在移动平台使用CPU是很难满足需求的,要考虑到性能、耗电、实时观看体验等等因素,因此我们需要使用GPU来实现。

Demo场景设计

我们想要实现这样一个Demo或者简单的App,首先我们需要预览视频的界面,然后给出多种特效的选择菜单,当用户选择其中某种特效时会实时显示该特效的预览效果,并且将特效的开始作用时间和作用时长记录到内存的结构体中,最后当用户点击保存按钮时,可以离线保存为视频文件。

基于现有框架的开发

那么我们需要用到哪些已有的框架或者已有的项目来完成这个功能呢?可以思考下,既然有预览界面,则一定需要视频播放器。播放器的基本功能包括了解码和音视频的渲染,此外再加上逻辑控制、音视频对齐就可以成为一个视频播放器。

视频播放器中视频解码模块是非常重要的,通过它可以将视频文件解码为视频帧,并且输出到解码纹理队列中,接下来就是本App最核心的工作——处理,视频处理模块会按照时间戳将对应的纹理进行处理,并放入到渲染队列,最后输出模块会将渲染队列中的纹理输出到屏幕上,而在离线保存场景下,则是将渲染队列中的纹理编码输出到本地,也就是封装成mp4或者flv等等格式写入本地磁盘。

鉴于处理模块是本App的核心,而我们今天所讲的特效也都是在该模块中完成的,因此接下来我们一起来看下它的具体实现方法。

视频处理

  • 镜像

https://mmbiz.qpic.cn/mmbiz_png/icicA9AEakF1HgCYlA0cM6vyneSqDS6JCQQwgQ1DibG9zYsyY1ib43BTpjAiaiacZ2JTVCpfiamqibZic77R9g1XfIccLibg/640?wx_fmt=png&wxfrom=5&wx_lazy=1

首先跟大家分享一个最简单的特效——镜像,先生成一个16:9的屏幕比例的画布,将它分割为四部分,每部分画一个相同的视频帧,因为屏幕被分割为4部分,我们的物体坐标在渲染时就不能设定为全屏的。在OpenGL中物体坐标,左下角为(-1,-1),右上角为(1,1),这样我们就可以分别计算出4部分的物体坐标。

确认好物体坐标后,我们接下来就要确认画什么?也就是将视频帧以什么样的方式画在物体坐标上,这时就需要控制纹理坐标,我们可以看到OpenGL的纹理坐标定义:从左下角(0,0)到右上角(1,1),实际画的时候左上角是我们完整的纹理,右上角我们需要做镜像处理,左下角需要做横向翻转,右下角则是针对右上角视频帧做横向翻转,这样就可以实现简单的镜像效果。

  • 镜像模糊

https://mmbiz.qpic.cn/mmbiz_png/icicA9AEakF1HgCYlA0cM6vyneSqDS6JCQAwvv71yBo2K5LDdtE9hvS7hx17ibiathjowlaqmFjKzWcMeNOmCS4OBg/640?wx_fmt=png&wxfrom=5&wx_lazy=1

相对于前面的特效,这个特效只需要做一对镜像,但他的背景是需要做高斯模糊的,如果用CPU来做,通过两个大的“for”循环就可以实现,对于GPU也是相同的,不过代码会相对复杂一些。假如我们要计算中间25这个点的高斯模糊,我们需要先得出下图中的像素值,乘上各自点的高斯权重,然后做加权平均,最终把高斯模糊的效果放在下面成为背景,然后再将镜像的纹理画在上面就可以实现了。

  • 电击效果

在了解了两个简单的特效实现之后,我们一起来看一些复杂特效的实现方法,首先是电击效果,实际上它的实现就是反选的处理,只需要使用下面代码就可以:

gl_FragColor  = vec4((1.0 – texture.rgb), texture.w);

但想要达到一个很好的效果,其中还是有一些小技巧,也就是需要把握好节奏。假如我们现在有250ms运动的视频帧,再排上180ms静止的反选视频帧就可以实现了,如下方动图演示:假设50ms为一帧,那么对于10帧总时间为500ms的视频帧来说,前5帧都不变,依旧是正常的效果,从第6帧开始我们做反选并且保证画面是静止的,也就是说第7、8、9帧同样放第6帧,而第10帧时我们渲染正常的第10帧,这样周而复始就可以实现电击效果。

  • 灵魂出窍

https://mmbiz.qpic.cn/mmbiz_png/icicA9AEakF1HgCYlA0cM6vyneSqDS6JCQyG1FOGQuX7SvVYOIqK6bia0F3eTic2TNXibSjmwqCZSRU1JJVQsAhYMrg/640?wx_fmt=png&wxfrom=5&wx_lazy=1

这个特效就是人影有一个向外扩散的效果,同样它的节奏也是非常重要的,尤其是能与音乐的配合才能达到一个完美的效果。那么它的实现过程如下:首先我们每隔15帧拷贝一帧作为“灵魂”并且按照比例放大,这里特别需要提到的是SRT(Scale/Rotate/Translate),基于这三个的组合我们可以写一个TransformEffect,它可以利用通用的SRT矩阵变化纹理。

在得到放大后的“灵魂”(拷贝帧),我们就需要考虑把“灵魂”和“肉体”(原本视频帧)混合起来,这里需要用到GLES的一个内嵌Mix函数将两个纹理进行mix即可。那么同理,我们还可以实现眩晕、影随的效果:眩晕是将每一帧向两侧做位移再与本帧进行mix,而影随则是将之前的帧缓存下来,以一定的间隔和当前帧做mix。

  • 动态晕影

其实晕影效果在GPUImage中也有设置,它的实现首先需要构造一个纯黑色的图片,然后再与原始视频帧做mix就可以,在处理过程中有两点需要注意:首先交界处要做平滑处理,然后非常重要的依旧是节奏,我们Demo中的节奏时间列表如下:

https://mmbiz.qpic.cn/mmbiz_png/icicA9AEakF1HgCYlA0cM6vyneSqDS6JCQeqsWSPEeaK8ictNT6lyX6drDbyY1fLWiaLwzh2o6nY856wRsj69vPZYw/640?wx_fmt=png&wxfrom=5&wx_lazy=1

  • 木头人

木头人效果就是在视频中有一个bar——彩色且可动的区域,在bar区域以外则是静止且高斯模糊的,实现方法是每隔一定时间(Demo中是1.5s)冷冻一帧做高斯模糊处理,并且取灰度值放在后面,按照移动的边框距离将两帧进行mix。

https://mmbiz.qpic.cn/mmbiz_gif/icicA9AEakF1HgCYlA0cM6vyneSqDS6JCQPvtRY8azwbgjiagia2NuOia4WicHhz9hJxDcpJvicKKIH3W8AAoia2ibd8m2w/0?wx_fmt=gif&wxfrom=5&wx_lazy=1

  • 九宫格

九宫格效果中想要实现9个画面的效果可以参考第一个镜像特效的处理,而如何保证移动、放大、缩小时效果的平滑变化是最关键的,首先我们需要构建一个大纹理——相比原画长、宽分别扩大3倍,然后我们通过TransformEffect来进行位移、缩放。

  • 旋转木马

最后为大家介绍旋转木马特效,这也是本次分享中最复杂的,因为它的处理不再是简单的链式结构,而是graph。那么旋转木马特效其实就是四个画面中只有一个画面是彩色且可动的,其余三个都是黑白、静止的。我们假设左上角为1-3帧,右上角为4-6帧,左下角为7-9帧,右下角为10-12帧依次排列,那么在第1帧时,四个画面分别会显示1,4,7,10帧,而此时只有第一帧为彩色的,其余是黑白的,同时除左上角外其余三个画面都是冰冻状态。当左上角画面变为第3帧时,左上角画面变为黑白、静止,右上角的画面变为彩色、可动的,以此类推。

如上述视频所示,它的实现方法如下:首先每个画面都包含一个队列,然后我们把解码出来的视频帧以此按照左上、右上、左下、右下的顺序填充,当然在实现中可能以时间为依据会更加合理,当右下队列中有了第一帧时,我们才会绘制出第一帧效果也就是说特效才会开始,此时视频中显示的是第1,5,9,13帧,当左上绘制出第二帧时,解码器会将解码好的第14帧给到右下的队列中,以此类推。而当左上画面绘制出第4帧后,右上的队列开始绘制,同时解码器解码出来的视频帧将填充到左上的队列中,周而复始就能达到旋转木马的效果。

在绘制阶段有两个关键点:第一,对于活动区域而言,我们需要取出活动队列中的视频帧进行绘制,同时非活动区域取出队列中首帧进行灰度绘制;第二,对于填充区域来说,我们要按照当前时间戳与第一帧时间戳计算出填充区域,并且将当前帧入队到填充区域的队列中。

以上是针对demo中特效实现的讲解,非常感谢。

5 评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注