轉載地址 : http://blog.csdn.net/new_szsheep/article/details/41348581
圖形架構
每一個開發者都應該知道Surface, SurfaceHolder, EGLSurface, SurfaceView, GLSurfaceView, SurfaceTexture, TextureView 以及 SurfaceFlinger。
這篇文章主要描述了Android系統級圖形架構的必要元素,以及如何被應用框架以及多媒體系統應用。這裏主要集中說明圖形buffer如何在系統間
流轉。如果你想了解SurfaceView以及TextureView爲什麼如此運行,以及Surface和EGLSurface如何交互,那你就來對地方了。
這裏假設你掌握一些Android設備以及應用開發的知識概念,但不需要詳細瞭解應用框架,同時這裏提及的API細節不多。這裏的內容與其他
公開的文章沒有太多重疊。這文章的目標是提供一種對圖形從如何着色到輸出中涉及事件的認知,好讓你在開發應用時候可以獲得一些建議選擇。
要達到此目的,我們要置底而上的描述UI類如何工作而不是如何使用。
文章的前面部分涉及到一些後面會用到的背景材料,故此最好就是從頭到尾的閱讀此文章,不要跳到感興趣的部分。我會從解釋Android圖形Buffer開始,
接着描述合成以及顯示的機制,在進一步瞭解高層提供合成數據的機制。
這文章主要涉及Android 4.4的系統,與早期的版本工作機制有區別,甚至未來很有可能也不一樣。跟版本相關的特性會在文章一些地方給指出。
從多方面,我都建議參考Grafika的開源代碼,這是google爲測試而弄的開源項目,它比看例子更快的瞭解系統。
BufferQueue 和 gralloc
要了解Android的圖形系統如何工作,我們要從場景的背後開始。Android所有圍繞圖形相關的中心點是一個成爲BufferQueue的類。它的作用十分的簡單:
把提供圖形數據buffer的生產者與接受圖形數據並顯示或進一步處理的消費者連接起來。生產者與消費者可以存在與不同的進程。幾乎所有涉及到在圖形
系統中移動的事情,都依賴BufferQueue。
基本的用法很直接。生產者請求一個空的buffer(dequeueBuffer()),然後給此buffer定義一些特性,如寬,高,像素格式以及使用標誌。生產者然後對此buffer
植入內容,並把它歸還到隊列(queueBuffer())。過後,消費者從隊列獲取buffer(acquireBuffer()) 並使用。當消費者用完後,它會把buffer歸還給隊列(releaseBuffer())。
大部分最近的Android設備都支持"sync Framework"。這允許具備硬件異步處理圖形數據的系統很好工作。舉個例子,生產者可以提交了一系列OpenGL ES的
繪畫命令,然後在着色未完成時把buffer放進隊列。buffer帶有一個可以提示內容準備好的(fence)圍欄。另一種(fence)圍欄可以在buffer被歸還到空列表時候附上,
好讓消費者可以在buffer還在用的時候釋放buffer。當buffer在系統中移動時,這種方法提升了延遲以及吞吐量。
隊列的一些特性,如它能維持的最大數目buffer,是由生產者和消費者聯合決定的。
有需要時候,BufferQueue會創建buffer來響應。buffer會一直保留直到其特性改變。舉個例子,如果生產者開始請求不同大小的buffer,則舊buffer會被釋放,新的
buffer會被按需創建。
數據結構目前總是由消費者創建以及“擁有”。在Android 4.3裏,僅僅生產者是Binder化,也就是說生產者可能存在在另一個進程,而消費者與創建隊列的進程同屬一個進程。
在4.4版本,這個改進了一點,朝着更大衆化的去實現。
buffer內容從不由BufferQueue拷貝。移動那麼大量的數據是低效的。因此,buffer總是以句柄的方式移動。
gralloc HAL
實際的buffer創建是通過被稱爲"gralloc"內存創建器,這個是按照設備商指定的接口來實現的。alloc函數接受如你期望的參數:寬,高,像素格式以及一套使用標誌。
這些使用標誌值得進一步關注。
gralloc創建器並不只是另外一種在堆上的內存創建器。在某些情況,創建的內容也許不是與buffer一致或完全不能在用戶空間訪問。創建的本質是由使用標誌覺定的,
如下面屬性:
- 內存如何經常從軟件上訪問(CPU)
- 內存如何經常從硬件上訪問(GPU)
- 是否內存被作爲一個OpenGL ES紋理被使用
- 是否內存被視頻編碼器使用
某些值不能在特定的平臺上被組合。舉個例子,”視頻編碼器“ 標誌也許需要 YUV 像素, 所以如果增加” 從軟件訪問“ 和 指定 RGBA 8888 的格式會導致操作失敗。
由gralloc創建器返回的句柄可以在進程間通過Binder的方式傳遞。
SurfaceFlinger 和 Hardware Composer
擁有圖形數據buffer是美好的事,讓他在你的設備屏幕顯示出來會是更好的事情。這就需要SurfaceFlinger和Hardware Composer HAL。
SurfaceFlinger的作用就是從多頭源那裏接收buffer數據,合成他們併發往顯示器。曾幾何時,這個動作是由軟件把數據貼到硬件幀buffer來完成的,但這已是很久
以前的事情。
當一個應用調到前景,WindowManager服務就會要求SurfaceFlinger來繪製表面。SurfaceFlinger會創建圖層,它的主要構件是BufferQueue,對於這個BufferQueue,
SurfaceFlinger是作爲消費者角色。一個生產者的Binder 對象被從WindowManager傳到應用,藉助此Binder可以讓應用開始發送幀到SurfaceFlinger。(注意:
WindowManager 使用術語 ”window“ 而不是 ”Layer“,”Layer" 對其來說意味着其他東西。我們將會暫時的使用SurfaceFlinger術語。對於SurfaceFlinger應該實際被叫做
LayerFlinger這點是存在爭議的)
對於大多數應用,任何時候在屏幕上總是存在三個層:位於屏幕頂部的狀態欄,位於屏幕底部的導航欄,和應用自身的UI。其他一些應用會比這多或少。
舉個例子:默認的Home應用有一個分離的層作爲牆紙,而全屏遊戲也許會隱藏狀態欄。每一個圖層都可以獨立刷新。狀態欄和導航欄由系統進程來着色,
而應用圖層則由app着色,兩者之間無協調。
設備顯示總是在一定頻率下刷新的,典型的手機、平板是60幀每秒。如果顯示內容以一半頻率刷新,”tearing(撕裂)“就很明顯了。所以,在兩個刷新間隔間更新內容
顯得很重要。系統會接收來自顯示器的信號,通知其可以安全更新內容。因爲歷史原因,我們都稱這個爲 VSYNC 信號。
刷新率也許會隨時間變化,舉個例子,一些移動設備會在58到62之間變動,依賴於當前的情況。對於HDMI付着的電視,這個理論上下降到24到48幀率來適應視頻。
因爲我們只能在一個刷新間隔更新屏幕一次,以200的幀率提交buffer給顯示器將會是浪費,因爲大部分的幀從不會看得到。SurfaceFlinger只有當顯示器準備好接收新東西之
後纔會喚醒,而不是應用一提交buffer就開始執行。
當VSYNC信號到達,SurfaceFlinger會遍歷它的圖層列表來找新buffer(提交)。如果找到一個新的,則獲取出來。如果沒,他繼續使用之前獲取的buffer。SurfaceFlinger
總是要有東西去顯示,故此它總會掛着一個buffer。如果從沒有圖層上的buffer提交,則此圖層會被忽略。
一旦SurfaceFlinger收集到所有可見圖層的buffer,它會詢問Hardware Composer如何來組合。
Hardware Composer
Hardware Composer HAL ("HWC") 首次在Android 3.0被引進,經過數年已經變得很穩定了。它主要的目的是選擇最高效的途徑來合成buffer。作爲HAL,它的實現是依賴
設備的,並通常由OEM顯示硬件廠家完成。
這個方法的價值在於可以很容易的識別你該什麼時候用”Overlay planes“。Overlay plane的使用目的就是把多個buffer在顯示器而不是GPU那裏合成起來。舉個例子,假設你
有一臺Android手機豎着擺,屏幕上有狀態欄和導航欄以及其他地方是應用UI。每一個圖層的內容都在分開的buffer裏。你可以先把應用的內容畫好在一個草稿圖層
那裏,接着把狀態欄的圖層着色,接着是導航欄,最後把草稿圖層的buffer發往顯示設備。又或者,你可以把三個buffer都送到顯示設備硬件,然後告知它從三個不同的buffer
獲取內容,在屏幕的不同部分進行着色。明顯,最後的辦法更高效。
正如你所想的,不同顯示設備的處理能力明顯有區別。overlay的數量,圖層是否可旋轉或混合,以及對位置和重疊的束縛會比較困難通過API來展現。因此HWC如下工作:
- SurfaceFlinger提供給HWC一個完整的圖層列表,並詢問”你將如何處理它“
- HWC通過在每個圖層上標明”重疊 overlay"或”GLES 合成“來響應。
- SurfaceFlinger來處理所有的GLES合成,把輸出buffer發給HWC並讓HWC處理剩餘的事情
<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 19px; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="pln" style="color: rgb(0, 0, 0);"> type </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> source crop </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> frame name </span><span class="pun" style="color: rgb(102, 102, 0);">------------+-----------------------------------+--------------------------------</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">320.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">240.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">48</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">411</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1032</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1149</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">SurfaceView</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">/</span><span class="pln" style="color: rgb(0, 0, 0);">com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="typ" style="color: rgb(102, 0, 102);">PlayMovieSurfaceActivity</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">StatusBar</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">144.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">NavigationBar</span><span class="pln" style="color: rgb(0, 0, 0);"> FB TARGET </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC_FRAMEBUFFER_TARGET</span>這表明了屏幕上有哪些圖層,這些圖層是否被Overlay(HWC)處理或OpenGL ES合成(GLES)處理,以及其他一堆你大概不關心的數據(句柄,提示,標誌以及其他我們
爲了避免存在顯示的撕裂情況,系統需要雙buffer:當後buffer準備時候,前buffer就顯示。在VSYNC信號這個點,如果後buffer準備好了,你就要迅速的切換他們。這個在你可
以直接在幀buffer上繪畫的系統上是可行的,但當加入合成步驟時候,就會出現顯示停頓。因爲SurfaceFlinger的觸發方式,會導致雙buffer管道中出現氣泡。
假設第N幀正被顯示,第N+1幀已經由SurfaceFlinger取出準備在下一個VSYNC信號發給顯示器。(假設幀N是由Overlay來組合的,因此我們不能在顯示器沒用完此Buffer的情況下更改Buffer的內容。)當VSYNC信號到達時,HWC交替buffer。當應用正開始着色第N+2幀到曾經存儲幀N的Buffer時,SurfaceFlinger正掃描圖層列表,尋求更新的
Buffer。SurfaceFlinger此時是不會找到任何新Buffer,故此打算準備在下一個VSYNC信號來時又顯示N+1幀。過後,應用完成了N+2的着色,並準備入列給SurfaceFlinger,但
已經太遲了。這效果相當於砍了最大幀率的一半。
我們可以通過三個Buffer來彌補。在VSYNC信號前,幀N正在顯示,幀N+1已經合成好(或安排給一個Overlay Plane)並且準備顯示,幀N+2已經列隊準備被SurfaceFlinger獲
取。當屏幕翻轉,Buffer通過無氣泡階段旋轉。應用只有少於一個VSYNC的時間來做着色和入列。SurfaceFlinger/HWC在離下一個翻轉時刻之前進行合成時擁有一個完整的
VSYNC時間。下面圖顯示了對於任何應用想在屏幕上顯示東西,至少要花去兩個VSYNC週期。隨着時延的增加,設備會認爲對觸摸輸入的響應減少了。
圖1. SurfaceFlinger + BufferQueue
上圖描述了SurfaceFlinger和BufferQueue的流程。
- 紅Buffer填滿後,滑向BufferQueue
- 在紅Buffer離開應用後,藍Buffer滑進來,替換紅Buffer
- 綠Buffer和系統UI滑進HWC(顯示SurfaceFlinger依然擁有這些Buffer,但現在HWC已經準備把他們通過疊加方式在下一個VSYNC信號時送到屏幕)
- 紅色Buffer躍進SurfaceFlinger,替代綠Buffer
- 綠Buffer跳進顯示,替代藍Buffer,虛線綠色對出現在BufferQueue(替代之前的藍Buffer)
- 藍色Buffer的圍欄發出信號,應用app中的藍Buffer清空
- 顯示矩形那裏從(藍Buffer+系統UI)變成(綠Buffer+系統UI)
與Surface一起工作的東西需要一個SurfaceHolder,尤其是SurfaceView。原本的概念是,Surface代表原始的合成器管理的buffer,而SurfaceHolder由應用app管理,並跟蹤
更高一層信息,如維度和格式。Java語言定義與底層本地實現是對應的。可以說,這樣分割不再有用,但這成爲公共API的一部分很久了。
一般說來,任何與View相關的,都涉及一個SurfaceHolder。一些其他APIs,如MediaCodec,會自己在Surface上操作。你可以很容易的從SurfaceHolder獲取Surface,
以便當你擁有它的時候可以傳遞給後者。
設置和獲取Surface參數,如大小,格式的API通過SurfaceHolder來實現。
EGLSurface和OpenGL ES
OpenGL ES定義了着色圖形的API。它不是定義窗口系統。爲了允許GLES可以在多平臺上使用,它被設計成與一個知道如何通過操作系統創建和訪問窗口的庫結合。Android
用的庫叫做EGL。如果你要畫多邊形圖,你調用GLES;如果你要把你着色的圖放到屏幕,你調用EGL。
在你使用GLES做任何事前,必須創建一個GL上下文。在EGL中,這意味着創建一個EGLContext和EGLSurface。GLES操作作用與當前上下文,此上下文是由線程本地存儲訪
問而不是作爲一個參數來傳遞。這意味着你必須注意你當前着色代碼執行所在的線程,以及在那個線程上是那個上下文。
EGLSurface可以是一個由EGL創建的離屏Buffer(稱爲”pbuffer“)或一個由操作系統創建的窗口。EGL窗口surface通過eglCreateWindowSurface函數創建的。它以”window
object“ 爲參數,在android上可以是SurfaceView,SurfaceTexture,SurfaceHolder或Surface,所有這些內裏都有一個BufferQueue。當你調用此函數,EGL創建一個新的EGLSurface對象,把其連接到窗口對象的BufferQueue的生產者接口。從那個時候開始,着色那個EGLSurface會使一個Buffer被出列,着色,然後被消費者入列使用。
(術語”window“ 表明被期待的使用,但記住的是,輸出不一定是最終出現在顯示器)
EGL 並不提供上鎖/解鎖的調用。相反,你發起繪畫命令,然後調用eglSwapBuffers函數來提交當前幀。這個方法名字來自前後Buffer交換的傳統,但實際上已經完全不一樣
了。
同一時刻僅有一個EGLSurface可以被關聯到一個Surface---你只能有一個生產者連接到BufferQueue-----但如果你銷燬了EGLSurface,它會解除與BufferQueue的連接,並
允許其他來連接。
一個給定的線程可以通過改變”current“來在多個EGLSurface之間切換。一個EGLSurface必須是隻屬於一個線程在某一個時刻的current。
最普遍的錯誤就是當考慮到EGLSurface時,假設它只是Surface的另一面(像SurfaceHolder)。它是相關但完全獨立的概念。你可以在沒有Surface支持的EGLSurface上
繪畫,也可以在沒有EGL下使用Surface。EGLSurface僅僅是給GLES一個地方去畫。
ANativeWindow
公共的Surface類是在Java語言下實現的。在C/C++環境下,與之等價的是一個ANativeWindow 類, 部分由Android NDK暴露。你可以調用ANativeWindow_FromSurface函數
來從Surface獲得ANativeWindow。就像是它在Java環境下的表弟,你可以對其上鎖,在軟件方式下繪畫,然後解鎖並投遞。
要從本地化代碼中創建一個EGL window surface,你必須傳遞一個EGLNativeWindowType實例到eglCreateWindowSurface函數那裏。EGLNativeWindowType只
是ANativeWindow的同義詞,所以你可以隨意的cast一方到一方。
事實上,基本的”native window“類型僅僅是對BufferQueue的生產者端的一個包裝,不應大驚小怪。
SurfaceView和GLSurfaceView
到此,我們都瀏覽了底層的構件,是時後看看他們如何適應於app所基於的上層結構。
Android應用UI框架是基於對從View開始的分層。大部分的細節不會影響這裏討論,但有助瞭解UI元素把自己填進矩形區域所經歷複雜的測量和佈局過程。當應用被推到
前面時候,所有可視的View對象都渲染到由WindowManager設置的SurfaceFlinger創建的Surface。佈局以及渲染都是由應用的UI線程執行的。
不管你有多少個佈局和View,所有的事情都渲染到一個簡單的緩存。不管View是否硬件加速的,這都是正確的。
SurfaceView採取跟其他View一樣的參數,因此你可以給予它一個位置,大小,並給他配上其他元素。當要着色的時候,它內容卻完全是透明的。SurfaceView的View部分僅僅
是一個透明的佔位符。
當SurfaceView的View部分即將可見時,框架會要求WindowManager去要求SurfaceFlinger創建一個新的Surface。(這不是同步發生的,這就是爲什麼你必須提供一個回調
函數來通知你Surface創建完成)默認地,新Surface被置於應用app UI Surface之後,但默認的”Z-ordering“可以被覆蓋來使Surface置於頂部。
無論你着色什麼哦那個西到這個Surface,最終會由SurfaceFlinger合成,而不是由應用app。SurfaceView真正實力是:你獲取的Surface可以由分離的線程或分離的進程來着色,與由app UI執行的任何着色動作是隔離的,並且Buffer直接跑到SurfaceFlinger。你不能完全忽視UI線程--你依然要協調Activity的生命週期,還有你也許需要因爲大小或位置
的改變而做適當調整---但你自己擁有一整個Surface,由應用appUI來混合,其他圖層則交由Hardware Composer完成。
這個新Surface是BufferQueue的生產者,SurfaceFlinger是BufferQueue的消費者,花點時間注意這點是很值得的。你可以用任何可以提供給BufferQueue的機制來更新Surface
。你可以:使用Surface提供的Canvas函數,依附一個EGLSurface和GLES來在其上繪畫,並配置一個MediaCodec視頻解碼器來對它寫操作。
Composition 和 Hardware Scaler(合成與硬件縮放)
現在,我們有了更多一點的上下文了,回頭看看dumpsys SurfaceFlinger結果中我們之前忽略的幾項很有幫助。回到Hardware Composer討論,我們看下面的輸出結果:
<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 19px; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> frame name </span><span class="pun" style="color: rgb(102, 102, 0);">------------+-----------------------------------+--------------------------------</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">320.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">240.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">48</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">411</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1032</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1149</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">SurfaceView</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">/</span><span class="pln" style="color: rgb(0, 0, 0);">com</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">android</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">grafika</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="typ" style="color: rgb(102, 0, 102);">PlayMovieSurfaceActivity</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">75</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">StatusBar</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">144.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1776</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">NavigationBar</span><span class="pln" style="color: rgb(0, 0, 0);"> FB TARGET </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080.0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920.0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">|</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1080</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="lit" style="color: rgb(0, 102, 102);">1920</span><span class="pun" style="color: rgb(102, 102, 0);">]</span><span class="pln" style="color: rgb(0, 0, 0);"> HWC_FRAMEBUFFER_TARGET</span>
SurfaceTexure類是一個相對新來者,在Android3.0引入。如SurfaceView是Surface和View的結合,SurfaceTexture差不多是Surface和GLES Texture的結合。
當你創建一個SurfaceTexture,你創建了一個BufferQueue,並且其應用app作爲消費端。當新Buffer被生產者入列,你的應用app會由回調函數被通知(onFrameAvailable)。
你的應用app調用updateTextImage來釋放之前持有的Buffer,並從隊列請求一個新Buffer,並調用EGL的一些函數來使Buffer作爲外部texture對於GLES可用。
外部texture(GL_TEXTURE_EXTERNAL_OES)與由GLES(GL_TEXTURE_2D)創建的texture並相當一樣。你必須有點區別的配置你的着色器,並且有些事情你不能讓它們做
。但關鍵點是:你可以直接從你BufferQueue接收到的數據來着色紋理化的多邊形。
你也許想知道,我們如何可以保證Buffer的數據格式是GLES可以辨別的東西---gralloc 支持廣泛的格式。當SurfaceTexture 創建 BufferQueue時,它設置消費者使用標誌爲
GRALLOC_USAGE_HW_TEXTURE,確保任何由gralloc創建的Buffer對GLES有用。
因爲SurfaceTexture與EGL上下文交互,你必須小心地從合適線程去調用它的方法。這個在類文檔中被提出。
如果你更深入的閱讀類文檔,你也許會發現一些奇怪的調用。一個取時間戳,其他取變形矩陣,每一個的值都由前一個調用updateTexImage來設置。這證明了BufferQueue
不單單傳遞緩存句柄,而是更多的到消費者。每一個緩存都伴隨時間戳以及變形參數。
爲了效率,提供了轉換功能。在一些案例,源數據對於消費者也許是“錯誤”擺向;但我們不是先旋轉它在發送,相反我們可以把數據以目前的擺向並伴隨一個修正它的轉換來發
送。轉換矩陣可以與其他在已用的數據點上的轉換融合,這可以減少負載。
時間戳對於特定的Buffer源很有用。舉個例子,假設你把攝像頭的輸出連接到生產者接口(SetPreviewWith)。如果你想創建一個視頻,你必須爲每一幀設置呈現的時間戳;但
你希望時間戳基於幀被捕獲的時間,不是被你應用app接收Buffer的時間。隨Buffer提供的時間戳是由攝像頭代碼設置,導致一系列更加一致的時間戳。
SurfaceTexture和Surface
如果你仔細查看API,你將發現對於應用,只有一個路徑來創建一個簡單的surface,那就是通過把SurfaceTexture作爲唯一構造函數的參數來創建。(API11之前,根本沒有Surface的公共構造函數)。如果你把SurfaceTexture視爲Surface和Texture的結合,這也許有一點落伍了。
在引擎蓋子下面,SurfaceTexture被稱爲GLConsumer,更準確的反應了其作爲BufferQueue的消費者以及擁有者的角色。當你創建來自SurfaceTexture的Surface時,你正在做的事情是創建一個代表SurfaceTexture的BufferQueue生產者一方的對象。
案例學習:Grafika的“Continuous Capture” Activity
攝像頭可以提供適合錄製電影的幀流。如果你要在屏幕顯示,你創建一個SurfaceView,傳Surface給setPreviewDisplay,並讓生產者(攝像頭)與消費者(SurfaceFlinger)
做餘下的事情。如果你要錄製視頻,你調用MediaCodec的createInputSurface創建Surface,傳給攝像頭,並又一次你可以坐在那裏休息一下。如果你同時既要顯示視頻又要
錄製,那你就參與多點。
“Continuous capture” 應用顯示來自Camera中正在錄製的Video。這個案例,編碼的視頻被寫到內存中的環形Buffer,並可以隨時寫到磁盤裏。只要你跟蹤到每一個東西的位
置,那麼實現是簡單的。
有三個BufferQueue參與。應用app使用SurfaceTexture接收來自攝像頭的幀,並轉換爲一個外部的GLES texture。應用app聲明瞭一個SurfaceView,我們用它來顯示幀,並且我
們用一個輸入surface配置MediaCodec編碼器來創建視頻。因此,一個BufferQueue由app創建,一個由SurfaceFlinger,和一個由mediaserver。
圖2. Grafika‘s continuous capture activity
在上面的圖,箭頭顯示了來自攝像頭的數據傳播方向。BufferQueue標以顏色(紫色生產者,藍綠色消費者)。注意“Camera”實際上是在mediaserver進程。
編好的H.264 視頻走向應用app進程中內存的環形buffer,然後當“capture”按鈕按下,MediaMuxer類會用來把視頻數據以.mp4文件保存到磁盤。
所有的三個BufferQueue由應用app中一個單獨的EGL上下文來處理,GLES的操作是在UI線程中執行的。在UI線程中做SurfaceView着色一般是不建議的,但因爲我們正在做的
簡單操作是由GLES渠道異步處理的,因此這樣做應該沒問題。(如果視頻編碼器上鎖了且我們屏蔽了嘗試出列一個Buffer的動作,應用app將會變得無響應。但在那點上,不管
如何我們可能就失敗了)編碼數據的處理---管理環形buffer並寫到磁盤---是由分離的線程處理的。
大部分的配置是發生在SurfaceView的回調函數 surfaceCreated上的。EGLContext被創建,且EGLSurface也爲顯示和視頻編碼器而創建。當新一幀數據到達,我們會告訴
SurfaceTexture去獲取它並讓它作爲GLES texture的存在,接着對每一個EGLSurface用GLES命令來着色(傳遞來自Surface Texture的變形參數以及時間戳)。編碼線程
從MediaCodec拉取編碼輸出,並存放到內存。
TextureView
TexureView在Android 4.0引進。這是目前這裏討論最複雜的View對象,結合了View和SurfaceTexture。
回憶一下,SurfaceTexture是一個GL消費者,消費着圖形數據buffer並讓他們作爲texture的存在。TextureView封裝了SurfaceTexture,接管了響應回調函數以及請求新Buffer的
責任。新Buffer的到來,導致了TextureView產生了一個View刷新請求。當被要求繪畫時候,TextureView使用最近接收Buffer的內容作爲數據源,並在View狀態下指明它在什麼地點和什麼方式來着色。
你可以用GLES在TextureView如向你在SurfaceView一樣着色。只要把SurfaceTexture傳遞給EGL窗口的創建調用。然而,這樣做會暴露潛在問題。
在我們看到的大部分東西里,BufferQueue已經在不同的進程中傳遞buffer。當使用GLES着色一個TextureView,生產者和消費者都在一個進程,甚至都在一個線程。假設我們
從UI線程連續快速的提交幾個Buffers。EGL buffer交換調用將需要從BufferQueue出列一個buffer,它將會停止直到有一個buffer是存在。只有等到消費者請求一個buffer來着色
時候,纔會有可以被出列的buffer存在,但這個也是在這個UI線程發生的,因此我們被卡住了。
解決方案就是讓BufferQueue保證永遠有一個Bufer存在可以被出列,以至於Buffer交換調用不會停止。一個保證這個的方法就是讓BufferQueue在新buffer入列時候丟棄之前入列的Buffer的內容,並對最新Buffer數目和最大獲取Buffer數目設置限制。(如果你的隊列有三個Buffers,所有三個Buffer都已被消費者獲取,從而沒有Buffer可以出列,緩存的
交換調用就會失敗或掛起。因此我們必須防止消費者一次獲取超過2個buffers)。丟棄Buffer通常是不可取的,因此只有在特定條件下才會如此,如消費者和生產者在同一個進程中。
SurfaceView或TextureView?
SurfaceView和TextureView都擔當類似的角色,但卻有非常不同的實現。要決定哪一個是最優,需要對權衡取捨有一定的理解。
因爲TextureView是View層級裏的一個正常公民,它表現的像其他View,可以覆蓋或被其他元素覆蓋。你可以使用簡單的API調用執行任意的變形和獲取以Bitmap格式的內容
的操作。
與TextureView最大的區別是在合成階段的表現。對於SurfaceView,內容是被寫到一個SurfaceFlinger合成好分離的圖層,原則上帶有覆蓋。對於TextureView,View的合成
總是與GLES合成的,並且更新其內容也會引發其他View元素重畫(例如,他們被置於TextureView的頂部)。在View着色完成後,應用app UI圖層必須由SurfaceFlinger來與
其他圖層合成,因此你實際上合成了每個可視像素兩次。對於全屏視頻播放器,或僅僅作爲佈局在視頻之上的UI元素的任何應用,SurfaceView提供了更好的表現。
如早期注意的,DRM保護視頻僅能在一個覆蓋面上展現。支持內容保護的視頻播放器必須以SurfaceView來實現。
案例學習:Grafika’s 播放視頻(TextureView)
Grafika包含了一對視頻播放器,一個以TextureView實現,另外一個以SurfaceView實現。視頻解碼的內容,即從MediaCodec發往一個surface的幀,對兩個都是一樣的。最
感興趣的不同實現是要求展現正確縱橫比的階段。
當SurfaceView 要求一個FrameLayout的客製化實現,通過簡單配置變形矩陣給TextureView#setTransform函數就可以更改SurfaceTexture的大小。對於前者,你正在通過
WindowManager來發送新窗口位置以及大小給SurfaceFlinger;對於後者,你只要不同方式的着色它。
否則,兩個實現方式都遵循一樣的模式。一旦Surface被創建,播放被激活。當“play”被按下,視頻解碼線程就開始,Surface作爲輸出目標。之後,應用app代碼不需要做任何
事,---合成和顯示會要麼由SurfaceFlinger(對於SurfaceView)或要麼由TextureView處理。
案例學習:Grafika‘s 雙解碼
這個應用演示了在TextureView內部操作SurfaceTexture。
這個應用的基本組織結構是一對TextureView,並排的顯示不同的視頻。爲了模擬一個視頻會議應用的需求,我們要在應用因爲方向轉變而被停止並且被恢復情況下,保持
Mediaodec解碼器活躍。技巧就是你不能在沒有完全重新配置Surface情況下更改MediaCodec解碼器要用到的Surface,而重新配置會是一個相當繁重的操作;因此我們要保持
Surface處於活躍。Surface只是一個指向SurfaceTexture的BufferQueue生產者句柄,而且SurfaceTexture由TextureView管理。因此我們也要保持SurfaceTexture處於活躍。因
此,我們如何處理TextureView的拆除呢?
很巧合,TextureView提供了一個setSurfaceTexture的函數來處理我們希望做的。我們從TextureView獲取一個SurfaceTexture的參考,並保存在一個靜態字段裏。當
應用被關閉,我們從onSurfaceTextureDestroyed返回“false”來阻止SurfaceTexture的毀滅。當應用重新開始,我們把舊的SurfaceTexture填充到新的TextureView那裏。
TextureView類處理EGL 上下文的創建和銷燬。
每一個視頻解碼器從另外一個線程來驅動。第一眼看去,它似乎向EGL需要線程本地化一樣;但記住,解碼輸出的緩存實際上正從mediaserver被髮送到我們的BufferQueue的
消費者那裏(SurfaceTexture)。TextureView 幫我們操心渲染,而且它們在UI線程那裏執行。
以SurfaceView實現的應用也許會有一點難度。我們不能只是創建一對SurfaceView,並引導輸出到它們那裏,因爲Surface會在應用方向改變時候被銷燬。除此之外,那會增加
兩個圖層,而覆蓋層的數量限制會強烈的促使我們要保持圖層在最小的數目。因此相反的,我們要創建一對SurfaceTexture來接收來自視頻解碼器的輸出,並在應用執行渲染
工作,使用GLES渲染兩個四邊形texture到SurfaceView的Surface上。
Conclusion
我們希望這個頁面可以提供對於內視Android在系統層面上處理圖形的方法有很好的幫助。一些相關話題的信息和建議可以在下面的附錄中找到。
附錄A:Game Loops
一個很流行的實現遊戲循環的方法如下:
<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 19px; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="kwd" style="color: rgb(0, 0, 136);">while</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="pln" style="color: rgb(0, 0, 0);">playing</span><span class="pun" style="color: rgb(102, 102, 0);">)</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln" style="color: rgb(0, 0, 0);"> advance state </span><span class="kwd" style="color: rgb(0, 0, 136);">by</span><span class="pln" style="color: rgb(0, 0, 0);"> one frame render the </span><span class="kwd" style="color: rgb(0, 0, 136);">new</span><span class="pln" style="color: rgb(0, 0, 0);"> frame sleep </span><span class="kwd" style="color: rgb(0, 0, 136);">until</span><span class="pln" style="color: rgb(0, 0, 0);"> it</span><span class="pun" style="color: rgb(102, 102, 0);">’</span><span class="pln" style="color: rgb(0, 0, 0);">s time to </span><span class="kwd" style="color: rgb(0, 0, 136);">do</span><span class="pln" style="color: rgb(0, 0, 0);"> the </span><span class="kwd" style="color: rgb(0, 0, 136);">next</span><span class="pln" style="color: rgb(0, 0, 0);"> frame </span><span class="pun" style="color: rgb(102, 102, 0);">}</span>這個有一些問題,最基本的想法就是遊戲可以定義“frame”是什麼。不同的顯示會以不同的頻率刷新,以及刷新率會變化。如果你生產的幀比顯示他們的速度快,你不得不偶
爾丟棄一個。如果你生產的幀太慢,SurfaceFlinger會定期出現不能成功獲取一個新緩存,並會重新顯示前一幀。這兩個情況都會產生顯示的問題。
你所需要做的就是匹配好顯示幀率,並且依據從前一幀開始所過的時間多少來決定遊戲狀態的推進。有兩個方法來達到如此:1. 把BufferQueue填滿,並依靠來回地
“swap buffer”。2. 使用Choreographer(API 16+)
Queue Stuffing(隊列填充)
這是很容易實現: 只要能多快有多快的切換Buffer。在Android早期版本,SurfaceView#lockCanvas會讓你休眠100ms作爲懲罰。現在由BufferQueue來控制步調,並且
BufferQueue可以如SurfaceFlinger一樣快速的清空。
這種方法的例子可以在Android Breakout那裏看到。它使用GLSurfaceView,調用應用的OnDrawFrame回調然後交換Buffer來運行一個循環。如果BufferQueue是滿的,
eglSwapBuffers調用會等待直到有一個Buffer存在。當SurfaceFlinger釋放Buffers時候,它們就變得可獲取的,一般是在獲取一個新Buffer到顯示之後。因爲這個是在VSYNC
時刻發生的,你的繪畫週期時間將會匹配刷新率,大部分的。
這個方法也有一些問題。第一,app被綁在SurfaceFlinger activity,這個將依據需要做的工作多少以及是否需要與其他進程爭奪CPU時間來決定需要花費不同的時間。
既然你的遊戲狀態是依據切換緩存間隔的時間來推進,你的動畫就不會在固定的頻率刷新。當不是很一致運行在平均60fps下,但你很有可能不會察覺這個碰撞。
第二,第一對Buffer的切換會發生的非常迅速,這是因爲此時BufferQueue還不滿。幀之間計算的時間幾乎接近0,所以遊戲會產生一些不產生任何東西的幀。在遊戲中如
Breakout,會在每一次刷新時刻去更新屏幕,除了遊戲第一次啓動,隊列總是滿的,因此影響不會很明顯。遊戲經常暫停動畫,然後又回到儘可能快的模式,也許會看到奇怪的
打嗝。
Choreographer
Choreographer允許你設立會在下一個VSYNC時刻調用的回調函數。實際的VSYNC時機會作爲參數傳遞。所以即使你應用app沒有立刻喚醒,你依然有一個顯示開始刷新的
時刻計劃。使用則個值而不是當前時間,可以給你遊戲狀態更新邏輯提供一致的時間源。
不幸的,事實上在每一個VSYNC時刻後你獲得一個回調這事並不能保證你的回調被及時的執行或你不能快速的處理它。你的app要檢測落後的情況並人爲的丟棄一些幀。
在Grafika的“Record GL app” activity提供了這方面的例子。在一些設備上(Nexus 4 和 Nexus 5),如果你只坐在那裏看,那麼activity將會開始丟棄一些幀。GL 着色雖然很簡
單,但View元素常會被重繪,並且測量/佈局的傳遞在設備進入低功耗狀態下會花費很長時間。(依據systrace,Android 4.4系統上,時鐘變慢後它花費28ms而不是6ms。
如果你在屏幕上拖動你的手指,設備人爲你在於activity交互,因此時鐘會加速到比較高,你就從不會拋棄一幀)
簡單的補救就是在VSYNC時刻後如果當前時間超過N微妙,Choreographer回調就簡單的丟棄一幀。理想的N值是基於之前觀察的VSYNC的間隔。舉個例子,如果刷新週期在
16.7ms(60fps),如果你運行的滯後超過15ms,你也許會丟棄一幀。
如果你觀察“Record GL app”運行,你會看到丟棄幀計數器在增加,甚至可以在丟棄幀時候看到邊界在閃紅色。如果你的眼睛不是特別好,那麼你可能看不到動畫的斷續。在
60fps速度下,應用app會經常丟棄幀,只要動畫連續的以固定速度進行,則沒有任何人會注意這點。你能拋棄多少,在某程度上這是由你畫的內容,顯示的特性以及使用app
的人對於閃動有多敏感來決定。
Thread Management
一般來說,如果你在SurfaceView,GLSurfaceView,或 TextureView上着色,你要在指定的線程上進行。絕對不要在UI線程上做重活或一些不確定時間的事情上。
Breakout和“Record GL app” 使用指定的着色線程, 並且他們也在這個線程上更新動畫。只要遊戲狀態能迅速的刷新,這是一個很合理的方法。
其他遊戲完全的把遊戲邏輯和遊戲着色分開。如果你有一個簡單的遊戲,僅僅是每100毫秒移動一個塊,而不用做別的,你可以有一個指定的線程僅僅做如下:
<pre class="prettyprint" name="code" style="white-space: pre-wrap; word-wrap: break-word; font-size: 13px; margin-top: 0px; margin-bottom: 1em; color: rgb(0, 102, 0); line-height: 1.5; padding: 1em; overflow: auto; border: 1px solid rgb(221, 221, 221); background-color: rgb(247, 247, 247);"><span class="pln" style="color: rgb(0, 0, 0);">run</span><span class="pun" style="color: rgb(102, 102, 0);">()</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="typ" style="color: rgb(102, 0, 102);">Thread</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln" style="color: rgb(0, 0, 0);">sleep</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="lit" style="color: rgb(0, 102, 102);">100</span><span class="pun" style="color: rgb(102, 102, 0);">);</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="kwd" style="color: rgb(0, 0, 136);">synchronized</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="pln" style="color: rgb(0, 0, 0);">mLock</span><span class="pun" style="color: rgb(102, 102, 0);">)</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">{</span><span class="pln" style="color: rgb(0, 0, 0);"> moveBlock</span><span class="pun" style="color: rgb(102, 102, 0);">();</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">}</span><span class="pln" style="color: rgb(0, 0, 0);"> </span><span class="pun" style="color: rgb(102, 102, 0);">}</span>(你也許需要以睡眠時間爲基礎的固定時鐘來防止漂移----sleep() 不是很一致, moveBlock() 接受一個非零時間---但你懂的)
當繪畫代碼喚醒,它只是抓住鎖,獲取當前位置的塊,釋放鎖並開始繪畫。與其基於inter-frame的delta時間來做分級運動,你只有有一個線程移動事物,另外一個線程在繪畫
開始後繪畫出現在任何地方的東西。
對於擁有任何複雜度的場景,你會需要創建一個由喚醒時間排序的即將到來的事件列表,並休眠直到下一個事件到達,但這是一樣的思想。
附錄B: SurfaceView 和 Activity 生命週期
當使用SurfaceView時候,從其他線程而不是UI線程來着色Surface被認爲是很實際的做法。這引起了一個關於其他線程與Activity生命週期交互的問題。
1. Application onCreate / onResume / onPause
2. Surface create / changed / destroyed
當Activity開始,你的回調按照以下次序:
- onCreate
- onReusme
- surfaceCreated
- surfaceChanged
- onPause
- surfaceDestroyed (在Surface即將消失時候調用)
上述都是主要關注着色線程如何配置以及是否它是運行的。一個相關的關注是提取當activity被殺死時,來自線程的狀態。(in onPuase 或 onScreenInstantceState), 方法
#1 會比較好,因爲一旦着色線程被連接上,他的狀態可以不需要同步原語就能訪問。
你可以看方法#2 在Grafik的“Hardware scaler exercise”的例子
附錄C:使用systrace跟蹤BufferQueue
如果你真的需要了解圖像Buffer如何周圍移動,你需要使用systrace。系統層的圖形代碼結構化很好,如大多數相關的應用框架代碼一樣。使能“gfx”和“view”標籤,同時一般的
“sched"。
對如何高效使用systrace的全部描述會填充相當長的文檔。一個顯著的項就是在trace中出現的BufferQueue。如果你之前用過systrace,你可能會看到他們,但卻不肯定他們是
什麼。作爲例子,如果你在Grafik的”play video“跑的時候抓到一份trace,你可以看到一行標有”SurfaceView“。這行告訴你在給定時刻有多少個buffer在隊列中。
當app激活時,你會注意到這個值在增加---觸發Mediacoder解碼器渲染幀-----以及當SurfaceFlinger工作時候,消費Buffer時候,這個值在減少。如果你以30fps顯示視頻,隊列
的值會從0到1變化,因爲60fps頻率的顯示可以很容易的跟上源。(你也將注意到SurfaceFlinger僅僅在有事情做的時候才喚醒,並不是60次沒秒。系統儘量避免工作,在沒有
更新屏幕時刻會完全停止VSYNC)
如果你打開”play video“並抓到一份新trace,你會看到一行有很長名字的的一行(com.android.grafika/com.android.grafika.PlayMovieActivity"),這是主UI圖層,無疑這是另一
個BufferQueue。因爲TextureView着色到UI圖層,而不是另外一個圖層,你會在這裏看到所有的視頻驅動的更新。