<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android 圖形系統(tǒng)之 SurfaceTexture

          共 11133字,需瀏覽 23分鐘

           ·

          2023-10-12 12:47

          SurfaceTexture是離屏渲染和TextureView的核心,內(nèi)部包含了一個(gè)BufferQueue,可以把Surface生成的圖像流,轉(zhuǎn)換為紋理,供業(yè)務(wù)方進(jìn)一步加工使用。整個(gè)架構(gòu)如下圖所示:

          SurfaceTexture.png
          1. 首先,通過(guò)Canvas、OpenGL、Camera或者Video Decoder生成圖像流。

          2. 接著,圖像流通過(guò)Surface入隊(duì)到BufferQueue,并通知到GLConsumer。

          3. 然后,GLConsumer從BufferQueue獲取圖像流GraphicBuffer,并轉(zhuǎn)換為紋理。

          4. 最后,業(yè)務(wù)方可以對(duì)紋理進(jìn)一步處理,例如:上特效或者上屏。

          下面我們分別看下SurfaceTexture初始化以及圖像數(shù)據(jù)在SurfaceTexture內(nèi)部的流轉(zhuǎn)。

          SurfaceTexture初始化

          new SurfaceTexture(textureId)啟動(dòng)SurfaceTexture初始化,核心邏輯如下所示:

          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)用updateTexImageGraphicBuffer更新到紋理,否則啥也不做,忽略一些圖形數(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)流程,如下所示:

          SurfaceTexture幀回調(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)用updateTexImageGraphicBuffer更新到紋理。這里的紋理就是我們創(chuàng)建SurfaceTexture時(shí)傳入的紋理ID,整個(gè)更新流程如下所示:

          SurfaceTexture-updateTexImage

          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ò)EglImageEGLImageKHR實(shí)現(xiàn)的,這里我們看下在Android平臺(tái)上使用EGLImageKHR的方式:

          1. 紋理目標(biāo)需要從GL_TEXTURE_2D替換為GL_TEXTURE_EXTERNAL_OES,例如:glBindTexture、glTexParameteri等函數(shù)。

          2. 片元著色器中,聲明OES擴(kuò)展:#extension GL_OES_EGL_image_external : require。同時(shí),使用samplerExternalOES替代sampler2D紋理類型。

          3. 基于GraphicBuffer圖形數(shù)據(jù),通過(guò)eglCreateImageKHR創(chuàng)建EGLImageKHR。

          GraphicBuffer可以從BufferQueue中獲取,也可以lock后獲取內(nèi)存地址,寫(xiě)入圖形數(shù)據(jù),具體可參考GraphicBuffer.cpp。

          1. 通過(guò)glEGLImageTargetTexture2DOESEGLImageKHR填充為紋理數(shù)據(jù)

          glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, static_cast(EGLImageKHR));

          1. 最后,使用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é)GraphicBufferEGLImageKHR和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ì)有心人有所幫助。

          參考文檔

          1. Using GL_OES_EGL_image_external on Android https://gist.github.com/rexguo/6696123

          2. 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)資料和源碼


          推薦:

          Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能

          全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了

          一文掌握 YUV 圖像的基本處理

          抖音傳送帶特效是怎么實(shí)現(xiàn)的?

          所有你想要的圖片轉(zhuǎn)場(chǎng)效果,都在這了

          面試官:如何利用 Shader 實(shí)現(xiàn) RGBA 到 NV21 圖像格式轉(zhuǎn)換?

          我用 OpenGL ES 給小姐姐做了幾個(gè)抖音濾鏡


          項(xiàng)目疑難問(wèn)題解答、大廠內(nèi)部推薦、面試指導(dǎo)、簡(jiǎn)歷指導(dǎo)、代碼指導(dǎo)、offer 選擇建議、學(xué)習(xí)路線規(guī)劃,可以點(diǎn)擊找我一對(duì)一解答。

          瀏覽 1207
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜ww| 欧美一级免费在线 | 男女拍拍的视频 | 大陆成人一区 | 操B一区 艹逼国产 |