La clase TextureView es un objeto de vista que combina una vista con SurfaceTexture.
Renderizado con OpenGL ES
Un objeto TextureView envuelve una SurfaceTexture, respondiendo a devoluciones de llamada y adquiriendo nuevos buffers. Cuando un TextureView adquiere nuevos buffers, TextureView emite una solicitud de invalidación de vista y dibuja utilizando el contenido del buffer más nuevo como fuente de datos, renderizando donde y como el estado de la vista indique que debería hacerlo.
OpenGL ES (GLES) puede renderizar en TextureView pasando SurfaceTexture a la llamada de creación de EGL, pero esto crea un problema. Cuando GLES se procesa en TextureView, los productores y consumidores de BufferQueue están en el mismo subproceso, lo que puede provocar que la llamada de intercambio de búfer se detenga o falle. Por ejemplo, si un productor envía varios buffers en rápida sucesión desde el subproceso de la interfaz de usuario, la llamada de intercambio de buffer EGL necesita retirar un buffer de BufferQueue. Sin embargo, debido a que el consumidor y el productor están en el mismo hilo, no habrá buffers disponibles y la llamada de intercambio se bloquea o falla.
Para garantizar que el intercambio de búfer no se detenga, BufferQueue siempre necesita un búfer disponible para quitarlo de la cola. Para implementar esto, BufferQueue descarta el contenido del búfer adquirido previamente cuando se pone en cola un nuevo búfer y impone restricciones en los recuentos mínimo y máximo de búfer para evitar que un consumidor consuma todos los búfer a la vez.
Elegir SurfaceView o TextureView
SurfaceView y TextureView cumplen funciones similares y ambos son ciudadanos de la jerarquía de vistas. Sin embargo, SurfaceView y TextureView tienen implementaciones diferentes. Un SurfaceView toma los mismos parámetros que otras vistas, pero el contenido de SurfaceView es transparente cuando se representa.
Un TextureView tiene un mejor manejo de alfa y rotación que un SurfaceView, pero un SurfaceView tiene ventajas de rendimiento al componer elementos de la interfaz de usuario superpuestos a videos. Cuando un cliente renderiza con SurfaceView, SurfaceView le proporciona al cliente una capa de composición separada. SurfaceFlinger compone la capa separada como una superposición de hardware si el dispositivo lo admite. Cuando un cliente renderiza con TextureView, el kit de herramientas de la interfaz de usuario compone el contenido de TextureView en la jerarquía de vistas con la GPU. Las actualizaciones del contenido pueden hacer que otros elementos de la vista se vuelvan a dibujar, por ejemplo, si las otras vistas están ubicadas encima de TextureView. Una vez que se completa la representación de la vista, SurfaceFlinger compone la capa de la interfaz de usuario de la aplicación y todas las demás capas, de modo que cada píxel visible se componga dos veces.
Estudio de caso: Play Video de Grafika
Play Video de Grafika incluye un par de reproductores de vídeo, uno implementado con TextureView y otro implementado con SurfaceView. La parte de decodificación de video de la actividad envía fotogramas desde MediaCodec a una superficie tanto para TextureView como para SurfaceView. La mayor diferencia entre las implementaciones son los pasos necesarios para presentar la relación de aspecto correcta.
Escalar SurfaceView requiere una implementación personalizada de FrameLayout. WindowManager necesita enviar una nueva posición de ventana y nuevos valores de tamaño a SurfaceFlinger. Escalar SurfaceTexture de TextureView requiere configurar una matriz de transformación con TextureView#setTransform()
.
Después de presentar la relación de aspecto correcta, ambas implementaciones siguen el mismo patrón. Cuando SurfaceView/TextureView crea la superficie, el código de la aplicación habilita la reproducción. Cuando un usuario toca reproducir , inicia un hilo de decodificación de video, con la superficie como destino de salida. Después de eso, el código de la aplicación no hace nada: la composición y la visualización las maneja SurfaceFlinger (para SurfaceView) o TextureView.
Estudio de caso: doble decodificación de Grafika
Double Decode de Grafika demuestra la manipulación de SurfaceTexture dentro de TextureView.
Double Decode de Grafika utiliza un par de objetos TextureView para mostrar dos videos reproducidos uno al lado del otro, simulando una aplicación de videoconferencia. Cuando la orientación de la pantalla cambia y la actividad se reinicia, los decodificadores MediaCodec no se detienen, simulando la reproducción de una transmisión de video en tiempo real. Para mejorar la eficiencia, el cliente deberá mantener viva la superficie. La superficie es un identificador de la interfaz del productor en BufferQueue de SurfaceTexture. Debido a que TextureView administra SurfaceTexture, el cliente necesita mantener viva SurfaceTexture para mantener viva la superficie.
Para mantener viva SurfaceTexture, Double Decode de Grafika obtiene referencias a SurfaceTextures de los objetos TextureView y las guarda en un campo estático. Luego, la doble decodificación de Grafika devuelve false
de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()
para evitar la destrucción de SurfaceTexture. TextureView luego pasa un SurfaceTexture a onSurfaceTextureDestroyed()
que se puede mantener durante el cambio de configuración de la actividad, que el cliente pasa al nuevo TextureView a través de setSurfaceTexture()
.
Hilos separados controlan cada decodificador de vídeo. Mediaserver envía buffers con salida decodificada a SurfaceTextures, los consumidores de BufferQueue. Los objetos TextureView realizan renderizado y se ejecutan en el subproceso de la interfaz de usuario.
Implementar la doble decodificación de Grafika con SurfaceView es más difícil que implementar con TextureView porque los objetos SurfaceView destruyen las superficies durante los cambios de orientación. Además, el uso de objetos SurfaceView agrega dos capas, lo cual no es ideal debido a las limitaciones en la cantidad de superposiciones disponibles en el hardware.