Flutter 实时音视频实践

阅读数:23 2019 年 11 月 30 日 13:23

Flutter 实时音视频实践

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

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

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

在医疗方面,我们最近也看到很多的案例。以前你要到医院挂号排队,等一上午,医生看一下,然后明天再来复诊时重新挂号,又是一个上午的时间过去了。现在有了 RTC 后,你可以通过视频直接在家里看医生,医生在视频中问你一些问题,如果病症简单,你甚至都不需要去医院。

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

架构逻辑与实现思路

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

Flutter 实时音视频实践

它自己里面会建立一个 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 怎么渲染视频?

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

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

实现思路一:Texture Widget

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

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

Flutter 实时音视频实践

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

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

Flutter 实时音视频实践

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

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

Flutter 实时音视频实践

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

Flutter 实时音视频实践

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

实现思路二:PlatformView

由于 Texture 会涉及到很多渲染的流程,所以很多人都会觉得它有些复杂。所以在 Flutter 1.0 版本中,Google 给出了一个新的东西,叫做 PlatformView。

Flutter 实时音视频实践

它提供了一种方法,让我们可以创建 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 的实现方式进行了性能对比,结果如下图所示。

Flutter 实时音视频实践

三者相比,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 的原因。

Flutter 实时音视频实践

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

本文转载自公众号声网 Agora(ID:shengwang-agora)。

原文链接:

https://mp.weixin.qq.com/s/8_epFcHKbaWT0P6IlyeXPg

评论

发布