谈一谈Android上的SurfaceTextureSurfaceTexture 是Android上做渲染的核心组件,它
发布时间:2026-09-28 | 浏览:1
导言:这篇文章介绍了SurfaceTexture的用法以及原理,对于常见用法不做过多描述,而重点介绍了内部实现以及EGLImage,包括实现共享纹理的两种方式。
1 什么是SurfaceTexture
2 SurfaceTexture的常见应用 - 相机与视频解码
3 SurfaceTexture的内部实现 - EGLImageKHR 3.1 SurfaceTexture是如何创建的 3.2 updateTexImage是如何将数据更新到纹理的
3.1 SurfaceTexture是如何创建的
3.2 updateTexImage是如何将数据更新到纹理的
4 EGLImageKHR 4.1 使用权限问题 4.2 EGLImageKHR的重要性质
4.2 EGLImageKHR的重要性质
5 共享纹理的两种实现 5.1 ShareContext 5.2 EGLImageKHR
5.1 ShareContext
5.2 EGLImageKHR
1 什么是SurfaceTexture
SurfaceTexture 是Android上做渲染的核心组件,它是 Surface 和 OpenGL ES纹理的组合,用于提供输出到 GLES 纹理的 Surface。从安卓渲染系统上来说, SurfaceTexture 是一个BufferQueue的消费者,当生产方将新的缓冲区排入队列时, onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage() ,这会释放先前占有的缓冲区,从队列中获取新缓冲区并执行 EGL 调用,从而使 GLES 可将此缓冲区作为外部纹理使用。
通过Camera、Video Decoder、OpenGL生成图像流。
图像流通过Surface入队到BufferQueue,并通知到GLConsumer。
GLConsumer从BufferQueue获取图像流GraphicBuffer,并转换为EXTERNAL OES纹理。
得到OES纹理后,用户方就可以将其转换成普通的纹理,然后应用特效或者上屏。
2 SurfaceTexture的常见应用 - 相机与视频解码
SurfaceTexture的最常见应用场景是作为相机或者视频解码器的输出,这种应用场景非常常见,这里就不做详细描述了,以相机为例:
3 SurfaceTexture的内部实现 - EGLImageKHR
SurfaceTexture使用时,最主要的两个方法:
SurfaceTexture (int texName) // 创建SurfaceTexture
void updateTexImage () // 将当前图片流更新到纹理
3.1 SurfaceTexture是如何创建的
首先需要自己创建一个纹理ID,传递给SurfaceTexture,比如
Framework层的SurfaceTexture创建代码如下
framework层会调用到 nativeInit 方法,最终调用到 SurfaceTexture_init 方法。
SurfaceTexture初始化后,向 GLConsumer 设置了 JNISurfaceTextureContext 监听器,该监听器会回调到Java层 SurfaceTexture.postEventFromNative 方法,进一步回调到注册到SurfaceTexture中的OnFrameAvailableListener监听器,用于通知业务层有新的 GraphicBuffer 入队了。这时候业务层就可以调用 updateTexImage 将GraphicBuffer更新到纹理。
3.2 updateTexImage是如何将数据更新到纹理的
按文档所说, OnFrameAvailableListener.onFrameAvailable 回调可以发生在任意线程,所以不能在回调中直接调用 updateTexImage ,而是必须切换到OpenGL线程调用 updateTexImage ,那么内部是怎么实现的呢?
应用层的 updateTexImage 会最终调用到native层的 GLConsumer::updateTexImage() 方法。
可以看到获取帧数据是 acquireBufferLocked 方法。
可以看到,SurfaceTexture最终会把GraphicBuffer创建一个EglImage对象,这个对象就保存了帧数据。
那么接下来的 bindTextureImageLocked 是如何将帧数据绑定到纹理上的呢?
再看下 createIfNeeded 方法,它会最终调用到 GLConsumer::EglImage::createImage
关键语句是 eglCreateImageKHR ,可以看到,这里创建了EGLImageKHR 对象。 现在已经生成了EGLImageKHR图像,接下来分析下 EGLImageKHR 图像是如何绑定的 GL_TEXTURE_EXTERNAL_OES 纹理的。
小结一下,整个 updateTexImage 方法大致分为这几步:
生成纹理内容EGLImageKHR 对象,通过 ConsumerBase::acquireBufferLocked 获取当前的显示内容 GraphicBuffer (比如相机帧数据),然后由此 GraphicBuffer 生成一个 EGLImageKHR 图像。
生成纹理内容EGLImageKHR 对象,通过 ConsumerBase::acquireBufferLocked 获取当前的显示内容 GraphicBuffer (比如相机帧数据),然后由此 GraphicBuffer 生成一个 EGLImageKHR 图像。
将 EGLImageKHR 图像通过 glEGLImageTargetTexture2DOES 绑定到 GL_TEXTURE_EXTERNAL_OES 型纹理上。
将 EGLImageKHR 图像通过 glEGLImageTargetTexture2DOES 绑定到 GL_TEXTURE_EXTERNAL_OES 型纹理上。
通过上面分析可以看到,SurfaceTexture的内部,主要是通过EGLImageKHR实现的,那么EGLImageKHR有什么作用呢? EGLImageKHR 是EGL定义的一种专门用于共享2D图像数据的扩展格式,它可以在EGL的各种client api之间共享数据(如OpenGL,OpenVG),它的本意是共享2D图像数据,但是并没有明确限定共享数据的格式以及共享的目的。 EGLImageKHR的创建函数原型是:
在Android系统中专门定义了一个称为 EGL_NATIVE_BUFFER_ANDROID 的Target,支持通 ANativeWindowBuffer 创建EGLImage对象,而Buffer则对应创建EGLImage对象时使用数据。
而在Android上定义的 EGLClientBuffer 是在native中定义的 GraphicBuffer 类,综上, EGLImageKHR 的基本使用流程如下:
如果EGLClientBuffer的数据是YUV格式的,还可以使用纹理Target为:GL_TEXTURE_EXTERNAL_OES, 该Target也是主要用于从EGLImage中产生纹理的情景。
Android NDK没有暴露 GraphicBuffer 相关接口,因此如果直接使用需要自行下载Android源码,编译并打包成动态库,需要注意的是,在API26之后,android已经禁止了私有native api的调用。 不过在API 26以后,Android NDK提供了 Hardware Buffer APIs 类,来实现这样的功能,这个后续文章会详细说明,欢迎订阅😁。 虽然大部分情况下我们直接使用SurfaceTexture即可。
4.2 EGLImageKHR的重要性质
EGLImageKHR其设计目的就是为了共享2D纹理数据的,因此驱动程序在底层实现的时候,往往实现了CPU与GPU对同一资源的访问,这样就可以做到无需拷贝的数据共享,降低功耗与提高性能。
在Android平台上,了解这点是非常重要的。而iOS平台由于使用了EAGL而不是EGL,因此并不会使用EGLImage,但也有自己的数据映射的方式。
从上面可以知道,实现共享纹理,就可以有两种实现方式:
一种是EGL的ShareContext机制
一种是共享内存,这里就是EGLImageKHR
5.1 ShareContext
EGL的ShareContext是常见的共享上下文的方式(iOS平台的EAGL叫ShareGroup)。
当 share_context 参数传入另一个EGL的context时,这两个EGLContext就可以共享纹理以及VBO等。
需要注意的是 container objects 不能被共享,比如:
Framebuffer objects
Vertex array objects
Transform feedback objects
Program pipeline objects
5.2 EGLImageKHR
这实际上是一种共享内存的方式,以实现共享纹理,最简单就是直接使用SurfaceTexture,这里不再详细说明。当然也可以使用HardwareBuffer与EGLImageKHR来实现。
这篇文章主要介绍了SurfaceTexture的原理,下篇文章将主要介绍 SurfaceTexture与共享内存的应用 ,敬请期待。