우선 5.0(API 21) 이상에서는 대부분~ 잘되는데 특정 환경(대부분 예뮬레이터들)에서 MediaProjectionImageReader로 통해서 Surface를 읽으면 아무 데이터가 없거나 NULL일 수 있다.

이는 네이티브, 예뮬 등 일부 환경에서 (물론 5.0도 이상일 수 있다!) openGLES 호환(EGL_RECORDABLE_ANDROID 값이 없는 등.. )이 좋지 않아서 발생하는 문제이다.

결론은 대상 문제가 발생하는 기기에서는 별도의 방법으로 처리해야 한다는 것… 일단 구글의 비공식 테스트 앱 (grafika)를 살펴보고 구현하면 된다.

우선 grafika의 gles를 모드 임포트 해준다.

  1. 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
)

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