Use MediaCodec in NDK

이 글은 아래 글의 후속이다.

Android MediaCodec, MediaMuxer 살펴보기

작업을 하다 보니까, 하위 버전에서 오래 걸리는 문제가 있어 고민하던 차에 ‘ndk를 이용해 cpp로 작업하면 좀 더 빠르지 않을까?’ 하는 생각이 있었다.

일단, 결론은 다음과 같다.

  • 데모용 코드로 작성했을 때 성능은 12% 정도 이득을 보임
  • API 26 이상부터 지원 가능

이러한 이유 때문에 실제로 도입은 못했지만, 그래도 어느정도 매칭은 가능해서 사용이 가능했었고, 몇 가지 겪었던 문제를 소개하고자 한다.

MediaExtractor – setDataSource 에서 FileDescriptor

MediaExtractor에 설정할 때, FileDescriptor 로 통해 설정하는 경우가 있다.

먼저, FileDescriptor를 파라미터에 선언한다.

external fun convert(input: FileDescriptor, inLength: Long, output: FileDescriptor, outLength: Long)

이에 대한 JNI Header는 다음과 같다.

extern "C"
JNIEXPORT jint JNICALL
Java_com_github_windsekirun_***_***_convert(JNIEnv *env, jobject thiz,
                                           jobject input,
                                           jlong inLength,
                                           jobject output,
                                           jlong outLength)

FileDescriptor 는 jobject 로 매칭되는데, 이를 FileDescriptor (정확히는 FileDescriptor.descriptor) 로 변환하려면 아래와 같은 코드를 사용한다.

static int jniGetFDFromFileDescriptor(JNIEnv * env, jobject fileDescriptor) {
   jint fd = -1;
   jclass fdClass = env->FindClass("java/io/FileDescriptor");

   if (fdClass != NULL) {
       jfieldID fdClassDescriptorFieldID = env->GetFieldID(fdClass, "descriptor", "I");
       if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
           fd = env->GetIntField(fileDescriptor, fdClassDescriptorFieldID);
      }
  }

   return fd;
}

마지막으로, 얻은 FileDescriptor는 아래와 같이 설정한다.

AMediaExtractor *extractor = AMediaExtractor_new();
media_status_t amresult = AMediaExtractor_setDataSourceFd(extractor, inputFd, 0, inLength);
if (amresult != AMEDIA_OK) {
   LOGE("Error setting extractor data source, err %d", amresult);
   return -1;
}

여기서 media_status_t 는 NdkMediaError.h 에 선언되어 있다. (https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/ndk/include/media/NdkMediaError.h;l=43?q=NdkMediaError.h)

MediaFormat 에서 KEY_COLOR_FORMAT 설정

지난 글에서는 setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) 로 설정했었는데, NDK에서는 아래와 같이 사용한다.

AMediaFormat *outputFormat = AMediaFormat_new();
AMediaFormat_setInt32(outputFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, 2130708361);

여기서 2130708361MediaCodecInfo.CodecCapabilities 에 선언되어 있는 COLOR_FormatSurface 의 값이다. (https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/MediaCodecInfo.java;l=218?q=CodecCapabilities)

Decoder용 MediaCodec에서 Surface 객체 관련

이 것이 도입을 하지 못하게 했었던 ‘API 26’ 문제 중 하나인데, surface = encoder.createInputSurface() 에 대응되는 아래 코드가 API 26부터 사용이 가능했다.

ANativeWindow *surface;
AMediaCodec_createInputSurface(encoder, &surface);
media_status_t decoderConfigure = AMediaCodec_configure(decoder, inFormat, surface, nullptr, 0);

Backport 되어 있는 것이 있는지 찾지는 못했지만, 위와 같이 사용할 수 있다.

Encoder에 SignalEndOfInputStream 보내기

encoder.signalEndOfInputStream() 에 대응되는 코드인 AMediaCodec_signalEndOfInputStream(encoder); 가 API 26부터 사용이 가능했다.

설명상으로는 Equivalent to submitting an empty buffer with AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM set 로 되어있어, encoder의 inputBuffer에 AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM 를 설정하면 될 것 같아서 시도해 보았지만, sf MediaCodecError -38 이라는 오류가 나오는 것 같았다.

이 것도 위와 마찬가지로 적절한 해결 방법을 찾지 못했다.

레퍼런스