TextureView

La clase TextureView es un objeto de vista que combina una vista con un SurfaceTexture.

Renderización con OpenGL ES

Un objeto TextureView encapsula un SurfaceTexture, responde a las devoluciones de llamada y adquiere nuevos búferes. Cuando un TextureView adquiere búferes nuevos, emite una solicitud de invalidación de la vista y dibuja con el contenido del búfer más reciente como fuente de datos, renderizando donde y como lo indica el estado de la vista.

OpenGL ES (GLES) puede renderizar en un TextureView pasando el SurfaceTexture a la llamada de creación de EGL, pero esto genera un problema. Cuando GLES renderiza en un TextureView, los productores y consumidores de BufferQueue se encuentran en el mismo subproceso, lo que puede provocar que la llamada de intercambio de búferes 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úfer de EGL debe quitar un búfer de la BufferQueue. Sin embargo, debido a que el consumidor y el productor se encuentran en el mismo subproceso, no habrá búferes disponibles y la llamada de intercambio se bloqueará o fallará.

Para evitar bloqueos de intercambio de búferes, BufferQueue siempre necesita un búfer disponible para la extracción de la cola. Para ello, BufferQueue descarta el contenido del búfer adquirido anteriormente cuando se pone en cola un búfer nuevo. También impone restricciones en los recuentos de búferes mínimos y máximos para evitar que un consumidor utilice todos los búferes a la vez.

Cómo elegir entre SurfaceView y TextureView

SurfaceView y TextureView cumplen roles similares y son elementos 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 renderiza.

Un TextureView controla mejor la rotación y el canal alfa que un SurfaceView, pero un SurfaceView tiene ventajas de rendimiento cuando se componen elementos de la IU superpuestos sobre videos. Cuando un cliente renderiza con un SurfaceView, este 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 un TextureView, el kit de herramientas de IU compone el contenido del TextureView en la jerarquía de vistas con la GPU. Las actualizaciones del contenido pueden hacer que se vuelvan a dibujar otros elementos de la vista, por ejemplo, si las otras vistas se posicionan sobre una TextureView. Una vez que se completa la renderización de la vista, SurfaceFlinger compone la capa de IU de la app y todas las demás capas, de modo que cada píxel visible se compone dos veces.

Caso de éxito: Reproducir video de Grafika

Grafika's Play Video 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 ajuste de escala de SurfaceView requiere una implementación personalizada de FrameLayout. WindowManager debe enviar nuevos valores de posición y tamaño de la ventana a SurfaceFlinger. Para escalar el SurfaceTexture de un TextureView, se 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 o TextureView crean la superficie, el código de la app habilita la reproducción. Cuando un usuario presiona play, 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: Doble decodificación de Grafika

Grafika's Double Decode muestra la manipulación de SurfaceTexture dentro de un TextureView.

La función Double Decode de Grafika usa un par de objetos TextureView para mostrar dos videos que se reproducen uno al lado del otro, lo que simula una app de videoconferencias. 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 activa la superficie. La superficie es un identificador para la interfaz del productor en el BufferQueue de SurfaceTexture. Dado que TextureView administra SurfaceTexture, el cliente debe mantener activo SurfaceTexture para mantener activa la superficie.

Para mantener activo el SurfaceTexture, el Double Decode de Grafika obtiene referencias a SurfaceTextures de los objetos TextureView y los guarda en un campo estático. Luego, la función Double Decode de Grafika devuelve false de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() para evitar la destrucción de SurfaceTexture. Luego, TextureView 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().

Cada decodificador de video se ejecuta en un subproceso independiente. Mediaserver envía búferes con la salida decodificada a los SurfaceTextures, los consumidores de BufferQueue. Los objetos TextureView realizan la renderización y se ejecutan en el subproceso de IU.

Implementar la función Double Decode de Grafika con SurfaceView es más difícil que hacerlo con TextureView, ya que los objetos SurfaceView destruyen las superficies durante los cambios de orientación. Además, usar objetos SurfaceView agrega dos capas, lo que no es ideal debido a las limitaciones en la cantidad de superposiciones disponibles en el hardware.