TextureView

A classe TextureView é um objeto de visualização que combina uma visualização com uma SurfaceTexture.

Renderização com OpenGL ES

Um objeto TextureView envolve uma SurfaceTexture, respondendo a callbacks e adquirindo novos buffers. Quando uma TextureView adquire novos buffers, ela emitirá uma solicitação de invalidação de visualização e renderizará usando o conteúdo do buffer mais recente como fonte de dados, renderizando onde e como o estado da visualização indicar.

O OpenGL ES (GLES) pode renderizar em uma TextureView transmitindo a SurfaceTexture para a chamada de criação do EGL, mas isso cria um problema. Quando o GLES renderiza em uma TextureView, os produtores e consumidores da BufferQueue estão na mesma linha de execução, o que pode fazer com que a chamada de troca de buffer seja interrompida ou falhe. Por exemplo, se um produtor enviar vários buffers em sucessão rápida da linha de execução da interface, a chamada de troca de buffer do EGL precisará retirar um buffer da fila BufferQueue. No entanto, como o consumidor e o produtor estão na mesma linha de execução, não haverá buffers disponíveis, e a chamada de troca será interrompida ou falhará.

Para garantir que a troca de buffer não seja interrompida, a BufferQueue sempre precisa de um buffer disponível para ser removido da fila. Para implementar isso, o BufferQueue descarta o conteúdo do buffer adquirido anteriormente quando um novo buffer é colocado na fila e coloca restrições nas contagens de buffer mínima e máxima para evitar que um consumidor consuma todos os buffers de uma só vez.

Como escolher SurfaceView ou TextureView

SurfaceView e TextureView têm funções semelhantes e são cidadãos da hierarquia de visualização. No entanto, SurfaceView e TextureView têm implementações diferentes. Uma SurfaceView usa os mesmos parâmetros que outras visualizações, mas o conteúdo da SurfaceView é transparente quando renderizado.

Uma TextureView tem um melhor processamento de alfa e rotação do que uma SurfaceView, mas uma SurfaceView tem vantagens de desempenho ao compor elementos da interface em camadas sobre vídeos. Quando um cliente é renderizado com uma SurfaceView, ela fornece ao cliente uma camada de composição separada. O SurfaceFlinger compõe a camada separada como uma sobreposição de hardware, se o dispositivo oferecer suporte a ela. Quando um cliente renderiza com uma TextureView, o kit de ferramentas da interface combina o conteúdo da TextureView na hierarquia de visualização com a GPU. As atualizações do conteúdo podem fazer com que outros elementos de visualização sejam renderizados novamente, por exemplo, se as outras visualizações estiverem posicionadas na parte de cima de uma TextureView. Depois que a renderização da visualização é concluída, o SurfaceFlinger compõe a camada da interface do app e todas as outras camadas, para que cada pixel visível seja composto duas vezes.

Estudo de caso: vídeo de jogo da Grafika

O Grafika's Play Video inclui dois players de vídeo, um implementado com TextureView e outro com SurfaceView. A parte de decodificação de vídeo da atividade envia frames do MediaCodec para uma superfície para TextureView e SurfaceView. A maior diferença entre as implementações são as etapas necessárias para apresentar a proporção correta.

A escalação da SurfaceView requer uma implementação personalizada do FrameLayout. O WindowManager precisa enviar uma nova posição da janela e novos valores de tamanho para o SurfaceFlinger. A escala de uma SurfaceTexture de TextureView requer a configuração de uma matriz de transformação com TextureView#setTransform().

Depois de apresentar a proporção correta, ambas as implementações seguem o mesmo padrão. Quando a SurfaceView/TextureView cria a superfície, o código do app ativa a reprodução. Quando um usuário toca em play, ele inicia uma linha de execução de decodificação de vídeo, com a superfície como destino de saída. Depois disso, o código do app não faz nada. A composição e a exibição são processadas pelo SurfaceFlinger (para a SurfaceView) ou pela TextureView.

Estudo de caso: decodificação dupla da Grafika

A decodificação dupla da Grafika demonstra a manipulação da SurfaceTexture dentro de uma TextureView.

A decodificação dupla da Grafika usa um par de objetos TextureView para mostrar dois vídeos em reprodução lado a lado, simulando um app de videoconferência. Quando a orientação da tela muda e a atividade é reiniciada, os decodificadores MediaCodec não param, simulando a reprodução de um stream de vídeo em tempo real. Para melhorar a eficiência, o cliente precisa manter a superfície ativa. A superfície é um identificador da interface de produtor na BufferQueue da SurfaceTexture. Como o TextureView gerencia a SurfaceTexture, o cliente precisa manter a SurfaceTexture ativa para manter a superfície ativa.

Para manter a SurfaceTexture ativa, a decodificação dupla da Grafika recebe referências a SurfaceTextures dos objetos TextureView e as salva em um campo estático. Em seguida, o Double Decode da Grafika retorna false de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() para impedir a destruição da SurfaceTexture. A TextureView transmite uma SurfaceTexture para onSurfaceTextureDestroyed() que pode ser mantida durante a mudança de configuração da atividade, que o cliente transmite para a nova TextureView por setSurfaceTexture().

Os threads separados acionam cada decodificador de vídeo. O Mediaserver envia buffers com saída decodificada para as SurfaceTextures, os consumidores da BufferQueue. Os objetos TextureView realizam a renderização e são executados na linha de execução de IU.

Implementar a decodificação dupla da Grafika com SurfaceView é mais difícil do que implementar com TextureView porque os objetos SurfaceView destroem superfícies durante mudanças de orientação. Além disso, o uso de objetos SurfaceView adiciona duas camadas, o que não é ideal devido às limitações no número de sobreposições disponíveis no hardware.