Home [Android] 동영상에 그림 그리기 (8) - BufferQueue, WindowManager, SurfaceFlinger
Post
Cancel

[Android] 동영상에 그림 그리기 (8) - BufferQueue, WindowManager, SurfaceFlinger

이전 포스트들에서 SurfaceTexture, Surface, MediaCodec 등 미디어, 그래픽에 쓰이는 API들에 대해 다루었습니다.. 이러한 API들의 역할과 사용 방법에 대해서는 다루었지만, 작동 방식에 대해 조금 더 깊이 알아보고 싶어서 Google I/O ‘18의 Drawn out: How Android renders 라는 주제의 발표와 공식 문서의 내용들을 조합하여 정리하려 합니다.

이번 포스트에서 다루는 내용들은 개발자가 직접 조작이나 설정해야 하는 부분은 아니지만, 그래픽이 어떤 과정을 거쳐 화면에 표시되는지 이해하는 것에 도움이 될 것입니다.

arch

(출처: AOSP 그래픽)

BufferQueue

BufferQueue는 이름 그대로 (그래픽) 버퍼의 큐입니다. 버퍼의 수는 생성할 때 결정할 수 있습니다.

중요한 부분은 BufferQueue는 두 개의 엔드포인트를 가지는데, 생산자와 소비자라고 합니다.

bufferqueue

(출처: AOSP 그래픽)

생산자는 dequeueBuffer()를 호출하여 BufferQueue에서 버퍼를 가져와 소유권을 얻습니다. 그 후 생산자 라는 이름에 맞게 내용을 채워넣을 수 있습니다. 그게 Canvas가 될 수도 있고, OpenGL ES가 될 수도 있으며, 픽셀 데이터를 직접 넣을 수도 있습니다.

생산자가 버퍼에 데이터를 모두 넣은 후에는, 다시 BufferQueue에 버퍼를 queueBuffer() 호출로 돌려줍니다. SurfaceTexture 클래스에서 OpenGL ES 렌더링이 끝난 후 eglSwapBuffers()를 호출하는 것도 이 queueBuffer() 과정에 해당합니다.

생산자의 역할은 끝났으니, 이제는 소비자 차례입니다. 소비자는 acquireBuffer()를 호출하여 BufferQueue에서 버퍼를 가져옵니다. 소비자는 가져온 버퍼로 원하는 작업을 수행하고(지난 포스트의 MediaCodec을 예로 들자면, 버퍼를 가져와서 MediaMuxer에 작성하였습니다.) releaseBuffer()로 다시 BufferQueue에 버퍼를 돌려줍니다.

이렇게 생산자와 소비자가 그래픽 버퍼를 BufferQueue를 통해 교환할 수 있기 때문에, 생산자와 소비자가 다른 성격의 API이더라도 그래픽 데이터를 전달할 수 있습니다. 따라서 이 앱에서 동영상을 재생하는 MediaPlayer로부터 OpenGL ES를 사용하는 SurfaceTexture에 프레임 단위로 동영상을 전달하는 것이 가능했던 것이죠.

BufferQueue는 소비자에 해당하는 컴포넌트에서 생성하여, 나머지 하나의 엔드포인트에 해당하는 생산자에게 Surface를 넘겨주어 버퍼를 채우도록 합니다.

이전에 다뤘던 SurfaceBufferQueue의 생산자쪽 엔드포인트에 전달하여 그래픽 버퍼를 가져오기 위해 사용된 것입니다. 이 엔드포인트의 반대편, 즉 소비자는 주로 SurfaceFlinger로, Window들의 그래픽 버퍼를 각각 BufferQueue로 모아 화면을 구성하는 역할을 합니다. 또한 제가 이전 포스트들에서 다룬 SurfaceTexture, MediaCodec API도 BufferQueue의 소비자 엔드포인트로 동작할 수 있습니다. SurfaceFlinger를 소비자로 사용하게 되는 방법 중 하나는 SurfaceView API를 사용하는 것인데, 이는 다음 포스트에서 그림이 그려진 동영상을 재생하는 내용을 다루면서 작성하도록 하겠습니다.

WindowManager

SurfaceFlinger를 소비자로 하는 생산자는 WindowManager 가 있습니다.

windowmanager_surfaceflinger

SurfaceFlinger는 버퍼를 WindowManager로부터 받아 합성하여 새로운 버퍼를 디스플레이로 보냅니다.

WindowManager는 SurfaceFlinger가 Surface를 디스플레이에 합성하는데 사용하는 버퍼와 Window의 메타데이터를 SurfaceFlinger에 제공합니다.

우리가 Dialog, Toast, Activity와 같은 요소들을 만들 때, Window 객체가 생성됩니다. 그리고 SurfaceFlinger 쪽에도 Window에 해당하는 객체가 있는데, 이를 Layer 라고 합니다.

Layer는 소비자의 역할로 BufferQueue를 생성하고 소유합니다. 그리고 Surface를 생산자에 해당하는 API들에게 전달하여 그래픽 버퍼들을 SurfaceFlinger로 가져올 수 있게 됩니다.

SurfaceFlinger

surfaceflinger

(출처: AOSP 그래픽)

SurfaceFlinger는 이와 같이 Layer로부터 그래픽 버퍼를 가져옵니다. 이러한 Layer는 하나가 아닌 여러 개이고, 여러 소스로부터 그래픽 버퍼를 가져옵니다.

위 그림처럼 홈 화면이나 상태 표시줄, 시스템 UI 등은 각각의 BufferQueue를 통해 따로 그래픽 버퍼가 들어오게 됩니다.

생산자들은 그래픽 버퍼에 데이터를 계속 입력할 수 있지만, 소비자인 SurfaceFlingerBufferQueue에서 그래픽 버퍼를 항상 가져오지 않습니다. 디스플레이 새로고침 사이에서만 버퍼를 가져옵니다. 이 새로고침 주기는 일반적으로 초당 60번, 즉 60fps로 동작하고 1프레임은 약 16ms동안 지속됩니다.

이렇게 새로운 버퍼를 탐색할 수 있도록 새로고침 신호를 주는 것을 VSYNC라고 합니다.

SurfaceFlinger는 (60fps로 가정하고)16ms라는 짧은 시간 내에 Layer 목록에서 새로운 버퍼를 찾아 갱신하고, 새 버퍼가 없다면 기존의 버퍼를 계속 사용합니다.

그래픽 버퍼를 모두 수집한 후에는 하드웨어 컴포저(HWC)에 합성 유형을 묻고, SurfaceFlinger가 이에 해당하는 Layer를 합성한 후, 출력 버퍼를 하드웨어 컴포저에 전달합니다.

이 이후 구성은 하드웨어에 가까운 내용이기도 하고, 저의 현재 지식 수준으로는 이해하기 어려운 내용이 많아 여기까지 작성하겠습니다. 이후 내용은 하드웨어 추상 계층(HAL)을 통해 디스플레이 하드웨어 OEM에 의해 수행됩니다.

(참고자료: SurfaceFlinger 및 WindowManager)

This post is licensed under CC BY 4.0 by the author.

[Android] 동영상에 그림 그리기 (7) - MediaCodec으로 인코딩

[Android] 동영상에 그림 그리기 (9) - SurfaceView로 화면 표시