La clase TextureView es un objeto View que combina una vista con una SurfaceTexture.
Renderización con OpenGL ES
Un objeto TextureView une un SurfaceTexture, responde a devoluciones de llamada y adquiere búferes nuevos. Cuando un TextureView adquiere búferes nuevos, emite una solicitud de invalidación de vista y dibuja con el contenido del búfer más reciente como fuente de datos, y se renderiza donde y como el estado de la vista indique que debe hacerlo.
OpenGL ES (GLES) puede renderizar en una TextureView pasando la SurfaceTexture a la llamada de creación de EGL, pero esto genera un problema. Cuando GLES renderiza en una TextureView, los productores y consumidores de BufferQueue se encuentran 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 búferes en rápida sucesión desde el subproceso de IU, la llamada de intercambio de búferes de EGL debe quitar un búfer de la cola de BufferQueue. Sin embargo, debido a que el consumidor y el productor están en el mismo subproceso, no habrá búferes disponibles y la llamada de intercambio se bloqueará o fallará.
Para garantizar que el intercambio de búferes no se detenga, BufferQueue siempre necesita un búfer disponible para quitar de la cola. Para implementar esto, BufferQueue descarta el contenido del búfer adquirido anteriormente cuando se pone en cola un búfer nuevo y coloca restricciones en los recuentos de búfer mínimos y máximos para evitar que un consumidor consuma todos los búferes a la vez.
Cómo elegir SurfaceView o TextureView
SurfaceView y TextureView cumplen funciones similares y son parte de la jerarquía de vistas. Sin embargo, SurfaceView y TextureView tienen implementaciones diferentes. Una SurfaceView toma los mismos parámetros que otras vistas, pero el contenido de SurfaceView es transparente cuando se renderiza.
Un objeto TextureView tiene un mejor manejo de alfa y rotación que un objeto SurfaceView, pero un objeto SurfaceView tiene ventajas de rendimiento cuando se combinan elementos de la IU en capas sobre videos. Cuando un cliente renderiza con una SurfaceView, esta le proporciona al cliente una capa de composición independiente. SurfaceFlinger compone la capa separada como una superposición de hardware si el dispositivo la admite. Cuando un cliente renderiza con una TextureView, el kit de herramientas de la IU 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 se ubican en la parte superior de una TextureView. Una vez que se completa la renderización de vistas, SurfaceFlinger compone la capa de la IU de la app y todas las demás capas, de modo que cada píxel visible se componga dos veces.
Caso de éxito: Video de reproducción de Grafika
Play Video de Grafika incluye un par de reproductores de video, uno implementado con TextureView y otro con SurfaceView. La parte de decodificación de video de la actividad envía fotogramas de MediaCodec a una superficie para TextureView y SurfaceView. La mayor diferencia entre las implementaciones son los pasos necesarios para presentar la relación de aspecto correcta.
El escalamiento de SurfaceView requiere una implementación personalizada de FrameLayout.
WindowManager debe enviar una nueva posición de ventana y nuevos valores de tamaño a SurfaceFlinger. Para escalar la SurfaceTexture de un TextureView, debes 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 app habilita la reproducción. Cuando un usuario presiona reproducir, se inicia un subproceso de decodificación de video, con la superficie como destino de salida. Después de eso, el código de la app no hace nada; SurfaceFlinger (para SurfaceView) o TextureView controlan la composición y la visualización.
Caso de éxito: Decodificado doble de Grafika
La decodificación doble de Grafika demuestra la manipulación de SurfaceTexture dentro de un TextureView.
La decodificación doble de Grafika usa un par de objetos TextureView para mostrar dos videos que se reproducen en paralelo, lo que simula una app de videoconferencia. Cuando cambia la orientación de la pantalla y se reinicia la actividad, los decodificadores de MediaCodec no se detienen, lo que simula la reproducción de una transmisión de video en tiempo real. Para mejorar la eficiencia, el cliente debe mantener la superficie activa. La superficie es un identificador de la interfaz del productor en el BufferQueue de SurfaceTexture. Debido a que TextureView administra la SurfaceTexture, el cliente debe mantenerla activa para mantener la superficie activa.
Para mantener la SurfaceTexture activa, la decodificación doble de Grafika obtiene referencias a SurfaceTextures de los objetos TextureView y las guarda en un campo estático.
Luego, la decodificación doble de Grafika muestra false
de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()
para evitar la destrucción de SurfaceTexture. Luego, TextureView pasa una SurfaceTexture a onSurfaceTextureDestroyed()
que se puede mantener durante el cambio de configuración de la actividad, que el cliente pasa a la nueva TextureView a través de setSurfaceTexture()
.
Cada decodificador de video tiene subprocesos independientes. Mediaserver envía búferes con salida decodificada a SurfaceTextures, los consumidores de BufferQueue. Los objetos TextureView realizan la renderización y se ejecutan en el subproceso de IU.
Implementar la decodificación doble de Grafika con SurfaceView es más difícil que implementarla con TextureView, ya que los objetos SurfaceView destruyen las superficies durante los cambios de orientación. Además, el uso de objetos SurfaceView agrega dos capas, lo que no es ideal debido a las limitaciones en la cantidad de superposiciones disponibles en el hardware.