Cocos 大表姐:Cocos 引擎中的音视频应用丨ECUG Meetup 回顾

731 阅读15分钟

本文根据「大表姐」李阳(Cocos 引擎布道师负责人)于 2021 年 6 月 26 日举办的 ECUG Meetup 第 1 期 | 2021 音视频技术最佳实践·杭州站上的分享整理而成。本文长达 5800 余字,主要结构如下:

  • Cocos 引擎介绍
  • Cocos Creator && Engine
  • 基于 FFmpeg 的 Cocos Creator 视频播放器
  • Cocos 的音视频播放及改造

获取「讲师完整版 PPT」 ,请添加 ECUG 小助手微信(微信ID:ECUGCON)并备注「ECUG PPT」。其余讲师的分享也将于后续陆续放出,敬请期待。


以下为分享正文:

bVcTWVK.webp

大家伙,我是 Cocos 引擎布道师负责人李阳。我写了大概七年的代码,一直是做基于 Cocos 引擎的手游研发,做的可能被大家熟悉的一款游戏是捕鱼达人。

我现在在 Cocos 做布道师负责人,在我看来这个职业就是开发者的辅助师、技术的导航仪,主要是协助用公司引擎的开发者更高效地开发出游戏,帮助他们解决技术上的问题,提供引擎部门的支持。

一、Cocos 引擎介绍

Cocos 以「技术驱动数字内容行业效率提升」为理念。从 2011 年成立到目前为止,在全球有 140 万个注册开发者,登记游戏数有将近 10 万。下载 Cocos 引擎制作游戏或者 APP 的大概有 16 亿设备,包括 203 个国家和地区。

bVcTWVO.webp

上图中是 Cocos 引擎的一些市场占有率。中国手游的占有率是 40%,中国小游戏的占有率 64%,在线教育 APP 占有率是 90%。在线教育领域 Cocos有专门的教育编辑器,用类似 PPT 的方式就可以开发出一个互动的游戏。

bVcTXm7.webp 上图左边是 2020 年 5 月的一些数据,右侧的是一些游戏案例。《剑与远征》不知道大家有没有玩过,《动物餐厅》是微信小游戏的创意游戏,用户和流水过亿级别的;《世界争霸》是一个月就研发出来的游戏,当然它的团队规模也是很大的。弹无虚发这个游戏介绍最后这个游戏,它是一个只有三个人的团队开发的,程序员只有一个。所以说,我们的引擎和编辑器是能够非常高效并且快速地研发出游戏来。

我们涉及的方面比较多,包括教育、车载、数字孪生、在线展会等等。包括腾讯、华为、网易等等,都是我们的合作伙伴,都在用 Cocos 引擎进行开发。

用 Cocos 编辑器可以进行可视化编辑,可以模块实现游戏创作,展现上的所见即所得的状态。

bVcTXnf.webp

Cocos 编辑器可以做 2D 和 3D 游戏。针对美术工作流它还有有动画编辑器,以及粒子系统等等,通过非常简单的模块化降低了游戏开发的门槛。

bVcTXng.webp

上图是我们跨平台的工作流。3ds Max、Maya 是第三方软件,用于做模型;UI和 Animation 可视化部分可以用cocos编辑器去做。Code IDE 去写我们的逻辑代码。我们可以完成所见即所得的研发体验。

二、Cocos Creator && Engine

bVcTXni.webp

上图则是 Cocos Creator 引擎的架构。Game Assets 是我们的资源,Game Logic 是我们写的代码。后面我们会有 Cocos Creator Engine,包括渲染、Scene Graph 以及我们的资源和数据,还有特效、物理以及组件化,UI,最后发布到平台。下面是我们的后端渲染,Metal 对应的是苹果,Vulkan 是在安卓平台上做渲染,WebGL 是web端。

Cocos 引擎目前在 2D 手游部分占的比例会比较大,但在 3D 上我们也有很好的表现。华为海思也帮我们实现了 延迟渲染的管线,后面我会详细介绍一下,相对来说非常的真实。

bVcTXnj.webp

我们的引擎部分是 100%开源的,开源引擎就在 Github 上。 在编辑器还有一个偏好设计,就是说我们下载下来会带有一个相对应版本的引擎。但是我们开源的部分,你可以去改我们的引擎,在 TS Engine 和 Native C++ Engine 上选择自己修改引擎的路径,用可定制化的引擎去做自己想要的版本。这也是我们更加多元性和定制化,给所有的开发者增加的接口。

bVcTXnk.webp

我稍微普及一下纹理渲染。CPU 从内存读出数据,然后把Vertex Data发送到 Vertex Shader,通过剪裁再到片段着色器,经过混合和测试,最后到屏幕上,这是整个的渲染流程。

bVcTXnm.webp Cocos Creator Renderer 中,RenderScene 管理引擎上的所有节点,FrameGraph 是数据的依赖。我们这边有两个 Pipeline,其中一个是 Forward,前向渲染。

前向渲染会有一个什么问题呢?它是相当线性的,并且每次将每个几何图形向下通过管道一次以生成最终图像。 但是模型和模型之间有一个重合的部分,比如前面有一个模型,中间有一个模型,后面还有一个模型,但其实真正摄像机看到的部分只有第一个。但是如果成像渲染的话,我会把三个都渲染一遍,但真正到可视化的时候只有一个,后面被遮挡住部分的计算是冗余的。

而在延迟渲染中,着色将延迟一点,直到所有几何图形都通过管道为止,生成最终图像。当顶点数据传到顶点着色器上,走到片断着色器,它会把这些数据存到G-Buffer上。下一阶段再把 G-Buffer的数据再传到顶点着色器上。这个时候,顶点着色器对 G-Buffer 数据的处理程序和上一阶段的顶点着色器程序是不一样的,是两套逻辑,它的处理数据的模式是不同的。

接下来,会到片断着色器上,片断着色器是以屏幕上的像素点为单位,只会去渲染要显示在当前像素上的图形就好了,其他被遮挡的图像并不在计算范围之内,它就会大量节省计算的时间。

刚才我们讲到不同平台的渲染接口,我们做了一个 GFX 的分装。它会把这些接口变成同样一个 API,因为我们用 GFX 这个阶段是开发者可以看到的,但是我要发布到不同平台,如果我的 API 不同,会增加开发者的工作时间和工作量,所以我们把它进行了封装。在开发游戏的时候,不考虑平台,都是用同样一套 API,等到去发布的时候,再去构建、去选择要用的渲染。

bVcTXnp.webp 这是原生部分的渲染,我讲一下 GFX Agent。GFX Agent 会传一些指令到 CPU 上,假如我们这个队列里是 20 个命令,每一帧的时候,首先我们要读取队列,队列里的指令要执行,然后再从队列里刨出去。

但是有时候,如果整个指令过于多的情况下,正常的话我们是 1 秒 60 帧,这是 OK。也就是这个时间范围内,如果我处理不完,我当前 CPU 传给 GPU 的指令就会出现卡顿。假如我现在一帧卡顿了,导致用了两帧的时间才完成一次渲染,那就说明我现在的帧已经从 60 掉到了 30,如果我卡顿用了三帧的时间,那我就从 60 直接掉到 20 帧(以上都是实时帧率)。

根据这个问题我们做了 GFX Agent,就是说当前的线程我们并非是实时处理GPU的编码命令,我们这个阶段只会收集并打包这些指令,我们会放到队列里。然后我会在执行这些指令的时候另开一条线程。

线程也是有队列的,如果当我的 CPU 来不及传一些指令到 GPU 的时候(大家肯定了解,卡顿大部分是 GPU 等待 CPU 的等待时间),会引起卡顿。 所以当 CPU 没有把指令传到 GPU 的时候,哪怕 GPU 没收到新的指令,但是在另外开启一个线程里,还是有东西可以被渲染的,就不会引起卡顿状况。

bVcTXnq.webp

我们现在的版本是 Cocos CreatorV3 版本,做了一些性能的提升:

第一,面向现代图形接口的多后端 GFX;第二,负载均衡的多线程渲染器。当我要发布命令到某些线程的时候,会对这些线程进行检测,或者说看一下哪个线程处理的任务比较少。这时候就会把任务放到任务比较少的线程里,会保证每个线程的任务是相对来说差不多的,维持负载均衡的状况;第三,高度可定制的渲染管线;第四,华为贡献的高性能延迟渲染管线;第五,基于移动端 TBR&TBDR GPU 的 Memoryless 架构。

因为我们知道,在移动端比较吃硬件,比如说我的量非常大的时候,移动端传到手机上,你可能会发现有一些发热或者说接收延迟的情况。TBR 它将需要渲染的画面分成一个个的区块(tile),每个区块的坐标通过中间缓冲器以列表形式保存在系统内存中。TBDR TBDR渲染的一个关键是延迟渲染。

三、基于 FFmpeg 的 Cocos Creator 视频播放器

下面,我讲一下基于 FFmpeg 的 Cocos Creator 视频播放器,以开心猴子为例子。我们在教育课件上会有一些互动的游戏,互动游戏肯定少不了播一些视频,这时就会有一些问题。

bVcTXnv.webp

比如说这张图,我们是以同样一套设计,Android、IOS、Web 上的视觉效果是不一致的。并且因为 fix top 设计的限制,我是不能使用遮罩组件限定形状的,它不支持远角的掩盖。还有场景切换时会出现一种状况,我退出了一个场景,但是我当前存在视频组件的残影,这都是在用引擎开发游戏时遇到我们的问题。

因此,我们的解决思路是什么?主要是分两部分:FFmpeg 和 Web Video Element。

bVcTXnz.webp

我们使用 FFmpeg 去做编码,再用 OpenGL 进行渲染,发布到手机上。Android 用 Oboe 去做一屏播放,IOS 用 AudioQueue 做音频上的播放。Web,我们应用了 Video 做元素去进行解码,用 WebGL 去做视频方面的渲染,以及用 Web Audio API 去做音视频的播放。这就是我们整体的解决方案的思路。

bVcTXnA.webp

这是一些音频划分。首先,会用 FFmpeg 解码音视频,对 ffplay 进行了改造,做了 AVplay。解码之后,各端接入音视频的解码,Android、IOS、Web 用的都是不同的。最后,我们有一个 jsb 绑定视频组件接口。因为我们整个引擎是用 js 语言,但是我们的音频播放器是用 C++写,所以我们必须通过 jsb 绑定,让 js 能够去调用其他语言的对象。

后面是音频方面的播放,我刚才也讲到,Android、IOS、Web 分别用的是不同的音频播放处理。最后,我们有一个优化与扩展,比如说边下边播、精准 seek、libyuv 代替 swscale。

对这点我稍微补充一下。为什么我们要做这样的事情,是因为如果想在我们游戏里,用引擎播放视频的话,整个框架是迁过来的,是没有办法和我们引擎当中的一些组件去做交互的,它也没有所谓的层级关系,我没有办法对这个视频进行操作,也没有办法通过用户这边的行为去对视频做一些互动。所以这是我们要去基于 FFmpeg 做修改的原因。因为这样的话,是等于把我们的 video 上的每一帧全部渲染到我们的引擎上,所以它会有一些效率上的问题,这就是我们为什么用 libyuv 代替 swscale 的原因。

我再说一下 AVplay 改造。我们对 FFmpeg 的源码进行了编译之后,发现有三个可执行的程序:

  • 第一,FFmpeg 用于音视频视频格式转换、视频剪接等;
  • 第二,ffplay 用于播放音视频,需要依赖 SDL;
  • 第三,ffprobe 用于分析音视频流媒体数据;

它有什么问题呢?虽然它是满足了我们对于播放音视频的需求,但是它在我们改造当中出现一些问题,比如说它在进行纹理缩放以及像素、格式转化的时候,效率非常低,并且它不支持 AndroidSL 文件的读取,所以我们对这个进行了改造。

bVcTXnB.webp

这是 AVplayer 整个改造的思维路。首先,我们会调用一个函数去初始化所有的信息,然后去创建一个 read thread 和 refresh thread。它会创建音频、视频以及字幕的解码程序。refresh thread 是对我们传过来的比如说视频上的图片序列、音频上的样本序列以及字幕的字符串序列进行消耗。以上是 AVPlayer 的整个架构。

bVcTXnE.webp

这是 JSB 绑定视频组件接口。JSB 绑定的原因,是为了让 JSB 去调用 C++的对象以及其他的语言,因为我们的引擎语言和视频播放器的语言不是一种,所以我们要让他们产生一个调用关系,所以我们对 JSB 端做一个绑定。在这里我们还做了一个分装的类,就是 Video,这是它的 UML。

bVcTXnF-1.webp

做完了 JSB 的绑定,就到了渲染的部分,就是我刚才说的我们要把视频上的每一帧都渲染到我们的引擎上,它主要就是三个步骤:

  • 第一,自定义材质,材质负责着色器程序;
  • 第二,自定义 Assembler,它负责传递顶点属性;
  • 第三,设置材质动态参数,比如说设置纹理、变换平移旋转缩放矩阵等。

关于自定义材质,着色器程序需要写在 effect 文件中,而 effect 被 material 使用,每个渲染组件,需要挂载 material 属性。由于视频展示,可以理解为图片帧动画渲染,因此可以直接使用 Cocos Creator 提供的 CCSprite 所用的 builtin-2d-sprite 材质。

关于自定义 Assembler,有了材质后,只需要关心位置坐标和纹理坐标传递,即要自定义 Assembler,可参考官方文档自定义 Assembler。原生端和 Web 端世界坐标计算方式(updateWorldVerts)不一样,否则会出现展示位置错乱问题。

关于设置材质动态参数,在视频播放器中,需要动态修改的就是纹理数据了,在移动端,ffplay 改造后的 AVPlayer 在播放过程,通过 ITextureRenderer.render 接口调用到 void Video::setImage 方法,实际在不断更新纹理数据。

bVcTXnG-1.webp

以上是基于 FFmpeg 做的改造,这是我们改造完之后的场景(图示)。刚才那个视频其实已经是我们渲染出来的每一帧的动画,所以它就能形成一个互动的状态。

四、Cocos 的音频播放及改造

最后,讲一下 Cocos 音视频的播放及改造。

Cocos 的音视频播放相对来说是比较简单的,IOS 是 OpenAL,Android 是 OpenSL,Web 是 WebAudio 和 DomAudio,也是分不同的平台去做的。

bVcTXnH.webp

这是 Cocos 引擎的音频。其实在引擎当中比较简单,一个是循环播放的类似于背景音乐,还有一个是音效,比如说我发一个子弹的音效的声音。

在这边,我们还是基于上面的例子,也是对音频在移动端进行了轻微的改造,主要是为了替换 ffplay 程序中的 SDL 音频相关接口,主要是开启、关闭、暂停和恢复等。

bVcTXnL.webp

接下来,我们也会在音频上加入一些 3D 的音效,支持一些主流媒体的音效平台。刚才我讲到的在教育方面的应用,其实我们在直播上也可以用我们的 Cocos 引擎去做一些开发方面的互动游戏,今后我们也会把更多其他场景下的游戏案例分享给大家。

以上是我的分享,谢谢!


关于七牛云、ECUG 和 ECUG Meetup

七牛云:七牛云成立于 2011 年,作为国内知名的云计算及数据服务提供商,七牛云持续在海量文件存储、CDN 内容分发、视频点播、互动直播及大规模异构数据的智能分析与处理等领域的核心技术进行深度投入,致力于以数据科技全面驱动数字化未来,赋能各行各业全面进入数据时代。

ECUG:全称为 Effective Cloud User Group(实效云计算用户组),成立于 2007 年的 CN Erlounge II,由许式伟发起,是科技领域不可或缺的高端前沿团体。作为行业技术进步的一扇窗口,ECUG 汇聚众多技术人,关注当下热点技术与尖端实践,共同引领行业技术的变革。

ECUG Meetup :ECUG、七牛云联合打造的技术分享系列活动,定位于开发者以及技术从业者的线下聚会,目标是为开发者打造一个高质量的学习与社交平台,期待每一位参会者之间知识的共创、共建和相互影响,产生新知推动认知发展以及技术进步,通过交流促进行业共同进步,为开发者以及技术从业者打造更好的交流平台与发展空间。