CryptoAsync: Creating MediaCodec.CryptoException
CryptoException is created with MediaCodec.CryptoInfo
Added callback onCryptoError(MediaCodec.CryptoException)
as another MediaCodec callback to signal decryption errors.
Bug: 254050543
Change-Id: Ifb8bfb44b38178ec99e6dbb607825c642bd4e46f
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index df9c539..2541a506 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -571,6 +571,10 @@
void onError(…) {
…
}
+ {@literal @Override}
+ void onCryptoError(…) {
+ …
+ }
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
@@ -1774,6 +1778,7 @@
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
private static final String EOS_AND_DECODE_ONLY_ERROR_MESSAGE = "An input buffer cannot have "
+ "both BUFFER_FLAG_END_OF_STREAM and BUFFER_FLAG_DECODE_ONLY flags";
+ private static final int CB_CRYPTO_ERROR = 6;
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -1901,6 +1906,12 @@
break;
}
+ case CB_CRYPTO_ERROR:
+ {
+ mCallback.onCryptoError(mCodec, (MediaCodec.CryptoException) msg.obj);
+ break;
+ }
+
case CB_OUTPUT_FORMAT_CHANGE:
{
mCallback.onOutputFormatChanged(mCodec,
@@ -2104,12 +2115,25 @@
*/
public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2;
+ /**
+ * This flag should be used on a secure decoder only. MediaCodec configured with this
+ * flag does decryption in a separate thread. The flag requires MediaCodec to operate
+ * asynchronously and will throw CryptoException if any, in the onCryptoError()
+ * callback. Applications should override the default implementation of
+ * onCryptoError() and access the associated CryptoException.
+ *
+ * CryptoException thrown will contain {@link MediaCodec.CryptoInfo}
+ * This can be accessed using getCryptoInfo()
+ */
+ public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4;
+
/** @hide */
@IntDef(
flag = true,
value = {
CONFIGURE_FLAG_ENCODE,
CONFIGURE_FLAG_USE_BLOCK_MODEL,
+ CONFIGURE_FLAG_USE_CRYPTO_ASYNC,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ConfigureFlag {}
@@ -2523,19 +2547,20 @@
public final static class CryptoException extends RuntimeException
implements MediaDrmThrowable {
public CryptoException(int errorCode, @Nullable String detailMessage) {
- this(detailMessage, errorCode, 0, 0, 0);
+ this(detailMessage, errorCode, 0, 0, 0, null);
}
/**
* @hide
*/
public CryptoException(String message, int errorCode, int vendorError, int oemError,
- int errorContext) {
+ int errorContext, @Nullable CryptoInfo cryptoInfo) {
super(message);
mErrorCode = errorCode;
mVendorError = vendorError;
mOemError = oemError;
mErrorContext = errorContext;
+ mCryptoInfo = cryptoInfo;
}
/**
@@ -2654,6 +2679,16 @@
return mErrorCode;
}
+ /**
+ * Returns CryptoInfo associated with this {@link CryptoException}
+ * if any
+ *
+ * @return CryptoInfo object if any. {@link MediaCodec.CryptoException}
+ */
+ public @Nullable CryptoInfo getCryptoInfo() {
+ return mCryptoInfo;
+ }
+
@Override
public int getVendorError() {
return mVendorError;
@@ -2670,6 +2705,7 @@
}
private final int mErrorCode, mVendorError, mOemError, mErrorContext;
+ private CryptoInfo mCryptoInfo;
}
/**
@@ -5088,6 +5124,25 @@
public abstract void onError(@NonNull MediaCodec codec, @NonNull CodecException e);
/**
+ * Called only when MediaCodec encountered a crypto(decryption) error when using
+ * a decoder configured with CONFIGURE_FLAG_USE_CRYPTO_ASYNC flag along with crypto
+ * or descrambler object.
+ *
+ * @param codec The MediaCodec object
+ * @param e The {@link MediaCodec.CryptoException} object with error details.
+ */
+ public void onCryptoError(@NonNull MediaCodec codec, @NonNull CryptoException e) {
+ /*
+ * A default implementation for backward compatibility.
+ * Use of CONFIGURE_FLAG_USE_CRYPTO_ASYNC requires override of this callback
+ * to receive CrytoInfo. Without an orverride an exception is thrown.
+ */
+ throw new IllegalStateException(
+ "Client must override onCryptoError when the codec is " +
+ "configured with CONFIGURE_FLAG_USE_CRYPTO_ASYNC.", e);
+ }
+
+ /**
* Called when the output format has changed
*
* @param codec The MediaCodec object.
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 5b0c2a2..9a4aa33 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -184,6 +184,8 @@
jmethodID postEventFromNativeID;
jmethodID lockAndGetContextID;
jmethodID setAndUnlockContextID;
+ jmethodID cryptoInfoSetID;
+ jmethodID cryptoInfoSetPatternID;
jfieldID cryptoInfoNumSubSamplesID;
jfieldID cryptoInfoNumBytesOfClearDataID;
jfieldID cryptoInfoNumBytesOfEncryptedDataID;
@@ -203,6 +205,7 @@
static fields_t gFields;
static const void *sRefBaseOwner;
+jint MediaErrorToJavaError(status_t err);
////////////////////////////////////////////////////////////////////////////////
@@ -1068,6 +1071,180 @@
return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get());
}
+static void AMessageToCryptoInfo(JNIEnv * env, const jobject & obj,
+ const sp<AMessage> & msg) {
+ if(msg == nullptr || obj == nullptr) {
+ ALOGE("CryptoAsync Nothing to do in AMessagetoCryptoInfo");
+ return;
+ }
+ size_t numSubSamples = 0;
+ sp<ABuffer> subSamplesBuffer;
+ sp<ABuffer> keyBuffer;
+ sp<ABuffer> ivBuffer;
+ CryptoPlugin::Mode mode;
+ CryptoPlugin::Pattern pattern;
+ CHECK(msg->findInt32("mode", (int*)&mode));
+ CHECK(msg->findSize("numSubSamples", &numSubSamples));
+ CHECK(msg->findBuffer("subSamples", &subSamplesBuffer));
+ CHECK(msg->findInt32("encryptBlocks", (int32_t *)&pattern.mEncryptBlocks));
+ CHECK(msg->findInt32("skipBlocks", (int32_t *)&pattern.mSkipBlocks));
+ CHECK(msg->findBuffer("iv", &ivBuffer));
+ CHECK(msg->findBuffer("key", &keyBuffer));
+
+ // subsamples
+ ScopedLocalRef<jintArray> samplesOfEncryptedDataArr(env, env->NewIntArray(numSubSamples));
+ ScopedLocalRef<jintArray> samplesOfClearDataArr(env, env->NewIntArray(numSubSamples));
+ jboolean isCopy;
+ jint *dstEncryptedSamples =
+ env->GetIntArrayElements(samplesOfEncryptedDataArr.get(), &isCopy);
+ jint * dstClearSamples =
+ env->GetIntArrayElements(samplesOfClearDataArr.get(), &isCopy);
+
+ CryptoPlugin::SubSample * samplesArray =
+ (CryptoPlugin::SubSample*)(subSamplesBuffer.get()->data());
+
+ for(int i = 0 ; i < numSubSamples ; i++) {
+ dstEncryptedSamples[i] = samplesArray[i].mNumBytesOfEncryptedData;
+ dstClearSamples[i] = samplesArray[i].mNumBytesOfClearData;
+ }
+ env->ReleaseIntArrayElements(samplesOfEncryptedDataArr.get(), dstEncryptedSamples, 0);
+ env->ReleaseIntArrayElements(samplesOfClearDataArr.get(), dstClearSamples, 0);
+ // key and iv
+ jbyteArray keyArray = NULL;
+ jbyteArray ivArray = NULL;
+ if (keyBuffer.get() != nullptr && keyBuffer->size() > 0) {
+ keyArray = env->NewByteArray(keyBuffer->size());
+ jbyte * dstKey = env->GetByteArrayElements(keyArray, &isCopy);
+ memcpy(dstKey, keyBuffer->data(), keyBuffer->size());
+ env->ReleaseByteArrayElements(keyArray,dstKey,0);
+ }
+ if (ivBuffer.get() != nullptr && ivBuffer->size() > 0) {
+ ivArray = env->NewByteArray(ivBuffer->size());
+ jbyte *dstIv = env->GetByteArrayElements(ivArray, &isCopy);
+ memcpy(dstIv, ivBuffer->data(), ivBuffer->size());
+ env->ReleaseByteArrayElements(ivArray, dstIv,0);
+ }
+ // set samples, key and iv
+ env->CallVoidMethod(
+ obj,
+ gFields.cryptoInfoSetID,
+ (jint)numSubSamples,
+ samplesOfClearDataArr.get(),
+ samplesOfEncryptedDataArr.get(),
+ keyArray,
+ ivArray,
+ mode);
+ if (keyArray != NULL) {
+ env->DeleteLocalRef(keyArray);
+ }
+ if (ivArray != NULL) {
+ env->DeleteLocalRef(ivArray);
+ }
+ // set pattern
+ env->CallVoidMethod(
+ obj,
+ gFields.cryptoInfoSetPatternID,
+ pattern.mEncryptBlocks,
+ pattern.mSkipBlocks);
+}
+
+static void CryptoErrorToJavaError(status_t err, jint& jerr, std::string& defaultMsg) {
+ switch(err) {
+ case ERROR_DRM_NO_LICENSE:
+ jerr = gCryptoErrorCodes.cryptoErrorNoKey;
+ defaultMsg = "Crypto key not available";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ jerr = gCryptoErrorCodes.cryptoErrorKeyExpired;
+ defaultMsg = "License expired";
+ break;
+ case ERROR_DRM_RESOURCE_BUSY:
+ jerr = gCryptoErrorCodes.cryptoErrorResourceBusy;
+ defaultMsg = "Resource busy or unavailable";
+ break;
+ case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
+ jerr = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
+ defaultMsg = "Required output protections are not active";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ jerr = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
+ defaultMsg = "Attempted to use a closed session";
+ break;
+ case ERROR_DRM_INSUFFICIENT_SECURITY:
+ jerr = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
+ defaultMsg = "Required security level is not met";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ jerr = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
+ defaultMsg = "Operation not supported in this configuration";
+ break;
+ case ERROR_DRM_FRAME_TOO_LARGE:
+ jerr = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
+ defaultMsg = "Decrytped frame exceeds size of output buffer";
+ break;
+ case ERROR_DRM_SESSION_LOST_STATE:
+ jerr = gCryptoErrorCodes.cryptoErrorLostState;
+ defaultMsg = "Session state was lost, open a new session and retry";
+ break;
+ default: // Other negative DRM error codes go out best-effort.
+ jerr = MediaErrorToJavaError(err);
+ defaultMsg = StrCryptoError(err);
+ break;
+ }
+}
+static jthrowable createCryptoException(JNIEnv *env, status_t err,
+ const char * msg = NULL, const sp<ICrypto> & crypto = NULL,
+ const sp<AMessage> & cryptoInfo = NULL) {
+ jthrowable exception = nullptr;
+ jmethodID constructID = nullptr;
+ ScopedLocalRef<jobject> cryptoInfoObject(env);
+ std::string defaultMsg = "Unknown Error";
+ jint jerr = 0;
+ // Get a class ref for CryptoException
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/media/MediaCodec$CryptoException"));
+ CHECK(clazz.get() != NULL);
+
+ // Get constructor ref for CryptoException
+ constructID = env->GetMethodID(clazz.get(), "<init>",
+ "(Ljava/lang/String;IIIILandroid/media/MediaCodec$CryptoInfo;)V");
+ CHECK(constructID != NULL);
+
+ // create detailed message for exception
+ CryptoErrorToJavaError(err, jerr, defaultMsg);
+ std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
+ DrmStatus dStatus(err, originalMsg.c_str());
+ std::string detailedMsg(
+ DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
+ jstring msgObj = env->NewStringUTF(detailedMsg.c_str());
+
+ if (cryptoInfo != nullptr) {
+ // Class ref for CryptoInfo
+ ScopedLocalRef<jclass> clazzCryptoInfo(
+ env, env->FindClass("android/media/MediaCodec$CryptoInfo"));
+ CHECK(clazzCryptoInfo.get() != NULL);
+
+ // Constructor reference for CryptoInfo
+ jmethodID constructCryptoInfo =
+ env->GetMethodID(clazzCryptoInfo.get(), "<init>", "()V");
+ CHECK(constructCryptoInfo != NULL);
+
+ // Create CryptoInfo jobject
+ cryptoInfoObject.reset(
+ env->NewObject(clazzCryptoInfo.get(), constructCryptoInfo));
+ CHECK(cryptoInfoObject.get() != NULL);
+
+ // Translate AMesage to CryptoInfo
+ AMessageToCryptoInfo(env, cryptoInfoObject.get(), cryptoInfo);
+ }
+
+ exception = (jthrowable)env->NewObject(
+ clazz.get(), constructID, msgObj, jerr,
+ dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext(),
+ cryptoInfoObject.get());
+
+ return exception;
+}
void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
int32_t arg1, arg2 = 0;
jobject obj = NULL;
@@ -1107,6 +1284,17 @@
break;
}
+ case MediaCodec::CB_CRYPTO_ERROR:
+ {
+ int32_t err, actionCode;
+ AString errorDetail;
+ CHECK(msg->findInt32("err", &err));
+ CHECK(msg->findInt32("actionCode",&actionCode));
+ CHECK(msg->findString("errorDetail", &errorDetail));
+ obj = (jobject)createCryptoException(env, err, errorDetail.c_str(), NULL, msg);
+ break;
+ }
+
case MediaCodec::CB_ERROR:
{
int32_t err, actionCode;
@@ -1144,7 +1332,6 @@
default:
TRESPASS();
}
-
env->CallVoidMethod(
mObject,
gFields.postEventFromNativeID,
@@ -1229,7 +1416,6 @@
}
}
-jint MediaErrorToJavaError(status_t err);
} // namespace android
@@ -1280,70 +1466,8 @@
static void throwCryptoException(JNIEnv *env, status_t err, const char *msg,
const sp<ICrypto> &crypto) {
- ScopedLocalRef<jclass> clazz(
- env, env->FindClass("android/media/MediaCodec$CryptoException"));
- CHECK(clazz.get() != NULL);
-
- jmethodID constructID =
- env->GetMethodID(clazz.get(), "<init>", "(Ljava/lang/String;IIII)V");
- CHECK(constructID != NULL);
-
- std::string defaultMsg = "Unknown Error";
-
- /* translate OS errors to Java API CryptoException errorCodes (which are positive) */
- jint jerr = 0;
- switch (err) {
- case ERROR_DRM_NO_LICENSE:
- jerr = gCryptoErrorCodes.cryptoErrorNoKey;
- defaultMsg = "Crypto key not available";
- break;
- case ERROR_DRM_LICENSE_EXPIRED:
- jerr = gCryptoErrorCodes.cryptoErrorKeyExpired;
- defaultMsg = "License expired";
- break;
- case ERROR_DRM_RESOURCE_BUSY:
- jerr = gCryptoErrorCodes.cryptoErrorResourceBusy;
- defaultMsg = "Resource busy or unavailable";
- break;
- case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION:
- jerr = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection;
- defaultMsg = "Required output protections are not active";
- break;
- case ERROR_DRM_SESSION_NOT_OPENED:
- jerr = gCryptoErrorCodes.cryptoErrorSessionNotOpened;
- defaultMsg = "Attempted to use a closed session";
- break;
- case ERROR_DRM_INSUFFICIENT_SECURITY:
- jerr = gCryptoErrorCodes.cryptoErrorInsufficientSecurity;
- defaultMsg = "Required security level is not met";
- break;
- case ERROR_DRM_CANNOT_HANDLE:
- jerr = gCryptoErrorCodes.cryptoErrorUnsupportedOperation;
- defaultMsg = "Operation not supported in this configuration";
- break;
- case ERROR_DRM_FRAME_TOO_LARGE:
- jerr = gCryptoErrorCodes.cryptoErrorFrameTooLarge;
- defaultMsg = "Decrytped frame exceeds size of output buffer";
- break;
- case ERROR_DRM_SESSION_LOST_STATE:
- jerr = gCryptoErrorCodes.cryptoErrorLostState;
- defaultMsg = "Session state was lost, open a new session and retry";
- break;
- default: /* Other negative DRM error codes go out best-effort. */
- jerr = MediaErrorToJavaError(err);
- defaultMsg = StrCryptoError(err);
- break;
- }
-
- std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
- DrmStatus dStatus(err, originalMsg.c_str());
- std::string detailedMsg(DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
- jstring msgObj = env->NewStringUTF(detailedMsg.c_str());
-
- jthrowable exception =
- (jthrowable)env->NewObject(clazz.get(), constructID, msgObj, jerr,
- dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext());
-
+ jthrowable exception = createCryptoException(
+ env, err, msg, crypto, /* cryptoInfo */ NULL);
env->Throw(exception);
}
@@ -2963,6 +3087,12 @@
clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
CHECK(clazz.get() != NULL);
+ gFields.cryptoInfoSetID = env->GetMethodID(clazz.get(), "set", "(I[I[I[B[BI)V");
+ CHECK(gFields.cryptoInfoSetID != NULL);
+
+ gFields.cryptoInfoSetPatternID = env->GetMethodID(clazz.get(), "setPattern", "(II)V");
+ CHECK(gFields.cryptoInfoSetPatternID != NULL);
+
gFields.cryptoInfoNumSubSamplesID =
env->GetFieldID(clazz.get(), "numSubSamples", "I");
CHECK(gFields.cryptoInfoNumSubSamplesID != NULL);