TextureView

La classe TextureView è un oggetto di visualizzazione che combina una visualizzazione con una SurfaceTexture.

Rendering con OpenGL ES

Un oggetto TextureView esegue il wrapping di una SurfaceTexture, rispondendo ai callback e acquisendo nuovi buffer. Quando un TextureView acquisisce nuovi buffer, emette una richiesta di invalidazione della visualizzazione e disegna utilizzando i contenuti del buffer più recente come origine dati, eseguendo il rendering ovunque e comunque lo stato della visualizzazione indica che dovrebbe.

OpenGL ES (GLES) può eseguire il rendering su un TextureView passando SurfaceTexture alla chiamata di creazione EGL, ma questo crea un problema. Quando GLES esegue il rendering su una TextureView, i produttori e i consumatori di BufferQueue si trovano nello stesso thread, il che può causare l'interruzione o l'errore della chiamata di scambio del buffer. Ad esempio, se un produttore invia diversi buffer in rapida successione dal thread UI, la chiamata di scambio del buffer EGL deve estrarre un buffer dalla BufferQueue. Tuttavia, poiché il consumer e il producer si trovano nello stesso thread, non saranno disponibili buffer e la chiamata di scambio si bloccherà o non andrà a buon fine.

Per evitare interruzioni dello scambio di buffer, BufferQueue ha sempre bisogno di un buffer disponibile per l'estrazione dalla coda. A questo scopo, BufferQueue elimina i contenuti del buffer acquisito in precedenza quando viene accodato un nuovo buffer. Inoltre, impone restrizioni al conteggio minimo e massimo dei buffer per impedire a un consumer di utilizzare tutti i buffer contemporaneamente.

Scegliere SurfaceView o TextureView

SurfaceView e TextureView svolgono ruoli simili e fanno parte della gerarchia di visualizzazione. Tuttavia, SurfaceView e TextureView hanno implementazioni diverse. Una SurfaceView accetta gli stessi parametri delle altre visualizzazioni, ma i contenuti di SurfaceView sono trasparenti durante il rendering.

TextureView gestisce meglio l'alfa e la rotazione rispetto a SurfaceView, ma SurfaceView offre vantaggi in termini di prestazioni quando si compongono elementi dell'interfaccia utente a livelli sopra i video. Quando un client esegue il rendering con una SurfaceView, quest'ultima fornisce al client un livello di composizione separato. SurfaceFlinger compone il livello separato come overlay hardware se supportato dal dispositivo. Quando un client esegue il rendering con un elemento TextureView, il toolkit UI compone i contenuti di TextureView nella gerarchia delle visualizzazioni con la GPU. Gli aggiornamenti ai contenuti potrebbero causare il ridisegno di altri elementi della visualizzazione, ad esempio se le altre visualizzazioni sono posizionate sopra un TextureView. Al termine del rendering della visualizzazione, SurfaceFlinger compone il livello dell'interfaccia utente dell'app e tutti gli altri livelli, in modo che ogni pixel visibile venga composto due volte.

Case study: Grafika's Play Video

Grafika's Play Video include una coppia di video player, uno implementato con TextureView e uno con SurfaceView. La parte di decodifica video dell'attività invia i frame da MediaCodec a una superficie per TextureView e SurfaceView. La differenza maggiore tra le implementazioni sono i passaggi necessari per presentare le proporzioni corrette.

Il ridimensionamento di SurfaceView richiede un'implementazione personalizzata di FrameLayout. WindowManager deve inviare nuovi valori di posizione e dimensioni della finestra a SurfaceFlinger. Il ridimensionamento di SurfaceTexture di TextureView richiede la configurazione di una matrice di trasformazione con TextureView#setTransform().

Dopo aver presentato le proporzioni corrette, entrambe le implementazioni seguono lo stesso pattern. Quando SurfaceView/TextureView crea la superficie, il codice dell'app consente la riproduzione. Quando un utente tocca Riproduci, viene avviato un thread di decodifica video, con la superficie come destinazione di output. Dopodiché, il codice dell'app non fa nulla: la composizione e la visualizzazione vengono gestite da SurfaceFlinger (per SurfaceView) o da TextureView.

Case study: Grafika's Double Decode

Grafika's Double Decode mostra la manipolazione di SurfaceTexture all'interno di TextureView.

La funzionalità Double Decode di Grafika utilizza una coppia di oggetti TextureView per mostrare due video riprodotti uno accanto all'altro, simulando un'app di videoconferenza. Quando l'orientamento dello schermo cambia e l'attività viene riavviata, i decoder MediaCodec non si arrestano, simulando la riproduzione di uno stream video in tempo reale. Per migliorare l'efficienza, il client deve mantenere attiva la superficie. La superficie è un handle dell'interfaccia del producer in BufferQueue di SurfaceTexture. Poiché TextureView gestisce SurfaceTexture, il client deve mantenere attivo SurfaceTexture per mantenere attiva la superficie.

Per mantenere attiva la SurfaceTexture, la doppia decodifica di Grafika ottiene riferimenti alle SurfaceTexture dagli oggetti TextureView e li salva in un campo statico. Quindi, Double Decode di Grafika restituisce false da TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() per impedire la distruzione di SurfaceTexture. TextureView passa quindi una SurfaceTexture a onSurfaceTextureDestroyed() che può essere mantenuta durante la modifica della configurazione dell'attività, che il client passa al nuovo TextureView tramite setSurfaceTexture().

Thread separati gestiscono ogni decodificatore video. Mediaserver invia buffer con output decodificato a SurfaceTexture, i consumer di BufferQueue. Gli oggetti TextureView eseguono il rendering e vengono eseguiti sul thread UI.

L'implementazione della doppia decodifica di Grafika con SurfaceView è più difficile rispetto all'implementazione con TextureView perché gli oggetti SurfaceView distruggono le superfici durante i cambi di orientamento. Inoltre, l'utilizzo di oggetti SurfaceView aggiunge due livelli, il che non è l'ideale a causa delle limitazioni al numero di overlay disponibili sull'hardware.