音视频技术在Flutter浪潮下的研发探索
署名2021-01-26

关于音视频技术在Flutter浪潮下的研发探索大家有多少了解?本次分享的主要内容是针对闲鱼APP在当下流行的跨平台框架Flutter的大规模实践,介绍其在音视频技术领域碰到的一些困难以及解决方案。分享内容主要分为四个方面,首先会对Flutter有一个简单介绍以及选择Flutter作为跨平台框架的原因,其次会介绍Flutter中与音视频技术关系非常大的外接纹理概念,以及对它做出的一些优化。  

一、Flutter  

Flutter是一个跨平台框架,以往的做法是将音频、视频和网络这些模块都下沉到C++层或者ARM层,在其上封装成一个音视频的SDK,供UI层的PC、iOS和Android调用。而Flutter做为一个UI层的跨平台框架,顾名思义就是在UI层也实现了一个跨平台开发。可以预想的是未Flutter发展的好的话,会逐渐变为一个从底层到UI层的一个全链路的跨平台开发,技术人员分别负责SDK和UI层的开发。  

二、为什么Flutter可以实现高性能  

原生的native组件渲染以IOS为例,苹果的UIKit通过调用平台自己的绘制框架QuaztCore来实现UI的绘制,图形绘制也是调用底层的API,比如OpenGL、Metal等。而Flutter也是和原生API逻辑一致,也是通过调用底层的绘制框架层SKIA实现UI层。这样相当于Flutter他自己实现了一套UI框架,提供了一种性能超越原生API的跨平台可能性。  

在闲鱼实践Flutter的过程中,混合栈和音视频是其中比较难解决的两个问题,混合栈是指一个APP在Flutter过程中不可能一口气将所有业务全部重写为Flutter,所以这是一个逐步迭代的过程,这期间原生native界面与Flutter界面共存的状态就称之为混合栈。闲鱼在混合栈上也有一些比较好的输出,例如FlutterBoost。  

三、外接纹理  

在讲音视频之前需要简要介绍一下外接纹理的概念,我们将它称之为是Flutter和Frame之间的桥梁。Flutter渲染一帧屏幕数据首先要做的是,GPU发出的VC信号在Flutter的UI线程,通过AOT编译的机器码结合当前DartRuntime,生成LayerTreeUI树,LayerTree上每一个叶子节点都代表了当前屏幕上所需要渲染的每一个元素,包含了这些元素渲染所需要的内容。将LayerTree抛给GPU线程,在GPU线程内调用Skia去完成整个UI的渲染过程。  

LayerTree中有PictureLayer和TextureLayer两个比较重要的节点。PictureLayer主要负责屏幕图片的渲染,Flutter内部实现了一套图片解码逻辑,在IO线程将图片读取或者从网络上拉取之后,通过解码能够在IO线程上加载出纹理,交给GPU线程将图片渲染到屏幕上。  

TextureLayer渲染过程:首先判断Layer是否已经初始化,如果没有就创建一个Texture,然后将TextureAttach到一个SufaceTexture上。这个SufaceTexture是音视频的native代码可以获取到的对象,通过这个对象创建的Suface,我们可以将视频数据、摄像头数据解码放到Suface中,然后Flutter端通过监听SufaceTexture的数据更新就可以顺利把刚才创建的数据更新到它的纹理中,然后再将纹理交给SKIA渲染到屏幕上。  

通过对Flutter渲染过程分析,我们知道Flutter底层需要渲染的数据就是GPU纹理,而我们经过美颜滤镜处理完成以后的结果也是GPU纹理,如果可以将它直接交给Flutter渲染,那就可以避免GPU->CPU->GPU这样的无用循环。这样的方法是可行的,但是需要一个条件,就是OpenGL上下文共享。  

四、OpenGL  

在说上下文之前,得提到一个和上线文息息相关的概念:线程。Flutter引擎启动后会启动四个线程:第一个线程是UI线程,这是Flutter自己定义的UI线程,主要负责GPU发出的VSync信号时候用当前Dart编译的机器码和当前运行环境创建出LayerTree。还有就是IO线程和GPU线程。和大部分OpenGL处理解决方案中一样,Flutter也采取一个线程责资源加载,一部分负责资源渲染这种思路。  

两个线程之间纹理共享有两种方式。一种是EGLImage(IOS是CVOpenGLESTextureCache)。一种是OpenGLShareContext。Flutter通过ShareContext来实现纹理共享,将IO线程的Context和GPU线程的Context进行Share,放到同一个ShareGroup下面,这样两个线程下资源是互相可见可以共享的。  

Platform线程是主线程,Flutter中有一个很奇怪的设定,GPU线程和主线程共用一个Context。并且在主线程也有很多OpenGL操作。音视频端美颜处理完成的OpenGL纹理能够让Flutter直接使用的条件就是Flutter的上下文需要和平台音视频相关的OpenGL上下文处在一个ShareGroup下面。由于Flutter主线程的Context就是GPU的 Context,所以在音视频端主线程中有一些OpenGL操作的话,很有可能使Flutter整个OpenGL被破坏掉。所以需要将所有的OpenGL操作都限制在子线程中。通过上述这两个条件的处理,我们就可以在没有增加GPU消耗的前提下实现美颜和滤镜等等功能。