Android 圖形系統(tǒng)之 SurfaceTexture
SurfaceTexture是離屏渲染和TextureView的核心,內(nèi)部包含了一個(gè)BufferQueue,可以把Surface生成的圖像流,轉(zhuǎn)換為紋理,供業(yè)務(wù)方進(jìn)一步加工使用。整個(gè)架構(gòu)如下圖所示:
首先,通過(guò)Canvas、OpenGL、Camera或者Video Decoder生成圖像流。
接著,圖像流通過(guò)Surface入隊(duì)到BufferQueue,并通知到GLConsumer。
然后,GLConsumer從BufferQueue獲取圖像流GraphicBuffer,并轉(zhuǎn)換為紋理。
最后,業(yè)務(wù)方可以對(duì)紋理進(jìn)一步處理,例如:上特效或者上屏。
下面我們分別看下SurfaceTexture初始化以及圖像數(shù)據(jù)在SurfaceTexture內(nèi)部的流轉(zhuǎn)。
SurfaceTexture初始化
new SurfaceTexture(textureId)啟動(dòng)SurfaceTexture初始化,核心邏輯如下所示:
SurfaceTexture_init是SurfaceTexture初始化的核心代碼,如下所示:
static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, jint texName, jboolean singleBufferMode, jobject weakThiz)
{
// 創(chuàng)建BufferQueueCore、BufferQueueProducer、BufferQueueConsumer
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
if (singleBufferMode) { // 單緩沖
consumer->setMaxBufferCount(1); // 雙緩沖、三緩沖就是指這里
}
// Java層的SurfaceTexture,實(shí)際對(duì)應(yīng)Native層GLConsumer
sp<GLConsumer> surfaceTexture;
if (isDetached) {
surfaceTexture = new GLConsumer(consumer,GL_TEXTURE_EXTERNAL_OES,true,!singleBufferMode);
} else {
surfaceTexture = new GLConsumer(consumer,texName,GL_TEXTURE_EXTERNAL_OES,true,!singleBufferMode);
}
// If the current context is protected, inform the producer.
if (isProtectedContext()) {
consumer->setConsumerUsageBits(GRALLOC_USAGE_PROTECTED);
}
// 為Java層SurfaceTexture.mSurfaceTexture設(shè)置GLConsumer對(duì)象地址
SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
// 為Java層SurfaceTexture.mProducer設(shè)置producer對(duì)象地址
SurfaceTexture_setProducer(env, thiz, producer);
// SurfaceTexture jclass
jclass clazz = env->GetObjectClass(thiz);
// weakThiz表示Java層SurfaceTexture對(duì)象的弱引用,JNISurfaceTextureContext是JNI封裝類,負(fù)責(zé)回調(diào)Java層SurfaceTexture.postEventFromNative方法
sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz, clazz));
// 為GLConsumer設(shè)置回調(diào)(回調(diào)到j(luò)ava層)
surfaceTexture->setFrameAvailableListener(ctx);
// 為Java層SurfaceTexture.mFrameAvailableListener設(shè)置ctx的對(duì)象地址
SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
}
SurfaceTexture初始化后,向GLConsumer設(shè)置了JNISurfaceTextureContext監(jiān)聽(tīng)器,該監(jiān)聽(tīng)器會(huì)回調(diào)到Java層SurfaceTexture.postEventFromNative方法,進(jìn)一步回調(diào)到注冊(cè)到SurfaceTexture中的OnFrameAvailableListener監(jiān)聽(tīng)器,用于通知業(yè)務(wù)層有新的GraphicBuffer入隊(duì)了。如果業(yè)務(wù)層對(duì)最新的GraphicBuffer感興趣,則調(diào)用updateTexImage把GraphicBuffer更新到紋理,否則啥也不做,忽略一些圖形數(shù)據(jù)。
GLConsumer是BufferQueue的直接消費(fèi)者,負(fù)責(zé)把GraphicBuffer轉(zhuǎn)化為紋理。然后通過(guò)監(jiān)聽(tīng)類wp<FrameAvailableListener> mFrameAvailableListener通知間接消費(fèi)者消費(fèi)紋理。當(dāng)間接消費(fèi)者是SurfaceFlinger時(shí),監(jiān)聽(tīng)類為L(zhǎng)ayer,Layer進(jìn)一步通知SurfaceFlinger去合成所有Layer,然后上屏。當(dāng)間接消費(fèi)者是SurfaceTexture時(shí),監(jiān)聽(tīng)類為JNISurfaceTextureContext,用于通知Java層新的圖像數(shù)據(jù)可用了。
SurfaceTexture圖像數(shù)據(jù)流轉(zhuǎn)
這塊主要看下生產(chǎn)者Surface的創(chuàng)建,業(yè)務(wù)層收到幀可用通知以及更新目標(biāo)紋理的流程。
基于SurfaceTexture創(chuàng)建生產(chǎn)者Surface
基于紋理ID創(chuàng)建SurfaceTexture后,一般會(huì)創(chuàng)建Surface,此時(shí)Surface是生產(chǎn)者,SurfaceTexture(Native層對(duì)應(yīng)GLConsumer)是消費(fèi)者。消費(fèi)者負(fù)責(zé)把從BufferQueue中獲取的GraphicBuffer轉(zhuǎn)換為紋理,然后業(yè)務(wù)層可以對(duì)紋理進(jìn)一步處理,例如:上特效或者上屏。
作為生產(chǎn)者的Surface通過(guò)BufferQueueProducer,向BufferQueue寫(xiě)GraphicBuffer;作為消費(fèi)者的GLConsumer通過(guò)BufferQueueConsumer,從BufferQueue讀GraphicBuffer。
根據(jù)SurfaceTexture創(chuàng)建Surface的核心邏輯在Native層:根據(jù)SurfaceTexture持有的BufferqueueProducer創(chuàng)建一個(gè)Surface對(duì)象,并把該對(duì)象的地址綁定到Java層Surface.mNativeObject變量。核心代碼如下所示:
// 基于SurfaceTexture創(chuàng)建Surface
public Surface(SurfaceTexture surfaceTexture) {
synchronized (mLock) {
mName = surfaceTexture.toString();
// 保存Native層Surface對(duì)象地址
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
}
}
// 根據(jù)SurfaceTexture持有的BufferqueueProducer創(chuàng)建Surface,并返回對(duì)象地址
static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz, jobject surfaceTextureObj) {
// 從Java層surfaceTexture.mProducer中獲取BufferqueueProducer的對(duì)象地址,并創(chuàng)建BufferqueueProducer。
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
// 基于BufferqueueProducer,創(chuàng)建Native Surface對(duì)象
sp<Surface> surface(new Surface(producer, true));
// 返回Surface對(duì)象地址
surface->incStrong(&sRefBaseOwner);
return jlong(surface.get());
}
可見(jiàn),創(chuàng)建Native層Surface對(duì)象,BufferqueueProducer參數(shù)是必須的,它負(fù)責(zé)從BufferQueue中出隊(duì)和入隊(duì)GraphicBuffer。
創(chuàng)建好Surface后,就可以通過(guò)多種方式向Surface繪制圖像數(shù)據(jù)了,例如:Canvas繪制、Camera輸出、視頻解碼器渲染和OpenGL渲染。
下面,我們分兩步看下圖形數(shù)據(jù)是怎么更新到目標(biāo)紋理的?
業(yè)務(wù)層收到幀可用通知
這里,我們以Canvas繪制為例,看下業(yè)務(wù)層收到幀可用回調(diào)流程,如下所示:
Java層Surface調(diào)用unlockCanvasAndPost方法后,Native層Surface通過(guò)BufferqueueProducer把GraphicBuffer入隊(duì)到BufferQueue,同時(shí)通過(guò)BufferQueueCore的sp<IConsumerListener> mConsumerListener(實(shí)現(xiàn)類為BufferQueue::ProxyConsumerListener)監(jiān)聽(tīng)器通知消費(fèi)者,然后一步步通知到Java層為SurfaceTexture設(shè)置的OnFrameAvailableListener監(jiān)聽(tīng)器。
業(yè)務(wù)層主動(dòng)更新目標(biāo)紋理
Java層OnFrameAvailableListener監(jiān)聽(tīng)器收到回調(diào)后,需要從OpenGL線程調(diào)用updateTexImage把GraphicBuffer更新到紋理。這里的紋理就是我們創(chuàng)建SurfaceTexture時(shí)傳入的紋理ID,整個(gè)更新流程如下所示:
OnFrameAvailableListener.onFrameAvailable回調(diào)可以發(fā)生在任意線程,所以不能在回調(diào)中直接調(diào)用updateTexImage,而是必須切換到OpenGL線程調(diào)用。因?yàn)閡pdateTexImage調(diào)用鏈涉及到OpenGL操作,所以必須在GL線程。
核心代碼是GLConsumer::updateTexImage:
首先,通過(guò)
BufferQueueConsumer從BufferQueue中獲取可用的BufferItem,其中包含了GraphicBuffer。然后,基于
GraphicBuffer創(chuàng)建EglImage及EGLImageKHR。最后,基于
EGLImageKHR更新紋理內(nèi)容。
簡(jiǎn)單來(lái)說(shuō),通過(guò)updateTexImage,我們把最新的圖形數(shù)據(jù)更新到了紋理,至于如何使用紋理,就是業(yè)務(wù)層的事情了。
基于GraphicBuffer更新OES紋理
上面講到,updateTexImage方法最終會(huì)把GraphicBuffer更新到目標(biāo)紋理,實(shí)際是通過(guò)EglImage及EGLImageKHR實(shí)現(xiàn)的,這里我們看下在Android平臺(tái)上使用EGLImageKHR的方式:
紋理目標(biāo)需要從
GL_TEXTURE_2D替換為GL_TEXTURE_EXTERNAL_OES,例如:glBindTexture、glTexParameteri等函數(shù)。片元著色器中,聲明OES擴(kuò)展:
#extension GL_OES_EGL_image_external : require。同時(shí),使用samplerExternalOES替代sampler2D紋理類型。基于
GraphicBuffer圖形數(shù)據(jù),通過(guò)eglCreateImageKHR創(chuàng)建EGLImageKHR。
GraphicBuffer可以從BufferQueue中獲取,也可以lock后獲取內(nèi)存地址,寫(xiě)入圖形數(shù)據(jù),具體可參考GraphicBuffer.cpp。
通過(guò)
glEGLImageTargetTexture2DOES把EGLImageKHR填充為紋理數(shù)據(jù)
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, static_cast(EGLImageKHR));
最后,使用
eglDestroyImageKHR銷毀EGLImageKHR。
創(chuàng)建和銷毀EGLImageKHR的函數(shù)原型如下所示:
// 創(chuàng)建EGLImageKHR,在Android平臺(tái)上,ctx可以是EGL_NO_CONTEXT,target是EGL_NATIVE_BUFFER_ANDROID,buffer是由GraphicBuffer創(chuàng)建來(lái)的。
EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
// 銷毀EGLImageKHR
EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image)
具體使用方式可參考GLConsumer::EglImage::createImage。
GLConsumer內(nèi)部封裝了EglImage類,負(fù)責(zé)GraphicBuffer、EGLImageKHR和OES紋理的處理邏輯,核心代碼如下所示:
// EglImage根據(jù)GraphicBuffer創(chuàng)建EGLImageKHR,然后使用EGLImageKHR更新紋理,是GLConsumer中負(fù)責(zé)把從BufferQueue獲取的GraphicBuffer,轉(zhuǎn)換為紋理的工具類。
class EglImage : public LightRefBase<EglImage>{
public:
// 唯一的構(gòu)造函數(shù),接收一個(gè)GraphicBuffer參數(shù)
EglImage(sp<GraphicBuffer> graphicBuffer);
// 如果參數(shù)發(fā)生變更,則調(diào)用createImage創(chuàng)建內(nèi)部的EGLImageKHR
status_t createIfNeeded(EGLDisplay display, const Rect& cropRect, bool forceCreate = false);
// 把EGLImageKHR綁定的GraphicBuffer圖形數(shù)據(jù)上傳到目標(biāo)紋理
void bindToTextureTarget(uint32_t texTarget){
glEGLImageTargetTexture2DOES(texTarget, static_cast<GLeglImageOES>(mEglImage));
}
private:
// 創(chuàng)建內(nèi)部的EGLImageKHR
EGLImageKHR createImage(EGLDisplay dpy, const sp<GraphicBuffer>& graphicBuffer, const Rect& crop);
// 用于創(chuàng)建EGLImageKHR的GraphicBuffer
sp<GraphicBuffer> mGraphicBuffer;
// 根據(jù)GraphicBuffer創(chuàng)建的EGLImageKHR
EGLImageKHR mEglImage;
// 創(chuàng)建EGLImageKHR需要的參數(shù)
EGLDisplay mEglDisplay;
// 創(chuàng)建EGLImageKHR時(shí),使用的裁剪區(qū)域
Rect mCropRect;
}
總結(jié)
SurfaceTexture是離屏渲染的核心,例如:我們可以把SurfaceTexture設(shè)置給Camera接收攝像頭圖像數(shù)據(jù),并轉(zhuǎn)換為OES紋理,然后可以利用OpenGL對(duì)OES紋理做進(jìn)一步特效處理,最后上屏或者錄制成視頻。所以,理解SurfaceTexture底層原理有助于業(yè)務(wù)層開(kāi)發(fā)和問(wèn)題排查,希望本文對(duì)有心人有所幫助。
參考文檔
Using GL_OES_EGL_image_external on Android https://gist.github.com/rexguo/6696123
EGL_KHR_image_base.txt https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_image_base.tx
來(lái)源:https://juejin.cn/post/6844904161645953038
-- END --
進(jìn)技術(shù)交流群,掃碼添加我的微信:Byte-Flow
獲取相關(guān)資料和源碼
推薦:
全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了
所有你想要的圖片轉(zhuǎn)場(chǎng)效果,都在這了
面試官:如何利用 Shader 實(shí)現(xiàn) RGBA 到 NV21 圖像格式轉(zhuǎn)換?
