/* * Copyright 2012, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaCodec-JNI" #include #include "android_media_MediaCodec.h" #include "android_media_MediaCrypto.h" #include "android_media_Utils.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" #include "jni.h" #include "JNIHelp.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace android { // Keep these in sync with their equivalents in MediaCodec.java !!! enum { DEQUEUE_INFO_TRY_AGAIN_LATER = -1, DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED = -2, DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED = -3, }; enum { EVENT_CALLBACK = 1, EVENT_SET_CALLBACK = 2, }; static struct CryptoErrorCodes { jint cryptoErrorNoKey; jint cryptoErrorKeyExpired; jint cryptoErrorResourceBusy; jint cryptoErrorInsufficientOutputProtection; } gCryptoErrorCodes; static struct CodecActionCodes { jint codecActionTransient; jint codecActionRecoverable; } gCodecActionCodes; struct fields_t { jfieldID context; jmethodID postEventFromNativeID; jfieldID cryptoInfoNumSubSamplesID; jfieldID cryptoInfoNumBytesOfClearDataID; jfieldID cryptoInfoNumBytesOfEncryptedDataID; jfieldID cryptoInfoKeyID; jfieldID cryptoInfoIVID; jfieldID cryptoInfoModeID; }; static fields_t gFields; //////////////////////////////////////////////////////////////////////////////// JMediaCodec::JMediaCodec( JNIEnv *env, jobject thiz, const char *name, bool nameIsType, bool encoder) : mClass(NULL), mObject(NULL) { jclass clazz = env->GetObjectClass(thiz); CHECK(clazz != NULL); mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); cacheJavaObjects(env); mLooper = new ALooper; mLooper->setName("MediaCodec_looper"); mLooper->start( false, // runOnCallingThread true, // canCallJava PRIORITY_FOREGROUND); if (nameIsType) { mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus); } else { mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus); } CHECK((mCodec != NULL) != (mInitStatus != OK)); } void JMediaCodec::cacheJavaObjects(JNIEnv *env) { jclass clazz = (jclass)env->FindClass("java/nio/ByteBuffer"); mByteBufferClass = (jclass)env->NewGlobalRef(clazz); CHECK(mByteBufferClass != NULL); ScopedLocalRef byteOrderClass( env, env->FindClass("java/nio/ByteOrder")); CHECK(byteOrderClass.get() != NULL); jmethodID nativeOrderID = env->GetStaticMethodID( byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); CHECK(nativeOrderID != NULL); jobject nativeByteOrderObj = env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID); mNativeByteOrderObj = env->NewGlobalRef(nativeByteOrderObj); CHECK(mNativeByteOrderObj != NULL); env->DeleteLocalRef(nativeByteOrderObj); nativeByteOrderObj = NULL; mByteBufferOrderMethodID = env->GetMethodID( mByteBufferClass, "order", "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); CHECK(mByteBufferOrderMethodID != NULL); mByteBufferAsReadOnlyBufferMethodID = env->GetMethodID( mByteBufferClass, "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); CHECK(mByteBufferAsReadOnlyBufferMethodID != NULL); mByteBufferPositionMethodID = env->GetMethodID( mByteBufferClass, "position", "(I)Ljava/nio/Buffer;"); CHECK(mByteBufferPositionMethodID != NULL); mByteBufferLimitMethodID = env->GetMethodID( mByteBufferClass, "limit", "(I)Ljava/nio/Buffer;"); CHECK(mByteBufferLimitMethodID != NULL); } status_t JMediaCodec::initCheck() const { return mInitStatus; } void JMediaCodec::registerSelf() { mLooper->registerHandler(this); } void JMediaCodec::release() { if (mCodec != NULL) { mCodec->release(); mCodec.clear(); mInitStatus = NO_INIT; } if (mLooper != NULL) { mLooper->unregisterHandler(id()); mLooper->stop(); mLooper.clear(); } } JMediaCodec::~JMediaCodec() { if (mCodec != NULL || mLooper != NULL) { /* MediaCodec and looper should have been released explicitly already * in setMediaCodec() (see comments in setMediaCodec()). * * Otherwise JMediaCodec::~JMediaCodec() might be called from within the * message handler, doing release() there risks deadlock as MediaCodec:: * release() post synchronous message to the same looper. * * Print a warning and try to proceed with releasing. */ ALOGW("try to release MediaCodec from JMediaCodec::~JMediaCodec()..."); release(); ALOGW("done releasing MediaCodec from JMediaCodec::~JMediaCodec()."); } JNIEnv *env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mObject); mObject = NULL; env->DeleteGlobalRef(mClass); mClass = NULL; deleteJavaObjects(env); } void JMediaCodec::deleteJavaObjects(JNIEnv *env) { env->DeleteGlobalRef(mByteBufferClass); mByteBufferClass = NULL; env->DeleteGlobalRef(mNativeByteOrderObj); mNativeByteOrderObj = NULL; mByteBufferOrderMethodID = NULL; mByteBufferAsReadOnlyBufferMethodID = NULL; mByteBufferPositionMethodID = NULL; mByteBufferLimitMethodID = NULL; } status_t JMediaCodec::setCallback(jobject cb) { if (cb != NULL) { if (mCallbackNotification == NULL) { mCallbackNotification = new AMessage(kWhatCallbackNotify, this); } } else { mCallbackNotification.clear(); } return mCodec->setCallback(mCallbackNotification); } status_t JMediaCodec::configure( const sp &format, const sp &bufferProducer, const sp &crypto, int flags) { sp client; if (bufferProducer != NULL) { mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */); } else { mSurfaceTextureClient.clear(); } return mCodec->configure(format, mSurfaceTextureClient, crypto, flags); } status_t JMediaCodec::createInputSurface( sp* bufferProducer) { return mCodec->createInputSurface(bufferProducer); } status_t JMediaCodec::start() { return mCodec->start(); } status_t JMediaCodec::stop() { mSurfaceTextureClient.clear(); return mCodec->stop(); } status_t JMediaCodec::flush() { return mCodec->flush(); } status_t JMediaCodec::reset() { return mCodec->reset(); } status_t JMediaCodec::queueInputBuffer( size_t index, size_t offset, size_t size, int64_t timeUs, uint32_t flags, AString *errorDetailMsg) { return mCodec->queueInputBuffer( index, offset, size, timeUs, flags, errorDetailMsg); } status_t JMediaCodec::queueSecureInputBuffer( size_t index, size_t offset, const CryptoPlugin::SubSample *subSamples, size_t numSubSamples, const uint8_t key[16], const uint8_t iv[16], CryptoPlugin::Mode mode, int64_t presentationTimeUs, uint32_t flags, AString *errorDetailMsg) { return mCodec->queueSecureInputBuffer( index, offset, subSamples, numSubSamples, key, iv, mode, presentationTimeUs, flags, errorDetailMsg); } status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) { return mCodec->dequeueInputBuffer(index, timeoutUs); } status_t JMediaCodec::dequeueOutputBuffer( JNIEnv *env, jobject bufferInfo, size_t *index, int64_t timeoutUs) { size_t size, offset; int64_t timeUs; uint32_t flags; status_t err = mCodec->dequeueOutputBuffer( index, &offset, &size, &timeUs, &flags, timeoutUs); if (err != OK) { return err; } ScopedLocalRef clazz( env, env->FindClass("android/media/MediaCodec$BufferInfo")); jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); env->CallVoidMethod(bufferInfo, method, (jint)offset, (jint)size, timeUs, flags); return OK; } status_t JMediaCodec::releaseOutputBuffer( size_t index, bool render, bool updatePTS, int64_t timestampNs) { if (updatePTS) { return mCodec->renderOutputBufferAndRelease(index, timestampNs); } return render ? mCodec->renderOutputBufferAndRelease(index) : mCodec->releaseOutputBuffer(index); } status_t JMediaCodec::signalEndOfInputStream() { return mCodec->signalEndOfInputStream(); } status_t JMediaCodec::getFormat(JNIEnv *env, bool input, jobject *format) const { sp msg; status_t err; err = input ? mCodec->getInputFormat(&msg) : mCodec->getOutputFormat(&msg); if (err != OK) { return err; } return ConvertMessageToMap(env, msg, format); } status_t JMediaCodec::getOutputFormat(JNIEnv *env, size_t index, jobject *format) const { sp msg; status_t err; if ((err = mCodec->getOutputFormat(index, &msg)) != OK) { return err; } return ConvertMessageToMap(env, msg, format); } status_t JMediaCodec::getBuffers( JNIEnv *env, bool input, jobjectArray *bufArray) const { Vector > buffers; status_t err = input ? mCodec->getInputBuffers(&buffers) : mCodec->getOutputBuffers(&buffers); if (err != OK) { return err; } *bufArray = (jobjectArray)env->NewObjectArray( buffers.size(), mByteBufferClass, NULL); if (*bufArray == NULL) { return NO_MEMORY; } for (size_t i = 0; i < buffers.size(); ++i) { const sp &buffer = buffers.itemAt(i); jobject byteBuffer = NULL; err = createByteBufferFromABuffer( env, !input /* readOnly */, true /* clearBuffer */, buffer, &byteBuffer); if (err != OK) { return err; } if (byteBuffer != NULL) { env->SetObjectArrayElement( *bufArray, i, byteBuffer); env->DeleteLocalRef(byteBuffer); byteBuffer = NULL; } } return OK; } // static status_t JMediaCodec::createByteBufferFromABuffer( JNIEnv *env, bool readOnly, bool clearBuffer, const sp &buffer, jobject *buf) const { // if this is an ABuffer that doesn't actually hold any accessible memory, // use a null ByteBuffer *buf = NULL; if (buffer->base() == NULL) { return OK; } jobject byteBuffer = env->NewDirectByteBuffer(buffer->base(), buffer->capacity()); if (readOnly && byteBuffer != NULL) { jobject readOnlyBuffer = env->CallObjectMethod( byteBuffer, mByteBufferAsReadOnlyBufferMethodID); env->DeleteLocalRef(byteBuffer); byteBuffer = readOnlyBuffer; } if (byteBuffer == NULL) { return NO_MEMORY; } jobject me = env->CallObjectMethod( byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj); env->DeleteLocalRef(me); me = env->CallObjectMethod( byteBuffer, mByteBufferLimitMethodID, clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size())); env->DeleteLocalRef(me); me = env->CallObjectMethod( byteBuffer, mByteBufferPositionMethodID, clearBuffer ? 0 : buffer->offset()); env->DeleteLocalRef(me); me = NULL; *buf = byteBuffer; return OK; } status_t JMediaCodec::getBuffer( JNIEnv *env, bool input, size_t index, jobject *buf) const { sp buffer; status_t err = input ? mCodec->getInputBuffer(index, &buffer) : mCodec->getOutputBuffer(index, &buffer); if (err != OK) { return err; } return createByteBufferFromABuffer( env, !input /* readOnly */, input /* clearBuffer */, buffer, buf); } status_t JMediaCodec::getImage( JNIEnv *env, bool input, size_t index, jobject *buf) const { sp buffer; status_t err = input ? mCodec->getInputBuffer(index, &buffer) : mCodec->getOutputBuffer(index, &buffer); if (err != OK) { return err; } // if this is an ABuffer that doesn't actually hold any accessible memory, // use a null ByteBuffer *buf = NULL; if (buffer->base() == NULL) { return OK; } // check if buffer is an image sp imageData; if (!buffer->meta()->findBuffer("image-data", &imageData)) { return OK; } int64_t timestamp = 0; if (!input && buffer->meta()->findInt64("timeUs", ×tamp)) { timestamp *= 1000; // adjust to ns } jobject byteBuffer = NULL; err = createByteBufferFromABuffer( env, !input /* readOnly */, input /* clearBuffer */, buffer, &byteBuffer); if (err != OK) { return OK; } jobject infoBuffer = NULL; err = createByteBufferFromABuffer( env, true /* readOnly */, true /* clearBuffer */, imageData, &infoBuffer); if (err != OK) { env->DeleteLocalRef(byteBuffer); byteBuffer = NULL; return OK; } jobject cropRect = NULL; int32_t left, top, right, bottom; if (buffer->meta()->findRect("crop-rect", &left, &top, &right, &bottom)) { ScopedLocalRef rectClazz( env, env->FindClass("android/graphics/Rect")); CHECK(rectClazz.get() != NULL); jmethodID rectConstructID = env->GetMethodID( rectClazz.get(), "", "(IIII)V"); cropRect = env->NewObject( rectClazz.get(), rectConstructID, left, top, right + 1, bottom + 1); } ScopedLocalRef imageClazz( env, env->FindClass("android/media/MediaCodec$MediaImage")); CHECK(imageClazz.get() != NULL); jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;ZJIILandroid/graphics/Rect;)V"); *buf = env->NewObject(imageClazz.get(), imageConstructID, byteBuffer, infoBuffer, (jboolean)!input /* readOnly */, (jlong)timestamp, (jint)0 /* xOffset */, (jint)0 /* yOffset */, cropRect); // if MediaImage creation fails, return null if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); *buf = NULL; } if (cropRect != NULL) { env->DeleteLocalRef(cropRect); cropRect = NULL; } env->DeleteLocalRef(byteBuffer); byteBuffer = NULL; env->DeleteLocalRef(infoBuffer); infoBuffer = NULL; return OK; } status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { AString name; status_t err = mCodec->getName(&name); if (err != OK) { return err; } *nameStr = env->NewStringUTF(name.c_str()); return OK; } status_t JMediaCodec::setParameters(const sp &msg) { return mCodec->setParameters(msg); } void JMediaCodec::setVideoScalingMode(int mode) { if (mSurfaceTextureClient != NULL) { native_window_set_scaling_mode(mSurfaceTextureClient.get(), mode); } } static jthrowable createCodecException( JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) { ScopedLocalRef clazz( env, env->FindClass("android/media/MediaCodec$CodecException")); CHECK(clazz.get() != NULL); const jmethodID ctor = env->GetMethodID(clazz.get(), "", "(IILjava/lang/String;)V"); CHECK(ctor != NULL); ScopedLocalRef 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 &msg) { int32_t arg1, arg2 = 0; jobject obj = NULL; CHECK(msg->findInt32("callbackID", &arg1)); JNIEnv *env = AndroidRuntime::getJNIEnv(); switch (arg1) { case MediaCodec::CB_INPUT_AVAILABLE: { CHECK(msg->findInt32("index", &arg2)); break; } case MediaCodec::CB_OUTPUT_AVAILABLE: { CHECK(msg->findInt32("index", &arg2)); size_t size, offset; int64_t timeUs; uint32_t flags; CHECK(msg->findSize("size", &size)); CHECK(msg->findSize("offset", &offset)); CHECK(msg->findInt64("timeUs", &timeUs)); CHECK(msg->findInt32("flags", (int32_t *)&flags)); ScopedLocalRef clazz( env, env->FindClass("android/media/MediaCodec$BufferInfo")); jmethodID ctor = env->GetMethodID(clazz.get(), "", "()V"); jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); obj = env->NewObject(clazz.get(), ctor); if (obj == NULL) { if (env->ExceptionCheck()) { ALOGE("Could not create MediaCodec.BufferInfo."); env->ExceptionClear(); } jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags); break; } case MediaCodec::CB_ERROR: { int32_t err, actionCode; CHECK(msg->findInt32("err", &err)); CHECK(msg->findInt32("actionCode", &actionCode)); // note that DRM errors could conceivably alias into a CodecException obj = (jobject)createCodecException(env, err, actionCode); if (obj == NULL) { if (env->ExceptionCheck()) { ALOGE("Could not create CodecException object."); env->ExceptionClear(); } jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } break; } case MediaCodec::CB_OUTPUT_FORMAT_CHANGED: { sp format; CHECK(msg->findMessage("format", &format)); if (OK != ConvertMessageToMap(env, format, &obj)) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } break; } case MediaCodec::CB_CODEC_RELEASED: { if (!msg->findInt32("reason", &arg2)) { arg2 = MediaCodec::REASON_UNKNOWN; } break; } default: TRESPASS(); } env->CallVoidMethod( mObject, gFields.postEventFromNativeID, EVENT_CALLBACK, arg1, arg2, obj); env->DeleteLocalRef(obj); } void JMediaCodec::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatCallbackNotify: { handleCallback(msg); break; } default: TRESPASS(); } } } // namespace android //////////////////////////////////////////////////////////////////////////////// using namespace android; static sp setMediaCodec( JNIEnv *env, jobject thiz, const sp &codec) { sp old = (JMediaCodec *)env->GetLongField(thiz, gFields.context); if (codec != NULL) { codec->incStrong(thiz); } if (old != NULL) { /* release MediaCodec and stop the looper now before decStrong. * otherwise JMediaCodec::~JMediaCodec() could be called from within * its message handler, doing release() from there will deadlock * (as MediaCodec::release() post synchronous message to the same looper) */ old->release(); old->decStrong(thiz); } env->SetLongField(thiz, gFields.context, (jlong)codec.get()); return old; } static sp getMediaCodec(JNIEnv *env, jobject thiz) { return (JMediaCodec *)env->GetLongField(thiz, gFields.context); } 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 clazz( env, env->FindClass("android/media/MediaCodec$CryptoException")); CHECK(clazz.get() != NULL); jmethodID constructID = env->GetMethodID(clazz.get(), "", "(ILjava/lang/String;)V"); CHECK(constructID != NULL); jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error"); /* translate OS errors to Java API CryptoException errorCodes (which are positive) */ switch (err) { case ERROR_DRM_NO_LICENSE: err = gCryptoErrorCodes.cryptoErrorNoKey; break; case ERROR_DRM_LICENSE_EXPIRED: err = gCryptoErrorCodes.cryptoErrorKeyExpired; break; case ERROR_DRM_RESOURCE_BUSY: err = gCryptoErrorCodes.cryptoErrorResourceBusy; break; case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION: err = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection; break; default: /* Other negative DRM error codes go out as is. */ break; } jthrowable exception = (jthrowable)env->NewObject(clazz.get(), constructID, err, msgObj); env->Throw(exception); } static jint throwExceptionAsNecessary( JNIEnv *env, status_t err, int32_t actionCode = ACTION_CODE_FATAL, const char *msg = NULL) { switch (err) { case OK: return 0; case -EAGAIN: return DEQUEUE_INFO_TRY_AGAIN_LATER; case INFO_FORMAT_CHANGED: return DEQUEUE_INFO_OUTPUT_FORMAT_CHANGED; case INFO_OUTPUT_BUFFERS_CHANGED: return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED; case INVALID_OPERATION: jniThrowException(env, "java/lang/IllegalStateException", msg); return 0; default: 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, jobject thiz, jobject cb) { sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->setCallback(cb); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_native_configure( JNIEnv *env, jobject thiz, jobjectArray keys, jobjectArray values, jobject jsurface, jobject jcrypto, jint flags) { sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp format; status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format); if (err != OK) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } sp bufferProducer; if (jsurface != NULL) { sp surface(android_view_Surface_getSurface(env, jsurface)); if (surface != NULL) { bufferProducer = surface->getIGraphicBufferProducer(); } else { jniThrowException( env, "java/lang/IllegalArgumentException", "The surface has been released"); return; } } sp crypto; if (jcrypto != NULL) { crypto = JCrypto::GetCrypto(env, jcrypto); } err = codec->configure(format, bufferProducer, crypto, flags); throwExceptionAsNecessary(env, err); } static jobject android_media_MediaCodec_createInputSurface(JNIEnv* env, jobject thiz) { ALOGV("android_media_MediaCodec_createInputSurface"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } // Tell the MediaCodec that we want to use a Surface as input. sp bufferProducer; status_t err = codec->createInputSurface(&bufferProducer); if (err != NO_ERROR) { throwExceptionAsNecessary(env, err); return NULL; } // Wrap the IGBP in a Java-language Surface. return android_view_Surface_createFromIGraphicBufferProducer(env, bufferProducer); } static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_start"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->start(); throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, "start failed"); } static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_stop"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->stop(); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_reset"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->reset(); if (err != OK) { // treat all errors as fatal for now, though resource not available // errors could be treated as transient. // 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); } static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_flush"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->flush(); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_queueInputBuffer( JNIEnv *env, jobject thiz, jint index, jint offset, jint size, jlong timestampUs, jint flags) { ALOGV("android_media_MediaCodec_queueInputBuffer"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } AString errorDetailMsg; status_t err = codec->queueInputBuffer( index, offset, size, timestampUs, flags, &errorDetailMsg); throwExceptionAsNecessary( env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); } static void android_media_MediaCodec_queueSecureInputBuffer( JNIEnv *env, jobject thiz, jint index, jint offset, jobject cryptoInfoObj, jlong timestampUs, jint flags) { ALOGV("android_media_MediaCodec_queueSecureInputBuffer"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } jint numSubSamples = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID); jintArray numBytesOfClearDataObj = (jintArray)env->GetObjectField( cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID); jintArray numBytesOfEncryptedDataObj = (jintArray)env->GetObjectField( cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID); jbyteArray keyObj = (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID); jbyteArray ivObj = (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID); jint mode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID); status_t err = OK; CryptoPlugin::SubSample *subSamples = NULL; jbyte *key = NULL; jbyte *iv = NULL; if (numSubSamples <= 0) { err = -EINVAL; } else if (numBytesOfClearDataObj == NULL && numBytesOfEncryptedDataObj == NULL) { err = -EINVAL; } else if (numBytesOfEncryptedDataObj != NULL && env->GetArrayLength(numBytesOfEncryptedDataObj) < numSubSamples) { err = -ERANGE; } else if (numBytesOfClearDataObj != NULL && env->GetArrayLength(numBytesOfClearDataObj) < numSubSamples) { 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 >= (signed)(INT32_MAX / sizeof(*subSamples))) ) { err = -EINVAL; } else { jboolean isCopy; jint *numBytesOfClearData = (numBytesOfClearDataObj == NULL) ? NULL : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy); jint *numBytesOfEncryptedData = (numBytesOfEncryptedDataObj == NULL) ? NULL : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy); subSamples = new CryptoPlugin::SubSample[numSubSamples]; for (jint i = 0; i < numSubSamples; ++i) { subSamples[i].mNumBytesOfClearData = (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i]; subSamples[i].mNumBytesOfEncryptedData = (numBytesOfEncryptedData == NULL) ? 0 : numBytesOfEncryptedData[i]; } if (numBytesOfEncryptedData != NULL) { env->ReleaseIntArrayElements( numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0); numBytesOfEncryptedData = NULL; } if (numBytesOfClearData != NULL) { env->ReleaseIntArrayElements( numBytesOfClearDataObj, numBytesOfClearData, 0); numBytesOfClearData = NULL; } } if (err == OK && keyObj != NULL) { if (env->GetArrayLength(keyObj) != 16) { err = -EINVAL; } else { jboolean isCopy; key = env->GetByteArrayElements(keyObj, &isCopy); } } if (err == OK && ivObj != NULL) { if (env->GetArrayLength(ivObj) != 16) { err = -EINVAL; } else { jboolean isCopy; iv = env->GetByteArrayElements(ivObj, &isCopy); } } AString errorDetailMsg; if (err == OK) { err = codec->queueSecureInputBuffer( index, offset, subSamples, numSubSamples, (const uint8_t *)key, (const uint8_t *)iv, (CryptoPlugin::Mode)mode, timestampUs, flags, &errorDetailMsg); } if (iv != NULL) { env->ReleaseByteArrayElements(ivObj, iv, 0); iv = NULL; } if (key != NULL) { env->ReleaseByteArrayElements(keyObj, key, 0); key = NULL; } delete[] subSamples; subSamples = NULL; throwExceptionAsNecessary( env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); } static jint android_media_MediaCodec_dequeueInputBuffer( JNIEnv *env, jobject thiz, jlong timeoutUs) { ALOGV("android_media_MediaCodec_dequeueInputBuffer"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return -1; } size_t index; status_t err = codec->dequeueInputBuffer(&index, timeoutUs); if (err == OK) { return (jint) index; } return throwExceptionAsNecessary(env, err); } static jint android_media_MediaCodec_dequeueOutputBuffer( JNIEnv *env, jobject thiz, jobject bufferInfo, jlong timeoutUs) { ALOGV("android_media_MediaCodec_dequeueOutputBuffer"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return 0; } size_t index; status_t err = codec->dequeueOutputBuffer( env, bufferInfo, &index, timeoutUs); if (err == OK) { return (jint) index; } return throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_releaseOutputBuffer( JNIEnv *env, jobject thiz, jint index, jboolean render, jboolean updatePTS, jlong timestampNs) { ALOGV("android_media_MediaCodec_renderOutputBufferAndRelease"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->releaseOutputBuffer(index, render, updatePTS, timestampNs); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env, jobject thiz) { ALOGV("android_media_MediaCodec_signalEndOfInputStream"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->signalEndOfInputStream(); throwExceptionAsNecessary(env, err); } static jobject android_media_MediaCodec_getFormatNative( JNIEnv *env, jobject thiz, jboolean input) { ALOGV("android_media_MediaCodec_getFormatNative"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject format; status_t err = codec->getFormat(env, input, &format); if (err == OK) { return format; } throwExceptionAsNecessary(env, err); return NULL; } static jobject android_media_MediaCodec_getOutputFormatForIndexNative( JNIEnv *env, jobject thiz, jint index) { ALOGV("android_media_MediaCodec_getOutputFormatForIndexNative"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject format; status_t err = codec->getOutputFormat(env, index, &format); if (err == OK) { return format; } throwExceptionAsNecessary(env, err); return NULL; } static jobjectArray android_media_MediaCodec_getBuffers( JNIEnv *env, jobject thiz, jboolean input) { ALOGV("android_media_MediaCodec_getBuffers"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobjectArray buffers; status_t err = codec->getBuffers(env, input, &buffers); if (err == OK) { return buffers; } // if we're out of memory, an exception was already thrown if (err != NO_MEMORY) { throwExceptionAsNecessary(env, err); } return NULL; } static jobject android_media_MediaCodec_getBuffer( JNIEnv *env, jobject thiz, jboolean input, jint index) { ALOGV("android_media_MediaCodec_getBuffer"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject buffer; status_t err = codec->getBuffer(env, input, index, &buffer); if (err == OK) { return buffer; } // if we're out of memory, an exception was already thrown if (err != NO_MEMORY) { throwExceptionAsNecessary(env, err); } return NULL; } static jobject android_media_MediaCodec_getImage( JNIEnv *env, jobject thiz, jboolean input, jint index) { ALOGV("android_media_MediaCodec_getImage"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject image; status_t err = codec->getImage(env, input, index, &image); if (err == OK) { return image; } // if we're out of memory, an exception was already thrown if (err != NO_MEMORY) { throwExceptionAsNecessary(env, err); } return NULL; } static jobject android_media_MediaCodec_getName( JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_getName"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jstring name; status_t err = codec->getName(env, &name); if (err == OK) { return name; } throwExceptionAsNecessary(env, err); return NULL; } static void android_media_MediaCodec_setParameters( JNIEnv *env, jobject thiz, jobjectArray keys, jobjectArray vals) { ALOGV("android_media_MediaCodec_setParameters"); sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp params; status_t err = ConvertKeyValueArraysToMessage(env, keys, vals, ¶ms); if (err == OK) { err = codec->setParameters(params); } throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_setVideoScalingMode( JNIEnv *env, jobject thiz, jint mode) { sp codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } if (mode != NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW && mode != NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) { jniThrowException(env, "java/lang/InvalidArgumentException", NULL); return; } codec->setVideoScalingMode(mode); } static void android_media_MediaCodec_native_init(JNIEnv *env) { ScopedLocalRef clazz( env, env->FindClass("android/media/MediaCodec")); CHECK(clazz.get() != NULL); gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J"); CHECK(gFields.context != NULL); gFields.postEventFromNativeID = env->GetMethodID( clazz.get(), "postEventFromNative", "(IIILjava/lang/Object;)V"); CHECK(gFields.postEventFromNativeID != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo")); CHECK(clazz.get() != NULL); gFields.cryptoInfoNumSubSamplesID = env->GetFieldID(clazz.get(), "numSubSamples", "I"); CHECK(gFields.cryptoInfoNumSubSamplesID != NULL); gFields.cryptoInfoNumBytesOfClearDataID = env->GetFieldID(clazz.get(), "numBytesOfClearData", "[I"); CHECK(gFields.cryptoInfoNumBytesOfClearDataID != NULL); gFields.cryptoInfoNumBytesOfEncryptedDataID = env->GetFieldID(clazz.get(), "numBytesOfEncryptedData", "[I"); CHECK(gFields.cryptoInfoNumBytesOfEncryptedDataID != NULL); gFields.cryptoInfoKeyID = env->GetFieldID(clazz.get(), "key", "[B"); CHECK(gFields.cryptoInfoKeyID != NULL); gFields.cryptoInfoIVID = env->GetFieldID(clazz.get(), "iv", "[B"); CHECK(gFields.cryptoInfoIVID != NULL); gFields.cryptoInfoModeID = env->GetFieldID(clazz.get(), "mode", "I"); CHECK(gFields.cryptoInfoModeID != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException")); CHECK(clazz.get() != NULL); jfieldID field; field = env->GetStaticFieldID(clazz.get(), "ERROR_NO_KEY", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorNoKey = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_KEY_EXPIRED", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorKeyExpired = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_RESOURCE_BUSY", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorResourceBusy = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_INSUFFICIENT_OUTPUT_PROTECTION", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection = 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/NullPointerException", NULL); return; } const char *tmp = env->GetStringUTFChars(name, NULL); if (tmp == NULL) { return; } sp codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder); 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); } static void android_media_MediaCodec_native_finalize( JNIEnv *env, jobject thiz) { android_media_MediaCodec_release(env, thiz); } static JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaCodec_release }, { "native_reset", "()V", (void *)android_media_MediaCodec_reset }, { "native_setCallback", "(Landroid/media/MediaCodec$Callback;)V", (void *)android_media_MediaCodec_native_setCallback }, { "native_configure", "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;" "Landroid/media/MediaCrypto;I)V", (void *)android_media_MediaCodec_native_configure }, { "createInputSurface", "()Landroid/view/Surface;", (void *)android_media_MediaCodec_createInputSurface }, { "native_start", "()V", (void *)android_media_MediaCodec_start }, { "native_stop", "()V", (void *)android_media_MediaCodec_stop }, { "native_flush", "()V", (void *)android_media_MediaCodec_flush }, { "native_queueInputBuffer", "(IIIJI)V", (void *)android_media_MediaCodec_queueInputBuffer }, { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", (void *)android_media_MediaCodec_queueSecureInputBuffer }, { "native_dequeueInputBuffer", "(J)I", (void *)android_media_MediaCodec_dequeueInputBuffer }, { "native_dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", (void *)android_media_MediaCodec_dequeueOutputBuffer }, { "releaseOutputBuffer", "(IZZJ)V", (void *)android_media_MediaCodec_releaseOutputBuffer }, { "signalEndOfInputStream", "()V", (void *)android_media_MediaCodec_signalEndOfInputStream }, { "getFormatNative", "(Z)Ljava/util/Map;", (void *)android_media_MediaCodec_getFormatNative }, { "getOutputFormatNative", "(I)Ljava/util/Map;", (void *)android_media_MediaCodec_getOutputFormatForIndexNative }, { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;", (void *)android_media_MediaCodec_getBuffers }, { "getBuffer", "(ZI)Ljava/nio/ByteBuffer;", (void *)android_media_MediaCodec_getBuffer }, { "getImage", "(ZI)Landroid/media/Image;", (void *)android_media_MediaCodec_getImage }, { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V", (void *)android_media_MediaCodec_setParameters }, { "setVideoScalingMode", "(I)V", (void *)android_media_MediaCodec_setVideoScalingMode }, { "native_init", "()V", (void *)android_media_MediaCodec_native_init }, { "native_setup", "(Ljava/lang/String;ZZ)V", (void *)android_media_MediaCodec_native_setup }, { "native_finalize", "()V", (void *)android_media_MediaCodec_native_finalize }, }; int register_android_media_MediaCodec(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaCodec", gMethods, NELEM(gMethods)); }