上篇文章分享了开眼快创在Flutter跨端实现上的一些实践,本篇文章继续介绍一下Flutter在音视频领域的实践和探索,以及在工程提效方面的一些工作。
音视频实践
开眼快创的业务场景有很多的音视频功能,包括视频播放、直播、模板视频、视频编辑等,后续可能还会增加拍摄功能。其中最复杂的是视频编辑模块,主要有以下几个特点:
-
功能复杂,包含导入、导出,剪辑:分割、删除、变速、复制、排序,音乐:原声、音乐、音效、录音,字幕,滤镜、特效,多轨道,撤销,草稿等等功能;
-
状态复杂,太多视频编辑的状态,和当前页面用户操作的中间状态等等;
-
技术复杂,涉及多个技术领域,需要跨多部门合作开发;
-
逻辑复杂,这里主要涉及到一个变速,变速的需求将时间对齐这件简单事情的复杂度提升了一个次元;
-
UI复杂,这里主要复杂在时间轴部分,视频轴,音乐轴,音效轴等等;
-
性能要求高,频繁的数据通信和视频预览对性能影响较大,后续的优化其实主要也是针对这一点。
对于这块功能使用原生开发还是使用纯Flutter开发,团队内部有过一些讨论,最终选择了用纯Flutter进行开发。因为使用Flutter可以在功能复杂的提前下实现一套业务代码运行在双端,可以提升研发效率并保持双端复杂逻辑的统一。同时,跨端渲染也可以实现双端UI的一致性,在后期功能维护阶段,减轻业务维护成本。
在人力少且任务重的情况下,我们通过四个版本两个月的迭代,完成了视频编辑主流功能的实现并双端上线。对比原生开发,Flutter的业务开发效率基本是原生开发的两倍。
在使用Flutter开发视频编辑模块过程中,我们也碰到了几个问题:
-
如何实现视频播放
-
复杂UI和复杂状态如何管理
-
如何跨多端实现高效的数据通信
-
如何保证性能
接下来介绍下在针对这几个问题做的一些工作。
外接纹理
由于Flutter是跨端渲染,那么在音视频场景下该如何实现Flutter侧的视频播放?在视频播放、编辑预览等场景可以使用外接纹理的方案来实现,其基本流程有3个步骤(如下图):
-
Flutter通知Native创建Texture,并生成TextureId,返回给Flutter。
-
Flutter声明Texture Widget,通过TextureId将Native Texture绑定到到Texture Widget。
-
Texture Widget对应TextureLayer,Native通过Texture将Native侧的纹理数据映射给Flutter侧,TextureLayer将具体绘制内容提交给Flutter Engine,最后交给Skia合成上屏。
Native最左边表示原始图像流的生成方式:Video(本地/网络视频流)、Camera(摄像头拍摄的视频流)、Software/Hardware Render(使用Skia/GL绘制的图像流)。Native Texture在Android平台上是Surface Texture,在iOS平台上对应FlutterTexture。Android中的Surface Texture是比较核心的渲染组件,用于提供输出到OpenGL ES纹理的Surface。
Surface Texture是典型的生产者-消费者模型,其中维护了一个Buffer Queue队列,Surface是它的生产者,GLConsumer是它的消费者。GLConsumer拿到了Surface的原始图像流,最终转化GL Texture纹理,Texture纹理可以提供给Surface View、Texture View等使用,也可以贡献给类型为GL_TEXTURE_EXTERNAL_OES的纹理使用。
Surface Texture内部主要是通过E