TextureView

La classe TextureView est un objet de vue qui combine une vue avec une SurfaceTexture.

Rendu avec OpenGL ES

Un objet TextureView encapsule une SurfaceTexture, répond aux rappels et acquiert de nouveaux tampons. Lorsqu'un TextureView acquiert de nouveaux tampons, un TextureView émet une demande d'invalidation de vue et dessine en utilisant le contenu du tampon le plus récent comme source de données, rendant le rendu où et comme l'état d'affichage l'indique.

OpenGL ES (GLES) peut effectuer le rendu sur un TextureView en transmettant la SurfaceTexture à l'appel de création EGL, mais cela crée un problème. Lorsque GLES s'affiche sur TextureView, les producteurs et les consommateurs de BufferQueue se trouvent dans le même thread, ce qui peut entraîner le blocage ou l'échec de l'appel d'échange de tampon. Par exemple, si un producteur soumet plusieurs tampons en succession rapide à partir du thread de l'interface utilisateur, l'appel d'échange de tampon EGL doit retirer un tampon de la file d'attente de BufferQueue. Cependant, comme le consommateur et le producteur sont sur le même thread, aucun tampon n'est disponible et l'appel d'échange se bloque ou échoue.

Pour garantir que l'échange de tampon ne s'arrête pas, BufferQueue a toujours besoin d'un tampon disponible pour être retiré de la file d'attente. Pour implémenter cela, BufferQueue supprime le contenu du tampon précédemment acquis lorsqu'un nouveau tampon est mis en file d'attente et impose des restrictions sur le nombre minimum et maximum de tampons pour empêcher un consommateur de consommer tous les tampons en même temps.

Choisir SurfaceView ou TextureView

SurfaceView et TextureView remplissent des rôles similaires et sont tous deux citoyens de la hiérarchie des vues. Cependant, SurfaceView et TextureView ont des implémentations différentes. Un SurfaceView prend les mêmes paramètres que les autres vues, mais le contenu de SurfaceView est transparent lors du rendu.

Un TextureView a une meilleure gestion de l'alpha et de la rotation qu'un SurfaceView, mais un SurfaceView présente des avantages en termes de performances lors de la composition d'éléments d'interface utilisateur superposés à des vidéos. Lorsqu'un client effectue un rendu avec un SurfaceView, celui-ci fournit au client un calque de composition distinct. SurfaceFlinger compose la couche séparée en tant que superposition matérielle si elle est prise en charge par l'appareil. Lorsqu'un client effectue un rendu avec TextureView, la boîte à outils de l'interface utilisateur compose le contenu de TextureView dans la hiérarchie des vues avec le GPU. Les mises à jour du contenu peuvent entraîner le redessin d’autres éléments de vue, par exemple si les autres vues sont positionnées au-dessus d’un TextureView. Une fois le rendu de la vue terminé, SurfaceFlinger compose la couche d'interface utilisateur de l'application et toutes les autres couches, de sorte que chaque pixel visible soit composé deux fois.

Étude de cas : la vidéo Play de Grafika

Play Video de Grafika comprend une paire de lecteurs vidéo, un implémenté avec TextureView et un implémenté avec SurfaceView. La partie décodage vidéo de l'activité envoie des images de MediaCodec à une surface pour TextureView et SurfaceView. La plus grande différence entre les implémentations réside dans les étapes nécessaires pour présenter le rapport hauteur/largeur correct.

La mise à l’échelle de SurfaceView nécessite une implémentation personnalisée de FrameLayout. WindowManager doit envoyer une nouvelle position de fenêtre et de nouvelles valeurs de taille à SurfaceFlinger. La mise à l'échelle de SurfaceTexture d'un TextureView nécessite la configuration d'une matrice de transformation avec TextureView#setTransform() .

Après avoir présenté le rapport hauteur/largeur correct, les deux implémentations suivent le même modèle. Lorsque SurfaceView/TextureView crée la surface, le code de l'application active la lecture. Lorsqu'un utilisateur appuie sur play , il démarre un fil de décodage vidéo, avec la surface comme cible de sortie. Après cela, le code de l'application ne fait rien : la composition et l'affichage sont gérés par SurfaceFlinger (pour SurfaceView) ou par TextureView.

Étude de cas : le double décodage de Grafika

Le Double Decode de Grafika démontre la manipulation de la SurfaceTexture à l'intérieur d'un TextureView.

Le Double Decode de Grafika utilise une paire d'objets TextureView pour afficher deux vidéos jouées côte à côte, simulant une application de vidéoconférence. Lorsque l'orientation de l'écran change et que l'activité redémarre, les décodeurs MediaCodec ne s'arrêtent pas, simulant la lecture d'un flux vidéo en temps réel. Pour améliorer l'efficacité, le client doit garder la surface vivante. La surface est un handle vers l'interface du producteur dans BufferQueue de SurfaceTexture. Étant donné que TextureView gère la SurfaceTexture, le client doit conserver la SurfaceTexture active pour maintenir la surface en vie.

Pour conserver SurfaceTexture en vie, le Double Decode de Grafika obtient des références aux SurfaceTextures à partir des objets TextureView et les enregistre dans un champ statique. Ensuite, le Double Decode de Grafika renvoie false depuis TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() pour empêcher la destruction de SurfaceTexture. TextureView transmet ensuite une SurfaceTexture à onSurfaceTextureDestroyed() qui peut être conservée tout au long du changement de configuration de l'activité, que le client transmet au nouveau TextureView via setSurfaceTexture() .

Des threads séparés pilotent chaque décodeur vidéo. Mediaserver envoie des tampons avec une sortie décodée aux SurfaceTextures, les consommateurs BufferQueue. Les objets TextureView effectuent le rendu et s'exécutent sur le thread de l'interface utilisateur.

L'implémentation du Double Decode de Grafika avec SurfaceView est plus difficile que l'implémentation avec TextureView car les objets SurfaceView détruisent les surfaces lors des changements d'orientation. De plus, l'utilisation d'objets SurfaceView ajoute deux couches, ce qui n'est pas idéal en raison des limitations du nombre de superpositions disponibles sur le matériel.