TextureView

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

Rendering con OpenGL ES

Un oggetto TextureView esegue il wrapping di un SurfaceTexture, risponde ai callback e acquisisce nuovi buffer. Quando un oggetto TextureView acquisisce nuovi buffer, TextureView invia 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 indichi che dovrebbe.

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

Per evitare blocchi dello scambio di buffer, BufferQueue ha sempre bisogno di un buffer disponibile per la rimozione dalla coda. A questo scopo, BufferQueue elimina i contenuti del buffer acquisito in precedenza quando viene messo in coda un nuovo buffer. Inoltre, impone limitazioni al numero minimo e massimo di buffer per impedire a un consumatore di consumare tutti i buffer contemporaneamente.

Scegliere SurfaceView o TextureView

SurfaceView e TextureView svolgono ruoli simili e fanno parte della gerarchia di oggetti View. Tuttavia, SurfaceView e TextureView hanno implementazioni diverse. Un oggetto 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 sovrapposti ai video. Quando un client esegue il rendering con un oggetto SurfaceView, SurfaceView 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 TextureView, il toolkit dell'interfaccia utente compone i contenuti dell'oggetto TextureView nella gerarchia di oggetti View con la GPU. Gli aggiornamenti dei contenuti potrebbero causare il ridisegno di altri elementi della visualizzazione, ad esempio se le altre visualizzazioni sono posizionate sopra 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: Play Video di Grafika

Play Video di Grafika include una coppia di video player, uno implementato con TextureView e uno implementato con SurfaceView. La parte di decodifica video dell'attività invia frame da MediaCodec a una superficie sia per TextureView sia per SurfaceView. La differenza principale 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 un oggetto 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 o TextureView crea la superficie, il codice dell'app abilita la riproduzione. Quando un utente tocca Riproduci, viene avviato un thread di decodifica video, con la superficie come target di output. Dopodiché, il codice dell'app non esegue alcuna operazione: la composizione e la visualizzazione vengono gestite da SurfaceFlinger (per SurfaceView) o da TextureView.

Case study: Double Decode di Grafika

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

Double Decode di Grafika utilizza una coppia di oggetti TextureView per mostrare due video riprodotti fianco a fianco, 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 produttore in BufferQueue di SurfaceTexture. Poiché l'oggetto TextureView gestisce SurfaceTexture, il client deve mantenere attivo SurfaceTexture per mantenere attiva la superficie.

Per mantenere attivo SurfaceTexture, Double Decode di Grafika ottiene riferimenti a 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 un SurfaceTexture a onSurfaceTextureDestroyed() che può essere mantenuto durante la modifica della configurazione dell'attività, che il client passa al nuovo oggetto TextureView tramite setSurfaceTexture().

Thread separati gestiscono ogni decoder video. Mediaserver invia buffer con output decodificato a SurfaceTexture, i consumatori di BufferQueue. Gli oggetti TextureView eseguono il rendering e vengono eseguiti nel thread dell'interfaccia utente.

L'implementazione di Double Decode di Grafika con SurfaceView è più difficile rispetto all'implementazione con TextureView, perché gli oggetti SurfaceView distruggono le superfici durante le modifiche dell'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.