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, il envoie une requête d'invalidation de la vue et dessine à l'aide du contenu du dernier tampon comme source de données, en affichant l'élément où et comme l'état de la vue l'indique.
OpenGL ES (GLES) peut effectuer un rendu sur un TextureView en transmettant la SurfaceTexture à l'appel de création EGL, mais cela crée un problème. Lorsque GLES effectue un rendu sur une TextureView, les producteurs et les consommateurs de BufferQueue se trouvent dans le même thread, ce qui peut entraîner l'arrêt ou l'échec de l'appel de remplacement de tampon. Par exemple, si un producteur envoie plusieurs tampons en succession rapide à partir du thread d'interface utilisateur, l'appel de remplacement de tampon EGL doit retirer un tampon de la file d'attente BufferQueue. Toutefois, comme le consommateur et le producteur se trouvent sur le même thread, aucun tampon n'est disponible et l'appel de swap se bloque ou échoue.
Pour s'assurer que l'échange de mémoire tampon ne se bloque pas, BufferQueue a toujours besoin d'une mémoire tampon disponible à déqueuer. 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 minimal et maximal 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 font tous deux partie de la hiérarchie des vues. Cependant, SurfaceView et TextureView ont des implémentations différentes. Une SurfaceView utilise les mêmes paramètres que les autres vues, mais son contenu est transparent lors de l'affichage.
Une TextureView offre une meilleure gestion de l'alpha et de 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 à des vidéos. Lorsqu'un client effectue un rendu avec une SurfaceView, la SurfaceView fournit au client une couche de composition distincte. SurfaceFlinger compose la couche distincte en superposition matérielle si l'appareil est compatible. Lorsqu'un client effectue un rendu avec une TextureView, le kit 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 la recomposition 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'interface utilisateur de l'application et toutes les autres couches, de sorte que chaque pixel visible soit composé deux fois.
Étude de cas: Vidéo de jeu de Grafika
Play Video de Grafika inclut une paire de lecteurs vidéo, l'un implémenté avec TextureView et l'autre avec SurfaceView. La partie de l'activité consacrée au décodage vidéo envoie des images de MediaCodec vers une surface pour TextureView et SurfaceView. La plus grande 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 une nouvelle position de fenêtre et de nouvelles valeurs de taille à SurfaceFlinger. La mise à l'échelle de la SurfaceTexture d'une TextureView nécessite de configurer une matrice de transformation avec TextureView#setTransform()
.
Après avoir présenté le format correct, 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 Play (Lecture), un thread de décodage vidéo est lancé, 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 décodage de Grafika
Double décodage de Grafika montre comment manipuler SurfaceTexture dans une TextureView.
Le double décodage 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 gestionnaire de l'interface du producteur dans la file d'attente de tampons 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, le double décodage de Grafika obtient des références aux SurfaceTextures à partir des objets TextureView et les enregistre dans un champ statique.
Ensuite, le double décodage de Grafika renvoie false
à partir de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()
pour éviter la destruction de SurfaceTexture. TextureView transmet ensuite une SurfaceTexture à onSurfaceTextureDestroyed()
qui peut être conservée lors de la modification de la configuration de l'activité, que le client transmet à la nouvelle 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 de 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 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 calques, ce qui n'est pas idéal en raison des limites imposées au nombre de superpositions disponibles sur le matériel.