TextureView

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

Effectuer le rendu avec OpenGL ES

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

OpenGL ES (GLES) peut effectuer le rendu sur un objet TextureView en transmettant la SurfaceTexture à l'appel de création EGL, mais cela pose un problème. Lorsque GLES effectue le rendu sur un TextureView, les producteurs et les consommateurs 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 envoie plusieurs tampons rapidement à partir du thread d'UI, l'appel d'échange de tampon EGL doit retirer un tampon de la BufferQueue. Cependant, comme le consommateur et le producteur se trouvent sur le même thread, aucun tampon ne sera disponible et l'appel d'échange sera suspendu ou échouera.

Pour éviter les blocages d'échange de tampon, BufferQueue a toujours besoin d'un tampon disponible pour la mise en file d'attente. Pour ce faire, BufferQueue supprime le contenu du tampon précédemment acquis lorsqu'un nouveau tampon est mis en file d'attente. Il impose également des restrictions sur le nombre minimal et maximal de tampons pour empêcher un consommateur de consommer tous les tampons à la fois.

Choisir SurfaceView ou TextureView

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

TextureView gère mieux l'alpha et la rotation que SurfaceView, mais SurfaceView offre des avantages en termes de performances lors de la composition d'éléments d'interface utilisateur superposés sur des vidéos. Lorsqu'un client effectue le rendu avec un objet SurfaceView, SurfaceView fournit au client un calque de composition distinct. SurfaceFlinger compose le calque distinct en tant que superposition matérielle si l'appareil le prend en charge. Lorsqu'un client effectue le rendu avec TextureView, la boîte à outils d'UI compose le contenu de l'objet 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 de TextureView. Une fois le rendu de la vue terminé, SurfaceFlinger compose la couche d'UI de l'application et toutes les autres couches, de sorte que chaque pixel visible est composé deux fois.

Étude de cas : Play Video de Grafika

Grafika's Play Video inclut une paire de lecteurs vidéo, l'un implémenté avec TextureView et l'autre avec SurfaceView. La partie décodage vidéo de l'activité envoie des frames de MediaCodec à une surface pour TextureView et SurfaceView. La principale différence entre les implémentations réside dans les étapes requises pour présenter le format correct.

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

Après avoir présenté le bon format, les deux implémentations suivent le même modèle. Lorsque SurfaceView ou TextureView crée la surface, le code de l'application permet la lecture. Lorsqu'un utilisateur appuie sur Lecture, un thread de décodage vidéo démarre, 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 : Double Decode de Grafika

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

La fonctionnalité Double Decode de Grafika utilise une paire d'objets TextureView pour afficher deux vidéos côte à côte, simulant une application de visioconfé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 maintenir la surface active. La surface est un handle de l'interface du producteur dans la BufferQueue de SurfaceTexture. Étant donné que l'objet TextureView gère SurfaceTexture, le client doit maintenir SurfaceTexture en vie pour maintenir la surface en vie.

Pour maintenir la SurfaceTexture en vie, la double décodage de Grafika obtient des références à SurfaceTextures à partir des objets TextureView et les enregistre dans un champ statique. Ensuite, la fonction Double Decode de Grafika renvoie false de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() pour éviter la destruction de SurfaceTexture. TextureView transmet ensuite une SurfaceTexture à onSurfaceTextureDestroyed() qui peut être conservée lors du changement de configuration de l'activité, que le client transmet au nouvel objet TextureView via setSurfaceTexture().

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

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