天天看點

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

SurfaceView, GLSurfaceView, SurfaceTexture和TextureView是Android當中名字比較繞,關系又比較密切的幾個類。本文基于Android 5.0(Lollipop)的代碼理1下它們的基本原理,聯系與區分。

SurfaceView從Android 1.0(API level 1)時就有 。它繼承自類View,是以它本質上是1個View。但與普通View不同的是,它有自己的Surface。我們知道,1般的Activity包括的多個View會組成View hierachy的樹形結構,隻有最頂層的DecorView,也就是根結點視圖,才是對WMS可見的。這個DecorView在WMS中有1個對應的WindowState。相應地,在SF中對應的Layer。而SurfaceView自帶1個Surface,這個Surface在WMS中有自己對應的WindowState,在SF中也會有自己的Layer。以下圖所示:

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

也就是說,雖然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它與宿主視窗是分離的。這樣的好處是對這個Surface的渲染可以放到單獨線程去做,渲染時可以有自己的GL context。這對1些遊戲、視訊等性能相幹的利用非常有益,由于它不會影響主線程對事件的響應。但它也有缺點,由于這個Surface不在View hierachy中,它的顯示也不受View的屬性控制,是以不能進行平移,縮放等變換,也不能放在其它ViewGroup中,1些View中的特性也沒法使用。

GLSurfaceView從Android 1.5(API level 3)開始加入,作為SurfaceView的補充。它可以看做是SurfaceView的1種典型使用模式。在SurfaceView的基礎上,它加入了EGL的管理,并自帶了渲染線程。另外它定義了使用者需要實作的Render接口,提供了用Strategy pattern更改具體Render行動的靈活性。作為GLSurfaceView的Client,隻需要将實作了渲染函數的Renderer的實作類設定給GLSurfaceView便可。如: public class TriangleActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { mGLView = new GLSurfaceView(this); mGLView.setRenderer(new RendererImpl(this)); 相幹類圖以下。其中SurfaceView中的SurfaceHolder主要是提供了1坨操作Surface的接口。GLSurfaceView中的EglHelper和GLThread分别實作了上面提到的管理EGL環境和渲染線程的工作。GLSurfaceView的使用者需要實作Renderer接口。

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

SurfaceTexture 從Android 3.0(API level 11)加入。和SurfaceView不同的是,它對圖象流的處理其實不直接顯示,而是轉為GL外部紋理,是以可用于圖象流資料的2次處理(如Camera濾鏡,桌面殊效等)。比如Camera的預覽資料,變成紋理後可以交給GLSurfaceView直接顯示,也能夠通過SurfaceTexture交給TextureView作為View heirachy中的1個硬體加速層來顯示。首先,SurfaceTexture從圖象流(來自Camera預覽,視訊解碼,GL繪制場景等)中取得幀資料,當調用updateTexImage()時,根據内容流中最近的圖象更新SurfaceTexture對應的GL紋理對象,接下來,就能夠像操作普通GL紋理1樣操作它了。從下面的類圖中可以看出,它核心管理着1個BufferQueue的Consumer和Producer兩端。Producer端用于内容流的源輸出資料,Consumer端用于拿GraphicBuffer并生成紋理。SurfaceTexture.OnFrameAvailableListener用于讓SurfaceTexture的使用者知道有新資料到來。JNISurfaceTextureContext是OnFrameAvailableListener從Native到Java的JNI跳闆。其中SurfaceTexture中的attachToGLContext()和detachToGLContext()可讓多個GL context同享同1個内容源。

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

Android 5.0中将BufferQueue的核心功能分離出來,放在BufferQueueCore這個類中。BufferQueueProducer和BufferQueueConsumer分别是它的生産者和消費者實作基類(分别實作了IGraphicBufferProducer和IGraphicBufferConsumer接口)。它們都是由BufferQueue的靜态函數createBufferQueue()來建立的。Surface是生産者真個實作類,提供dequeueBuffer/queueBuffer等硬體渲染接口,和lockCanvas/unlockCanvasAndPost等軟體渲染接口,使内容流的源可以往BufferQueue中填graphic buffer。GLConsumer繼承自ConsumerBase,是消費者真個實作類。它在基類的基礎上添加了GL相幹的操作,如将graphic buffer中的内容轉為GL紋理等操作。到此,以SurfaceTexture為中心的1個pipeline大體是這樣的:

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

TextureView 在4.0(API level 14)中引入。它可以将内容流直接投影到View中,可以用于實作Live preview等功能。和SurfaceView不同,它不會在WMS中單獨建立視窗,而是作為View hierachy中的1個普通View,是以可以和其它普通View1樣進行移動,旋轉,縮放,動畫等變化。值得注意的是TextureView必須在硬體加速的視窗中。它顯示的内容流資料可以來自App程序或是遠端程序。從類圖中可以看到,TextureView繼承自View,它與其它的View1樣在View hierachy中管理與繪制。TextureView重載了draw()方法,其中主要把SurfaceTexture中收到的圖象資料作為紋理更新到對應的HardwareLayer中。SurfaceTexture.OnFrameAvailableListener用于通知TextureView内容流有新圖象到來。SurfaceTextureListener接口用于讓TextureView的使用者知道SurfaceTexture已準備好,這樣就能夠把SurfaceTexture交給相應的内容源。Surface為BufferQueue的Producer接口實作類,使生産者可以通過它的軟體或硬體渲染接口為SurfaceTexture内部的BufferQueue提供graphic buffer。

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

下面以VideoDumpView.java(位于/frameworks/base/media/tests/MediaDump/src/com/android/mediadump/)為例分析下SurfaceTexture的使用。這個例子的效果是從MediaPlayer中拿到視訊幀,然後顯示在螢幕上,接着把螢幕上的内容dump到指定檔案中。由于SurfaceTexture本身隻産生紋理,是以這裡還需要GLSurfaceView配合來做最後的渲染輸出。

首先,VideoDumpView是GLSurfaceView的繼承類。在構造函數VideoDumpView()中會建立VideoDumpRenderer,也就是GLSurfaceView.Renderer的執行個體,然後調setRenderer()将之設成GLSurfaceView的Renderer。 109 public VideoDumpView(Context context) {...116 mRenderer = new VideoDumpRenderer(context);117 setRenderer(mRenderer);118 } 随後,GLSurfaceView中的GLThread啟動,建立EGL環境後回調VideoDumpRenderer中的onSurfaceCreated()。 519 public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {...551 // Create our texture. This has to be done each time the surface is created.552 int[] textures = new int[1];553 GLES20.glGenTextures(1, textures, 0);554555 mTextureID = textures[0];556 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);...575 mSurface = new SurfaceTexture(mTextureID);576 mSurface.setOnFrameAvailableListener(this);577578 Surface surface = new Surface(mSurface);579 mMediaPlayer.setSurface(surface); 這裡,首先通過GLES建立GL的外部紋理。外部紋理說明它的真正内容是放在ion配置設定出來的系統實體記憶體中,而不是GPU中,GPU中隻是保護了其中繼資料。接着根據前面建立的GL紋理對象建立SurfaceTexture。流程以下:

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

SurfaceTexture的參數為GLES接口函數glGenTexture()得到的紋理對象id。在初始化函數SurfaceTexture_init()中,先建立GLConsumer和相應的BufferQueue,再将它們的指針通過JNI放到SurfaceTexture的Java層對象成員中。 230static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,231 jint texName, jboolean singleBufferMode, jobject weakThiz)232{...235 BufferQueue::createBufferQueue(&producer, &consumer);...242 sp<GLConsumer> surfaceTexture;243 if (isDetached) {244 surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES,245 true, true);246 } else {247 surfaceTexture = new GLConsumer(consumer, texName,248 GL_TEXTURE_EXTERNAL_OES, true, true);249 }...256 SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);257 SurfaceTexture_setProducer(env, thiz, producer);...266 sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz,267 clazz));268 surfaceTexture->setFrameAvailableListener(ctx);269 SurfaceTexture_setFrameAvailableListener(env, thiz, ctx); 由于直接的Listener在Java層,而觸發者在Native層,是以需要從Native層回調到Java層。這裡通過JNISurfaceTextureContext當了跳闆。JNISurfaceTextureContext的onFrameAvailable()起到了Native和Java的橋接作用: 180void JNISurfaceTextureContext::onFrameAvailable()...184 env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz); 其中的fields.postEvent早在SurfaceTexture_classInit()中被初始化為SurfaceTexture的postEventFromNative()函數。這個函數往所線上程的消息隊列中放入消息,異步調用VideoDumpRenderer的onFrameAvailable()函數,通知VideoDumpRenderer有新的資料到來。

回到onSurfaceCreated(),接下來建立供外部生産者使用的Surface類。Surface的構造函數之1帶有參數SurfaceTexture。 133 public Surface(SurfaceTexture surfaceTexture) {...140 setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture)); 它實際上是把SurfaceTexture中建立的BufferQueue的Producer接口實作類拿出來後建立了相應的Surface類。 135static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,136 jobject surfaceTextureObj) {137 sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));...144 sp<Surface> surface(new Surface(producer, true)); 這樣,Surface為BufferQueue的Producer端,SurfaceTexture中的GLConsumer為BufferQueue的Consumer端。當通過Surface繪制時,SurfaceTexture可以通過updateTexImage()來将繪制結果綁定到GL的紋理中。

回到onSurfaceCreated()函數,接下來調用setOnFrameAvailableListener()函數将VideoDumpRenderer(實作SurfaceTexture.OnFrameAvailableListener接口)作為SurfaceTexture的Listener,由于它要監聽内容流上是不是有新資料。接着将SurfaceTexture傳給MediaPlayer,由于這裡MediaPlayer是生産者,SurfaceTexture是消費者。後者要接收前者輸出的Video frame。這樣,就通過Observer pattern建立起了1條通知鍊:MediaPlayer -> SurfaceTexture -> VideDumpRenderer。在onFrameAvailable()回調函數中,将updateSurface标志設為true,表示有新的圖象到來,需要更新Surface了。為毛不在這兒馬上更新紋理呢,由于目前可能不在渲染線程。SurfaceTexture對象可以在任意線程被建立(回調也會在該線程被調用),但updateTexImage()隻能在含有紋理對象的GL context所線上程中被調用。是以1般情況下回調中不能直接調用updateTexImage()。

與此同時,GLSurfaceView中的GLThread也在運作,它會調用到VideoDumpRenderer的繪制函數onDrawFrame()。 372 public void onDrawFrame(GL10 glUnused) {...377 if (updateSurface) {...380 mSurface.updateTexImage();381 mSurface.getTransformMatrix(mSTMatrix);382 updateSurface = false;...394 // Activate the texture.395 GLES20.glActiveTexture(GLES20.GL_TEXTURE0);396 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);...421 // Draw a rectangle and render the video frame as a texture on it.422 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);...429 DumpToFile(frameNumber); 這裡,通過SurfaceTexture的updateTexImage()将内容流中的新圖象轉成GL中的紋理,再進行坐标轉換。綁定剛生成的紋理,畫到螢幕上。全部流程以下:

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

最後onDrawFrame()調用DumpToFile()将螢幕上的内容倒到檔案中。在DumpToFile()中,先用glReadPixels()從螢幕中把像素資料存到Buffer中,然後用FileOutputStream輸出到檔案。

上面講了SurfaceTexture,下面看看TextureView是如何工作的。還是從例子着手,Android的關于TextureView的官方文檔(http://developer.android.com/reference/android/view/TextureView.html)給了1個簡潔的例子LiveCameraActivity。它它可以将Camera中的内容放在View中進行顯示。在onCreate()函數中首先建立TextureView,再将Activity(實作了TextureView.SurfaceTextureListener接口)傳給TextureView,用于監聽SurfaceTexture準備好的信号。 protected void onCreate(Bundle savedInstanceState) { ... mTextureView = new TextureView(this); mTextureView.setSurfaceTextureListener(this); ... } TextureView的構造函數其實不做主要的初始化工作。主要的初始化工作是在getHardwareLayer()中,而這個函數是在其基類View的draw()中調用。TextureView重載了這個函數: 348 HardwareLayer getHardwareLayer() {...358 mLayer = mAttachInfo.mHardwareRenderer.createTextureLayer();359 if (!mUpdateSurface) {360 // Create a new SurfaceTexture for the layer.361 mSurface = new SurfaceTexture(false);362 mLayer.setSurfaceTexture(mSurface);363 }364 mSurface.setDefaultBufferSize(getWidth(), getHeight());365 nCreateNativeWindow(mSurface);366367 mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);368369 if (mListener != null && !mUpdateSurface) {370 mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());371 }...390 applyUpdate();391 applyTransformMatrix();392393 return mLayer;394 } 由于TextureView是硬體加速層(類型為LAYER_TYPE_HARDWARE),它首先通過HardwareRenderer建立相應的HardwareLayer類,放在mLayer成員中。然後建立SurfaceTexture類,具體流程見前文。以後将HardwareLayer與SurfaceTexture做綁定。接着調用Native函數nCreateNativeWindow,它通過SurfaceTexture中的BufferQueueProducer建立Surface類。注意Surface實作了ANativeWindow接口,這意味着它可以作為EGL Surface傳給EGL接口進而進行硬體繪制。然後setOnFrameAvailableListener()将監聽者mUpdateListener注冊到SurfaceTexture。這樣,當内容流上有新的圖象到來,mUpdateListener的onFrameAvailable()就會被調用。然後需要調用注冊在TextureView中的SurfaceTextureListener的onSurfaceTextureAvailable()回調函數,通知TextureView的使用者SurfaceTexture已就緒。全部流程大體以下:

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

注意這裡這裡為TextureView建立了DeferredLayerUpdater,而不是像Android 4.4(Kitkat)中傳回GLES20TextureLayer。由于Android 5.0(Lollipop)中在App端分離出了渲染線程,并将渲染工作放到該線程中。這個線程還能接收VSync信号,是以它還能自己處理動畫。事實上,這裡DeferredLayerUpdater的建立就是通過同步方式在渲染線程中做的。DeferredLayerUpdater,顧名思義,就是将Layer的更新要求先記錄在這,當渲染線程真正要畫的時候,再進行真實的操作。其中的setSurfaceTexture()會調用HardwareLayer的Native函數nSetSurfaceTexture()将SurfaceTexture中的surfaceTexture成員(類型為GLConsumer)傳給DeferredLayerUpdater,這樣以後要更新紋理時DeferredLayerUpdater就知道從哪裡更新了。

前面提到初始化中會調用onSurfaceTextureAvailable()這個回調函數。在它的實作中,TextureView的使用者就能夠将準備好的SurfaceTexture傳給資料源子產品,供資料源輸出之用。如: public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mCamera = Camera.open(); ... mCamera.setPreviewTexture(surface); mCamera.startPreview(); ... } 看1下setPreviewTexture()的實作,其中把SurfaceTexture中初始化時建立的GraphicBufferProducer拿出來傳給Camera子產品。 576static void android_hardware_Camera_setPreviewTexture(JNIEnv *env,577 jobject thiz, jobject jSurfaceTexture)...585 producer = SurfaceTexture_getProducer(env, jSurfaceTexture);...594 if (camera->setPreviewTarget(producer) != NO_ERROR) { 到這裡,1切都初始化地差不多了。接下來當内容流有新圖象可用,TextureView會被通知到(通過SurfaceTexture.OnFrameAvailableListener接口)。SurfaceTexture.OnFrameAvailableListener是SurfaceTexture有新内容來時的回調接口。TextureView中的mUpdateListener實作了該接口: 755 public void onFrameAvailable(SurfaceTexture surfaceTexture) {756 updateLayer();757 invalidate();758 } 可以看到其中會調用updateLayer()函數,然後通過invalidate()函數申請更新UI。updateLayer()會設定mUpdateLayer标志位。這樣,當下次VSync到來時,Choreographer通知App通太重繪View hierachy。在UI重繪函數performTranversals()中,作為View hierachy的1份子,TextureView的draw()函數被調用,其中便會相繼調用applyUpdate()和HardwareLayer的updateSurfaceTexture()函數。 138 public void updateSurfaceTexture() {139 nUpdateSurfaceTexture(mFinalizer.get());140 mRenderer.pushLayerUpdate(this);141 } updateSurfaceTexture()實際通過JNI調用到android_view_HardwareLayer_updateSurfaceTexture()函數。在其中會設定相應DeferredLayerUpdater的标志位mUpdateTexImage,它表示在渲染線程中需要更新該層的紋理。

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

前面提到,Android 5.0引入了渲染線程,它是1個更大的topic,超越本文範圍,這裡隻說相幹的部份。作為背景知識,下面隻畫出了相幹的類。可以看到,ThreadedRenderer作為新的HardwareRenderer替換了Android 4.4中的Gl20Renderer。其中比較關鍵的是RenderProxy類,需要讓渲染線程幹活時就通過這個類往渲染線程發任務。RenderProxy中指向的RenderThread就是渲染線程的主體了,其中的threadLoop()函數是主循環,大多數時間它會poll線上程的Looper上等待,當有同步要求(或VSync信号)過來,它會被喚醒,然後處理TaskQueue中的任務。TaskQueue是RenderTask的隊列,RenderTask代表1個渲染線程中的任務。如DrawFrameTask就是RenderTask的繼承類之1,它主要用于渲染目前幀。而DrawFrameTask中的DeferredLayerUpdater集合就寄存着之前對硬體加速層的更新操作申請。

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

當主線程準備好渲染資料後,會以同步方式讓渲染線程完成渲染工作。其中會先調用processLayerUpdate()更新所有硬體加速層中的屬性,繼而調用到DeferredLayerUpdater的apply()函數,其中檢測到标志位mUpdateTexImage被置位,因而會調用doUpdateTexImage()真正更新GL紋理和轉換坐标。

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

最後,總結下這幾者的區分和聯系。簡單地說,SurfaceView是1個有自己Surface的View。它的渲染可以放在單獨線程而不是主線程中。其缺點是不能做變形和動畫。SurfaceTexture可以用作非直接輸出的内容流,這樣就提供2次處理的機會。與SurfaceView直接輸出相比,這樣會有若幹幀的延遲。同時,由于它本身管理BufferQueue,是以記憶體消耗也會略微大1些。TextureView是1個可以把内容流作為外部紋理輸出在上面的View。它本身需要是1個硬體加速層。事實上TextureView本身也包括了SurfaceTexture。它與SurfaceView+SurfaceTexture組合相比可以完成類似的功能(即把内容流上的圖象轉成紋理,然後輸出)。區分在于TextureView是在View hierachy中做繪制,是以1般它是在主線程上做的(在Android 5.0引入渲染線程後,它是在渲染線程中做的)。而SurfaceView+SurfaceTexture在單獨的Surface上做繪制,可以是使用者提供的線程,而不是系統的主線程或是渲染線程。另外,與TextureView相比,它還有個好處是可以用Hardware overlay進行顯示。