当前位置: 首页 > news >正文

谈一谈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跨平台的能力提高生产力,并将性能损失降到最小。

相关文章:

  • Android 11 强制用户使用系统相机?
  • 3A之自动白平衡(AWB)篇
  • Shader基础技巧整理
  • 一起用Gradle Transform API + ASM完成代码织入呀~
  • 用shader做一个柿子颜色的过场动画
  • 只需跟着Google学android:ViewModel篇
  • 我用 OpenGL 实现了那些年流行的相机滤镜
  • 有必要去研究Handler和Binder么?
  • 音视频交流群又来啦~~~
  • Android 手机如何拍摄RAW图
  • 华为手机刷微博体验更好?技术角度的分析和思考
  • 播放视频时如何调整音频的音量
  • 视频码控:CBR、VBR和ABR
  • OpenGL ES 相机 LUT 滤镜
  • Android 11 正式发布 | 开发者们的舞台已就绪
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CSS相对定位
  • IndexedDB
  • JavaScript 奇技淫巧
  • Laravel 中的一个后期静态绑定
  • PHP变量
  • 番外篇1:在Windows环境下安装JDK
  • 关于Java中分层中遇到的一些问题
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 爬虫模拟登陆 SegmentFault
  • 前端知识点整理(待续)
  • 如何学习JavaEE,项目又该如何做?
  • 无服务器化是企业 IT 架构的未来吗?
  • 小程序 setData 学问多
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 06-01 点餐小程序前台界面搭建
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • "无招胜有招"nbsp;史上最全的互…
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (bean配置类的注解开发)学习Spring的第十三天
  • (ZT)薛涌:谈贫说富
  • (二)c52学习之旅-简单了解单片机
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (一)Thymeleaf用法——Thymeleaf简介
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • ***测试-HTTP方法
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • .gitignore文件_Git:.gitignore
  • .NET Framework与.NET Framework SDK有什么不同?
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET 跨平台图形库 SkiaSharp 基础应用
  • /dev下添加设备节点的方法步骤(通过device_create)
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现
  • [BUUCTF 2018]Online Tool
  • [CF543A]/[CF544C]Writing Code