TextureView

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

Rendu avec OpenGL ES

Un objet TextureView encapsule un SurfaceTexture, répondant aux rappels et acquérant de nouveaux tampons. Lorsqu'une TextureView acquiert de nouveaux tampons, elle é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 une TextureView en transmettant la SurfaceTexture à l'appel de création EGL, mais cela pose un problème. Lorsque GLES effectue le rendu sur une TextureView, les producteurs et les consommateurs BufferQueue se trouvent dans le même thread, ce qui peut entraîner l'arrêt 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, étant donné que 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 entre SurfaceView et TextureView

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

TextureView gère mieux la transparence et la rotation qu'une SurfaceView, mais une 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 une SurfaceView, celle-ci lui fournit une couche de composition distincte. SurfaceFlinger compose la couche distincte en tant que superposition matérielle si l'appareil le prend en charge. Lorsqu'un client effectue le rendu avec une TextureView, le kit d'outils d'UI compose le contenu de la 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'une 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

Play Video de Grafika inclut deux 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'une 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 schéma. Lorsque SurfaceView/TextureView crée la surface, le code de l'application active 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 comment manipuler la SurfaceTexture à l'intérieur d'une 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 TextureView gère SurfaceTexture, le client doit maintenir SurfaceTexture en vie pour maintenir la surface en vie.

Pour maintenir la SurfaceTexture active, la double décodage de Grafika obtient des références aux 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 nouveau 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 couches, ce qui n'est pas idéal en raison des limites du nombre de calques disponibles sur le matériel.