TextureView

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

Renderizar com OpenGL ES

Um objeto TextureView encapsula uma SurfaceTexture, respondendo a callbacks e adquirindo novos buffers. Quando um objeto TextureView adquire novos buffers, TextureView 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 um objeto TextureView transmitindo o SurfaceTexture para a chamada de criação do EGL, mas isso cria um problema. Quando o GLES renderiza em um 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 de interface, 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, a 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 vez.

Escolher SurfaceView ou TextureView

SurfaceView e 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. Um objeto SurfaceView usa os mesmos parâmetros que outras visualizações, mas o conteúdo SurfaceView é transparente quando renderizado.

O TextureView tem melhor processamento de alfa e rotação do que o SurfaceView, mas o SurfaceView tem vantagens de desempenho ao compor elementos da interface em camadas sobre vídeos. Quando um cliente renderiza com um objeto SurfaceView, o SurfaceView 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 TextureView, o kit de ferramentas de UI combina o conteúdo do objeto 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 acima de 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 da Grafika 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.

O escalonamento de SurfaceView requer uma implementação personalizada de FrameLayout. O WindowManager precisa enviar uma nova posição e novos valores de tamanho para o SurfaceFlinger. Para dimensionar a SurfaceTexture de um objeto 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 SurfaceView ou 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 SurfaceView) ou pelo TextureView.

Estudo de caso: decodificação dupla da Grafika

O Double Decode do Grafika demonstra a manipulação da SurfaceTexture dentro de TextureView.

A decodificação dupla do Grafika usa um par de objetos TextureView para mostrar dois vídeos 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 objeto TextureView gerencia a SurfaceTexture, o cliente precisa manter a SurfaceTexture ativa 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, o Double Decode do Grafika retorna false de TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed() para evitar a destruição da SurfaceTexture. Em seguida, TextureView transmite uma SurfaceTexture para onSurfaceTextureDestroyed(), que pode ser mantida durante a mudança de configuração da atividade. O cliente transmite isso para o novo objeto 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 interface.

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.