Android图形系统之SurfaceTextureSurfaceTexture是离屏渲染和TextureView的核心
发布时间:2026-09-28 | 浏览:1
SurfaceTexture是离屏渲染和TextureView的核心,内部包含了一个BufferQueue,可以把Surface生成的图像流,转换为纹理,供业务方进一步加工使用。整个架构如下图所示:
首先,通过Canvas、OpenGL、Camera或者Video Decoder生成图像流。
接着,图像流通过Surface入队到BufferQueue,并通知到GLConsumer。
然后,GLConsumer从BufferQueue获取图像流GraphicBuffer,并转换为纹理。
最后,业务方可以对纹理进一步处理,例如:上特效或者上屏。
下面我们分别看下SurfaceTexture初始化以及图像数据在SurfaceTexture内部的流转。
SurfaceTexture初始化
new SurfaceTexture(textureId) 启动SurfaceTexture初始化,核心逻辑如下所示:
SurfaceTexture初始化后,向 GLConsumer 设置了 JNISurfaceTextureContext 监听器,该监听器会回调到Java层 SurfaceTexture.postEventFromNative 方法,进一步回调到注册到SurfaceTexture中的OnFrameAvailableListener监听器,用于通知业务层有新的 GraphicBuffer 入队了。如果业务层对最新的 GraphicBuffer 感兴趣,则调用 updateTexImage 把 GraphicBuffer 更新到纹理,否则啥也不做,忽略一些图形数据。
GLConsumer 是BufferQueue的直接消费者,负责把 GraphicBuffer 转化为纹理。然后通过监听类 wp<FrameAvailableListener> mFrameAvailableListener 通知间接消费者消费纹理。 当间接消费者是 SurfaceFlinger 时,监听类为Layer,Layer进一步通知 SurfaceFlinger 去合成所有Layer,然后上屏。 当间接消费者是 SurfaceTexture 时,监听类为JNISurfaceTextureContext,用于通知Java层新的图像数据可用了。
SurfaceTexture图像数据流转
这块主要看下生产者 Surface 的创建,业务层收到帧可用通知以及更新目标纹理的流程。
基于SurfaceTexture创建生产者Surface
基于纹理ID创建 SurfaceTexture 后,一般会创建Surface,此时Surface是生产者,SurfaceTexture(Native层对应GLConsumer)是消费者。消费者负责把从BufferQueue中获取的GraphicBuffer转换为纹理,然后业务层可以对纹理进一步处理,例如:上特效或者上屏。
作为生产者的Surface通过BufferQueueProducer,向BufferQueue写GraphicBuffer;作为消费者的GLConsumer通过BufferQueueConsumer,从BufferQueue读GraphicBuffer。
根据 SurfaceTexture 创建 Surface 的核心逻辑在Native层:根据SurfaceTexture持有的BufferqueueProducer创建一个Surface对象,并把该对象的地址绑定到Java层 Surface.mNativeObject 变量。核心代码如下所示:
可见,创建Native层 Surface 对象, BufferqueueProducer 参数是必须的,它负责从 BufferQueue 中出队和入队 GraphicBuffer 。
创建好 Surface 后,就可以通过多种方式向Surface绘制图像数据了,例如:Canvas绘制、Camera输出、视频解码器渲染和OpenGL渲染。
下面,我们分两步看下图形数据是怎么更新到目标纹理的?
这里,我们以Canvas绘制为例,看下业务层收到帧可用回调流程,如下所示:
Java层OnFrameAvailableListener监听器收到回调后,需要从OpenGL线程调用 updateTexImage 把 GraphicBuffer 更新到纹理。这里的纹理就是我们创建SurfaceTexture时传入的纹理ID,整个更新流程如下所示:
OnFrameAvailableListener.onFrameAvailable回调可以发生在任意线程,所以不能在回调中直接调用updateTexImage,而是必须切换到OpenGL线程调用。因为updateTexImage调用链涉及到OpenGL操作,所以必须在GL线程。
核心代码是 GLConsumer::updateTexImage :
首先,通过 BufferQueueConsumer 从BufferQueue中获取可用的 BufferItem ,其中包含了 GraphicBuffer 。
然后,基于 GraphicBuffer 创建EglImage及EGLImageKHR。
最后,基于 EGLImageKHR 更新纹理内容。
简单来说,通过 updateTexImage ,我们把最新的图形数据更新到了纹理,至于如何使用纹理,就是业务层的事情了。
基于GraphicBuffer更新OES纹理
上面讲到, updateTexImage 方法最终会把 GraphicBuffer 更新到目标纹理,实际是通过 EglImage 及 EGLImageKHR 实现的,这里我们看下在Android平台上使用 EGLImageKHR 的方式:
纹理目标需要从 GL_TEXTURE_2D 替换为 GL_TEXTURE_EXTERNAL_OES ,例如:glBindTexture、glTexParameteri等函数。
纹理目标需要从 GL_TEXTURE_2D 替换为 GL_TEXTURE_EXTERNAL_OES ,例如:glBindTexture、glTexParameteri等函数。
片元着色器中,声明OES扩展: #extension GL_OES_EGL_image_external : require 。同时,使用 samplerExternalOES 替代 sampler2D 纹理类型。
片元着色器中,声明OES扩展: #extension GL_OES_EGL_image_external : require 。同时,使用 samplerExternalOES 替代 sampler2D 纹理类型。
基于 GraphicBuffer 图形数据,通过 eglCreateImageKHR 创建 EGLImageKHR 。
基于 GraphicBuffer 图形数据,通过 eglCreateImageKHR 创建 EGLImageKHR 。
GraphicBuffer 可以从BufferQueue中获取,也可以lock后获取内存地址,写入图形数据,具体可参考 GraphicBuffer.cpp 。
通过 glEGLImageTargetTexture2DOES 把 EGLImageKHR 填充为纹理数据
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, static_cast(EGLImageKHR));
最后,使用 eglDestroyImageKHR 销毁 EGLImageKHR 。
创建和销毁 EGLImageKHR 的函数原型如下所示:
具体使用方式可参考 GLConsumer::EglImage::createImage 。
GLConsumer 内部封装了 EglImage 类,负责 GraphicBuffer 、 EGLImageKHR 和OES纹理的处理逻辑,核心代码如下所示:
SurfaceTexture是离屏渲染的核心,例如:我们可以把SurfaceTexture设置给Camera接收摄像头图像数据,并转换为OES纹理,然后可以利用OpenGL对OES纹理做进一步特效处理,最后上屏或者录制成视频。所以,理解 SurfaceTexture 底层原理有助于业务层开发和问题排查,希望本文对有心人有所帮助。
Using GL_OES_EGL_image_external on Android
EGL_KHR_image_base.txt