TextureView

TextureView 클래스는 뷰를 SurfaceTexture와 결합하는 뷰 객체입니다.

OpenGL ES를 통한 테더링

TextureView 객체는 SurfaceTexture를 래핑하여 콜백에 응답하고 새 버퍼를 획득합니다. 새 버퍼를 획득한 TextureView는 뷰 무효화 요청을 실행하고 최신 버퍼의 콘텐츠를 데이터 소스로 사용하여 그리기를 실행한 후 뷰 상태에 표시된 내용에 따라 그대로 렌더링을 실행합니다.

OpenGL ES(GLES)는 SurfaceTexture를 EGL 생성 호출에 전달하여 TextureView에 대해 렌더링할 수 있지만 이 경우 문제가 발생합니다. GLES가 TextureView에 대해 렌더링하면 BufferQueue 생산자 및 소비자가 같은 스레드에 위치하며, 이로 인해 버퍼 전환 호출이 지연되거나 실패할 수 있습니다. 예를 들어 생산자가 UI 스레드에서 여러 개의 버퍼를 연달아 빠르게 제출하는 경우 EGL 버퍼 전환 호출은 버퍼를 BufferQueue에서 대기 해제해야 합니다. 하지만 소비자와 생산자가 같은 스레드에 있으므로 사용 가능한 버퍼가 없고 전환 호출은 중단되거나 실패합니다.

버퍼 전환이 지연되지 않도록 하려면 BufferQueue에 항상 대기 해제 가능한 버퍼가 있어야 합니다. 이를 구현하기 위해 BufferQueue는 새 버퍼가 큐에 대기할 때 이전에 획득한 버퍼의 콘텐츠를 폐기하고 최소 및 최대 버퍼 수에 제한을 적용하여 소비자가 모든 버퍼를 동시에 소비하지 못하게 합니다.

SurfaceView 또는 TextureView 선택

SurfaceView 및 TextureView는 비슷한 역할을 하며 둘 다 뷰 계층 구조의 시티즌입니다. 하지만 SurfaceView와 TextureView는 서로 구현이 다릅니다. SurfaceView는 다른 뷰와 동일한 매개변수를 취하지만 SurfaceView 콘텐츠는 렌더링 시 투명합니다.

TextureView는 SurfaceView보다 알파 및 회전 처리가 뛰어나지만 SurfaceView는 UI 요소로 계층화된 다른 동영상을 합성하는 경우 성능적 이점을 지닙니다. 클라이언트가 SurfaceView로 렌더링하면 SurfaceView는 클라이언트에 별도의 합성 레이어를 제공합니다. SurfaceFlinger는 기기에서 지원하는 경우 별도의 레이어를 하드웨어 오버레이로 작성합니다. 클라이언트가 TextureView로 렌더링하면 UI 툴킷은 TextureView의 콘텐츠를 GPU가 포함된 뷰 계층 구조로 합성합니다. 예를 들어 다른 뷰가 TextureView 위에 배치되면 콘텐츠 업데이트로 인해 다른 뷰 요소가 다시 그려질 수 있습니다. 뷰 렌더링이 완료되면 SurfaceFlinger는 앱 레이어 및 기타 모든 레이어를 합성합니다. 따라서 보이는 모든 픽셀이 두 번 합성됩니다.

우수사례: Grafika의 재생 동영상

Grafika의 재생 동영상에는 각각 TextureView 및 SurfaceView로 구현된 한 쌍의 동영상 플레이어가 포함됩니다. 활동의 동영상 디코딩 부분은 MediaCodec의 프레임을 TextureView 및 SurfaceView의 노출 영역으로 전송합니다. 두 구현 간의 가장 큰 차이점은 올바른 가로세로 비율을 표시하는 데 필요한 단계입니다.

SurfaceView를 확장하려면 FrameLayout의 맞춤 구현이 필요합니다. WindowManager는 새 창 위치와 새 크기 값을 SurfaceFlinger에 전송해야 합니다. TextureView의 SurfaceTexture를 확장하려면 TextureView#setTransform()으로 변환 매트릭스를 구성해야 합니다.

올바른 가로세로 비율을 표시한 후에는 두 구현 모두 동일한 패턴을 따라야 합니다. SurfaceView/TextureView가 노출 영역을 생성하면 앱 코드는 재생을 사용 설정합니다. 사용자가 재생을 탭하면 동영상 디코딩 스레드가 시작되고 노출 영역이 출력 타겟이 됩니다. 이후에는 앱 코드가 아무것도 하지 않으며, 구성 및 표시가 SurfaceFlinger(SurfaceView의 경우) 또는 TextureView에 의해 처리됩니다.

우수사례: Grafika의 이중 디코드

Grafika의 이중 디코드는 TextureView 내의 SurfaceTexture에 대한 조작을 보여 줍니다.

Grafika의 이중 디코드는 TextureView 객체 쌍으로 두 개의 동영상을 나란히 표시하여 화상 회의 앱을 시뮬레이션합니다. 화면 방향이 변경되고 활동이 재시작되면 MediaCodec 디코더가 중지되지 않고 실시간 동영상 스트림의 재생을 시뮬레이션합니다. 효율성을 개선하려면 클라이언트가 노출 영역 연결을 유지해야 합니다. 노출 영역은 SurfaceTexture의 BufferQueue에 위치한 생산자 인터페이스의 핸들입니다. TextureView는 SurfaceTexture를 관리하므로 클라이언트가 SurfaceTexture의 연결을 유지하여 노출 영역의 연결을 유지해야 합니다.

SurfaceTexture의 연결 유지를 위해 Grafika의 이중 디코드는 TextureView 객체에서 SurfaceTextures 참조를 가져온 후 정적 필드에 이를 저장합니다. 그런 다음 Grafika의 이중 디코드는 TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed()에서 false를 반환하여 SurfaceTexture가 삭제되지 않도록 합니다. 그러면 TextureView는 활동 구성 변경사항 전체에 걸쳐 유지 가능한 onSurfaceTextureDestroyed()에 SurfaceTexture를 전달합니다. 클라이언트는 setSurfaceTexture()를 통해 이러한 변경사항을 새 TextureView에 전달합니다.

개별 스레드는 각 동영상 디코더를 구동합니다. Mediaserver는 디코딩된 출력을 포함하는 버퍼를 BufferQueue의 소비자인 SurfaceTextures에 전송합니다. TextureView 객체는 렌더링을 진행하고 UI 스레드에서 실행합니다.

SurfaceView로 Grafika의 이중 디코드를 구현하기는 TextureView로 구현하는 것보다 어렵습니다. 이는 SurfaceView 객체가 방향 변경 도중 노출 영역을 삭제하기 때문입니다. 또한 SurfaceView 객체는 2개의 레이어를 추가합니다. 이 경우 하드웨어에서 사용 가능한 오버레이 수가 제한되므로 이는 이상적인 방식이 아닙니다.