Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f9aa0bc authored by Andy Hung's avatar Andy Hung
Browse files

Update exception handling for MediaCodec

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
parent fa387ad6
Loading
Loading
Loading
Loading
+115 −75
Original line number Diff line number Diff line
@@ -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,21 +782,19 @@ 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;
    }
}

static void android_media_MediaCodec_native_setCallback(
        JNIEnv *env,
@@ -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();

    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);
    tmp = NULL;

    if (err != OK) {
        jniThrowException(
                env,
                "java/io/IOException",
                "Failed to allocate component instance");
        return;
    }

    env->ReleaseStringUTFChars(name, tmp);

    codec->registerSelf();

    setMediaCodec(env,thiz, codec);
+2 −0
Original line number Diff line number Diff line
@@ -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;