MediacCodec GLSurfaceView 렌더링

임재현·2022년 7월 20일
1

Android의 MediaCodec에서 디코딩된 영상Frame을 Surfaceivew로 렌더링하는 방법은 크게 두가지가 있다.

1. getOutputBuffer() 또는 getOutputImage()을 통해 디코딩된 YUV Frame데이터를 GLSurfaceView에 직접그리는 방법
2. Mediacodec에 SurfaceView의 Surface를 전달하여 Mediacdoec에서 렌더링하는 하는 방법

두 가지 방법중 텍스트앱의 구현 상황에 적합한 방법을 선택할수 있지만 2번째의 방법의 성능이 훨씬 좋다고 말할 수 있다.
Mediacodec에서 SurfaceView를 사용하여 렌더링 했을 경우 최종적으로 SurfaceFlinger의 별로 layer위에서 그려지는데 많은 채널의 Mediacodec에서 렌더링하고 Release할 때 ANR이 발생한다. Android 12이상에서 발생하는 SurfaceFlinger버그 인듯 하다.
E/ClientCache: failed to erase buffer, could not retrieve buffer
위 에러로그가 출력되는 동안 UI가 Block된다.
4채널 이상의 영상을 Mediacodec을 통해 렌더링할 경우 SurfaceView가 아닌 GLSurfaceView를 사용해야 된다.

Mediacodec과 GLSurfaceView를 이용하여 렌더링하는 방법을 알아보자
GLSurfaceView는 OpenGL을 통해 영상Frame을 그리게 된다.
OpenGL로 영상을 그리기 위해서는 Vertex, Fragment, Program 쉐이더가 필요하다.

    private final String mVertexShader =
            "precision mediump float;\n" +
                    "attribute mediump vec4 position;\n" +
                    "attribute mediump vec2 texcoord;\n" +
                    "varying   mediump vec2 vtexcoord;\n" +
                    "void main() {\n" +
                    "  vtexcoord = texcoord;\n" +
                    "  gl_Position = position;\n" +
                    "}";
                    
    private final String mFragmentShader =
            "#extension GL_OES_EGL_image_external : require\n"+
            "precision mediump float;\n"+
            "varying vec2 vtexcoord;\n"+
            "uniform samplerExternalOES sampler2d;\n"+
            "void main() {\n"+
            "   gl_FragColor = texture2D(sampler2d, vtexcoord);\n"+
            "}";

위의 Vertex Shader와 Fragment Shader를 loadShader하고 Program을 생성한다.
쉐이더의 position과 texcoord를 초기화한다. 그리고 아래와 같이 texture를 생성한다.

GLES31.glGenTextures(textureCount, mTextureId, 0);
GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
GLES31.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,mTextureId[0]);
GLES31.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
GLES31.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES31.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES31.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);

Texture를 생성한 뒤 TextureId를 이용하여 SurfaceTexture를 생성한다.

mSurfaceTexture = new SurfaceTexture(mTextureId[0]);

GLSurfaceView의 렌더러에서 생성한 SurfaceTexture에
setOnFrameAvailableListener를 달고 Surface를 생성한다.

surfaceTexture.setOnFrameAvailableListener(this);
Surface surface = new Surface(surfaceTexture);

이렇게 생성된 surface는 Mediacodec에 전달한다. Mediacodec에서 releaseOutputBuffer(outIndex, true)를 호출할때마다 onFrameAvailable이 호출되고 그때 GLSurfaceView의 requestRender()를 호출하게 되면 최종적으로 렌더러의 onDrawFrame이 호출되고 영상이 갱신된다.

@Override
public void onDrawFrame(GL10 gl) {
  GLES31.glClear(GLES31.GL_COLOR_BUFFER_BIT | GLES31.GL_DEPTH_BUFFER_BIT);
  mSurfaceTexture.updateTexImage();
  draw();
}

void draw() {
  GLES31.glUseProgram(mProgram);

  GLES31.glEnableVertexAttribArray(mPositionHandle);
  GLES31.glVertexAttribPointer(mPositionHandle, 3, GLES31.GL_FLOAT, false, 0, mVertexBuffer);
  GLES31.glEnableVertexAttribArray(mTexCoordHandle);
  GLES31.glVertexAttribPointer(mTexCoordHandle, 2, GLES31.GL_FLOAT, false, 0, mTexCoorBuffer);

  GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
  GLES31.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId[0]);
  GLES31.glUniform1i(GLES31.glGetUniformLocation(mProgram, "sampler2d"), 0);

 GLES31.glDrawElements(GLES31.GL_TRIANGLES, drawOrder.length, GLES31.GL_UNSIGNED_SHORT, drawListBuffer);
 checkGlError("glDrawElements");

 GLES31.glDisableVertexAttribArray(mPositionHandle);
 GLES31.glDisableVertexAttribArray(mTexCoordHandle);
}
profile
앱 만들어보자

0개의 댓글