본문 바로가기

JAVA

안드로이드 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를 해주도록 한다.