Flutter 实时视频渲染:Texture与PlatformView

前两期 RTC Dev Meetup:Flutter 开发技术专场 的回顾视频已被上传至了 RTC 开发者社区,欢迎你们浏览与交流。 咱们整理了其中一个演讲内容——由声网 Agora 高级架构师 张乾泽分享的“Flutter 实时音视频实践”。如下为演讲实录。缓存

我今天给你们介绍的是 Flutter 实时音视频开发实践方面的经验。我叫张乾泽,毕业于英国牛津大学,以前在 SAP 主要担任移动端架构师,2017年加入声网Agora,目前是声网Agora 的高级架构师,主要负责 RTC 技术在娱乐、直播、教育等行业的应用研发工做。架构

首先咱们讲到 RTC 应用,什么样是 RTC 应用呢?RTC 是 Real-time Communications。你们平时在平常生活中已经接触到很是很是多的RTC应用,好比说直播,你们平时会用手机看熊猫直播、斗鱼直播,实时看主播的游戏画面、他们唱歌的画面、跳舞的画面。还有教育,经过互联网能够实现远程教育,好比学生和美国的老师能够直接进行实时互动。框架

在医疗方面,咱们最近也看到不少的案例。之前你要到医院挂号排队,等一上午,医生看一下,而后明天再来复诊时从新挂号,又是一个上午的时间过去了。如今有了 RTC 后,你能够经过视频直接在家里看医生,医生在视频中问你一些问题,若是病症简单,你甚至都不须要去医院。ide

今天咱们就拿常见的视频通话的应用,来看看如何基于 Flutter 来开发实现,以及其中会有什么样的困难。性能

架构逻辑与实现思路

首先讲一下 Flutter 的架构逻辑。若是比较熟悉 Flutter 的朋友可能会知道,它的渲染方式跟 React Native 最不同的地方就是它对于本身的一些原生控件,没有利用系统自己提供 View。那么开发应用的时候,怎么渲染出来的呢?优化

它本身里面会创建一个 Layer Tree,Layer Tree 里面是它的一个树状结构,它把数据或者渲染的地方发到 Skia,Skia 获得 GPU 的 Vsync 信号的时候,再把这些数据传给 GPU 统一渲染。彻底没有经过相似 UI View 这样的 Native 的 View 的。编码

在知道这个概念以后,我就开始着手开发这个应用。这个应用由于它自己就提供了一些经常使用的组件,好比 App 的一个框架,一些按钮和导航栏,Flutter 有现成的组件可让你直接把这些东西画上去,只须要经过一些很是简单的配置便可。设计

Flutter 自己也提供了与 Native SDK 的通信的方式,叫作 Channel Massage,跟 React Native 调用 Native 的思路很是像。因此咱们有了声网 Agora SDK,会帮咱们处理音频编解码、音频的传输,而后咱们能够利用 Flutter 自己的组件把 UI 也画好,好像这个东西就 Work 了。当一切都彷佛变得那么美好的时候,我卡在了视频上。Flutter 怎么渲染视频?3d

在 Native 平台都有系统组件来渲染视频,但 Flutter 没有这样的东西。我如今要把实时视频渲染出来。orm

因而我查看了 Flutter 的源码,其中有一个组件叫 Video player,这是放视频的。其中有一个 Texture Widget,它在这种模式下提供了一种机制,可让你进行视频的渲染。

实现思路一:Texture Widget

首先视频是由一帧帧图像组成的。Flutter 的 Texture 提供了一个能够放在 Layer Tree 里的组件,组件中的数据源须要由你经过 Native 端来提供。

在这里以 iOS 为例,iOS 就须要提供 CVPixelBufferRef。这是一个数据,对应的就是视频中的一帧画面。把这个数据做为数据源提供给Texture Widget,而后Texture Widget就能够把你提供的这些数据显示出来,最终就变成了一个视频。

上图是大体的实现架构。首先要在 Dart 上实现一个 Native 的 Plugin。Plugin Registar是Plugin的一个入口,有一个属性叫作 TextureRegistry,你能够在这里注册一个你本身的Texture类,这个类可能实现了一个协议叫Flutter Texture。你实现这个协议过程当中有两个方法须要实现:一个方法叫作 CVPixelBufferRef,就是要提供数据源到你的Texture。另外一个方法你须要本身主动去调,叫作textureFrameAvailable,它的做用是告诉 TextureRegistry 如今能够根据提供的数据来更新画面了,就是我提供数据。

其中有一个问题,如今有 TextureWidget,若是更新数据源的话,它怎么才能知道我要更新哪一个Texture呢?其实,在注册这Texture的时候,它会返回一个 TextureID。你在建立 Texture Widget 的时候须要把这个 TextureID 传进去。这样的话 Texture 的组件就会跟你提供的这个实践进行绑定,知道你提供的数据源就是为这个 Texture 提供的数据。以此类推,咱们也能够有不少个 Texture。

回到咱们以前的设计来看(如上图),咱们这边是一个视频通话应用,咱们有不少本地的视频,有不少的远端的视频,这些视频均可以是不一样的 Texture Widget,放在Dart的页面容器中,而后咱们分别为它们提供数据源,进行数据渲染。

是否是感受如今一切准备就绪了?其实还有一个问题。Flutter官方的 Github 提供了一些示例代码,可是它提供的功能是让你播放一个静态缓存,咱们这里提供的是一个实时的视频流,不是一个视频文件。不可能以读取一个文件的方式,把里面的数据一帧一帧地转化以后,传给 Texture Widget。咱们须要有一个方法拿到这些实时的数据,把它做为数据源传给Texture。

这时候咱们就须要使用声网Agora SDK。首先给你们普及一个概念,你们想象一下平时在看一个直播的时候,直播那边有一个摄像头,他玩一些游戏,他跳一些舞、唱一些歌,是怎样让我在手机里面看到这些数据的呢?总结来说是一个很是简单的流程,如上图所示。首先主播有一个摄像头,进行数据的采集。这些数据是裸数据。在拿到这些数据后,咱们须要进行编码。在编码过程当中,咱们能够对数据进行处理和格式化,好比说让它有一个特定的分辨率和帧率。直播不会把摄像头的全部的数据都传过来,他会对数据进行压缩、处理,这就是编码的过程。在编码完成后,咱们就有一个视频数据,它会经过声网的云服务,快速传输到你的手机上。手机客户端上也有声网的 SDK,在拿到数据后,会把数据进行解码,并提供一个方法,将这些视频数据传到UI View 上,最终显示为你所看到的视频。

如上图所示,基于刚才的架构,咱们添加了一个声网 SDK。SDK 有一个方法叫作 AgoraVideoSink。它提供了一个回调,会把收到的全部视频数据按照你想要的格式传给你。咱们在获得回调数据后,再把他传给Flutter Texture。咱们能够设定一个更新频率,每当获得一次回调后,经过 notify 告诉 TextureRegistery 将新的数据更新到 Texture。经过这样的一个过程,咱们就能实现一个 Flutter 的实时音视频 App 了。

实现思路二:PlatformView

因为 Texture 会涉及到不少渲染的流程,因此不少人都会以为它有些复杂。因此在 Flutter 1.0版本中,Google 给出了一个新的东西,叫作 PlatformView。

它提供了一种方法,让咱们能够建立 UI View,并加到 Dart 的 LayerTree 里。在 Dart 中的类对应到 iOS 和 Android 平台分别是UIKitView 和 AndroidView。

PlatformView 该怎么用呢?在 PluginRegistar 中新增了 ViewFactory,ViewFactory 只有 CreateView 这一个方法须要实现。你能够在这个方法里首先提供一个 Identifier,在实现该方法后,能够返回一个你想要的 PlatformView,并与 Dart 组件绑定在一块儿。

由于咱们的 SDK 支持传递 Native 的 View,而后将视频渲染到上面。如上图,咱们基于刚才的架构作一下改动。声网 SDK 中提供了一个 CustomViewFoctory,它的做用就是建立一个原生的 View,而后 SDK 的 AgoraRtcEngine 会与 View 绑定,在收到数据流的时候会自动将它渲染到这个 View 上。由于每一个 View 会对应一个 ViewID。那么咱们只须要经过 ViewID 就能够获得这个 View,而后把它渲染出来。

性能对比

总结一下,在 Flutter 中实现实时音视频有两种方式,一种是 Texture,另外一种是 PlatformView。咱们将它们与 Native 的实现方式进行了性能对比,结果以下图所示。

三者相比,Texture 的实现方式性能比较差。PlatformView 的性能与 Native 相近。就目前的 Demo 来看,形成Texture 的方法性能较差的缘由可能有两个:

第一,在收到视频数据以后,会马上更新 Texture,但这样作的必要性不大,若是进行一些优化的话,还能够提高一些性能;

第二,咱们是经过一个回调来获取数据,而后将它提供给 Flutter 的 Texture,这个过程是经过 CPU 来完成的,但一般来说,渲染的工做须要由 GPU 来作,因此数据从 GPU 到 CPU 又再到 GPU,这个过程很是耗费资源。

总结一下,我我的对于 Texture 和 PlatformView 的优缺点比较。我我的以为Texture这个东西实现“更Dart”,什么叫“更Dart”?就是没有不少原生的参与,好比 UI View ,更符合 Dart 的生态环境,在设计上更加纯粹。可是它也有缺点。若是用它作一些事情,不作特殊处理的话,可能很难避免 CPU、GPU 中间数据拷贝,形成性能损失。若是你要用它去作一个地图,那你就得把地图数据渲染放在Flutter上,用Flutter的逻辑所有从新实现一遍。这个对于大多数开发者来讲,对于正在蓬勃发展中的一个生态来讲是很是不利的。因此我我的认为这也是 Google 在 Flutter 1.0加入了PlatformView 的缘由。

使用 PlatformView 有什么优势呢?我刚才说了,它能够把以前实现 Native View 的功能直接放在Dart里。对于开发者来讲,更加简单易用。也能够避免前者存在在 CPU、GPU 数据拷贝带来的性能损失问题。但问题,它在设计上不那么纯粹,会引入一下不可控的因素。


详细 PPT 与回顾视频,请点击这里获取