写点什么

Flutter 实时音视频实践

  • 2019-11-30
  • 本文字数:3767 字

    阅读完需:约 12 分钟

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。那么开发应用的时候,怎么渲染出来的呢?



它自己里面会建立一个 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 就可以把你提供的这些数据显示出来,最终就变成了一个视频。



上图是大致的实现架构。首先要在 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 数据拷贝带来的性能损失问题。但问题,它在设计上不那么纯粹,会引入一下不可控的因素。


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


原文链接:


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


2019-11-30 13:232033

评论

发布
暂无评论
发现更多内容

一文读懂:开源大数据调度系统Taier1.2版本新增的「工作流」到底是什么?

袋鼠云数栈

MyBatis之缓存机制和动态SQL

Andy

MyBatis之关系映射(上)

Andy

深入浅出Python——Python高级语法之异常、模块、包

何极光

Python 异常 模块 10月月更

SQL的基本介绍

芯动大师

Python 数据库SQL 10月月更

Java | 面向对象(二)

陌上

编程 Java、 10月月更

Java | 继承

陌上

Java 编程 10月月更

投资成本低且无需专人维护!华为云会议让沟通更高效

秃头也爱科技

MyBatis初步理解

Andy

Spring整合MyBatis

Andy

SpringMVC之拦截器

Andy

深入浅出Python——Python高级语法之面向对象

何极光

Python 面向对象 10月月更

深入浅出Python——Python高级语法之文件操作

何极光

Python 文件操作 10月月更

MyBatis之关系映射(下)

Andy

安全架构师的运营一二事

I

运营 安全架构 企业安全 安全运营

EMQ助力构建工业生产数字孪生基础架构|智慧工厂系列专题03

EMQ映云科技

物联网 IoT 工业互联网 数字孪生 10月月更

SpringMVC 基础

Andy

深耕稳定安全会议服务,华为云会议具备会议全球接入能力

秃头也爱科技

MyBatis之识别器

Andy

SpringMVC之文件上传

Andy

MyBatis面试题(部分)

Andy

MyBatis程序基础实现

Andy

MyBatis之Annotation

Andy

开发者新手指南:进入 Web3 前,你需要掌握哪些必备技能?

TinTinLand

区块链 程序员 开发者 编程语言 招聘

赴一场开源盛会丨10月29日 COSCon'22 开源年会杭州分会场,这里只差一个「你」!

袋鼠云数栈

袋鼠云平台代码规范化编译部署的提效性改进实践

袋鼠云数栈

强烈推荐!商业洞察就用这个工具

产品海豚湾

产品经理 产品设计 产品运营 商业化 10月月更

企业业务沟通新选择,华为云会议支持全场景智能协作

秃头也爱科技

MySQL向表中添加列

okokabcd

MySQL

Java | 数组工具类Arrays、数学工具类Math

陌上

编程 Java、 10月月更

华为云桌面——云端上“最卷的云”

秃头也爱科技

Flutter 实时音视频实践_文化 & 方法_声网_InfoQ精选文章