大家好,我叫成锐林,是腾讯云智能创造平台云剪辑模块的负责人。今天和大家分享我在做云视频前端剪辑工具过程中一些有趣的事情。今天的分享主要是三个内容:
①第一个是为什么会有云剪辑的存在,让大家了解一下B端云剪辑的应用场景;
②第二个内容是云剪辑的前后端架构的设计与演进,这部分主要关注在渲染引擎的设计部分以及Web主要相关的应用以及围绕这样引擎的前端页面的设计和服务端的设计;
③第三部分是在线视频剪辑业务的技术展望。在线视频剪辑业务的从业者自然会对Chrome 94正式支持的WebCodecs比较感兴趣,聊一聊它在视频制作领域的一些应用限制和展望。
云剪辑是腾讯智能创作平台的一个子模块。它的诞生的业务背景是方便客户的用户们能够在客户的平台上面便捷的生产视频内容。在视频内容生产完成后就可以进行例如视频审核、视频直播、视频分享等一系列的流程。
-01-
云剪辑应用
云剪辑的应用场景,在Web端,我们实现了一个功能强大的在线剪辑工具,让用户打开网页就可以完成视频剪辑工作,相应的能力也迁移到了微信的小程序上,做成了一个叫微简小程序的插件,可以让大家在自家的小程序中快速集成一个剪辑服务。
同时也支持了功能强大的视频模板。主要有两种视频模板的形式。第一种是通过剪辑项目生成的视频模板。在剪辑页面做完一个视频项目之后,可以把里面的元素标记为卡槽,之后就可以在Web端、小程序端或者是服务端通过替换卡槽的内容来批量生成新的视频。第二种是通过AE插件将AE项目导出为视频模板。系统会自动识别AE里面的卡槽内容,在Web端和服务端替换里面的卡槽内容来生成一个新的视频。
这是AE做出来的视频模板在前端页面上面的预览效果。可以在右边替换里面的视频图片或者文字的内容做成一个新的视频,同时对外开放的API可以通过API的形式来替换里面的内容批量生成这个模板。
近期我们也推出了一个全新的数字人剪辑能力。主要有三个特点。
首先数字人与剪辑轨道做了深度的结合。使用者可以灵活的配置和剪辑数字人的视频;
第二个特点是它支持了文本驱动和音频驱动的两种模式来制作数字人视频;
第三个特点,它可以快速定制个人专属形象,可以提供照片或者视频做成一个专属的数字人,之后通过文字或者语音的方式驱动它生产更多专属的个人数字人视频。
客户可以通过两个方法使用云剪辑功能。下面来看一下客户是如何使用我们云剪辑的。第一种方法是PaaS接入。只要按照腾讯云官网的起步开发就可以将完整的剪辑功能嵌入到客户自己的Web应用中。左图是腾讯会议通过iFrame的方式来接入前端剪辑项目。第二个方法是通过我们的前端组件和服务端的API。右图是腾讯会议中类似于YouTube里简单裁剪的场景,可以让客户开完会之后,简单的对视频进行重点内容的裁剪生成新的视频。同时有一个C端的地址。本质上来说,我们的C端地址也是云剪辑的B端客户,所有能力与接口和外部客户是一样的。腾讯会议使用前端组件和服务端API,搭建了一个简单剪辑的场景。云剪辑提供的能力远不止于此。客户可以结合自己Web应用的设计风格和业务能力,构建一个完全不一样的前端剪辑页面。
云剪辑通过三步实现B端化。第一步,创建一个腾讯云的账号,开通云点播。腾讯云所有媒体资源都是存储在云点播上的。第二步,通过API创建一个项目,把云点播里面的媒体资源导入到这个项目,再返回一个签名给前端。第三步,前端通过上一步返回的签名,初始化iFrame页面,此时可以立刻打开剪辑项目,并且里面拥有由服务端注入的媒体资源,并且前端页面外部可以通过API和iFrame进行交互,可以随时修改里面的内容、注入新的元素,让用户上传自己的媒体资源。
-02-
云剪辑前后端架构的设计与演进
前面介绍了B端云剪辑的基本能力和应用场景,下面介绍云剪辑前后端架构的设计与演进。
云剪辑的技术要求主要有三个。第一个是要求能够实时渲染。画面要能够实时响应时间轴的更新。第二个要求可以进行比较复杂的交互。包括媒体资源的操作、时间轴的操作以及画布元素的更新,保证操作的流畅性和数据的稳定性。第三个是多端渲染。上图展示了Web端、小程序端和服务端三个渲染场景。通过设计来实现多端渲染效果的一致性。
这是我们渲染引擎的一个整体的架构。首先,腾讯云的渲染引擎通过轨道数据和时间进行驱动,团队做很多的工作保证每一帧的内容能够被精确渲染。在前端很容易写出一些非阻塞性的代码。很多在线剪辑工具上面可以看到,当播放或者seek到某一帧,某些元素会延迟出现。由网络带来的渲染不确定性是不能接受的。第二,通过游戏化父子关系分层树的设计来设计渲染引擎,极大的提升了素材类型的可拓展性。我们把一切轨道元素都称之为Clip。腾讯云也对PAG素材进行了支持,可以很好的与PAG素材特效和模板生态进行互通。
前面提到,我们的渲染是由数据和时间进行驱动的,这里主要有四个内容的更新。
首先是timer的更新。数据准备好之后,会由timer驱动整个画布的更新。画面的更新分为两步,第一步是用户播放行为,第二步是用户在画布里实际的操作行为。每一帧的更新,都需要一定的准备工作,找出时间轴上面当前应该被渲染的元素、不应该被渲染的元素以及根据预测即将要被渲染的元素。其次是缓存的更新。由preloader进行元素预加载,并进缓存的创建和销毁的管理。
第三个是Clip的更新。Clip是所有元素的基类。例如元素的宽高位置等基础的属性拖拽旋转缩放等操作。最后是用户行为的更新。用户在渲染引擎中可以进行很多操作,例如拖拽视频贴纸等。我们会把画面元素的更新同步回轨道数据,保证数据的一致性。
视频剪辑项目少不了各种特效的添加。在项目中通过Shader实现了一些视频的效果,例如特效转场、蒙版、主场动画等。
这些都是常见的在短视频里面的一些效果。如何实现这类效果的开发与复用呢?
这是片源着色器的代码,这个代码最核心的部分是一个main函数。函数的返回值是一个颜色值,也就是RGBA。什么操作都不做就返回0000,那么右边的画面是全黑的。如果右边画面的分辨率是720×1280,main函数就会执行720×1280次。第二个是纹理的输入。在这个程序中输入两个图片纹理,在main函数里面可以拿到这个纹理像素点映射后的颜色值。什么都不做就返回图一的颜色值,那最终的画面是图一的完整的画面。如果返回图二的颜色值,最终的画面就是图二。
这是动画的逻辑。假如动画时长是2秒,当前运行时间是一秒,这个百叶窗效果就会处于一个动画的中间状态,通过计算让一部分像素显示图1的像素颜色,一部分像素显示图2的像素颜色。这里有个比较意思的电,#iChannel、#iUniform 这种 # 开头的形式不是标准的 Shader 写法,会被我们的 VSCode 插件解析成一个标准的入参。
编写完之后可以通过调节右下角这个东西进行实时的Shader调试。编写Shader较为随意,可以在编写特效的时候入参任意的纹理和变量,同时没有原生的这种import组件复用的模式,不利于程序的设计和复用。腾讯云通过VSCode插件的方案解决这些问题。在设计渲染引擎的Shader Controller模块时,对入参的uniform进行严格的约束,只使用规定好变量的入参,并且尽可能把可以重用的方法进行封装。后面的开发者只需要编写他渲染的部分,不用关心通用的逻辑。通用的uniform就是上面提到 # 开头的变量。例如进度变量,有了进度变量就可以在 main 函数中得到当前的动画进度,从而在 main 函数中根据当前进度计算应该展示的动画样式。类似的公共入参还有一些标准化的像素坐标、UV、画布比例等,可以通过右下角调节实现实时的预览。
Import方法是通过合并Shader实现。Shader本身不提供原生的input阶段和机制。在工具的编译阶段采用通用方法的拼接。在编写的阶段,开发者只需要编写main函数里具体的逻辑,就可以使用其通用的方法,例如计算比例、计算位置、计算取色、计算进度等,在生成代码的阶段,把这部分代码和后面开发者编写的渲染部分的代码进行合并,调试完成之后点击右上角的导出就可以新生成剪辑器所需要Shader文件,上架新的特效。
渲染引擎不是孤立的存在,需要配合轨道数据,拼装轨道数据也离不开编辑器。前端编辑器主要有四个模块。第一个是已经提到的实时渲染引擎。第二个是素材的模块。每引入一种类型的素材都会经过仔细的调研与思考。一开始只允许使用Web主要的模式进行渲染,不允许在校园引擎上面叠加环境依赖的东西。第三,剪辑轨道。第四,素材的补充模块。方便客户导入通用的AE模板用于制作平台专属的贴纸、文字特效等。
第一版剪辑轨道性能比较差。通过视图中控,在元素被点击的时候,把被点击的数据提交到视图中控,并生成拖拽的实体,其他所有元素都可以通过监听视图中控这个实体的变化来更新。拖拽的过程中,驱动坐标更新,寻找允许拼接的区域或者自动对齐的区域,随后进行影子元素的渲染。当用户拖拽放锁时,才进行真正的轨道更新。通过这样的设计,使轨道操作面有很大的提升。
操作逻辑离不开媒体元素。目前在线剪辑工具的资源管理主要有两种模式。一种是纯云端,一种是纯本地。一开始使用的是纯云端的模式,所有的资源都围绕云点播。纯本地模式类似国外的Clipchamp。纯本地模式不能跨设备协作,而且存在缓存文件丢失的风险。但纯云端模式用户得等待视频上传和转码完成后,才能编辑该视频。腾讯云采用本地云端双模式支撑剪辑工作流。当一个文件导入时,解析视频,判断媒体资源能不能被直接编辑。如果能被直接被编辑,开启本地剪辑工作流,进行封面图、雪碧图的截取,并将视频导入到剪辑轨道。剪辑器的背后会进行资源的上传和转码工作。等上传和转码完成之后,就进行云端化的替换。此后,用户无论是更换设备还是更换用户,项目始终保持数据稳定和可用。
绿色的部分是前端的应用场景。腾讯云的实时渲染引擎,已经很好地支持Web端和小程序端的渲染工作。在服务端导出部分,通过协议和后台进行对接,约定同一个渲染协议,后台通过FFmpeg、OpenGL拼接最终的轨道数据。这样做不仅会导致前后端不一致,而且会消耗大量的后端人力。
经过验证,将渲染引擎丢到服务端是可行的。整个程序由渲染引擎的Node进程进行驱动,封装一个共享内存的Node拓展模块,用于快速传递其中的视频帧和音频帧数据,再封装一个Node编解码拓展模块。底层是基于改造后的FFmpeg。渲染引擎在前端应用时,是基于数据和时间进行驱动的。由于采用分层架构的设计,大部分的改造只需要改造preloader加载数据的部分,Clip对外渲染的API是一致的,可以很好的复用一部分渲染的逻辑。
为了避免IO损耗,不可避免的要封装一个共享内存拓展,用于提供给渲染引擎和编解码模块进行音视频帧数据的传递。共享内存分为两个部分——共享内存写模块和共享内存读模块。FFmpeg在接收到预加载的事件后,会预取视频帧放到共享内存。当渲染引擎的某一帧需要某个视频帧的时候,就会通过handle从共享内存里面取出这部分的buffer进行渲染。渲染完之后,再把渲染结果放到共享内存供编码器读取。
编解码模块的Node拓展。这里封装了一个编解码的Node拓展程序,提供给渲染引擎的主进程进行调用。渲染引擎一开始就会创建一个编码的子进程,在渲染的过程中,也会根据预加载的结果按需创建解码子进程。进程间通过共享内存的信息进行传递。帧率对齐,多少帧解码,就会返回相应数据量的音频帧和视频帧。渲染引擎就会拿到这一帧的数据进行画面的渲染和音频的处理。
视频的整体合成调度流程如图。由于渲染引擎具有帧精确的设计,无论分片多少,始终可以保持渲染的一致性。30秒的视频,可以分三片,也可以分十片。不管从几片开始渲染,最终的渲染结果都完全一致。这为分布式渲染提供了很好的底层支撑。
我们同时会对轨道数据逐帧去分析,只有真正需要渲染的内容才会走进渲染的逻辑,否则会送去编码或者是转码。在完成所有的分片任务之后,会进行总分片的转封装,完成视频合成的流程。
上述流程做完之后就可以上线。如何保证渲染效果的一致性呢?通过编写所有元素和效果的测试用例集,先生成预期结果的MP4,后续每次迭代都通过SSMI结构相似性来逐帧比对两个视频的差异,最终保证合成的视频跟原本是没有差别的。但很多时候视频剪辑效果复杂,想要保证复杂情况下的渲染一致性就要直接把线上的数据作为测试用例集。每次发布前,会有影子环境进行抽样比对,每一次的任务也是通过SSMI结构相似性比对每一帧的差异。只有通过了所有的比对,才会允许发布。把容易出错的case,会汇总到bad case,之后我们也会不断完善,在迭代过程保证发布的质量。本地的测试用例集加上发布前的影子环境,后台服务发布是整个中心里最没有负担的,能跑过就证明可以发布。
-03-
浏览器原生编解码能力助力云剪辑
Chrome从86版本开始引入WebCodecs。也是从86版本开始,渲染总会有一些莫名其妙的bug。大概92版本才修复好并稳定下来。WebCodecs意在在浏览器提供高效的音视频编解码API。在WebCodecs出现之前,已经有VideoRecorder和MSE两套编解码相关的API,但他们都有很多限制。WebCodecs的出现,让音视频业务有了更大的想象空间。
纯浏览器剪辑虽然不涉及服务端,但绕不开视频转码。因为浏览器对视频格式的支持有限,很多格式不能够直接在浏览器播放。在剪辑之前需要对一些不支持的视频格式进行转码。相信大家都已经听过,或者在业务中使用过FFmpeg的wasm版本。Wasm的内存限制,导致对于视频剪辑场景来说比较紧张的。此外最关键的一点就是性能问题。就算支持了SIMD,1080p的MOV视频转码也只能够达到0.3倍数,用户体验很差。为了提升转码效率,可以在浏览器里面起十个worker,谁闲就用谁。首先获取视频的原信息,分布式的进行转码,最后转封装。为了避免声音出问题,可以把声音直接取出来转封装。这个模式下可以达到三四倍速转码效率,一分钟的视频,大概十几秒就可以装码完成。
WebCodecs与渲染引擎进行结合,效率提升十分明显。33秒的原片,只需要九秒多的时间就完成解码渲染编码的整个流程。做的过程中发现,WebCodecs的API它只负责解码和编码,解封装和封装需要你自己搞定的。Video frame和Audio data都是很轻量级的对象,但持有很重的内存引用。在编码的时候,如果传入不同的宽高,编码器会自动进行缩放,这个时候可以把一些缩放的逻辑放到编码器,这样可以减少渲染的概率,提升性能。
WebCodecs有着出色的性能。但完全使用它代替浏览器端的FFmpeg还有比较大的差距。直到21年的年底,它的音频编码格式还只是Opus格式,还不能被MAC、 WINDOWS的原生播放器支持。虽然可以用FFmpeg的wasm版本把它的音频再转成AEC,但这就失去了纯浏览器制作视频的快乐。22年5月份终于定下 AAC 编码协议。H265的支持呼声在论坛一直很高,Chrome在104的版本终于支持了H265的硬解。与此同时,WebCodecs也支持了H265的解码。解封装和封装的整合,主流方案是通过FFmpeg的wasm版本进行支持。或者通过MP4 box纯手动来拼接。WebCodecs开发者被逼无奈,做解封装器 wasm 的开发。
编码的部分也有不少可以优化的地方。配置缓冲区的大小,是得开发者能够根据浏览器的性能来进行一些精细化的控制。
得益于我们渲染引擎的构建方式,以及分层设计,可以很快地将loader部分替换成WebCodecs。如果音视频的解码就能够像在服务端,以帧对齐的方式进行返回,对于很多开发者来说是非常便利。解码和编码的配置项可以让开发者更方便地使用WebCodecs。
今天的分享就到这里,谢谢大家!
LiveVideoStackCon 2023上海讲师招募中
本文分享自微信公众号 - LiveVideoStack(livevideostack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。