SurfaceView, TextureView, SurfaceTexture等的區別

SurfaceView, TextureView, SurfaceTexture等的區別

SurfaceView, GLSurfaceView, SurfaceTexture以及TextureView是Android當中名字比較繞,關係又比較密切的幾個類。本文基於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。如下圖所示:

技術分享

 

也就是說, 雖然在Client端(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即可。如:
Java代碼   收藏代碼
  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。
Java代碼   收藏代碼
  1. 109    public VideoDumpView(Context context) {  
  2. ...  
  3. 116        mRenderer = new VideoDumpRenderer(context);  
  4. 117        setRenderer(mRenderer);  
  5. 118    }  
隨後,GLSurfaceView中的GLThread啓動,創建EGL環境後回調VideoDumpRenderer中的onSurfaceCreated()。
Java代碼   收藏代碼
  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層對象成員中。
Cpp代碼   收藏代碼
  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的橋接作用:
Cpp代碼   收藏代碼
  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。
Java代碼   收藏代碼
  1. 133    public Surface(SurfaceTexture surfaceTexture) {  
  2. ...  
  3. 140            setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));  
它實際上是把SurfaceTexture中創建的BufferQueue的Producer接口實現類拿出來後創建了相應的Surface類。
Cpp代碼   收藏代碼
  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()。
Java代碼   收藏代碼
  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 <span style="padding: 0px; width: auto; height: auto; float: none;"><a target="_blank" style="padding: 0px; color: #333333;" href="http://cpro.baidu.com/cpro/ui/uijs.php?app_id=0&c=news&cf=1001&ch=0&di=128&fv=18&is_app=0&jk=ddd62cbeae8a0ad1&k=video&k0=video&kdi0=0&luki=7&n=10&p=baidu&q=65035100_cpr&rb=0&rs=1&seller_id=1&sid=d10a8aaebe2cd6dd&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1836545&u=http%3A%2F%2Fwww%2Ebubuko%2Ecom%2Finfodetail%2D656030%2Ehtml&urlid=0"><span style="padding: 0px; color: #0000ff; width: auto; height: auto;">video</span></a></span> 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準備好的信號。
Java代碼   收藏代碼
  1. protected void onCreate(Bundle savedInstanceState) {  
  2.     ...  
  3.     mTextureView = new TextureView(this);  
  4.     mTextureView.setSurfaceTextureListener(this);  
  5.     ...  
  6. }  
TextureView的構造函數並不做主要的初始化工作。主要的初始化工作是在getHardwareLayer()中,而這個 函數是在其基類View的draw()中調用。TextureView重載了這個函數:
Java代碼   收藏代碼
  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傳給數據源模塊,供數據源輸出之用。如:
Java代碼   收藏代碼
  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模塊。
Cpp代碼   收藏代碼
  1. 576static void android_<span style="padding: 0px; width: auto; height: auto; float: none;"><a target="_blank" style="padding: 0px; color: #333333;" href="http://cpro.baidu.com/cpro/ui/uijs.php?app_id=0&c=news&cf=1001&ch=0&di=128&fv=18&is_app=0&jk=ddd62cbeae8a0ad1&k=hardware&k0=hardware&kdi0=0&luki=3&n=10&p=baidu&q=65035100_cpr&rb=0&rs=1&seller_id=1&sid=d10a8aaebe2cd6dd&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1836545&u=http%3A%2F%2Fwww%2Ebubuko%2Ecom%2Finfodetail%2D656030%2Ehtml&urlid=0"><span style="padding: 0px; color: #0000ff; width: auto; height: auto;">hardware</span></a></span>_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實現了該接口:
Java代碼   收藏代碼
  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()函數。
Java代碼   收藏代碼
  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進行顯示。