谈一谈Flutter外接纹理
导言:这篇文章主要介绍在Android上SurfaceTexture的应用 - Flutter外接纹理,并给出了外接纹理的正确姿势,而阿里闲鱼的技术方案则是错误的姿势。
- 1 背景知识
- 2 实现原理
- 2.1 性能
- 2.2 应用
- 3 闲鱼技术方案
- 4 具体实现
- 4.1 流程图
- 4.2 关键代码
- 4.3 效果示意图
- 5 结语
1 背景知识
当我们用flutter做实时视频渲染时,往往是要对视频或者相机画面做滤镜处理的,如图:
如果我们要用flutter定义的消息通道机制来实现这个功能,就需要将摄像头采集的每一帧图片都要从原生传递到flutter中,这样做代价将会非常大,因为将图像或视频数据通过消息通道实时传输必然会引起内存和CPU的巨大消耗。为此,flutter提供了两种机制实现这一功能:
- PlatformView
- Texture Widget
PlatformView
实质上是将原有的NativeView嵌入到Flutter中显示,虽然使用和移植很简单,但并不是性能最优的做法。
而Texture Widget
是flutter提供的另一种机制,可以将native纹理共享给flutter进行渲染。但由于native纹理与flutter是两个OpenGL Context,如果直接使用的话,需要经过GPU -> CPU -> GPU的转换开销,这对于实时视频渲染是很难令人接受的。
所以解决方案就是共享纹理。
2 实现原理
在上篇文章谈一谈Android上的SurfaceTexture中,我们可以知道共享纹理有两种实现:
- ShareContext
- 共享内存
这两种方式均能实现共享纹理。
这里假设要共享纹理的是两个OpenGL环境A与B,A是自己的OpenGL环境,B是第三方的OpenGL环境。
当然A与B都是自己的OpenGL框架也可以。不过在实际开发中,B往往是第三方的OpenGL框架,不然干嘛要用共享纹理呢,直接在一个环境中开发就行了?
ShareContext比较适合A与B有大量交互的场景,方便互相使用,但由于共享了EGLContext
,对B的侵入较多,很容易对B造成影响。
共享内存并不会侵入B原有的渲染环境,但有大量需要共享的场景时,就不够灵活了,对于OpenGL,共享内存通过EGLImageKHR
来使用,在Android上,最简单的使用方式就是通过SurfaceTexture
,具体原理不再说明,请看上篇文章。
2.1 性能
由于显卡驱动在实现ShareContext
时,往往最终是通过地址映射与共享内存实现的,所以这两种方式其实性能上差别并不明显,基本一致。
2.2 应用
共享纹理在需要接入第三方渲染框架时是非常有用的。
比如在做滤镜开发中,有时要接入第三方的游戏引擎来渲染3D效果。
当接入Unity游戏引擎时,由于Unity是闭源的,并且只有SurfaceTexture的接口,所以只能使用共享内存实现。
而像开源的cocos2d,filament,gameplay等游戏引擎,这两种方式都可以使用,看应用场景即可。
3 闲鱼技术方案
在我调研flutter外接纹理的实现时,注意到阿里闲鱼团队的一篇文章:
文章地址:https://juejin.im/post/6844903662548942855
闲鱼的技术方案实质上是使用了第一种方式,ShareContext实现共享纹理。
EGL的ShareContext在苹果的EAGL框架中叫ShareGroup,实质是一个作用
由于flutter的engine并没有提供这种接口,所以他们需要修改engine的源代码,将两个OpenGL环境打通。确实这种方案能解决问题,但其实这种场景下,并没有大量交互的场景,只需要共享一个纹理就可以。
而这种方案将flutter的渲染环境直接暴露给外部,且不说以后升级flutter版本时痛苦的Merge过程,就是写代码时也大大增加了出bug的几率,一不小心就会误操作flutter渲染环境,给团队埋下了巨大的技术坑。
另外从性能上也并不会比共享内存更好,所以这是一种错误的姿势,在我看来,除了KPI这点,只会白白的增加工作量。
4 具体实现
由上面可以知道,flutter外接纹理的正确实现方式应该是使用共享内存,由于这里只涉及到OpenGL,因此在安卓这里就是使用SurfaceTexture
的方式。
事实上,这也是官方所推荐的方式,官方也提供了SurfaceTexture
相关的接口。
我这里写了一个Demo放在了github上,
地址是:https://github.com/KaelMa/external_plugin
4.1 流程图
4.2 关键代码
dart调用端
这里textureId的内容是native端传过来的
@override
Widget build(BuildContext context) {
return Container(
width: widget.width.toDouble(),
height: widget.height.toDouble(),
child: _externalPlugin.isInitialized
? Texture(textureId: _externalPlugin.textureId)
: Container(color: Colors.blue)
);
}
android端
创建SurfaceTexture
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
val arguments = call.arguments<Map<String,Int>>()
when (call.method) {
"create" -> {
val width = arguments["width"]
val height = arguments["height"]
// 调用flutter的接口,创建surfacetexture
surfaceEntry = textureRegistry.createSurfaceTexture()
val surfaceTexture = surfaceEntry.surfaceTexture().apply {
setDefaultBufferSize(width!!, height!!)
}
externalGLThread = ExternalGLThread(surfaceTexture, SimpleRenderer())
externalGLThread.start()
result.success(surfaceEntry.id())
}
"dispose" -> {
externalGLThread.dispose()
surfaceEntry.release()
}
else -> result.notImplemented()
}
}
使用SurfaceTexture
创建native端的EglContext
val eglConfig = chooseEglConfig()
val eglContext = createContext(egl, eglDisplay, eglConfig)
// 这里使用flutter框架创建的surfaceTexture创建windowSurface
val eglSurface = egl!!.eglCreateWindowSurface(eglDisplay, eglConfig, surfaceTexture, null)
4.3 效果示意图
这个Demo中间的矩形是用native端的OpenGL绘制的,并且每秒变化2次颜色。
5 结语
这篇文章主要介绍了flutter外接纹理的正确姿势,这也是SurfaceTexture
诸多应用中的一种。
按照这种外接纹理的方式,我们就可以使用flutter进行实时音视频开发,利用flutter跨平台的能力提高生产力,并将性能损失降到最小。
- 基于Docker的PHP开发环境
- 以太坊·物流场景初探
- Python接口自动化-3-POST请求
- 【Python环境】Python中的结构化数据分析利器-Pandas简介
- JAVA中使用Jedis操作Redis
- Tomcat搭建文件服务器
- Windows下SLmail邮件服务器缓冲区溢出理解及实验
- java使用mina和websocket通信
- 【机器学习】10 种机器学习算法的要点
- 写一个BASIC认证的https协议
- java发送邮件功能,以发送qq邮件为例
- spring boot加载复杂的yml文件获取不到值的问题
- JUC包下的CountDownLatch,CyclicBarrier,Semaphore
- java队列,ArrayBlockingQueue
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux 中几个正则表达式的用法
- 产品级垃圾文本分类器
- 突发!Windows XP 源码泄露!
- GitHub 神器:写代码、搜问题,全部都在「终端」完成!
- Redis系列:单机主从模式搭建
- Python 爬取链家成都二手房源信息 asyncio + aiohttp 异步爬虫实战
- 10 个冷门但又非常实用的 Docker 使用技巧!
- 利用 Shell 脚本实现邮件监控 Linux 系统的内存
- 拥有此神技,脚本调试从此与 echo、set、test 说分手!
- 很少用到,但掏出来让人感到牛逼的Web API
- 突击并发编程JUC系列-并发工具 CountDownLatch
- 推出 TF Lite Task Library 接口,简化 ML移动端开发流程
- 排序算法之我观
- 线性筛素数(探索中的不断优化)
- 麦森数