TextureView類別是將視圖與 SurfaceTexture 組合在一起的視圖物件。
使用 OpenGL ES 渲染
一個TextureView物件包裝了一個SurfaceTexture,回應回呼並取得新的緩衝區。當TextureView取得新的緩衝區時,TextureView會發出視圖無效請求,並使用最新緩衝區的內容作為其資料來源進行繪製,在視圖狀態指示的任何位置和方式進行渲染。
OpenGL ES (GLES)可以透過將 SurfaceTexture 傳遞給 EGL 建立呼叫來在 TextureView 上進行渲染,但這會產生問題。當 GLES 在 TextureView 上渲染時,BufferQueue 生產者和消費者位於同一執行緒中,這可能會導致緩衝區交換呼叫停止或失敗。例如,如果生產者從 UI 執行緒快速連續提交多個緩衝區,則 EGL 緩衝區交換呼叫需要從 BufferQueue 中出列緩衝區。但是,由於消費者和生產者位於同一執行緒上,因此不會有任何可用緩衝區,且交換呼叫會掛起或失敗。
為了確保緩衝區交換不會停止,BufferQueue 總是需要一個可用於出列的緩衝區。為了實現這一點,當新緩衝區排隊時,BufferQueue 會丟棄先前取得的緩衝區的內容,並對最小和最大緩衝區計數進行限制,以防止使用者一次消耗所有緩衝區。
選擇SurfaceView或TextureView
SurfaceView 和TextureView 扮演類似的角色,而且都是視圖層次結構的公民。然而,SurfaceView和TextureView有不同的實作。 SurfaceView 採用與其他視圖相同的參數,但 SurfaceView 內容在渲染時是透明的。
與 SurfaceView 相比,TextureView 具有更好的 Alpha 和旋轉處理能力,但在影片上分層合成 UI 元素時,SurfaceView 具有效能優勢。當客戶端使用 SurfaceView 進行渲染時,SurfaceView 為客戶端提供了單獨的組合層。如果裝置支援,SurfaceFlinger 將單獨的層組成為硬體覆蓋層。當客戶端使用TextureView進行渲染時,UI工具包會使用GPU將TextureView的內容合成到視圖層次結構中。內容更新可能會導致其他視圖元素重繪,例如,如果其他視圖位於 TextureView 之上。視圖渲染完成後,SurfaceFlinger 會合成套用 UI 圖層和所有其他圖層,以便每個可見像素都合成兩次。
案例研究:Grafika 的播放視頻
Grafika的Play Video包含一對影片播放器,一個使用TextureView實現,另一個使用SurfaceView實作。該活動的視訊解碼部分將幀從 MediaCodec 發送到 TextureView 和 SurfaceView 的表面。實現之間最大的差異是呈現正確的寬高比所需的步驟。
縮放 SurfaceView 需要 FrameLayout 的自訂實作。 WindowManager需要向SurfaceFlinger傳送新的視窗位置和新的大小值。縮放TextureView的SurfaceTexture需要使用TextureView#setTransform()
配置轉換矩陣。
提供正確的縱橫比後,兩種實作都遵循相同的模式。當 SurfaceView/TextureView 建立表面時,應用程式程式碼將啟用播放。當使用者點擊play時,它會啟動視訊解碼線程,並將表面作為輸出目標。此後,應用程式程式碼不執行任何操作 - 合成和顯示由 SurfaceFlinger(對於 SurfaceView)或 TextureView 處理。
案例研究:Grafika 的雙重解碼
Grafika 的 Double Decode示範了對 TextureView 內的 SurfaceTexture 的操作。
Grafika的Double Decode使用一對TextureView物件來顯示兩個並排播放的視頻,模擬視訊會議應用程式。當螢幕方向改變且 Activity 重新啟動時,MediaCodec 解碼器不會停止,模擬即時視訊串流的播放。為了提高效率,客戶應該保持表面活躍。 Surface 是 SurfaceTexture 的 BufferQueue 中生產者介面的句柄。因為TextureView管理SurfaceTexture,所以客戶端需要保持SurfaceTexture處於活動狀態才能使表面保持活動狀態。
為了保持 SurfaceTexture 處於活動狀態,Grafika 的 Double Decode 從 TextureView 物件取得 SurfaceTextures 的引用,並將它們保存在靜態欄位中。然後,Grafika的Double Decode從TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()
傳回false
,以防止SurfaceTexture被破壞。然後,TextureView 將 SurfaceTexture 傳遞給onSurfaceTextureDestroyed()
,該 SurfaceTexture 可以在活動配置變更期間進行維護,客戶端透過setSurfaceTexture()
將其傳遞給新的 TextureView 。
單獨的線程驅動每個視頻解碼器。 Mediaserver 將帶有解碼輸出的緩衝區傳送到 SurfaceTextures(BufferQueue 消費者)。 TextureView 物件執行渲染並在 UI 執行緒上執行。
使用SurfaceView 實作Grafika 的雙重解碼比使用TextureView 實作更困難,因為SurfaceView 物件在方向變更期間會破壞表面。此外,使用 SurfaceView 物件會添加兩個圖層,這並不理想,因為硬體上可用的疊加層數量有限。