2020-04-04 14:00+0900
안드로이드 MediaProjection 을 통해서 ImageReader로 버퍼 읽을시 검은 화면
우선 5.0(API 21) 이상에서는 대부분~ 잘되는데 특정 환경(대부분 예뮬레이터들)에서 MediaProjection
을 ImageReader
로 통해서 Surface
를 읽으면 아무 데이터가 없거나 NULL일 수 있다.
이는 네이티브, 예뮬 등 일부 환경에서 (물론 5.0도 이상일 수 있다!) openGLES 호환(EGL_RECORDABLE_ANDROID
값이 없는 등.. )이 좋지 않아서 발생하는 문제이다.
결론은 대상 문제가 발생하는 기기에서는 별도의 방법으로 처리해야 한다는 것… 일단 구글의 비공식 테스트 앱 (grafika)를 살펴보고 구현하면 된다.
우선 grafika의 gles
를 모드 임포트 해준다.
VirtualDisplay
가 사용할 수 있는Surface
를 만들어 준다.
eglCore = EglCore(null, EglCore.FLAG_TRY_GLES3)
eglConsumerSide = OffscreenSurface(eglCore, displayWidth, displayHeight)
eglConsumerSide!!.makeCurrent()
eglShader = Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT)
eglScreen = FullFrameRect(eglShader)
eglTextureId = eglScreen!!.createTextureObject()
eglTexture = SurfaceTexture(eglTextureId, false)
eglTexture!!.setDefaultBufferSize(displayWidth, displayHeight)
eglProducerSide = Surface(eglTexture)
eglTexture!!.setOnFrameAvailableListener(eglCallback)
eglBuffer = ByteBuffer.allocate(displayHeight * displayWidth * 4)
eglBuffer!!.order(ByteOrder.nativeOrder())
eglCurrentBitmap = Bitmap.createBitmap(displayWidth, displayHeight, Bitmap.Config.ARGB_8888)
virtualDisplay = mProjection!!.createVirtualDisplay(
"ScreenCapture",
displayWidth, displayHeight, screenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
eglProducerSide, null, null
)
- SurfaceTexture의 OnFrameAvailableListener
eglFrameRate++
eglConsumerSide?.makeCurrent()
eglTexture?.updateTexImage()
if (eglFrameRate < 10) {
return@OnFrameAvailableListener
}
eglTexture?.getTransformMatrix(eglMatrix)
eglConsumerSide?.makeCurrent()
eglScreen?.drawFrame(eglTextureId, eglMatrix)
eglConsumerSide?.swapBuffers()
eglBuffer?.rewind()
GLES20.glReadPixels(0, 0, displayWidth, displayHeight,
GLES10.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, eglBuffer)
eglBuffer?.rewind()
eglFrameRate = 0
eglCurrentBitmap?.copyPixelsFromBuffer(eglBuffer)
내 경우에는 퍼포먼스 향상을 위해 프레임이 10번 이상 변경될 때만 로직을 처리하도록 OnFrameAvailableListener에 elgFrameRate라는 변수를 추가했다. 이 부분은 알아서 판단하기 바람ㅎ
추가로 결과물의 Y축이 뒤집어져서(상하 반전) 출력될 수 있는데 Texture2dProgram
클래스의 VERTEX_SHADER
를 아래 값으로 수정한다.
= "uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uTexMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vec2 coordInterm = (uTexMatrix * aTextureCoord).xy;\n" +
" vTextureCoord = vec2(coordInterm.x, 1.0 - coordInterm.y);\n" +
"}\n";
테스트 결과 Bitmap을 정상적으로 가져올 수 있었다.
다만 퍼포먼스 문제로 인해(프레임 변화 시 5~10%의 CPU 사용) 별도의 루틴을 마련해서 기존 ImageReader
가 작동하지 않는 상황에서만 불가피하게 사용할 수 있도록 한다.
추가로, 각 Surface 등 Release가 가능한 것들은 Lifecycle에 맞춰 적절히 Release를 해주도록 한다.