TextureView

TextureView 類別是檢視區塊物件,可將檢視區塊與 SurfaceTexture 合併。

使用 OpenGL ES 算繪

TextureView 物件會包裝 SurfaceTexture,回應回呼並取得新緩衝區。TextureView 取得新緩衝區時,會發出檢視區塊失效要求,並使用最新緩衝區的內容做為資料來源進行繪製,在檢視區塊狀態指出應顯示的位置和方式進行算繪。

OpenGL ES (GLES) 可以藉由將 SurfaceTexture 傳遞至 EGL 建立呼叫,在 TextureView 上算繪,但這會造成問題。當 GLES 在 TextureView 上算繪時,BufferQueue 的生產者和消費者會位於同一執行緒,這可能會導致緩衝區交換呼叫停滯或失敗。舉例來說,如果生產端從 UI 執行緒快速連續提交多個緩衝區,EGL 緩衝區交換呼叫需要從 BufferQueue 取消緩衝區佇列。不過,由於取用端和生產端位於同一個執行緒,因此不會有任何緩衝區可用,交換呼叫會暫停或失敗。

為避免緩衝區交換停滯,BufferQueue 一律需要可供取消佇列的緩衝區。為此,當新的緩衝區加入佇列時,BufferQueue 會捨棄先前取得的緩衝區內容。此外,系統也會限制緩衝區數量下限和上限,避免消費者一次耗用所有緩衝區。

選擇 SurfaceView 或 TextureView

SurfaceView 和 TextureView 的角色類似,都是檢視區塊階層的成員。不過,SurfaceView 和 TextureView 的實作方式不同。SurfaceView 會採用與其他檢視區塊相同的參數,但算繪時,SurfaceView 內容會是透明的。

TextureView 的 Alpha 和旋轉處理功能優於 SurfaceView,但合成疊加在影片上的 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 建立 Surface 時,應用程式程式碼會啟用播放功能。使用者輕觸「播放」時,系統會啟動影片解碼執行緒,並將 Surface 做為輸出目標。之後,應用程式程式碼不會執行任何動作,組合和顯示作業會由 SurfaceFlinger (適用於 SurfaceView) 或 TextureView 處理。

個案研究:Grafika 的雙重解碼

Grafika 的 Double Decode 示範如何操控 TextureView 內的 SurfaceTexture。

Grafika 的 Double Decode 會使用一對 TextureView 物件並排顯示兩部影片,模擬視訊會議應用程式。當螢幕方向改變且活動重新啟動時,MediaCodec 解碼器不會停止,模擬即時影片串流的播放。為提升效率,用戶端應保持介面運作。這個介面是 SurfaceTexture 的 BufferQueue 中生產端介面的控制代碼。由於 TextureView 會管理 SurfaceTexture,因此用戶端必須讓 SurfaceTexture 維持運作,才能讓 Surface 維持運作。

為保持 SurfaceTexture 存續,Grafika 的 Double Decode 會從 TextureView 物件取得 SurfaceTexture 的參照,並將其儲存在靜態欄位中。接著,Grafika 的 Double Decode 會從 TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() 傳回 false,避免 SurfaceTexture 遭到破壞。接著,TextureView 會將 SurfaceTexture 傳遞至 onSurfaceTextureDestroyed(),這項作業可在活動設定變更時維持不變,而用戶端會透過 setSurfaceTexture() 將 SurfaceTexture 傳遞至新的 TextureView。

每個影片解碼器都會使用獨立的執行緒。Mediaserver 會將含有解碼輸出的緩衝區傳送至 SurfaceTextures (BufferQueue 消費者)。TextureView 物件會在 UI 執行緒上執行轉譯作業。

使用 SurfaceView 實作 Grafika 的 Double Decode 比使用 TextureView 困難,因為 SurfaceView 物件會在方向變更期間毀損表面。此外,使用 SurfaceView 物件會新增兩層,但硬體可用的疊加層數量有限,因此這並非理想做法。