Merge "CryptoAsync: Creating MediaCodec.CryptoException"

This commit is contained in:
TreeHugger Robot
2023-02-14 21:49:42 +00:00
committed by Android (Google) Code Review
3 changed files with 256 additions and 68 deletions

View File

@@ -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

View File

@@ -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;
}
/**
@@ -5087,6 +5123,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
*

View File

@@ -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);