一、前言
首先说说视频剪辑,视频剪辑其实是一个跨终端、跨多个知识栈的横向领域。任何平台任何语言下,只要有足够的计算能力,应该都可以进行视频剪辑。也相信看完这篇文章后,你对于视频的剪辑不会再感觉陌生。
传统的桌面剪辑软件是提供一个GUI界面,给用户提供所见既所得的编辑效果反馈,并在编辑完成之后产生编辑描述数据,最后交由图形图像模块、音频处理模块以及视频编码模块去生成最终视频文件。
参考大部分开源剪辑软件实现,前半部分GUI大部分通过操作系统图形界面或跨平台如QT、SDL、SWT方式实现,这部分我们称之为“前端”。而后面涉及图形图像、音频、编码相关部分,大部分都会有一套媒体编辑内核来支撑,例如Linux下著名的MLT、GStreamer等等,这里我们称之为“编辑内核”。
云剪辑,其实是剪辑端的一种,这里GUI“前端”需要通过浏览器呈现给用户,而“编辑内核”完全可以复用传统的媒体编辑框架。只是在云编辑场景下,“编辑内核”的计算能力是多用户共用,因此需要解决好资源调度问题即可。在Zookeeper、MQ流行的今天,这些调度问题在Java工程师眼里也都不是问题了。
那么云编辑需要解决以下几个问题:
- 可以描述的渲染效果
- 效果与时间的绑定(非线性)
- 处理逻辑跨平台
- 解决Javascript弱计算能力
二、技术选型
视频编辑最核心的3个步骤:1、将媒体文件从视频素材解码出来,2、对解码出来的每帧图片和音频进行处理,3、将处理之后的内容在GUI上展示,或传递给编码器。
在以上 GUI + 编辑内核 的思路之下,看似方案已经很明确,只是这里我们需要确定几件事情。例如GUI如何呈现编辑内核的内容。
我们也看到行业内一些做法:
- 编辑内核放在云端,通过直播的串流方式推给用户界面
- 编辑内核放在云端,前端GUI只提供少量信息给用户
而以上两种方式都存在一些缺陷,例如第一个的延时问题,第二个的缺失所见即所得问题。而整体看Web端侧,基于WebGL、Video Element、Blob、Web Audio、Web worker、Webassembly一系列令人激动的能力,Web已经有了长足的进步。对于在Web侧实现一套编辑内核看起来似乎已经有理论依据。
因此我们确定了一个方向:编辑内核的复用可以共用设计,而不一定追求共用代码。我们先不分平台,设计出内核架构,再看是否可以多平台轻量实现
1、管道
回顾上文提到的三个步骤:1、将媒体文件从视频素材解码出来,2、对解码出来的每帧图片和音频进行处理,3、将处理之后的内容在GUI上展示,或传递给编码器。同时考虑到这里的媒体解码除了是通过视频文件,也可以是摄像头采集,或通过类似OpenGL这样的图像库渲染出来的,因此可以把这3个环节抽象出3个核心组件:Producer、Filter、Consumer
+-------+ +-------+ +-------+
| | | | |Encoder|
|Decoder|-->|Process|-->| |
| | | | | GUI |
+-------+ +-------+ +-------+
^ ^ ^
Producer Filter Consumer
当Producer和Consumer中存在多个Filter的时候,管道也就产生了
+--------+ +-------+ +-------+ +--------+
|Producer|-->|Filter1|-->|Filter2|-->|Consumer|
+--------+ +-------+ +-------+ +--------+
这里也涉及管道是Producer推,还是Consumer拉。最终我们选择了Consumer拉的模式,至于原因主要考虑资源利用,因为最终有价值的内容是通过Consumer产出,而底下的Producer无法感知上面的逻辑。
2、裁剪、合并
表达裁剪和合并比较容易,对于一段视频会有一个总帧数,而裁剪其实只需记录其开始(in)和结束(out)的时间点即可
+---+-----------------+---+
| x | a | x |
+---+-----------------+---+
^ ^
in out
对于合并,更加简单,记录的多段视频的出现顺序即可,为了表示这种排列数据,新建一个模型PlayList
+-------+---+-------------+
|a | b |c |
+-------+---+-------------+
而剪和合都可以抽象的作为一个Producer
3、滤镜
滤镜主要是对内容的处理节点,他所处理的位置在Producer和Consumer中间,他是剪辑中重要的一环。这里核心需要实现的是:如何实现一套统一的处理逻辑,在多个端上可以完全重现
+--------+ +------+ +--------+
|Producer|-->|Filter|-->|Consumer|
+--------+ +------+ +--------+
这里我们选择了专业的图像处理框架OpenGL系,OpenGL、OpenGL ES、WebGL之间的关系如下:
如果要实现统一与平台无关的渲染逻辑,我们需要使用可编程着色器(GLSL),同时考虑多端兼容性,我们选取了OpenGL 2.0 -> OpenGL ES 2.0 -> WebGL 1.0 这条线作为我们的能力集。
4、过渡
过渡的效果设计一个视频画面慢慢转化到另一个视频画面,这里就涉及在中间某个时间内,其实有两个视频源。因此需要正式引入多轨道 Multitrack。和Playlist一样Multitrack也是Producer的一种实现。
+--------------------+ +-----------------------+
0: |a1 | |a2 |
+---------------+----+------------------+------+----------------+
1: |b1 |
+------------------------------+
轨道可以理解为图片的图层,真实的输出是a1的画面完全叠加在b1之上。当a1没有透明度的情况下,b1在a1结束前是不可见的。
也许这里感觉到多轨道并不能实现过渡效果。因为这里却少了一环,图片混合。
+----------+
|multitrack|
| +------+ | +-------------+ +-------+
| |track0|-|--->| |--->|tractor|
| +------+ | | | |\ |
| | | transitions | | \ |
| +------+ | | | | \ |
| |track1|-|--->| |--->|---o---|--->
| +------+ | +-------------+ | / |
| | | / |
| +------+ | |/ |
| |track2|-|---------------------->| |
| +------+ | +-------+
+----------+
这里的transitions和滤镜非常类似,通用可以通过GLSL来实现效果的跨平台性,只是滤镜只需要一个纹理输入,而过渡需要两个纹理。同时,过渡是一个时序相关的动作,因此需要有一个时间变量传入,例如过渡开始为0,而过渡结束为1,过渡过程在0-1之间进行。
+--------------------+ +-----------------------+
0: |a1 | |a2 |
+---------------+----+------------------+------+----------------+
1: |b1 |
+------------------------------+
^ ^ ^ ^
0 1 0 1
5、动画
计算机往往是看起来像什么,而不是是什么。实现动画其实可以参考CSS transition和iOS Core Animation的思路。即记录开始时间和结束时间,物体所在的位置。那么在开始时间和结束时间可以通过算法计算出当前位置。当窗口快速刷新时,看起来物体就进行了移动。
把相同原理应用与大小上,即实现了变大缩少动画,应用于角度上,即实现了旋转。当然这里只有线性变化是通用公示,当非线性动画场景下,如何实现多平台统一呢?答案是给出计算公式,我们使用了开源公式集,包括了easeIn、easeOut、easeInOut等等。
有了以上计算公式,提供开始时间、结束时间、开始状态值、结束时间值,在时间轴内任意一点可计算出当前值,这样既实现了完全平台无关。
6、平台实现
到此,基本设计方案都以完成。这里就给各个组件寻找实现方式即可。
- Producer:浏览器可以通过Video Element实现,Linux通过FFmpeg的解码函数实现
- Consumer:浏览器是提供给用户预览,因此通过Canvas实现,并通过requestAnimationFrame拉取,Linux端负责生成视频,因此使用FFmpeg的编码器实现
- 滤镜、过渡:基于OpenGL体系,通过GLSL实现,浏览器侧WebGL、Linux侧OpenGL
- 动画:基于描述语言,浏览器侧通过基于WebGL的pixi.js图像库实现,服务器侧通过QTWebkit内执行pixi.js图像库实现
7、Javascript弱计算能力
计算能力其实也可以作为平台特性放到上一节,但这块最为复杂,单独拿出来。
在一些场景下,例如视频的去抖,大部分的去抖算法,需要首先寻找视频中特征点,之后通过OpticFlow算法寻找运动轨迹,之后再通过前后帧数据寻找纠正矩阵,最后进行修正。
这里第一需要大量的CPU计算,第二去抖算法往往由算法团队通过C的动态库或静态库提供。理论上在共用设计的思路下,只要算法确定,可以用JS把算法再实现一次。但是令人遗憾的是JavaScript计算能力太弱,无法满足实时预览。
这里我们使用了Webassembly,在算法团队提供的C++实现上,在进行一轮包装,主要完成JS内图片数据向C数组拷贝和处理结果矩阵的回传工作,之后抖动纠正交由WebGL的纹理+变换矩阵来处理。
三、工程化
当所有和视频处理相关的问题都解决的时候,这件事其实已经成了,下面回到我们这些应用工程师的老本行——搭系统、建立模型、链流程。
整体模型主要是Producer、Filter、Transition三个接口,以及他们的若干实现。
视频生成构建3个应用,其中Editor作为淘拍PC的业务应用,对接前台编辑器。Biz对外提供统一视频制作能力整体方案,包括调度、生成、上传、发布。而Core应用作为底层生产单元。可以完成非淘系视频制作任务。
其中Core会JNI调用到媒体框架,依赖ffmpeg、x264、OpenGL、QTWebkit、SOX等,我们将环境打包到Docker里,部署在双显卡的物理机上。并通过Nvidia提供的Docker插件能直接调用到显卡。
前端SDK的部分因为还未对外开放,这里就不展开。
四、最优解
行之有效方案的方案就是好方案。但回头反思,他是最优方案吗?可能是、也可能不是。对于当前项目周期和业务目标看他是一个有前提的最优解。但全面来说他还有缺点,这里的跨平台是通过一套描述数据+共用GLSL+共用设计的方式实现。
而全量代码的跨平台应用是否可行呢?比较难,但至少也还是有理论依据。例如,SDL(Simple DirectMedia Layer)内置了与OpenGL的连通,并具备音频处理能力。SDL可以直接跨平台的跑在iOS、Android以及云端编辑后台。而Emscripten编译工具内置了SDL的API实现,将OpenGL映射到WebGL,将音频处理映射到Web Audio,可以完整编译SDL项目到Web端。可能最终的终局是可以实现一份代码,4个端上执行。
但目前看来,该项实现的前期投入成本会非常大。目前我们仅做了少量Demo论证可行性,暂时没有计划实施。
五、最后
最后,我们的云编辑平台,首先落在淘拍-电脑版产品中,主要面向商家生产主图视频、包括上面提到的裁剪、合并、滤镜、过渡、字幕、防抖等。其实云编辑的场景不止于此,非常适合制作素材均在云上,只需进行轻量化编辑的场景。