AudioTrack: Discontinuity detected

카테고리 없음 2021. 4. 30. 17:53 Posted by 아는 개발자

몇몇 영상에 대해 Exoplayer 라이브러리에서 AudioTrack: Discontinuity detected [expected 61128344, got 60909659] 에러를 뿜고 있었다. 영상도 렉이 걸리고 소리도 끊김이 있어서 한참 Exoplayer 라이브러리를 디버깅했었는데 이건 플레이어의 문제가 아니라 영상 파일이 문제였다. 사운드 인코딩 과정에서 버퍼 처리를 잘못해서 생긴 문제였는데 자세한 설명을 위해 아래 그림을 참조해보자.

 

 

트랜스코딩 과정은 Decoder에서 출력되는 Decoder Buffer를 Encoder Buffer에 복사하고 다시 Encoder에 입력하는 과정으로 이뤄진다. 위 그림에서 보면 Encoder Buffer와 Decoder Buffer모두 사이즈가 2048이기 때문에 Encoder에서는 빈 공간 없이 사운드 스트림을 채울 수 있다.

 

그런데 몇몇 기종에서는 Encoder Buffer의 크기와 Decoder Buffer 크기가 다르기도한다.  아래 그림은 encoder buffer의 기본 크기가 4096이고 Decoder Buffer의 기본 크기는 2048이다. 별다른 처리를 하지 않으면 Encoder Buffer는 앞부분에만 버퍼를 채우게 된다.

 

 

그러면 최종적으로 인코딩된 사운드의 스트림은 아래 그림처럼 중간중간에 빈 공간이 남아있게 되고, 결과적으로 불연속 오디오 로그가 띄게 되는 것이다. 플레이어에서는 받은 그대로 처리하기 때문에 소리도 이상하게 출력될 수 밖에 없다.

 

 

그러면 해결하는 방법은? 간단하다. Encoder Buffer를 꽉꽉 채워서 인코딩 하는 것이다. 아래 그림처럼 encoder와 decoder 버퍼의 크기가 다르다면 다음 것을 받아서 채워 넣은 다음 Encoder에 넣는다. 버퍼의 크기는 2의 제곱으로 떨어지기 때문에 나눠떨어지는 문제는 염려하진 않아도 된다. 

 

반대로 Decoder Buffer가 Encoder Buffer가 더 큰 경우도 있다. 이런 경우 불연속 에러는 발생하지 않겠지만 소리가 이상하게 들리게된다. 해결 방법은 비슷하다. 앞에선 Encoder Buffer에 두개의 decoder 버퍼를 담았다면 이번엔 Decoder Buffer를 쪼개서 넣는다. 아래 그림을 보면 Decoder 버퍼 앞부분은 Encoder Buffer 1, 뒷부분은 Encoder Buffer 2에 담아서 넣는다. 이런 방식이다.

 

 

버그를 많이 경험하다보니 강제로 인코딩 디코딩을 많이 배우게 된 것 같다. 물론 내가 배운게 아직 전부는 아니기 때문에 더 배워야할 것은 많지만 예전처럼 헤메지는 않게 된 것 같아서 기분은 좋다

728x90

이전 포스트에서는 카메라에서 담고 있는 프레임을 OpenGL로 그린 후 GLSurfaceView로 그려주는 작업을 했었다. 지금부터는 그려진 이미지를 비디오 파일로 만드는 작업에 대해서 분석해보고자 한다.

 

3. 미리보기 영상 인코딩하기 

 

MediaCodec을 사용한 비디오 인코딩 작업도 Renderer와 동일하게 OpenGL을 이용한 그리기 작업이 필요하다. 전반적인 구현 아이디어는 비디오 녹화용 EGL Context를 선언한 후 Renderer 클래스로부터 현재 촬영 중인 카메라의 이미지를 받아와 OpenGL로 다시 그려주고 Media Codec에서 받을 수 있는 Surface 형태로 보내는 것이다.

 

3.1 비디오 인코딩용 EGL Context 선언 

 

비디오 녹화 작업도 OpenGL 작업이 필요하므로 OpenGL 작업용 EGLContext를 만들어준다. 이때 Renderer로부터 카메라 촬영 이미지를 받아오기 위해 EGL 초기화 작업에 공유 EGL Context 정보(shared_context)를 추가한다. 

fun setVideoEncoder(videoEncoder: MediaVideoEncoder?) {
    this.videoEncoder = videoEncoder

    videoEncoder?.setEglContext(EGL14.eglGetCurrentContext(), textureId)
}

private fun createContext(shared_context: EGLContext?): EGLContext {
    val attrib_list = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
    val context =
        EGL14.eglCreateContext(mEglDisplay, mEglConfig, shared_context, attrib_list, 0)
    checkEglError("eglCreateContext")
    return context
}

 

3.2 Renderer로부터 카메라 이미지 받아오기 

 

Renderer로부터 새로운 프레임이 발생했다는 콜백을 받으면 VideoEncoder는 카메라로부터 이미지를 받아와서 새롭게 그려주게 된다. 카메라 이미지는 Renderer 클래스 내의 texture에 있으며 고유한 texture id를 EGL내에서 bind 해서 받아 올 수 있게 된다. VideoEncoder 클래스에 해당 textureId를 전달해서 VideoEncoder의 EGLDisplay에 그려준다.

fun draw(tex_id: Int, tex_matrix: FloatArray?) {
    GLES20.glUseProgram(hProgram)
    if (tex_matrix != null) GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, tex_matrix, 0)
    GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mMvpMatrix, 0)
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex_id)
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_NUM)
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0)
    GLES20.glUseProgram(0)
}

 

3.3 MediaCodec으로 이미지 버퍼 전달 

 

인코딩을 위해 새롭게 그린 이미지를 MediaCodec에서 만든 Surface에 버퍼로 전달한다. OpenGL 에서 제공하는 eglSwapBuffers 함수를 사용하면 MediaCodec이 받을 수 있는 surface에 전달이 가능하다.

private fun swap(surface: EGLSurface?): Int {
    if (!EGL14.eglSwapBuffers(mEglDisplay, surface)) {
        val err = EGL14.eglGetError()
        if (DEBUG) Log.w(TAG, "swap:err=$err")
        return err
    }
    return EGL14.EGL_SUCCESS
}

 

3.4 전달 받은 정보를 인코딩 

 

MediaCodec 고유 함수를 이용해 전달받은 정보를 인코딩한다. MediaCodec 관련 코드는 생략한다. 

 

4. 마치며 

 

포스트에선 전반적인 구현 아이디어만 다루었기 때문에 보기에는 쉽지만 실제 사용된 코드는 꽤 복잡했다. google에서 짜둔 클래스 간의 상속과 인터페이스 관계를 따라가는 게 생각보다 시간이 걸렸고 아직도 생소한 OpenGL 클래스의 역할과 내부 코드를 알지 못해 문서를 찾아가느라 어려웠다.

 

그래도 고생하면서 생소했던 카메라와 OpenGL 관련 지식을 배운게 개발자로서 큰 소득이다. 스노나 틱톡의 카메라 효과 코드를 보진 못했지만 아마 위 구현 방식과 크게 차이가 나지 않을 것 같다. 여기에 OpenGL 코드를 더 확장시키면 나도 촬영 중인 화면에 여기에 필터를 변경하고 스티커도 추가해볼 수 있겠다. 현재 구현된 코드를 한층 더 업그레이드시켜봐야겠다.

 

오디오까지 같이 녹화하고 싶다면 여기 깃허브 코드를 참조하면 좋다.

 

https://github.com/saki4510t/AudioVideoRecordingSample

728x90

MediaCodec - Encoding

개발/안드로이드 2020. 6. 21. 19:48 Posted by 아는 개발자

 

디코딩이 비디오 정보를 분해하는 작업이었다면 인코딩은 역으로 새로운 비디오를 만드는 작업이다. 포토샵이나 비타 같은 비디오 에디터를  이용해 기존의 비디오의 화질을 줄이고 영상에 자막을 입히거나 스티커를 붙이는 작업 모두 인코딩 작업의 일환이라고 볼 수 있다. 비디오 파일마다 갖고 있는 고유의 속성인 FPS, Bitrate, 가로 세로 크기 모두 인코딩 작업에서 설정된다.

 

앞서 말한 것 처럼 인코딩 작업은 광범위한데 이번 포스트에서는 인코딩의 가장 기본적이라 할 수 있는 예제인 기존에 디코딩한 비디오를 다시 똑같은 비디오로 인코딩하는 작업으로 설명해보려고 한다. 앞선 포스트에서 인코딩에 대해 설명했었는데 인코딩에서도 디코딩과 비슷한 작업이 많아 중복되는 내용에 대해서는 생략하고 이 포스트의 주제인 인코딩에 대해서 중점적으로 설명하려고 한다. 예제코드로 구글 미디어코덱 CTS 에 사용한 코드를 참고했다. 구글 코드 답지 않게 리팩토링도 덜되어있고(테스트코드니까) 알아보기가 쉽지는 않으니 이 글이 이 코드를 분석하려는 분들에게 많은 도움이 됐으면 좋겠다.

 

1. Create 

 

디코딩 작업과 비슷하게 인코딩도 MediaCodec을 이용해서 인코딩을 담당할 객체를 생성한다. MediaCodec.createByCodecName() 을 통해 객체를 생성하고 configure를 통해 구체적인 정보를 설정한다. 옛날 코드에서는 createByCodecName에 넣는 인자는 인코딩해서 생성할 비디오의 압축 방식을(대표적으로 mpeg인 video/avc 가 있다) 설정한다. configure의 첫번째 인자로 outputVideoFormat이 들어가는데 outputVideoFormat의 세팅 작업을 보면 대충 무엇을 하고 있는지 감이 올 것이다. 비디오의 비트레이트, 프레임레이트를 설정해주는 단계다. 이렇게만 설정을 해주면 알아서 이 값에 맞게 비디오가 만들어진다.

 

outputVideoFormat.setInteger(
	MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
outputVideoFormat.setInteger(
	MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
// Create a MediaCodec for the desired codec, then configure it as an encoder with
// our desired properties. Request a Surface to use for input.
AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
videoEncoder = createVideoEncoder(
	videoCodecInfo, outputVideoFormat, inputSurfaceReference);
    
private MediaCodec createVideoEncoder(
    MediaCodecInfo codecInfo,
    MediaFormat format,
    AtomicReference<Surface> surfaceReference)
    throws IOException {
    MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
    encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    // Must be called before start() is.
    surfaceReference.set(encoder.createInputSurface());
    encoder.start();
    return encoder;
}

 

2. Handle Buffer

 

2.1 Get Input Buffer

 

디코딩 작업과 비슷하게 버퍼를 처리하는 루틴을 가진다. 디코딩에서는 비디오 파일을 뽑아와서 OutputSurface 와 같은 뷰에 넣어주었다면 인코딩 작업에서는 새롭게 비디오로 만들 비디오 프레임 버퍼 정보를 핸들링 하게 된다. 인코더가 버퍼를 받아오는 부분은 코드로 바로 설명하는 것은 어려우니 먼저 아래 그림을 참고하도록 하자.

 

 

분석이 쉽지 않았다

 

 

인코더는 Surface를 통해서 비디오에 인코딩할 버퍼 정보를 받아오게 된다. 아래 그림 오른쪽 상단위 InputSurface가 인코더에 넣을 정보를 전송하는 곳이다. 전반적인 흐름을 설명하자면 디코더에서 받아온 정보는 잘게잘게 쪼게져서 OutputSurface로 이동하고 이 정보를 OutputSurface 내의 객체에서 호출한 OpenGL 코드를 통해 OpenGL Thread 메모리 영역에 저장한다. 여기서 그려지는 정보는 TextureRender를 거쳐서 인코더가 버퍼를 받을 수 있도록 매핑된 객체는 InputSurface로 이동하게 되는데 인코더는 여기서 받아온 정보를 MediaMuxer를 통해서 비디오 파일을 생성하게 된다.

 

아래 코드는 위 그림에서 인코더에게 버퍼를 전달하는 부분만 추출한 것이다. drawImage는 현재 디코더에서 받아온 정보를 실제 그림으로 그리는 코드다. 이 그림 버퍼는 앞서 설명한 것 처럼 고유한 OpenGL Thread 메모리 영역에 저장된다. 바로 다음에 이뤄지는 setPresentationTime 함수는 현재 프레임 버퍼가 차지하게 되는 시간대를 설정하는 함수다. 디코더 정보에 마이크로 세컨드 정보가 포함되어 있어서 이 정보를 통해 어디에 위치해야할 지 알 수 있다. 최종적으로 swapBuffers를 통해서 버퍼 정보를 인코더에 전달한다.

 

if (VERBOSE) Log.d(TAG, "output surface: await new image");
outputSurface.awaitNewImage();
if (VERBOSE) Log.d(TAG, "output surface: draw image");
outputSurface.drawImage();
inputSurface.setPresentationTime(
	videoDecoderOutputBufferInfo.presentationTimeUs * 1000);
if (VERBOSE) Log.d(TAG, "input surface: swap buffers");
inputSurface.swapBuffers();
if (VERBOSE) Log.d(TAG, "video encoder: notified of new frame");

 

* 여기서 OpenGL에 해당하는 클래스와 함수는 설명을 생략했다. 기초적인 OpenGL 지식이 있어야하고 설명하려면 밑도 끝도 없을 것 같아서.... 무엇보다 필자가 아직 OpenGL을 잘 모르는 것이 문제다. 간단한 정보만 설명하자면 CTS 코드에서는 받아온 비디오 정보랑 똑같이 입력할 수 있도록 구현해둔 상태다. 여기 있는 값을 잘만 이용하면 비디오에 자막과 워터 마크도 입힐 수 있고 비디오 크롭, 스케일 값도 조정하는 이펙트도 넣을 수 있다. 이런 기능을 구현해보고 싶으신 분은 OpenGL 코드를 공부해보면 좋을 것 같다.

 

* 꼭 OpenGL을 이용해서 정보를 전달하지 않는 방법도 있다. 대표적으로 오디오에서는 디코더에서 직접 인코더의 InputBuffer에 값을 넣어준다. 예제로 사용한 파일에서 오디오에대한 인코딩 작업도 있으니 관심있는 분은 참고해보시길!

 

2.2 Handle Buffer 

 

버퍼를 처리하는 부분은 디코더랑 꽤 비슷하다. 디코더에서는 빼낸 정보를 output surface에다가 넣었다면 인코더에서는 비디오를 생성할 수 있는 muxer라는 객체에 넣는 점만 다르다. 코드를 한번 살펴보자. swapBuffers로 넘어온 정보는 videoEncode Output 버퍼에 쌓여 있는데 dequeueoutputBuffer를 통해서 이 정보가 저장된 인덱스 정보를 얻어오고 인덱싱을 통해 ByteBuffer로 구체적인 정보를 받아온다. 디코딩처럼 Index 정보가 유효하지 않는 경우에는 무시하고 작업을 진행하도록 한다. 

 

받아온 ByteBuffer에는 버퍼의 크기와 플래그가 포함되어 있는데 플래그 비트 중 CODEC_CONFIG 값이 포함되면 무시하도록 한다. 이 값을 muxer에 포함시키면 비디오가 실행이 안되니 주의하도록 하는게 좋다. 사이즈 값이 0이 아니라면 비디오에 포함될 수 있는 유효한 정보라고 본다. writeSampleData 함수를 통해 인코딩할 비디오 정보를 넣은 후 버퍼 메모리를 release 해준다.

 

int encoderOutputBufferIndex = videoEncoder.dequeueOutputBuffer(
	videoEncoderOutputBufferInfo, TIMEOUT_USEC);
if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
	if (VERBOSE) Log.d(TAG, "no video encoder output buffer");
		break;
}
if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
	if (VERBOSE) Log.d(TAG, "video encoder: output buffers changed");
	videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
	break;
}
if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
	if (VERBOSE) Log.d(TAG, "video encoder: output format changed");
	if (outputVideoTrack >= 0) {
		fail("video encoder changed its output format again?");
	}
	encoderOutputVideoFormat = videoEncoder.getOutputFormat();
	break;
}

ByteBuffer encoderOutputBuffer = videoEncoderOutputBuffers[encoderOutputBufferIndex];
if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)	!= 0) {
	// Simply ignore codec config buffers.
	videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
	break;
}
if (videoEncoderOutputBufferInfo.size != 0) {
	muxer.writeSampleData(
		outputVideoTrack, encoderOutputBuffer, videoEncoderOutputBufferInfo);
}
if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)!= 0) {
	if (VERBOSE) Log.d(TAG, "video encoder: EOS");
	videoEncoderDone = true;
}
videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
videoEncodedFrameCount++;

 

2.3 Muxer 

 

Muxer에 대한 설명을 빼뜨렸는데, MediaMuxer는 새로운 비디오를 만들어줄 수 있는 클래스다. 첫번째 인자로 비디오로 생성될 파일의 경로와 이름을 파일 클래스를 통해서 넣어주고 두번째 인자로 생성될 비디오 파일의 확장자(mp4)를 설정해준다. 이것만 해주면 된다. 이렇게 선언만 해두고 인코더에서 받아온 버퍼 정보를 writeSampleData로 넣어주기만 하면 된다.

private MediaMuxer createMuxer() throws IOException {
	return new MediaMuxer(mOutputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
}

 

2.4 release 

 

디코딩과 마찬가지로 인코딩에 사용한 작업들도 release 해주는 단계가 필요하다. muxer와 encoder 객체 뿐만 아니라 인코더에 버퍼를 전달할 때 사용한 inputSurface까지 해줘야한다. 안해준다고 비디오가 안생성되는 것은 아니지만 메모리 정보는 잊지 않고 해제해주는것이 좋다.

 

 

728x90

'개발 > 안드로이드' 카테고리의 다른 글

status bar 영역 덮는 view 만들기  (0) 2020.06.24
Lottie 라이브러리  (0) 2020.06.24
MediaCodec - Encoding  (0) 2020.06.21
MediaCodec - Decoding  (0) 2020.05.24
MediaCodec - Getting Started  (0) 2020.05.24
Navigator - Getting Started  (0) 2020.04.20

MediaCodec - Getting Started

개발/안드로이드 2020. 5. 24. 10:19 Posted by 아는 개발자

 

0. FFmpeg - 한계

 

동영상과 관련된 작업을 처리하는 툴로 가장 유명한 것은 아마 FFmpeg 일 것이다. 이 라이브러리에서는 영상의 트랜스코딩(압축)을 지원할 뿐만 아니라 영상내 텍스트/이미지 삽입 또는 영상을 회전시키고 자를 수 있는 기능도 제공하며 실행도 대부분의 개발자들에게 익숙한 형태인 커맨드라인 딱 한줄만 입력하면돼 비디오에 대해서 잘 모르는 사람들도 쉽게 사용할 수 있다. 하지만 FFmpeg은 이 모든 작업들이 소프트웨어적으로 구현되어 있어 느리다는 단점이 있고 C, C++인 로우 레벨로 만들어진 빌드 파일을 JVM 위에 돌아가는 자바 언어단에서 별개의 도입하는 것은 꺼리낌이 있다. 개발 외적으로는 GPL 라이센스를 가지고 있어서 이 라이브러리의 수정사항을 공개해야한다는 법적 이슈가 있고 무엇보다 대중적으로 사용하는 h264 압축 방식을 사용하는 경우(mp4 파일을 생성하는 경우) 특허 이슈가 있다고 한다. 이 특허문제에 대해선 인터넷상에서 갑론을박이 많은데 가장 중요한 주체인 FFmpeg 공식 홈페이지에서도 "자기들은 변호사는 아니라 잘 모르겠다"고 답변하는 것으로 보아 자유롭게 사용하기에는 찝찝한 툴이다.

 

ffmpeg 공식 홈페이지 Patent issue에 대한 답변. "We do not know" 문구가 눈에 띈다.

 

1. MediaCodec - 어쩔 수 없이 써야하는 존재

 

 

플랫폼 개발자들도 이런 문제점을 인식해서인지 영상을 처리할 수 있는 고유의 라이브러리를 도입했는데 안드로이드의 경우 MediaCodec 라이브러리가 이에 해당한다. FFmpeg의 한계점을 극복하고자 도입한 라이브러리이기에 더이상 라이센스 문제도 없고 JVM위에서 동작하는 안드로이드에서 사용하기에 적합한 형태이며 하드웨어 가속을 지원해 소프트웨어적으로 돌아가는 FFmpeg보다 빠르다.

 

하지만 단점도 만만치 않다. FFmpeg은 트랜스코딩 뿐만 아니라 다양한 툴을 포함하고 있고 영상에 대해서 잘 몰라도 쉽게 사용할 수 있었다. 그러나 MediaCodec은 영상 정보를 추출하는 디코딩 작업과 바이너리 정보를 조합해 새로운 영상을 만드는 인코딩 작업만 제공할 뿐이며 원래 FFmpeg에 있었던 텍스트를 삽입하고 영상을 자르는 기능은 모두 스스로 만들어야 한다. 즉 이제는 디코딩/인코딩이 무엇인지, 영상 파일은 어떤식으로 이뤄져 있는지 그리고 텍스트를 삽입하고 영상을 자를 수 있는 그래픽의 기본 지식까지 겸비해야 한다는 뜻. 평소에 게임을 만들어본 사람이나 영상쪽에 관심있는 사람이 아니면 이쪽에 대해서 아마 잘 모를 것이다. 그리고 공부하려고 해도 진입장벽이 있는 부분이라 러닝 커브가 높다.

 

아쉽게도 단점은 이것 만이 아니다(ㅠㅠ). MediaCodec 라이브러리는 직접 하드웨어 장비와 연계된 부분이기 때문에 구글은 API만 뚫어주고 퀄컴, 삼성 LSI와 같은 칩 제조사에서 이 부분을 직접 구현했는데 이 부분이 칩(AP)에 따라서 다르다. 똑같은 갤럭시 스마트폰, 동일한 모델임에도 불구하고 국내에서 주로 사용하는 엑시노스 칩에서는 동작하는 반면 해외에서 사용하는 퀄컴 칩에서는 동작이 안될 수가 있다. 그리고 똑같은 안드로이드 버전이고 퀄컴칩을 사용하는데도 불구하고 사용하는 칩의 버전이 달라 갤럭시 노트8은 되고 갤럭시 S9은 되는 현상도 발생한다. 물론 이 경우는 코드를 잘못짠 것에 해당하기는 하나... 같은 플랫폼에서 똑같은 코드가 칩마다 다르게 동작할 수 있다는 점은 플랫폼 개발자로서 영 찝찝한 점이다. 안드로이드 버전별로 대응해왔던 것에서 이제는 국내용, 해외용도 모두 다 봐야 한다는 뜻이니까. 칩제조사와 플랫폼 벤더가 통합된 iOS 개발자들이 부러워지는 순간이다.

 

더 난감한 점은 게다가 이쪽 부분은 제조사에서 코드를 숨겨놔 에러가 발생해도 코드도 볼 수 없다는 사실이다... Logcat 메시지에서도 에러가 발생하면 알려주는 정보가 0x 로 시작하는 16진수의 플래그값 외에 알려주는게 더 없다. 스택오버플로우에라도 의지해볼 수 있다면 좋으련만 이상하게도 MediaCodec 관련 정보는 별로 없다. MediaCodec이 2012년도에 등장했는데도 아직까지 이렇게 정보가 많지 않다는 것을 보면 다들 MediaCodec으로 개발한 정보를 숨겨놓는건지 아니면 쓰려다가 지레 포기하고 외부 라이브러리를 사용한 것인지. MediaCodec을 이용한 오픈소스 프로젝트가 몇몇 있기는 한데 코드에서 정작 중요한 정보들은 byte code로 꽁꽁 숨겨놨다.(이럴거면 왜 공개했다고 한건지) 인터넷 상에서 정보를 찾기는 어렵고 개발하는데 난감하지만 대안이 없어 어쩔 수 없이 사용해야하는 라이브러리다.

 

 

2. MediaCodec - 개발 참고 자료

 

 

개발하기 어렵지만 그래도 참고할 만한 자료가 전혀 없는 것은 아니다. 단, 다른 라이브러리들처럼 친절한 문서 페이지는 기대하지 않는게 좋다.

 

 

2.1 CTS 코드

 

구글에서는 CTS (호환성 테스트) 검증에 사용한 코드를 공개하고 있다. 이 테스트 코드는 모든 제조사들이 출시하기 전에 PASS 해야하기 때문에 여기 코드들은 칩 디펜던시가 없이 모두 안정적으로 동작한다고 봐도 될 것 같다. https://bigflake.com/mediacodec/ 라는 사이트에서 MediaCodec과 관련된 CTS 테스트코드 주소와 테스트 목적에 대해서 짤막하게 소개해주고 있으니 여기서 구현하려는 것과 가장 가까운 테스트 코드를 참고하자. 테스트 코드를 보면 알겠지만 구글에서도 테스트 코드는 거지같이짜서  한눈에 보기가 쉽진 않다.

 

2.2 grafika 

 

구글에서 MediaCodec관련 문의가 하도 많이 들어와 만든 것인지 모르겠으나 MediaCodec 개발자로서는 한여름의 에어컨과도 같은 오픈소스다. https://github.com/google/grafika 여기에는 MediaCodec 라이브러리를 이용해 응용할 수 있는 무비 플레이어, 카메리 영상 처리, 비디오 트랜스코딩과 같은 다양한 예제를 담고 있다. README 페이지에 이 코드는 구글 공식 프로덕트가 아니고(그럼 구글 저장소에는 왜 있는건지?) 테스트를 제대로 하지 않아 안정적이지 않다고도 크게 써놔서 이 코드들이 모든 디바이스에서 동작할지는 확신 할 수 없지만, MediaCodec을 기본적으로 어떻게 써야할지 감을 익힐때 사용하면 유용하다. 

 

2013, 2014년도에 주로 작성되고 그 이후에는 최신 안드로이드버전 호환만 관리했기 때문에 모든 코드가 JAVA로 되어 있어 Kotlin으로 옮길 때 린트가 많이 생기는 단점이 있다.

 

728x90

'개발 > 안드로이드' 카테고리의 다른 글

MediaCodec - Encoding  (0) 2020.06.21
MediaCodec - Decoding  (0) 2020.05.24
MediaCodec - Getting Started  (0) 2020.05.24
Navigator - Getting Started  (0) 2020.04.20
안드로이드 그림자(Shadow) 효과 넣기  (0) 2020.04.18
Kotlin - Coroutine  (0) 2020.04.15