diff options
| -rw-r--r-- | core/api/current.txt | 3 | ||||
| -rw-r--r-- | media/java/android/media/MediaCodec.java | 59 | ||||
| -rw-r--r-- | media/jni/android_media_MediaCodec.cpp | 262 |
3 files changed, 256 insertions, 68 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 7fdc7b5072e1..9e55e757b1e5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -22527,6 +22527,7 @@ package android.media { field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1 field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1 field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2 + field public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; // 0x4 field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2 field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1 field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0 @@ -22556,6 +22557,7 @@ package android.media { public abstract static class MediaCodec.Callback { ctor public MediaCodec.Callback(); + method public void onCryptoError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CryptoException); method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException); method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int); method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo); @@ -22573,6 +22575,7 @@ package android.media { public static final class MediaCodec.CryptoException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable { ctor public MediaCodec.CryptoException(int, @Nullable String); + method @Nullable public android.media.MediaCodec.CryptoInfo getCryptoInfo(); method public int getErrorCode(); field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8 field @Deprecated public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4 diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index df9c539cae4d..2541a506760c 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -571,6 +571,10 @@ import java.util.concurrent.locks.ReentrantLock; void onError(…) { … } + {@literal @Override} + void onCryptoError(…) { + … + } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); // option B @@ -1774,6 +1778,7 @@ final public class MediaCodec { 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 @@ final public class MediaCodec { 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 @@ final public class MediaCodec { */ 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 @@ final public class MediaCodec { 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 @@ final public class MediaCodec { 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 @@ final public class MediaCodec { } private final int mErrorCode, mVendorError, mOemError, mErrorContext; + private CryptoInfo mCryptoInfo; } /** @@ -5088,6 +5124,25 @@ final public class MediaCodec { 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 5b0c2a203022..9a4aa3344374 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -184,6 +184,8 @@ struct fields_t { jmethodID postEventFromNativeID; jmethodID lockAndGetContextID; jmethodID setAndUnlockContextID; + jmethodID cryptoInfoSetID; + jmethodID cryptoInfoSetPatternID; jfieldID cryptoInfoNumSubSamplesID; jfieldID cryptoInfoNumBytesOfClearDataID; jfieldID cryptoInfoNumBytesOfEncryptedDataID; @@ -203,6 +205,7 @@ struct fields_t { static fields_t gFields; static const void *sRefBaseOwner; +jint MediaErrorToJavaError(status_t err); //////////////////////////////////////////////////////////////////////////////// @@ -1068,6 +1071,180 @@ static jthrowable createCodecException( 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 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { 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 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { default: TRESPASS(); } - env->CallVoidMethod( mObject, gFields.postEventFromNativeID, @@ -1229,7 +1416,6 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { } } -jint MediaErrorToJavaError(status_t err); } // namespace android @@ -1280,70 +1466,8 @@ static void throwCodecException(JNIEnv *env, status_t err, int32_t actionCode, c 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 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { 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); |