SurfaceTexture,TextureView,GLsurfaceview的區別與聯繫詳解

Android 中的SurfaceTexture,TextureView, GLsurfaceview的區別與聯繫詳解html

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

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

 

\

 

也就是說,雖然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它與宿主窗口是分離的。這樣的好處是對這個Surface的渲染能夠放到單獨線程去作,渲染時能夠有本身的GL context。這對一些遊戲、視頻等性能相干的利用很是有益,因爲它不會影響主線程對事件的響應。但它也有缺點,因爲這個Surface不在View hierachy中,它的顯示也不受View的屬性控制,因此不能進行平移,縮放等變換,也不能放在其它ViewGroup中,一些View中的特性也無法使用。json

GLSurfaceView從Android 1.5(API level 3)開始加入,做爲SurfaceView的補充。它能夠看作是SurfaceView的一種典型使用模式。在SurfaceView的基礎上,它加入了EGL的管理,並自帶了渲染線程。另外它定義了用戶須要實現的Render接口,提供了用Strategy pattern更改具體Render行動的靈活性。做爲GLSurfaceView的Client,只須要將實現了渲染函數的Renderer的實現類設置給GLSurfaceView即可。如:app

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接口。異步

 

\

 

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個BufferQueue的Consumer和Producer兩端。Producer端用於內容流的源輸出數據,Consumer端用於拿GraphicBuffer並生成紋理。SurfaceTexture.OnFrameAvailableListener用於讓SurfaceTexture的使用者知道有新數據到來。JNISurfaceTextureContext是OnFrameAvailableListener從Native到Java的JNI跳板。其中SurfaceTexture中的attachToGLContext()和detachToGLContext()可以讓多個GL context同享同一個內容源。ide

 

\

 

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大致是這樣的:函數

 

\

 

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。oop

 

\

 

下面以VideoDumpView.java(位於/frameworks/base/media/tests/MediaDump/src/com/android/mediadump/)爲例分析下SurfaceTexture的使用。這個例子的效果是從MediaPlayer中拿到視頻幀,而後顯示在屏幕上,接着把屏幕上的內容dump到指定文件中。因爲SurfaceTexture自己只產生紋理,因此這裏還須要GLSurfaceView配合來作最後的渲染輸出。post

首先,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);

554

555 mTextureID = textures[0];

556 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);

...

575 mSurface = new SurfaceTexture(mTextureID);

576 mSurface.setOnFrameAvailableListener(this);

577

578 Surface surface = new Surface(mSurface);

579 mMediaPlayer.setSurface(surface);

這裏,首先經過GLES建立GL的外部紋理。外部紋理說明它的真正內容是放在ion分配出來的系統物理內存中,而不是GPU中,GPU中只是保護了其元數據。接着根據前面建立的GL紋理對象建立SurfaceTexture。流程如下:

 

\

 

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 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 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 producer(SurfaceTexture_getProducer(env, surfaceTextureObj));

...

144 sp 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中的紋理,再進行座標轉換。綁定剛生成的紋理,畫到屏幕上。所有流程如下:

 

\

 

最後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);
366
367 mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
368
369 if (mListener != null && !mUpdateSurface) {
370 mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());371 }
...
390 applyUpdate();
391 applyTransformMatrix();
392
393 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已就緒。所有流程大致如下:

 

\

 

注意這裏這裏爲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引入了渲染線程,它是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集合就寄存着以前對硬件加速層的更新操做申請。

 

\

 

當主線程準備好渲染數據後,會以同步方式讓渲染線程完成渲染工做。其中會先調用processLayerUpdate()更新全部硬件加速層中的屬性,繼而調用到DeferredLayerUpdater的apply()函數,其中檢測到標誌位mUpdateTexImage被置位,於是會調用doUpdateTexImage()真正更新GL紋理和轉換座標。

 

\

 

最後,總結下這幾者的區分和聯繫。簡單地說,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進行顯示。