紋理視圖

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 的內容在渲染時是透明的。

TextureView 比 SurfaceView 具有更好的 alpha 和旋轉處理能力,但 SurfaceView 在合成分層在視頻上的 UI 元素時具有性能優勢。當客戶端使用 SurfaceView 進行渲染時,SurfaceView 會為客戶端提供單獨的合成層。如果設備支持,SurfaceFlinger 會將單獨的層組成為硬件覆蓋。當客戶端使用 TextureView 進行渲染時,UI 工具包將 TextureView 的內容與 GPU 組合到視圖層次結構中。對內容的更新可能會導致其他視圖元素重繪,例如,如果其他視圖位於 TextureView 的頂部。視圖渲染完成後,SurfaceFlinger 將應用 UI 層和所有其他層合成,這樣每個可見像素都會合成兩次。

案例研究:Grafika 的播放視頻

Grafika 的 Play Video包括一對視頻播放器,一個用 TextureView 實現,一個用 SurfaceView 實現。 Activity 的視頻解碼部分將來自 MediaCodec 的幀發送到 TextureView 和 SurfaceView 的表面。實現之間的最大區別是呈現正確縱橫比所需的步驟。

縮放 SurfaceView 需要 FrameLayout 的自定義實現。 WindowManager 需要向 SurfaceFlinger 發送新的窗口位置和新的大小值。縮放 TextureView 的 SurfaceTexture 需要使用TextureView#setTransform()配置轉換矩陣。

在呈現正確的縱橫比之後,兩種實現都遵循相同的模式。當 SurfaceView/TextureView 創建表面時,應用代碼啟用播放。當用戶點擊play時,它會啟動一個視頻解碼線程,並將表面作為輸出目標。之後,應用程序代碼不做任何事情——合成和顯示由 SurfaceFlinger(用於 SurfaceView)或 TextureView 處理。

案例研究:Grafika 的雙重解碼

Grafika 的雙重解碼演示了對 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() ,可以在活動配置更改期間進行維護,客戶端通過setSurfaceTexture()將其傳遞給新的 TextureView。

單獨的線程驅動每個視頻解碼器。 Mediaserver 將帶有解碼輸出的緩衝區發送到 SurfaceTextures,即 BufferQueue 消費者。 TextureView 對像在 UI 線程上執行渲染和執行。

使用 SurfaceView 實現 Grafika 的雙重解碼比使用 TextureView 實現更難,因為 SurfaceView 對像在方向更改期間會破壞表面。此外,使用 SurfaceView 對象會添加兩層,這並不理想,因為硬件上可用的疊加層數量有限。