OSDN Git Service

Update exception handling for MediaCodec
authorAndy Hung <hunga@google.com>
Wed, 30 Jul 2014 22:48:21 +0000 (15:48 -0700)
committerAndy Hung <hunga@google.com>
Fri, 8 Aug 2014 16:15:07 +0000 (16:15 +0000)
Throw MediaCodec.CodecException (versus IllegalStateException).
Print out status error codes as found (See MediaError.h for details).
Update error code mapping to CryptoException.

Bug: 12034929
Bug: 13976475
Change-Id: I23b951cb06807413f087c238a1d3526659f06e7b

media/jni/android_media_MediaCodec.cpp
media/jni/android_media_MediaCodec.h

index d033f76..f1e1099 100644 (file)
@@ -58,12 +58,17 @@ enum {
     EVENT_SET_CALLBACK = 2,
 };
 
-struct CryptoErrorCodes {
+static struct CryptoErrorCodes {
     jint cryptoErrorNoKey;
     jint cryptoErrorKeyExpired;
     jint cryptoErrorResourceBusy;
 } gCryptoErrorCodes;
 
+static struct CodecActionCodes {
+    jint codecActionTransient;
+    jint codecActionRecoverable;
+} gCodecActionCodes;
+
 struct fields_t {
     jfieldID context;
     jmethodID postEventFromNativeID;
@@ -101,10 +106,11 @@ JMediaCodec::JMediaCodec(
             PRIORITY_FOREGROUND);
 
     if (nameIsType) {
-        mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
+        mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus);
     } else {
-        mCodec = MediaCodec::CreateByComponentName(mLooper, name);
+        mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus);
     }
+    CHECK((mCodec != NULL) != (mInitStatus != OK));
 }
 
 void JMediaCodec::cacheJavaObjects(JNIEnv *env) {
@@ -147,7 +153,7 @@ void JMediaCodec::cacheJavaObjects(JNIEnv *env) {
 }
 
 status_t JMediaCodec::initCheck() const {
-    return mCodec != NULL ? OK : NO_INIT;
+    return mInitStatus;
 }
 
 void JMediaCodec::registerSelf() {
@@ -158,6 +164,7 @@ void JMediaCodec::release() {
     if (mCodec != NULL) {
         mCodec->release();
         mCodec.clear();
+        mInitStatus = NO_INIT;
     }
 
     if (mLooper != NULL) {
@@ -554,6 +561,34 @@ void JMediaCodec::setVideoScalingMode(int mode) {
     }
 }
 
+static jthrowable createCodecException(
+        JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/media/MediaCodec$CodecException"));
+    CHECK(clazz.get() != NULL);
+
+    const jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V");
+    CHECK(ctor != NULL);
+
+    ScopedLocalRef<jstring> msgObj(
+            env, env->NewStringUTF(msg != NULL ? msg : String8::format("Error %#x", err)));
+
+    // translate action code to Java equivalent
+    switch (actionCode) {
+    case ACTION_CODE_TRANSIENT:
+        actionCode = gCodecActionCodes.codecActionTransient;
+        break;
+    case ACTION_CODE_RECOVERABLE:
+        actionCode = gCodecActionCodes.codecActionRecoverable;
+        break;
+    default:
+        actionCode = 0;  // everything else is fatal
+        break;
+    }
+
+    return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get());
+}
+
 void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
     int32_t arg1, arg2 = 0;
     jobject obj = NULL;
@@ -605,19 +640,8 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
             CHECK(msg->findInt32("err", &err));
             CHECK(msg->findInt32("actionCode", &actionCode));
 
-            // use Integer object to pass the action code
-            ScopedLocalRef<jclass> clazz(
-                    env, env->FindClass("android/media/MediaCodec$CodecException"));
-            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V");
-
-            AString str;
-            const char *detail = "Unknown error";
-            if (msg->findString("detail", &str)) {
-                detail = str.c_str();
-            }
-            jstring msgObj = env->NewStringUTF(detail);
-
-            obj = env->NewObject(clazz.get(), ctor, err, actionCode, msgObj);
+            // note that DRM errors could conceivably alias into a CodecException
+            obj = (jobject)createCodecException(env, err, actionCode);
 
             if (obj == NULL) {
                 if (env->ExceptionCheck()) {
@@ -705,6 +729,11 @@ static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) {
     setMediaCodec(env, thiz, NULL);
 }
 
+static void throwCodecException(JNIEnv *env, status_t err, int32_t actionCode, const char *msg) {
+    jthrowable exception = createCodecException(env, err, actionCode, msg);
+    env->Throw(exception);
+}
+
 static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec$CryptoException"));
@@ -716,7 +745,7 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) {
 
     jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error");
 
-    /* translate OS errors to Java API CryptoException errorCodes */
+    /* translate OS errors to Java API CryptoException errorCodes (which are positive) */
     switch (err) {
         case ERROR_DRM_NO_LICENSE:
             err = gCryptoErrorCodes.cryptoErrorNoKey;
@@ -727,7 +756,7 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) {
         case ERROR_DRM_RESOURCE_BUSY:
             err = gCryptoErrorCodes.cryptoErrorResourceBusy;
             break;
-        default:
+        default:  /* Other negative DRM error codes go out as is. */
             break;
     }
 
@@ -738,13 +767,8 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) {
 }
 
 static jint throwExceptionAsNecessary(
-        JNIEnv *env, status_t err, const char *msg = NULL) {
-    if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
-        // We'll throw our custom MediaCodec.CryptoException
-        throwCryptoException(env, err, msg);
-        return 0;
-    }
-
+        JNIEnv *env, status_t err, int32_t actionCode = ACTION_CODE_FATAL,
+        const char *msg = NULL) {
     switch (err) {
         case OK:
             return 0;
@@ -758,20 +782,18 @@ static jint throwExceptionAsNecessary(
         case INFO_OUTPUT_BUFFERS_CHANGED:
             return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED;
 
-        case ERROR_DRM_NO_LICENSE:
-        case ERROR_DRM_LICENSE_EXPIRED:
-        case ERROR_DRM_RESOURCE_BUSY:
-            throwCryptoException(env, err, msg);
-            break;
+        case INVALID_OPERATION:
+            jniThrowException(env, "java/lang/IllegalStateException", msg);
+            return 0;
 
         default:
-        {
-            jniThrowException(env, "java/lang/IllegalStateException", msg);
-            break;
-        }
+            if (isCryptoError(err)) {
+                throwCryptoException(env, err, msg);
+                return 0;
+            }
+            throwCodecException(env, err, actionCode, msg);
+            return 0;
     }
-
-    return 0;
 }
 
 static void android_media_MediaCodec_native_setCallback(
@@ -781,7 +803,7 @@ static void android_media_MediaCodec_native_setCallback(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -800,7 +822,7 @@ static void android_media_MediaCodec_native_configure(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -842,7 +864,7 @@ static jobject android_media_MediaCodec_createInputSurface(JNIEnv* env,
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -865,13 +887,13 @@ static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", "no codec found");
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
     status_t err = codec->start();
 
-    throwExceptionAsNecessary(env, err, "start failed");
+    throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, "start failed");
 }
 
 static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
@@ -880,7 +902,7 @@ static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) {
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -895,8 +917,7 @@ static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) {
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        // should never be here
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -904,7 +925,10 @@ static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) {
     if (err != OK) {
         // treat all errors as fatal for now, though resource not available
         // errors could be treated as transient.
-        err = 0x80000000;
+        // we also should avoid sending INVALID_OPERATION here due to
+        // the transitory nature of reset(), it should not inadvertently
+        // trigger an IllegalStateException.
+        err = UNKNOWN_ERROR;
     }
     throwExceptionAsNecessary(env, err);
 }
@@ -915,7 +939,7 @@ static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) {
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -937,7 +961,7 @@ static void android_media_MediaCodec_queueInputBuffer(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -947,7 +971,7 @@ static void android_media_MediaCodec_queueInputBuffer(
             index, offset, size, timestampUs, flags, &errorDetailMsg);
 
     throwExceptionAsNecessary(
-            env, err, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+            env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
 }
 
 static void android_media_MediaCodec_queueSecureInputBuffer(
@@ -963,7 +987,7 @@ static void android_media_MediaCodec_queueSecureInputBuffer(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1005,7 +1029,7 @@ static void android_media_MediaCodec_queueSecureInputBuffer(
         err = -ERANGE;
     // subSamples array may silently overflow if number of samples are too large.  Use
     // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
-    } else if ( CC_UNLIKELY(numSubSamples >= INT32_MAX / sizeof(*subSamples)) ) {
+    } else if ( CC_UNLIKELY(numSubSamples >= (signed)(INT32_MAX / sizeof(*subSamples))) ) {
         err = -EINVAL;
     } else {
         jboolean isCopy;
@@ -1089,7 +1113,7 @@ static void android_media_MediaCodec_queueSecureInputBuffer(
     subSamples = NULL;
 
     throwExceptionAsNecessary(
-            env, err, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+            env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
 }
 
 static jint android_media_MediaCodec_dequeueInputBuffer(
@@ -1099,7 +1123,7 @@ static jint android_media_MediaCodec_dequeueInputBuffer(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return -1;
     }
 
@@ -1120,7 +1144,7 @@ static jint android_media_MediaCodec_dequeueOutputBuffer(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return 0;
     }
 
@@ -1143,7 +1167,7 @@ static void android_media_MediaCodec_releaseOutputBuffer(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1158,7 +1182,7 @@ static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env,
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1174,7 +1198,7 @@ static jobject android_media_MediaCodec_getFormatNative(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1197,7 +1221,7 @@ static jobject android_media_MediaCodec_getOutputFormatForIndexNative(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1220,7 +1244,7 @@ static jobjectArray android_media_MediaCodec_getBuffers(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1246,7 +1270,7 @@ static jobject android_media_MediaCodec_getBuffer(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1272,7 +1296,7 @@ static jobject android_media_MediaCodec_getImage(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1298,7 +1322,7 @@ static jobject android_media_MediaCodec_getName(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return NULL;
     }
 
@@ -1321,7 +1345,7 @@ static void android_media_MediaCodec_setParameters(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1340,7 +1364,7 @@ static void android_media_MediaCodec_setVideoScalingMode(
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
 
     if (codec == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
         return;
     }
 
@@ -1409,13 +1433,25 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) {
     CHECK(field != NULL);
     gCryptoErrorCodes.cryptoErrorResourceBusy =
         env->GetStaticIntField(clazz.get(), field);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$CodecException"));
+    CHECK(clazz.get() != NULL);
+    field = env->GetStaticFieldID(clazz.get(), "ACTION_TRANSIENT", "I");
+    CHECK(field != NULL);
+    gCodecActionCodes.codecActionTransient =
+        env->GetStaticIntField(clazz.get(), field);
+
+    field = env->GetStaticFieldID(clazz.get(), "ACTION_RECOVERABLE", "I");
+    CHECK(field != NULL);
+    gCodecActionCodes.codecActionRecoverable =
+        env->GetStaticIntField(clazz.get(), field);
 }
 
 static void android_media_MediaCodec_native_setup(
         JNIEnv *env, jobject thiz,
         jstring name, jboolean nameIsType, jboolean encoder) {
     if (name == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
         return;
     }
 
@@ -1427,19 +1463,23 @@ static void android_media_MediaCodec_native_setup(
 
     sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
 
-    status_t err = codec->initCheck();
-
-    env->ReleaseStringUTFChars(name, tmp);
-    tmp = NULL;
-
-    if (err != OK) {
-        jniThrowException(
-                env,
-                "java/io/IOException",
-                "Failed to allocate component instance");
+    const status_t err = codec->initCheck();
+    if (err == NAME_NOT_FOUND) {
+        // fail and do not try again.
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                String8::format("Failed to initialize %s, error %#x", tmp, err));
+        env->ReleaseStringUTFChars(name, tmp);
+        return;
+    } else if (err != OK) {
+        // believed possible to try again
+        jniThrowException(env, "java/io/IOException",
+                String8::format("Failed to find matching codec %s, error %#x", tmp, err));
+        env->ReleaseStringUTFChars(name, tmp);
         return;
     }
 
+    env->ReleaseStringUTFChars(name, tmp);
+
     codec->registerSelf();
 
     setMediaCodec(env,thiz, codec);
index f84a16a..9f2785a 100644 (file)
@@ -135,6 +135,8 @@ private:
 
     sp<AMessage> mCallbackNotification;
 
+    status_t mInitStatus;
+
     status_t createByteBufferFromABuffer(
             JNIEnv *env, bool readOnly, bool clearBuffer, const sp<ABuffer> &buffer,
             jobject *buf) const;