1. 程式人生 > >基於Android 5.0(Lollipop)比較SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

基於Android 5.0(Lollipop)比較SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

轉自:https://blog.csdn.net/ariesjzj/article/details/44062175

本文基於Android 5.0(Lollipop)的程式碼理一下它們的基本原理,聯絡與區別。

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

 

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

 

 

GLSurfaceView

從Android 1.5(API level 3)開始加入,作為SurfaceView的補充。它可以看作是SurfaceView的一種典型使用模式。在SurfaceView的基礎上,它加入了EGL的管理,並自帶了渲染執行緒。另外它定義了使用者需要實現的Render介面,提供了用Strategy pattern更改具體Render行為的靈活性。作為GLSurfaceView的Client,只需要將實現了渲染函式的Renderer的實現類設定給GLSurfaceView即可。如:

 
  1. public class TriangleActivity extends Activity {

  2. protected void onCreate(Bundle savedInstanceState) {

  3. mGLView = new GLSurfaceView(this);

  4. mGLView.setRenderer(new RendererImpl(this));

相關類圖如下。其中SurfaceView中的SurfaceHolder主要是提供了一坨操作Surface的介面。GLSurfaceView中的EglHelper和GLThread分別實現了上面提到的管理EGL環境和渲染執行緒的工作。GLSurfaceView的使用者需要實現Renderer介面。

 

 

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

 

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為中心的一個pipeline大體是這樣的:

 

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

下面以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。

 
  1. 109 public VideoDumpView(Context context) {

  2. ...

  3. 116 mRenderer = new VideoDumpRenderer(context);

  4. 117 setRenderer(mRenderer);

  5. 118 }

隨後,GLSurfaceView中的GLThread啟動,建立EGL環境後回撥VideoDumpRenderer中的onSurfaceCreated()。

 
  1. 519 public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {

  2. ...

  3. 551 // Create our texture. This has to be done each time the surface is created.

  4. 552 int[] textures = new int[1];

  5. 553 GLES20.glGenTextures(1, textures, 0);

  6. 554

  7. 555 mTextureID = textures[0];

  8. 556 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);

  9. ...

  10. 575 mSurface = new SurfaceTexture(mTextureID);

  11. 576 mSurface.setOnFrameAvailableListener(this);

  12. 577

  13. 578 Surface surface = new Surface(mSurface);

  14. 579 mMediaPlayer.setSurface(surface);

這裡,首先通過GLES建立GL的外部紋理。外部紋理說明它的真正內容是放在ion分配出來的系統實體記憶體中,而不是GPU中,GPU中只是維護了其元資料。接著根據前面建立的GL紋理物件建立SurfaceTexture。流程如下:

 

SurfaceTexture的引數為GLES介面函式glGenTexture()得到的紋理物件id。在初始化函式SurfaceTexture_init()中,先建立GLConsumer和相應的BufferQueue,再將它們的指標通過JNI放到SurfaceTexture的Java層物件成員中。

 
  1. 230static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,

  2. 231 jint texName, jboolean singleBufferMode, jobject weakThiz)

  3. 232{

  4. ...

  5. 235 BufferQueue::createBufferQueue(&producer, &consumer);

  6. ...

  7. 242 sp<GLConsumer> surfaceTexture;

  8. 243 if (isDetached) {

  9. 244 surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES,

  10. 245 true, true);

  11. 246 } else {

  12. 247 surfaceTexture = new GLConsumer(consumer, texName,

  13. 248 GL_TEXTURE_EXTERNAL_OES, true, true);

  14. 249 }

  15. ...

  16. 256 SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);

  17. 257 SurfaceTexture_setProducer(env, thiz, producer);

  18. ...

  19. 266 sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz,

  20. 267 clazz));

  21. 268 surfaceTexture->setFrameAvailableListener(ctx);

  22. 269 SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);

由於直接的Listener在Java層,而觸發者在Native層,因此需要從Native層回撥到Java層。這裡通過JNISurfaceTextureContext當了跳板。JNISurfaceTextureContext的onFrameAvailable()起到了Native和Java的橋接作用:

 
  1. 180void JNISurfaceTextureContext::onFrameAvailable()

  2. ...

  3. 184 env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);

其中的fields.postEvent早在SurfaceTexture_classInit()中被初始化為SurfaceTexture的postEventFromNative()函式。這個函式往所線上程的訊息佇列中放入訊息,非同步呼叫VideoDumpRenderer的onFrameAvailable()函式,通知VideoDumpRenderer有新的資料到來。

 

回到onSurfaceCreated(),接下來建立供外部生產者使用的Surface類。Surface的建構函式之一帶有引數SurfaceTexture。

 
  1. 133 public Surface(SurfaceTexture surfaceTexture) {

  2. ...

  3. 140 setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));

它實際上是把SurfaceTexture中建立的BufferQueue的Producer介面實現類拿出來後建立了相應的Surface類。

 
  1. 135static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,

  2. 136 jobject surfaceTextureObj) {

  3. 137 sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));

  4. ...

  5. 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建立起了一條通知鏈:MediaPlayer -> SurfaceTexture -> VideDumpRenderer。在onFrameAvailable()回撥函式中,將updateSurface標誌設為true,表示有新的影象到來,需要更新Surface了。為毛不在這兒馬上更新紋理呢,因為當前可能不在渲染執行緒。SurfaceTexture物件可以在任意執行緒被建立(回撥也會在該執行緒被呼叫),但updateTexImage()只能在含有紋理物件的GL context所線上程中被呼叫。因此一般情況下回調中不能直接呼叫updateTexImage()。

 

與此同時,GLSurfaceView中的GLThread也在執行,它會呼叫到VideoDumpRenderer的繪製函式onDrawFrame()。

 
  1. 372 public void onDrawFrame(GL10 glUnused) {

  2. ...

  3. 377 if (updateSurface) {

  4. ...

  5. 380 mSurface.updateTexImage();

  6. 381 mSurface.getTransformMatrix(mSTMatrix);

  7. 382 updateSurface = false;

  8. ...

  9. 394 // Activate the texture.

  10. 395 GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

  11. 396 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);

  12. ...

  13. 421 // Draw a rectangle and render the video frame as a texture on it.

  14. 422 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

  15. ...

  16. 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)給了一個簡潔的例子LiveCameraActivity。它它可以將Camera中的內容放在View中進行顯示。在onCreate()函式中首先建立TextureView,再將Activity(實現了TextureView.SurfaceTextureListener介面)傳給TextureView,用於監聽SurfaceTexture準備好的訊號。

 
  1. protected void onCreate(Bundle savedInstanceState) {

  2. ...

  3. mTextureView = new TextureView(this);

  4. mTextureView.setSurfaceTextureListener(this);

  5. ...

  6. }

TextureView的建構函式並不做主要的初始化工作。主要的初始化工作是在getHardwareLayer()中,而這個函式是在其基類View的draw()中呼叫。TextureView過載了這個函式:

 
  1. 348 HardwareLayer getHardwareLayer() {

  2. ...

  3. 358 mLayer = mAttachInfo.mHardwareRenderer.createTextureLayer();

  4. 359 if (!mUpdateSurface) {

  5. 360 // Create a new SurfaceTexture for the layer.

  6. 361 mSurface = new SurfaceTexture(false);

  7. 362 mLayer.setSurfaceTexture(mSurface);

  8. 363 }

  9. 364 mSurface.setDefaultBufferSize(getWidth(), getHeight());

  10. 365 nCreateNativeWindow(mSurface);

  11. 366

  12. 367 mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);

  13. 368

  14. 369 if (mListener != null && !mUpdateSurface) {

  15. 370 mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());

  16. 371 }

  17. ...

  18. 390 applyUpdate();

  19. 391 applyTransformMatrix();

  20. 392

  21. 393 return mLayer;

  22. 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傳給資料來源模組,供資料來源輸出之用。如:

 
  1. public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

  2. mCamera = Camera.open();

  3. ...

  4. mCamera.setPreviewTexture(surface);

  5. mCamera.startPreview();

  6. ...

  7. }

看一下setPreviewTexture()的實現,其中把SurfaceTexture中初始化時建立的GraphicBufferProducer拿出來傳給Camera模組。

 
  1. 576static void android_hardware_Camera_setPreviewTexture(JNIEnv *env,

  2. 577 jobject thiz, jobject jSurfaceTexture)

  3. ...

  4. 585 producer = SurfaceTexture_getProducer(env, jSurfaceTexture);

  5. ...

  6. 594 if (camera->setPreviewTarget(producer) != NO_ERROR) {

到這裡,一切都初始化地差不多了。接下來當內容流有新影象可用,TextureView會被通知到(通過SurfaceTexture.OnFrameAvailableListener介面)。SurfaceTexture.OnFrameAvailableListener是SurfaceTexture有新內容來時的回撥介面。TextureView中的mUpdateListener實現了該介面:

 
  1. 755 public void onFrameAvailable(SurfaceTexture surfaceTexture) {

  2. 756 updateLayer();

  3. 757 invalidate();

  4. 758 }

可以看到其中會呼叫updateLayer()函式,然後通過invalidate()函式申請更新UI。updateLayer()會設定mUpdateLayer標誌位。這樣,當下次VSync到來時,Choreographer通知App通過重繪View hierachy。在UI重繪函式performTranversals()中,作為View hierachy的一分子,TextureView的draw()函式被呼叫,其中便會相繼呼叫applyUpdate()和HardwareLayer的updateSurfaceTexture()函式。

 
  1. 138 public void updateSurfaceTexture() {

  2. 139 nUpdateSurfaceTexture(mFinalizer.get());

  3. 140 mRenderer.pushLayerUpdate(this);

  4. 141 }

updateSurfaceTexture()實際通過JNI呼叫到android_view_HardwareLayer_updateSurfaceTexture()函式。在其中會設定相應DeferredLayerUpdater的標誌位mUpdateTexImage,它表示在渲染執行緒中需要更新該層的紋理。



 

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

 

 

當主執行緒準備好渲染資料後,會以同步方式讓渲染執行緒完成渲染工作。其中會先呼叫processLayerUpdate()更新所有硬體加速層中的屬性,繼而呼叫到DeferredLayerUpdater的apply()函式,其中檢測到標誌位mUpdateTexImage被置位,於是會呼叫doUpdateTexImage()真正更新GL紋理和轉換座標。

 

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