/* * 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 <utils/Log.h> #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 <cutils/compiler.h> #include <gui/Surface.h> #include <media/ICrypto.h> #include <media/stagefright/MediaCodec.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/ALooper.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/foundation/AString.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/PersistentSurface.h> #include <nativehelper/ScopedLocalRef.h> #include <system/window.h> 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, EVENT_FRAME_RENDERED = 3, }; static struct CryptoErrorCodes { jint cryptoErrorNoKey; jint cryptoErrorKeyExpired; jint cryptoErrorResourceBusy; jint cryptoErrorInsufficientOutputProtection; jint cryptoErrorSessionNotOpened; jint cryptoErrorUnsupportedOperation; } gCryptoErrorCodes; static struct CodecActionCodes { jint codecActionTransient; jint codecActionRecoverable; } gCodecActionCodes; static struct CodecErrorCodes { jint errorInsufficientResource; jint errorReclaimed; } gCodecErrorCodes; static struct { jclass clazz; jfieldID mLock; jfieldID mPersistentObject; jmethodID ctor; jmethodID setNativeObjectLocked; } gPersistentSurfaceClassInfo; static struct { jint Unencrypted; jint AesCtr; jint AesCbc; } gCryptoModes; struct fields_t { jfieldID context; jmethodID postEventFromNativeID; jfieldID cryptoInfoNumSubSamplesID; jfieldID cryptoInfoNumBytesOfClearDataID; jfieldID cryptoInfoNumBytesOfEncryptedDataID; jfieldID cryptoInfoKeyID; jfieldID cryptoInfoIVID; jfieldID cryptoInfoModeID; jfieldID cryptoInfoPatternID; jfieldID patternEncryptBlocksID; jfieldID patternSkipBlocksID; }; static fields_t gFields; static const void *sRefBaseOwner; //////////////////////////////////////////////////////////////////////////////// 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<jclass> 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::enableOnFrameRenderedListener(jboolean enable) { if (enable) { if (mOnFrameRenderedNotification == NULL) { mOnFrameRenderedNotification = new AMessage(kWhatFrameRendered, this); } } else { mOnFrameRenderedNotification.clear(); } return mCodec->setOnFrameRenderedNotification(mOnFrameRenderedNotification); } 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<AMessage> &format, const sp<IGraphicBufferProducer> &bufferProducer, const sp<ICrypto> &crypto, int flags) { sp<Surface> client; if (bufferProducer != NULL) { mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */); } else { mSurfaceTextureClient.clear(); } return mCodec->configure(format, mSurfaceTextureClient, crypto, flags); } status_t JMediaCodec::setSurface( const sp<IGraphicBufferProducer> &bufferProducer) { sp<Surface> client; if (bufferProducer != NULL) { client = new Surface(bufferProducer, true /* controlledByApp */); } status_t err = mCodec->setSurface(client); if (err == OK) { mSurfaceTextureClient = client; } return err; } status_t JMediaCodec::createInputSurface( sp<IGraphicBufferProducer>* bufferProducer) { return mCodec->createInputSurface(bufferProducer); } status_t JMediaCodec::setInputSurface( const sp<PersistentSurface> &surface) { return mCodec->setInputSurface(surface); } 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, const CryptoPlugin::Pattern &pattern, int64_t presentationTimeUs, uint32_t flags, AString *errorDetailMsg) { return mCodec->queueSecureInputBuffer( index, offset, subSamples, numSubSamples, key, iv, mode, pattern, 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<jclass> 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<AMessage> 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<AMessage> 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<sp<ABuffer> > 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<ABuffer> &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<ABuffer> &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 == NULL) { ALOGV("createByteBufferFromABuffer - given NULL, returning NULL"); return OK; } 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<ABuffer> 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<ABuffer> 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<ABuffer> 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<jclass> rectClazz( env, env->FindClass("android/graphics/Rect")); CHECK(rectClazz.get() != NULL); jmethodID rectConstructID = env->GetMethodID( rectClazz.get(), "<init>", "(IIII)V"); cropRect = env->NewObject( rectClazz.get(), rectConstructID, left, top, right + 1, bottom + 1); } ScopedLocalRef<jclass> imageClazz( env, env->FindClass("android/media/MediaCodec$MediaImage")); CHECK(imageClazz.get() != NULL); jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "<init>", "(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<AMessage> &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<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; } /* translate OS errors to Java API CodecException errorCodes */ switch (err) { case NO_MEMORY: err = gCodecErrorCodes.errorInsufficientResource; break; case DEAD_OBJECT: err = gCodecErrorCodes.errorReclaimed; break; default: /* Other error codes go out as is. */ 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; 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<jclass> clazz( env, env->FindClass("android/media/MediaCodec$BufferInfo")); jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()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<AMessage> format; CHECK(msg->findMessage("format", &format)); if (OK != ConvertMessageToMap(env, format, &obj)) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } break; } default: TRESPASS(); } env->CallVoidMethod( mObject, gFields.postEventFromNativeID, EVENT_CALLBACK, arg1, arg2, obj); env->DeleteLocalRef(obj); } void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) { int32_t arg1 = 0, arg2 = 0; jobject obj = NULL; JNIEnv *env = AndroidRuntime::getJNIEnv(); sp<AMessage> data; CHECK(msg->findMessage("data", &data)); status_t err = ConvertMessageToMap(env, data, &obj); if (err != OK) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } env->CallVoidMethod( mObject, gFields.postEventFromNativeID, EVENT_FRAME_RENDERED, arg1, arg2, obj); env->DeleteLocalRef(obj); } void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatCallbackNotify: { handleCallback(msg); break; } case kWhatFrameRendered: { handleFrameRenderedNotification(msg); break; } default: TRESPASS(); } } } // namespace android //////////////////////////////////////////////////////////////////////////////// using namespace android; static sp<JMediaCodec> setMediaCodec( JNIEnv *env, jobject thiz, const sp<JMediaCodec> &codec) { sp<JMediaCodec> 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<JMediaCodec> 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<jclass> clazz( env, env->FindClass("android/media/MediaCodec$CryptoException")); CHECK(clazz.get() != NULL); jmethodID constructID = env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V"); CHECK(constructID != NULL); const char *defaultMsg = "Unknown Error"; /* translate OS errors to Java API CryptoException errorCodes (which are positive) */ switch (err) { case ERROR_DRM_NO_LICENSE: err = gCryptoErrorCodes.cryptoErrorNoKey; defaultMsg = "Crypto key not available"; break; case ERROR_DRM_LICENSE_EXPIRED: err = gCryptoErrorCodes.cryptoErrorKeyExpired; defaultMsg = "License expired"; break; case ERROR_DRM_RESOURCE_BUSY: err = gCryptoErrorCodes.cryptoErrorResourceBusy; defaultMsg = "Resource busy or unavailable"; break; case ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION: err = gCryptoErrorCodes.cryptoErrorInsufficientOutputProtection; defaultMsg = "Required output protections are not active"; break; case ERROR_DRM_SESSION_NOT_OPENED: err = gCryptoErrorCodes.cryptoErrorSessionNotOpened; defaultMsg = "Attempted to use a closed session"; break; case ERROR_DRM_CANNOT_HANDLE: err = gCryptoErrorCodes.cryptoErrorUnsupportedOperation; defaultMsg = "Operation not supported in this configuration"; break; default: /* Other negative DRM error codes go out as is. */ break; } jstring msgObj = env->NewStringUTF(msg != NULL ? msg : defaultMsg); 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; case BAD_VALUE: jniThrowException(env, "java/lang/IllegalArgumentException", 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_enableOnFrameRenderedListener( JNIEnv *env, jobject thiz, jboolean enabled) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->enableOnFrameRenderedListener(enabled); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_native_setCallback( JNIEnv *env, jobject thiz, jobject cb) { sp<JMediaCodec> 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<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp<AMessage> format; status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format); if (err != OK) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } sp<IGraphicBufferProducer> bufferProducer; if (jsurface != NULL) { sp<Surface> 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<ICrypto> crypto; if (jcrypto != NULL) { crypto = JCrypto::GetCrypto(env, jcrypto); } err = codec->configure(format, bufferProducer, crypto, flags); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_native_setSurface( JNIEnv *env, jobject thiz, jobject jsurface) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp<IGraphicBufferProducer> bufferProducer; if (jsurface != NULL) { sp<Surface> 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; } } status_t err = codec->setSurface(bufferProducer); throwExceptionAsNecessary(env, err); } sp<PersistentSurface> android_media_MediaCodec_getPersistentInputSurface( JNIEnv* env, jobject object) { sp<PersistentSurface> persistentSurface; jobject lock = env->GetObjectField( object, gPersistentSurfaceClassInfo.mLock); if (env->MonitorEnter(lock) == JNI_OK) { persistentSurface = reinterpret_cast<PersistentSurface *>( env->GetLongField(object, gPersistentSurfaceClassInfo.mPersistentObject)); env->MonitorExit(lock); } env->DeleteLocalRef(lock); return persistentSurface; } static jobject android_media_MediaCodec_createPersistentInputSurface( JNIEnv* env, jclass /* clazz */) { ALOGV("android_media_MediaCodec_createPersistentInputSurface"); sp<PersistentSurface> persistentSurface = MediaCodec::CreatePersistentInputSurface(); if (persistentSurface == NULL) { return NULL; } sp<Surface> surface = new Surface( persistentSurface->getBufferProducer(), true); if (surface == NULL) { return NULL; } jobject object = env->NewObject( gPersistentSurfaceClassInfo.clazz, gPersistentSurfaceClassInfo.ctor); if (object == NULL) { if (env->ExceptionCheck()) { ALOGE("Could not create PersistentSurface."); env->ExceptionClear(); } return NULL; } jobject lock = env->GetObjectField( object, gPersistentSurfaceClassInfo.mLock); if (env->MonitorEnter(lock) == JNI_OK) { env->CallVoidMethod( object, gPersistentSurfaceClassInfo.setNativeObjectLocked, (jlong)surface.get()); env->SetLongField( object, gPersistentSurfaceClassInfo.mPersistentObject, (jlong)persistentSurface.get()); env->MonitorExit(lock); } else { env->DeleteLocalRef(object); object = NULL; } env->DeleteLocalRef(lock); if (object != NULL) { surface->incStrong(&sRefBaseOwner); persistentSurface->incStrong(&sRefBaseOwner); } return object; } static void android_media_MediaCodec_releasePersistentInputSurface( JNIEnv* env, jclass /* clazz */, jobject object) { sp<PersistentSurface> persistentSurface; jobject lock = env->GetObjectField( object, gPersistentSurfaceClassInfo.mLock); if (env->MonitorEnter(lock) == JNI_OK) { persistentSurface = reinterpret_cast<PersistentSurface *>( env->GetLongField( object, gPersistentSurfaceClassInfo.mPersistentObject)); env->SetLongField( object, gPersistentSurfaceClassInfo.mPersistentObject, (jlong)0); env->MonitorExit(lock); } env->DeleteLocalRef(lock); if (persistentSurface != NULL) { persistentSurface->decStrong(&sRefBaseOwner); } // no need to release surface as it will be released by Surface's jni } static void android_media_MediaCodec_setInputSurface( JNIEnv* env, jobject thiz, jobject object) { ALOGV("android_media_MediaCodec_setInputSurface"); sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp<PersistentSurface> persistentSurface = android_media_MediaCodec_getPersistentInputSurface(env, object); status_t err = codec->setInputSurface(persistentSurface); if (err != NO_ERROR) { throwExceptionAsNecessary(env, err); } } static jobject android_media_MediaCodec_createInputSurface(JNIEnv* env, jobject thiz) { ALOGV("android_media_MediaCodec_createInputSurface"); sp<JMediaCodec> 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<IGraphicBufferProducer> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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 jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID); enum CryptoPlugin::Mode mode; if (jmode == gCryptoModes.Unencrypted) { mode = CryptoPlugin::kMode_Unencrypted; } else if (jmode == gCryptoModes.AesCtr) { mode = CryptoPlugin::kMode_AES_CTR; } else if (jmode == gCryptoModes.AesCbc) { mode = CryptoPlugin::kMode_AES_CBC; } else { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } jobject patternObj = env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID); CryptoPlugin::Pattern pattern; if (patternObj == NULL) { pattern.mEncryptBlocks = 0; pattern.mSkipBlocks = 0; } else { pattern.mEncryptBlocks = env->GetIntField(patternObj, gFields.patternEncryptBlocksID); pattern.mSkipBlocks = env->GetIntField(patternObj, gFields.patternSkipBlocksID); } 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, mode, pattern, 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> 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<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp<AMessage> 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<JMediaCodec> 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<jclass> 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); jfieldID field; field = env->GetStaticFieldID(clazz.get(), "CRYPTO_MODE_UNENCRYPTED", "I"); CHECK(field != NULL); gCryptoModes.Unencrypted = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "CRYPTO_MODE_AES_CTR", "I"); CHECK(field != NULL); gCryptoModes.AesCtr = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "CRYPTO_MODE_AES_CBC", "I"); CHECK(field != NULL); gCryptoModes.AesCbc = env->GetStaticIntField(clazz.get(), field); 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); gFields.cryptoInfoPatternID = env->GetFieldID(clazz.get(), "pattern", "Landroid/media/MediaCodec$CryptoInfo$Pattern;"); CHECK(gFields.cryptoInfoPatternID != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo$Pattern")); CHECK(clazz.get() != NULL); gFields.patternEncryptBlocksID = env->GetFieldID(clazz.get(), "mEncryptBlocks", "I"); CHECK(gFields.patternEncryptBlocksID != NULL); gFields.patternSkipBlocksID = env->GetFieldID(clazz.get(), "mSkipBlocks", "I"); CHECK(gFields.patternSkipBlocksID != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException")); CHECK(clazz.get() != NULL); 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); field = env->GetStaticFieldID(clazz.get(), "ERROR_SESSION_NOT_OPENED", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorSessionNotOpened = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_UNSUPPORTED_OPERATION", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorUnsupportedOperation = 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); field = env->GetStaticFieldID(clazz.get(), "ERROR_INSUFFICIENT_RESOURCE", "I"); CHECK(field != NULL); gCodecErrorCodes.errorInsufficientResource = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_RECLAIMED", "I"); CHECK(field != NULL); gCodecErrorCodes.errorReclaimed = env->GetStaticIntField(clazz.get(), field); clazz.reset(env->FindClass("android/view/Surface")); CHECK(clazz.get() != NULL); field = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); CHECK(field != NULL); gPersistentSurfaceClassInfo.mLock = field; jmethodID method = env->GetMethodID(clazz.get(), "setNativeObjectLocked", "(J)V"); CHECK(method != NULL); gPersistentSurfaceClassInfo.setNativeObjectLocked = method; clazz.reset(env->FindClass("android/media/MediaCodec$PersistentSurface")); CHECK(clazz.get() != NULL); gPersistentSurfaceClassInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); method = env->GetMethodID(clazz.get(), "<init>", "()V"); CHECK(method != NULL); gPersistentSurfaceClassInfo.ctor = method; field = env->GetFieldID(clazz.get(), "mPersistentObject", "J"); CHECK(field != NULL); gPersistentSurfaceClassInfo.mPersistentObject = 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<JMediaCodec> 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; } if (err == NO_MEMORY) { throwCodecException(env, err, ACTION_CODE_TRANSIENT, 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 const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaCodec_release }, { "native_reset", "()V", (void *)android_media_MediaCodec_reset }, { "native_releasePersistentInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaCodec_releasePersistentInputSurface}, { "native_createPersistentInputSurface", "()Landroid/media/MediaCodec$PersistentSurface;", (void *)android_media_MediaCodec_createPersistentInputSurface }, { "native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaCodec_setInputSurface }, { "native_enableOnFrameRenderedListener", "(Z)V", (void *)android_media_MediaCodec_native_enableOnFrameRenderedListener }, { "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 }, { "native_setSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaCodec_native_setSurface }, { "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)); }