우선 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를 해주도록 한다.