Flutter 在音视频研发中的不断探索

2,592 阅读6分钟

Flutter 简述

1.jpg Flutter 是一个跨平台框架,以往的做法是将音频、视频和网络这些模块都下沉到 C++ 层或者 ARM 层,在其上封装成一个音视频的 SDK ,供 UI 层的 PC 、 iOS 和 Android 调用。

而 Flutter 做为一个 UI 层的跨平台框架,顾名思义就是在 UI 层也实现了一个跨平台开发。可以预想的是未Flutter发展的好的话,会逐渐变为一个从底层到 UI 层的一个全链路的跨平台开发,技术人员分别负责 SDK 和 UI 层的开发。

2.jpg

Flutter 开发的优点有哪些

我们可以看一下,为什么 Flutter 可以实现高性能:

原生的native组件渲染以 IOS 为例,苹果的UIKit通过调用平台自己的绘制框架 QuaztCore 来实现 UI 的绘制,图形绘制也是调用底层的 API ,比如 OpenGL 、 Metal 等。

而 Flutter 也是和原生 API 逻辑一致,也是通过调用底层的绘制框架层 SKIA 实现 UI 层。这样相当于 Flutter 他自己实现了一套 UI 框架,提供了一种性能超越原生 API 的跨平台可能性。

image.png 我们说一个框架最终性能怎样,其实取决于设计者和开发者。至于现在到底是一个什么状况:

在闲鱼的实践中,我们发现在正常的开发没有特意的去优化 UI 代码的情况下,在一些低端机上,Flutter界面的流畅性是比Native界面要好的。

image.png

Flutter 音视频设计理念

为什么要使用 Flutter 进行音视频开发

随着Flutter在越来越多大厂的业务落地,大家不难发现,音视频是一块绕不开的业务。短视频、IM、新媒体等相对较重的业务中都会有音视频的身影,那么如何通过一个强大的跨平台框架去实现一个强大、高性能、可控的音视频播放功能呢?我们是否还仅仅停留在使用插件的上层API?相信能耐心看完本文会,你对Flutter上的音视频实现会比之前有更深入的理解

Flutter 开发音视频的实现思路

开始之前,大家可以先思考一下如果是你来做一个Flutter的视频播放器,你会如何去实现?你会遇到哪些困难呢?带着问题来看文章往往会更有收获。可能很大一部分同学都会和我一样首先跳出来一个词 —— PlatformView。确实,PlatformView看起来是个不错的方案,PlatformView作为一个在Flutter 1.0即发布的技术方案,算是比较成熟且上手较快的方案。但很显然,今天我们的主角不是它,为什么不是这个可爱的方案呢?请大家思考这样一个业务场景:

比如我们想调用摄像头来拍照或录视频,但在拍照和录视频的过程中我们需要将预览画面显示到我们的Flutter UI中,如果我们要用Flutter定义的消息通道机制来实现这个功能,就需要将摄像头采集的每一帧图片都要从原生传递到Flutter中,这样做代价将会非常大,因为将图像或视频数据通过消息通道实时传输必然会引起内存和CPU的巨大消耗!—— Flutter中文网

也正是因为有这个业务场景,可能我们今天的主角就要登场了——Texture(外接纹理),会不会有很大一部分好兄弟一脸懵逼?

简单的介绍一下:Texture可以理解为GPU内保存将要绘制的图像数据的一个对象,Flutter engine会将Texture的数据在内存中直接进行映射(而无需在原生和Flutter之间再进行数据传递),Flutter会给每一个Texture分配一个id,同时Flutter中提供了一个Texture组件。

const Texture({   Key key,   @required this.textureId, })

video_player

video_playerFlutter官方plugin中的音视频播放插件,我们不妨以这个插件为例,细看其中的一些端倪。我会通过几部分的个人认为比较关键的源码,给各位点出该插件的实现方案。

FLTVideoPlayer

image.png

首先我们可以看到源码中封装了一个叫FLTVideoPlayer的类

image.png

如果仅仅是PlatformView的简单展示,此处无需自己封装如此复杂的一个Player类,我对类中的方法和参数都做了注解,为了大家都能看懂,我给每一行都扣了注释。

注意,其实这个所谓的FLTVideoPlayer的核心点并不是那个看似亮眼的play方法,这里我要给大家介绍的是上面用虚线标出的初始化方法。看源码就可以发现,无论是加载本地A sset音频,或是url的音频,都调用了该方法。

FLTVideoPlayerPlugin

那我们如何将Native层的数据传输到Dart层呢?这就是我们插件要实现的部分。这部分直接贴出核心部分的代码吧。大家可以看到这里,我们选用的PlatformChannelEventChannel, 这个地方为什么不是methodChannel或者说BasicMessageChannel,其实答案已经在我们的上文当中了,我们需要将我们获取到的视频数据进行传输,更贴切的是一个流式的传输,而EventChannel就是为了数据流而生的。

image.png

再来仔细看看这个方法吧,方法中很显然,我们创建我们的EventChannel,并没有和以往简单插件一样用固定的channelName,此处我们的channel和我们的textureId相关。为什么这么设计呢?其实是为了我们的多窗口播放功能,也就是在插件的example展示的一个界面中多个播放画面的效果,其实这一类的设计还可以应用在视频通话实现中的多窗口会话,说白了就是可以在Flutter中对应多个不同的Widget

Flutter Source Code

有关Dart方面的具体实现策略也是主要通过EventChannel实现的,在EventChannel中会加入插件中支持的feature,包括暂停,轮播等。

image.png

我们首先找到了我们的EventChannel定义处。看起来一切正常,唯一最大的疑问是,textureId是怎么拿到的呢?是如何去和原生建立连接的呢?咱们继续往上找该方法的调用在一个MethodChannelVideoPlayer类的方法中调用,但还是看不出来textureId的来源。

image.png

OK,那就继续找,继续找此处videoEventsFor的调用点,但还是看不出来!仅仅看出来传入了一个私有变量,很巧合的也叫textureId.

image.png

点击跳转到create方法的实现,我们在这里初始化VideoPlayer,同时返回他的textureId

image.png

终于,我们到达尽头,尽头是一个BasicMessageChannel,我们在这里通过BasicMessageChannelFlutterNative层进行通信,在其中回调我们的textureId

image.png

总结

本文主要给各位介绍了 Flutter 中实现音视频的一种方案,外接纹理(Texture),这也是Flutter官方视频插件所采用的方案。应该也颠覆了各位以往对Flutter插件的一些理解。那么我们在选择实现方案时是选择PlatformView还是Texture呢?

image.png