A classe TextureView é um objeto de visualização que combina uma visualização com uma SurfaceTexture.
Renderização com OpenGL ES
Um objeto TextureView encapsula uma SurfaceTexture, respondendo a callbacks e adquirindo novos buffers. Quando uma TextureView adquire novos buffers, ela emite uma solicitação de invalidação de visualização e desenha usando o conteúdo do buffer mais recente como fonte de dados, renderizando onde e como o estado da visualização indica que deve ser.
O OpenGL ES (GLES) pode renderizar em uma TextureView transmitindo a SurfaceTexture para a chamada de criação da EGL, mas isso cria um problema. Quando o GLES renderiza em uma TextureView, os produtores e consumidores de BufferQueue estão na mesma thread, o que pode fazer com que a chamada de troca de buffer pare ou falhe. Por exemplo, se um produtor enviar vários buffers em rápida sucessão da linha de execução da UI, a chamada de troca de buffer EGL precisará remover um buffer da 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 vai ficar pendente ou falhar.
Para evitar interrupções na troca de buffers, o BufferQueue sempre precisa de um buffer disponível para remoção da fila. Para isso, o BufferQueue descarta o conteúdo do buffer adquirido anteriormente quando um novo buffer é enfileirado. Ele também impõe restrições às contagens mínimas e máximas de buffer para evitar que um consumidor use todos os buffers de uma só vez.
Escolher SurfaceView ou TextureView
O SurfaceView e o TextureView têm funções semelhantes e são elementos 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 melhor processamento de alfa e rotação do que uma SurfaceView, mas uma SurfaceView tem vantagens de performance ao compor elementos de interface em camadas sobre vídeos. Quando um cliente renderiza com um SurfaceView, ele 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 for compatível. Quando um cliente renderiza com uma TextureView, o kit de ferramentas de UI compõe o conteúdo da TextureView na hierarquia de visualização com a GPU. As atualizações no conteúdo podem fazer com que outros elementos de visualização sejam renderizados novamente, por exemplo, se as outras visualizações estiverem posicionadas em cima de um 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 reprodução da Grafika
O Play Video do Grafika inclui um par de 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.
Para dimensionar o SurfaceView, é necessário uma implementação personalizada do FrameLayout.
O WindowManager precisa enviar uma nova posição e novos valores de tamanho para o
SurfaceFlinger. Para escalonar a SurfaceTexture de uma TextureView, é necessário configurar uma matriz de transformação com TextureView#setTransform()
.
Depois de apresentar a proporção correta, as duas implementações seguem o mesmo padrão. Quando o SurfaceView/TextureView cria a superfície, o código do app ativa a reprodução. Quando um usuário toca em play, uma linha de execução de decodificação de vídeo é iniciada, 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
O Double Decode do Grafika demonstra a manipulação da SurfaceTexture em uma TextureView.
O Double Decode do Grafika usa um par de objetos TextureView para mostrar dois vídeos sendo reproduzidos 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 para a interface do produtor na BufferQueue da SurfaceTexture. Como o TextureView gerencia o SurfaceTexture, o cliente precisa manter o SurfaceTexture ativo para manter a superfície ativa.
Para manter a SurfaceTexture ativa, o Double Decode do Grafika recebe referências
a SurfaceTextures dos objetos TextureView e as salva em um campo estático.
Em seguida, a decodificação dupla do Grafika retorna false
de
TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()
para
evitar a destruição da SurfaceTexture. Em seguida, o TextureView transmite uma SurfaceTexture para onSurfaceTextureDestroyed()
, que pode ser mantida durante a mudança de configuração da atividade. O cliente transmite para o novo TextureView usando setSurfaceTexture()
.
Threads separados acionam cada decodificador de vídeo. O mediaserver envia buffers com saída decodificada para as SurfaceTextures, os consumidores do BufferQueue. Os objetos TextureView realizam a renderização e são executados na linha de execução de UI.
Implementar a decodificação dupla do 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.