/* * 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 #include "android_media_MediaCodec.h" #include "android_media_MediaCodecLinearBlock.h" #include "android_media_MediaCrypto.h" #include "android_media_MediaDescrambler.h" #include "android_media_MediaMetricsJNI.h" #include "android_media_Streams.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" #include "android_util_Binder.h" #include "jni.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, EVENT_FRAME_RENDERED = 3, EVENT_FIRST_TUNNEL_FRAME_READY = 4, }; // From MediaFormat.java enum { TYPE_NULL = 0, TYPE_INTEGER = 1, TYPE_LONG = 2, TYPE_FLOAT = 3, TYPE_STRING = 4, TYPE_BYTE_BUFFER = 5, }; static struct CryptoErrorCodes { jint cryptoErrorNoKey; jint cryptoErrorKeyExpired; jint cryptoErrorResourceBusy; jint cryptoErrorInsufficientOutputProtection; jint cryptoErrorSessionNotOpened; jint cryptoErrorInsufficientSecurity; jint cryptoErrorUnsupportedOperation; jint cryptoErrorFrameTooLarge; jint cryptoErrorLostState; } 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; static struct { jclass capsClazz; jmethodID capsCtorId; jclass profileLevelClazz; jfieldID profileField; jfieldID levelField; } gCodecInfo; static struct { jclass clazz; jobject nativeByteOrder; jmethodID orderId; jmethodID asReadOnlyBufferId; jmethodID positionId; jmethodID limitId; jmethodID getPositionId; jmethodID getLimitId; } gByteBufferInfo; static struct { jclass clazz; jmethodID ctorId; jmethodID sizeId; jmethodID getId; jmethodID addId; } gArrayListInfo; static struct { jclass clazz; jmethodID ctorId; jmethodID setInternalStateId; jfieldID contextId; jfieldID validId; jfieldID lockId; } gLinearBlockInfo; static struct { jclass clazz; jmethodID ctorId; jfieldID nameId; jfieldID typeId; } gDescriptorInfo; struct fields_t { jmethodID postEventFromNativeID; jmethodID lockAndGetContextID; jmethodID setAndUnlockContextID; jfieldID cryptoInfoNumSubSamplesID; jfieldID cryptoInfoNumBytesOfClearDataID; jfieldID cryptoInfoNumBytesOfEncryptedDataID; jfieldID cryptoInfoKeyID; jfieldID cryptoInfoIVID; jfieldID cryptoInfoModeID; jfieldID cryptoInfoPatternID; jfieldID patternEncryptBlocksID; jfieldID patternSkipBlocksID; jfieldID queueRequestIndexID; jfieldID outputFrameLinearBlockID; jfieldID outputFrameHardwareBufferID; jfieldID outputFrameChangedKeysID; jfieldID outputFrameFormatID; }; 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); mLooper = new ALooper; mLooper->setName("MediaCodec_looper"); mLooper->start( false, // runOnCallingThread true, // canCallJava ANDROID_PRIORITY_VIDEO); if (nameIsType) { mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus); if (mCodec == nullptr || mCodec->getName(&mNameAtCreation) != OK) { mNameAtCreation = "(null)"; } } else { mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus); mNameAtCreation = name; } CHECK((mCodec != NULL) != (mInitStatus != OK)); } status_t JMediaCodec::initCheck() const { return mInitStatus; } void JMediaCodec::registerSelf() { mLooper->registerHandler(this); } void JMediaCodec::release() { std::call_once(mReleaseFlag, [this] { if (mCodec != NULL) { mCodec->release(); mInitStatus = NO_INIT; } if (mLooper != NULL) { mLooper->unregisterHandler(id()); mLooper->stop(); mLooper.clear(); } }); } void JMediaCodec::releaseAsync() { std::call_once(mAsyncReleaseFlag, [this] { if (mCodec != NULL) { sp notify = new AMessage(kWhatAsyncReleaseComplete, this); // Hold strong reference to this until async release is complete notify->setObject("this", this); mCodec->releaseAsync(notify); } mInitStatus = NO_INIT; }); } JMediaCodec::~JMediaCodec() { if (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; } status_t JMediaCodec::enableOnFirstTunnelFrameReadyListener(jboolean enable) { if (enable) { if (mOnFirstTunnelFrameReadyNotification == NULL) { mOnFirstTunnelFrameReadyNotification = new AMessage(kWhatFirstTunnelFrameReady, this); } } else { mOnFirstTunnelFrameReadyNotification.clear(); } return mCodec->setOnFirstTunnelFrameReadyNotification(mOnFirstTunnelFrameReadyNotification); } 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 &format, const sp &bufferProducer, const sp &crypto, const sp &descrambler, int flags) { sp client; if (bufferProducer != NULL) { mSurfaceTextureClient = new Surface(bufferProducer, true /* controlledByApp */); } else { mSurfaceTextureClient.clear(); } constexpr int32_t CONFIGURE_FLAG_ENCODE = 1; AString mime; CHECK(format->findString("mime", &mime)); mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/")) && !(flags & CONFIGURE_FLAG_ENCODE); mHasCryptoOrDescrambler = (crypto != nullptr) || (descrambler != nullptr); mCrypto = crypto; return mCodec->configure( format, mSurfaceTextureClient, crypto, descrambler, flags); } status_t JMediaCodec::setSurface( const sp &bufferProducer) { sp 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* bufferProducer) { return mCodec->createInputSurface(bufferProducer); } status_t JMediaCodec::setInputSurface( const sp &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::queueBuffer( size_t index, const std::shared_ptr &buffer, int64_t timeUs, uint32_t flags, const sp &tunings, AString *errorDetailMsg) { return mCodec->queueBuffer( index, buffer, timeUs, flags, tunings, errorDetailMsg); } status_t JMediaCodec::queueEncryptedLinearBlock( size_t index, const sp &buffer, 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, const sp &tunings, AString *errorDetailMsg) { return mCodec->queueEncryptedBuffer( index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern, presentationTimeUs, flags, tunings, 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(), gByteBufferInfo.clazz, 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; } template static jobject CreateByteBuffer( JNIEnv *env, T *base, size_t capacity, size_t offset, size_t size, bool readOnly, bool clearBuffer) { jobject byteBuffer = env->NewDirectByteBuffer( const_cast::type *>(base), capacity); if (readOnly && byteBuffer != NULL) { jobject readOnlyBuffer = env->CallObjectMethod( byteBuffer, gByteBufferInfo.asReadOnlyBufferId); env->DeleteLocalRef(byteBuffer); byteBuffer = readOnlyBuffer; } if (byteBuffer == NULL) { return nullptr; } jobject me = env->CallObjectMethod( byteBuffer, gByteBufferInfo.orderId, gByteBufferInfo.nativeByteOrder); env->DeleteLocalRef(me); me = env->CallObjectMethod( byteBuffer, gByteBufferInfo.limitId, clearBuffer ? capacity : offset + size); env->DeleteLocalRef(me); me = env->CallObjectMethod( byteBuffer, gByteBufferInfo.positionId, clearBuffer ? 0 : offset); env->DeleteLocalRef(me); me = NULL; return byteBuffer; } // static template 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 == NULL) { ALOGV("createByteBufferFromABuffer - given NULL, returning NULL"); return OK; } if (buffer->base() == NULL) { return OK; } jobject byteBuffer = CreateByteBuffer( env, buffer->base(), buffer->capacity(), buffer->offset(), buffer->size(), readOnly, clearBuffer); *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::getOutputFrame( JNIEnv *env, jobject frame, size_t index) const { sp buffer; status_t err = mCodec->getOutputBuffer(index, &buffer); if (err != OK) { return err; } if (buffer->size() > 0) { std::shared_ptr c2Buffer = buffer->asC2Buffer(); if (c2Buffer) { switch (c2Buffer->data().type()) { case C2BufferData::LINEAR: { std::unique_ptr context{new JMediaCodecLinearBlock}; context->mBuffer = c2Buffer; ScopedLocalRef linearBlock{env, env->NewObject( gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; env->CallVoidMethod( linearBlock.get(), gLinearBlockInfo.setInternalStateId, (jlong)context.release(), true); env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); break; } case C2BufferData::GRAPHIC: { const C2Handle *c2Handle = c2Buffer->data().graphicBlocks().front().handle(); uint32_t width, height, format, stride, igbp_slot, generation; uint64_t usage, igbp_id; _UnwrapNativeCodec2GrallocMetadata( c2Handle, &width, &height, &format, &usage, &stride, &generation, &igbp_id, &igbp_slot); native_handle_t *grallocHandle = UnwrapNativeCodec2GrallocHandle(c2Handle); GraphicBuffer* graphicBuffer = new GraphicBuffer( grallocHandle, GraphicBuffer::CLONE_HANDLE, width, height, format, 1, usage, stride); ScopedLocalRef hardwareBuffer{ env, android_hardware_HardwareBuffer_createFromAHardwareBuffer( env, AHardwareBuffer_from_GraphicBuffer(graphicBuffer))}; env->SetObjectField( frame, gFields.outputFrameHardwareBufferID, hardwareBuffer.get()); break; } case C2BufferData::LINEAR_CHUNKS: [[fallthrough]]; case C2BufferData::GRAPHIC_CHUNKS: [[fallthrough]]; case C2BufferData::INVALID: [[fallthrough]]; default: return INVALID_OPERATION; } } else { if (!mGraphicOutput) { std::unique_ptr context{new JMediaCodecLinearBlock}; context->mLegacyBuffer = buffer; ScopedLocalRef linearBlock{env, env->NewObject( gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; env->CallVoidMethod( linearBlock.get(), gLinearBlockInfo.setInternalStateId, (jlong)context.release(), true); env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); } else { // No-op. } } } jobject formatMap; err = getOutputFormat(env, index, &formatMap); if (err != OK) { return err; } ScopedLocalRef mediaFormatClass{env, env->FindClass("android/media/MediaFormat")}; ScopedLocalRef format{env, env->NewObject( mediaFormatClass.get(), env->GetMethodID(mediaFormatClass.get(), "", "(Ljava/util/Map;)V"), formatMap)}; env->SetObjectField(frame, gFields.outputFrameFormatID, format.get()); env->DeleteLocalRef(formatMap); formatMap = nullptr; sp obj; if (buffer->meta()->findObject("changedKeys", &obj) && obj) { sp>> changedKeys{ (decltype(changedKeys.get()))obj.get()}; ScopedLocalRef changedKeysObj{env, env->GetObjectField( frame, gFields.outputFrameChangedKeysID)}; for (const std::string &key : changedKeys->value) { ScopedLocalRef keyStr{env, env->NewStringUTF(key.c_str())}; (void)env->CallBooleanMethod(changedKeysObj.get(), gArrayListInfo.addId, keyStr.get()); } } 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; } static jobject getCodecCapabilitiesObject( JNIEnv *env, const char *mime, bool isEncoder, const sp &capabilities) { Vector profileLevels; Vector colorFormats; sp defaultFormat = new AMessage(); defaultFormat->setString("mime", mime); capabilities->getSupportedColorFormats(&colorFormats); capabilities->getSupportedProfileLevels(&profileLevels); sp details = capabilities->getDetails(); jobject defaultFormatObj = NULL; if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) { return NULL; } ScopedLocalRef defaultFormatRef(env, defaultFormatObj); jobject detailsObj = NULL; if (ConvertMessageToMap(env, details, &detailsObj)) { return NULL; } ScopedLocalRef detailsRef(env, detailsObj); ScopedLocalRef profileLevelArray(env, env->NewObjectArray( profileLevels.size(), gCodecInfo.profileLevelClazz, NULL)); for (size_t i = 0; i < profileLevels.size(); ++i) { const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i); ScopedLocalRef srcRef(env, env->AllocObject( gCodecInfo.profileLevelClazz)); env->SetIntField(srcRef.get(), gCodecInfo.profileField, src.mProfile); env->SetIntField(srcRef.get(), gCodecInfo.levelField, src.mLevel); env->SetObjectArrayElement(profileLevelArray.get(), i, srcRef.get()); } ScopedLocalRef colorFormatsArray( env, env->NewIntArray(colorFormats.size())); for (size_t i = 0; i < colorFormats.size(); ++i) { jint val = colorFormats.itemAt(i); env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val); } return env->NewObject( gCodecInfo.capsClazz, gCodecInfo.capsCtorId, profileLevelArray.get(), colorFormatsArray.get(), isEncoder, defaultFormatRef.get(), detailsRef.get()); } status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const { sp codecInfo; status_t err = mCodec->getCodecInfo(&codecInfo); if (err != OK) { return err; } ScopedLocalRef nameObject(env, env->NewStringUTF(mNameAtCreation.c_str())); ScopedLocalRef canonicalNameObject(env, env->NewStringUTF(codecInfo->getCodecName())); MediaCodecInfo::Attributes attributes = codecInfo->getAttributes(); bool isEncoder = codecInfo->isEncoder(); Vector mediaTypes; codecInfo->getSupportedMediaTypes(&mediaTypes); ScopedLocalRef capsArrayObj(env, env->NewObjectArray(mediaTypes.size(), gCodecInfo.capsClazz, NULL)); for (size_t i = 0; i < mediaTypes.size(); i++) { const sp caps = codecInfo->getCapabilitiesFor(mediaTypes[i].c_str()); ScopedLocalRef capsObj(env, getCodecCapabilitiesObject( env, mediaTypes[i].c_str(), isEncoder, caps)); env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get()); } ScopedLocalRef codecInfoClazz(env, env->FindClass("android/media/MediaCodecInfo")); CHECK(codecInfoClazz.get() != NULL); jmethodID codecInfoCtorID = env->GetMethodID(codecInfoClazz.get(), "", "(Ljava/lang/String;Ljava/lang/String;I[Landroid/media/MediaCodecInfo$CodecCapabilities;)V"); *codecInfoObject = env->NewObject(codecInfoClazz.get(), codecInfoCtorID, nameObject.get(), canonicalNameObject.get(), attributes, capsArrayObj.get()); return OK; } status_t JMediaCodec::getMetrics(JNIEnv *, mediametrics::Item * &reply) const { mediametrics_handle_t reply2 = mediametrics::Item::convert(reply); status_t status = mCodec->getMetrics(reply2); // getMetrics() updates reply2, pass the converted update along to our caller. reply = mediametrics::Item::convert(reply2); return status; } status_t JMediaCodec::setParameters(const sp &msg) { return mCodec->setParameters(msg); } void JMediaCodec::setVideoScalingMode(int mode) { if (mSurfaceTextureClient != NULL) { // this works for components that queue to surface native_window_set_scaling_mode(mSurfaceTextureClient.get(), mode); // also signal via param for components that queue to IGBP sp msg = new AMessage; msg->setInt32("android._video-scaling", mode); (void)mCodec->setParameters(msg); } } void JMediaCodec::selectAudioPresentation(const int32_t presentationId, const int32_t programId) { sp msg = new AMessage; msg->setInt32("audio-presentation-presentation-id", presentationId); msg->setInt32("audio-presentation-program-id", programId); (void)mCodec->setParameters(msg); } status_t JMediaCodec::querySupportedVendorParameters(JNIEnv *env, jobject *namesObj) { std::vector names; status_t status = mCodec->querySupportedVendorParameters(&names); if (status != OK) { return status; } *namesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId); for (const std::string &name : names) { ScopedLocalRef nameStr{env, env->NewStringUTF(name.c_str())}; (void)env->CallBooleanMethod(*namesObj, gArrayListInfo.addId, nameStr.get()); } return OK; } status_t JMediaCodec::describeParameter(JNIEnv *env, jstring name, jobject *descObj) { const char *tmp = env->GetStringUTFChars(name, nullptr); CodecParameterDescriptor desc; status_t status = mCodec->describeParameter(tmp, &desc); env->ReleaseStringUTFChars(name, tmp); if (status != OK) { return status; } jint type = TYPE_NULL; switch (desc.type) { case AMessage::kTypeInt32: type = TYPE_INTEGER; break; case AMessage::kTypeSize: case AMessage::kTypeInt64: type = TYPE_LONG; break; case AMessage::kTypeFloat: type = TYPE_FLOAT; break; case AMessage::kTypeString: type = TYPE_STRING; break; case AMessage::kTypeBuffer: type = TYPE_BYTE_BUFFER; break; default: type = TYPE_NULL; break; } if (type == TYPE_NULL) { return BAD_VALUE; } *descObj = env->NewObject(gDescriptorInfo.clazz, gDescriptorInfo.ctorId); env->SetObjectField(*descObj, gDescriptorInfo.nameId, name); env->SetIntField(*descObj, gDescriptorInfo.typeId, type); return OK; } static void BuildVectorFromList(JNIEnv *env, jobject list, std::vector *vec) { ScopedLocalRef listClazz{env, env->FindClass("java/util/List")}; ScopedLocalRef iterClazz{env, env->FindClass("java/util/Iterator")}; jmethodID hasNextID = env->GetMethodID(iterClazz.get(), "hasNext", "()Z"); jmethodID nextID = env->GetMethodID(iterClazz.get(), "next", "()Ljava/lang/Object;"); jobject it = env->CallObjectMethod( list, env->GetMethodID(listClazz.get(), "iterator", "()Ljava/util/Iterator;")); while (env->CallBooleanMethod(it, hasNextID)) { jstring name = (jstring)env->CallObjectMethod(it, nextID); const char *tmp = env->GetStringUTFChars(name, nullptr); vec->push_back(tmp); env->ReleaseStringUTFChars(name, tmp); } } status_t JMediaCodec::subscribeToVendorParameters(JNIEnv *env, jobject namesObj) { std::vector names; BuildVectorFromList(env, namesObj, &names); return mCodec->subscribeToVendorParameters(names); } status_t JMediaCodec::unsubscribeFromVendorParameters(JNIEnv *env, jobject namesObj) { std::vector names; BuildVectorFromList(env, namesObj, &names); return mCodec->unsubscribeFromVendorParameters(names); } 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; } /* 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 &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; } default: TRESPASS(); } env->CallVoidMethod( mObject, gFields.postEventFromNativeID, EVENT_CALLBACK, arg1, arg2, obj); env->DeleteLocalRef(obj); } void JMediaCodec::handleFirstTunnelFrameReadyNotification(const sp &msg) { int32_t arg1 = 0, arg2 = 0; jobject obj = NULL; JNIEnv *env = AndroidRuntime::getJNIEnv(); sp 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_FIRST_TUNNEL_FRAME_READY, arg1, arg2, obj); env->DeleteLocalRef(obj); } void JMediaCodec::handleFrameRenderedNotification(const sp &msg) { int32_t arg1 = 0, arg2 = 0; jobject obj = NULL; JNIEnv *env = AndroidRuntime::getJNIEnv(); sp 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 &msg) { switch (msg->what()) { case kWhatCallbackNotify: { handleCallback(msg); break; } case kWhatFrameRendered: { handleFrameRenderedNotification(msg); break; } case kWhatAsyncReleaseComplete: { if (mLooper != NULL) { mLooper->unregisterHandler(id()); mLooper->stop(); mLooper.clear(); } break; } case kWhatFirstTunnelFrameReady: { handleFirstTunnelFrameReadyNotification(msg); break; } default: TRESPASS(); } } jint MediaErrorToJavaError(status_t err); } // namespace android //////////////////////////////////////////////////////////////////////////////// using namespace android; static sp setMediaCodec( JNIEnv *env, jobject thiz, const sp &codec, bool release = true) { sp old = (JMediaCodec *)env->CallLongMethod(thiz, gFields.lockAndGetContextID); 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) */ if (release) { old->release(); } old->decStrong(thiz); } env->CallVoidMethod(thiz, gFields.setAndUnlockContextID, (jlong)codec.get()); return old; } static sp getMediaCodec(JNIEnv *env, jobject thiz) { sp codec = (JMediaCodec *)env->CallLongMethod(thiz, gFields.lockAndGetContextID); env->CallVoidMethod(thiz, gFields.setAndUnlockContextID, (jlong)codec.get()); return codec; } static void android_media_MediaCodec_release(JNIEnv *env, jobject thiz) { // Clear Java native reference. sp codec = setMediaCodec(env, thiz, nullptr, false /* release */); if (codec != NULL) { codec->releaseAsync(); } } 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, const sp &crypto) { 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); std::string 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_INSUFFICIENT_SECURITY: err = gCryptoErrorCodes.cryptoErrorInsufficientSecurity; defaultMsg = "Required security level is not met"; break; case ERROR_DRM_CANNOT_HANDLE: err = gCryptoErrorCodes.cryptoErrorUnsupportedOperation; defaultMsg = "Operation not supported in this configuration"; break; case ERROR_DRM_FRAME_TOO_LARGE: err = gCryptoErrorCodes.cryptoErrorFrameTooLarge; defaultMsg = "Decrytped frame exceeds size of output buffer"; break; case ERROR_DRM_SESSION_LOST_STATE: err = gCryptoErrorCodes.cryptoErrorLostState; defaultMsg = "Session state was lost, open a new session and retry"; break; default: /* Other negative DRM error codes go out best-effort. */ err = MediaErrorToJavaError(err); defaultMsg = StrCryptoError(err); break; } std::string msgStr(msg != NULL ? msg : defaultMsg.c_str()); if (crypto != NULL) { msgStr = DrmUtils::GetExceptionMessage(err, msgStr.c_str(), crypto); } jstring msgObj = env->NewStringUTF(msgStr.c_str()); 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, const sp& crypto = 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, crypto); return 0; } throwCodecException(env, err, actionCode, msg); return 0; } } static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener( JNIEnv *env, jobject thiz, jboolean enabled) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_native_enableOnFrameRenderedListener( JNIEnv *env, jobject thiz, jboolean enabled) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { 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 codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { 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, jobject descramblerBinderObj, jint flags) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { 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); } sp descrambler; if (descramblerBinderObj != NULL) { descrambler = GetDescrambler(env, descramblerBinderObj); } err = codec->configure(format, bufferProducer, crypto, descrambler, flags); throwExceptionAsNecessary(env, err); } static void android_media_MediaCodec_native_setSurface( JNIEnv *env, jobject thiz, jobject jsurface) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); 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; } } status_t err = codec->setSurface(bufferProducer); throwExceptionAsNecessary(env, err); } sp android_media_MediaCodec_getPersistentInputSurface( JNIEnv* env, jobject object) { sp persistentSurface; jobject lock = env->GetObjectField( object, gPersistentSurfaceClassInfo.mLock); if (env->MonitorEnter(lock) == JNI_OK) { persistentSurface = reinterpret_cast( 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 = MediaCodec::CreatePersistentInputSurface(); if (persistentSurface == NULL) { return NULL; } sp 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; jobject lock = env->GetObjectField( object, gPersistentSurfaceClassInfo.mLock); if (env->MonitorEnter(lock) == JNI_OK) { persistentSurface = reinterpret_cast( 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 codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp persistentSurface = android_media_MediaCodec_getPersistentInputSurface(env, object); if (persistentSurface == NULL) { throwExceptionAsNecessary( env, BAD_VALUE, ACTION_CODE_FATAL, "input surface not valid"); return; } 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 codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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()); } struct NativeCryptoInfo { NativeCryptoInfo(JNIEnv *env, jobject cryptoInfoObj) : mEnv{env}, mIvObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID)}, mKeyObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID)} { mNumSubSamples = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID); ScopedLocalRef numBytesOfClearDataObj{env, (jintArray)env->GetObjectField( cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID)}; ScopedLocalRef numBytesOfEncryptedDataObj{env, (jintArray)env->GetObjectField( cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID)}; jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID); if (jmode == gCryptoModes.Unencrypted) { mMode = CryptoPlugin::kMode_Unencrypted; } else if (jmode == gCryptoModes.AesCtr) { mMode = CryptoPlugin::kMode_AES_CTR; } else if (jmode == gCryptoModes.AesCbc) { mMode = CryptoPlugin::kMode_AES_CBC; } else { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } ScopedLocalRef patternObj{ env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)}; if (patternObj.get() == nullptr) { mPattern.mEncryptBlocks = 0; mPattern.mSkipBlocks = 0; } else { mPattern.mEncryptBlocks = env->GetIntField( patternObj.get(), gFields.patternEncryptBlocksID); mPattern.mSkipBlocks = env->GetIntField( patternObj.get(), gFields.patternSkipBlocksID); } mErr = OK; if (mNumSubSamples <= 0) { mErr = -EINVAL; } else if (numBytesOfClearDataObj == nullptr && numBytesOfEncryptedDataObj == nullptr) { mErr = -EINVAL; } else if (numBytesOfEncryptedDataObj != nullptr && env->GetArrayLength(numBytesOfEncryptedDataObj.get()) < mNumSubSamples) { mErr = -ERANGE; } else if (numBytesOfClearDataObj != nullptr && env->GetArrayLength(numBytesOfClearDataObj.get()) < mNumSubSamples) { mErr = -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(mNumSubSamples >= (signed)(INT32_MAX / sizeof(*mSubSamples))) ) { mErr = -EINVAL; } else { jint *numBytesOfClearData = (numBytesOfClearDataObj == nullptr) ? nullptr : env->GetIntArrayElements(numBytesOfClearDataObj.get(), nullptr); jint *numBytesOfEncryptedData = (numBytesOfEncryptedDataObj == nullptr) ? nullptr : env->GetIntArrayElements(numBytesOfEncryptedDataObj.get(), nullptr); mSubSamples = new CryptoPlugin::SubSample[mNumSubSamples]; for (jint i = 0; i < mNumSubSamples; ++i) { mSubSamples[i].mNumBytesOfClearData = (numBytesOfClearData == nullptr) ? 0 : numBytesOfClearData[i]; mSubSamples[i].mNumBytesOfEncryptedData = (numBytesOfEncryptedData == nullptr) ? 0 : numBytesOfEncryptedData[i]; } if (numBytesOfEncryptedData != nullptr) { env->ReleaseIntArrayElements( numBytesOfEncryptedDataObj.get(), numBytesOfEncryptedData, 0); numBytesOfEncryptedData = nullptr; } if (numBytesOfClearData != nullptr) { env->ReleaseIntArrayElements( numBytesOfClearDataObj.get(), numBytesOfClearData, 0); numBytesOfClearData = nullptr; } } if (mErr == OK && mKeyObj.get() != nullptr) { if (env->GetArrayLength(mKeyObj.get()) != 16) { mErr = -EINVAL; } else { mKey = env->GetByteArrayElements(mKeyObj.get(), nullptr); } } if (mErr == OK && mIvObj.get() != nullptr) { if (env->GetArrayLength(mIvObj.get()) != 16) { mErr = -EINVAL; } else { mIv = env->GetByteArrayElements(mIvObj.get(), nullptr); } } } explicit NativeCryptoInfo(jint size) : mIvObj{nullptr, nullptr}, mKeyObj{nullptr, nullptr}, mMode{CryptoPlugin::kMode_Unencrypted}, mPattern{0, 0} { mSubSamples = new CryptoPlugin::SubSample[1]; mNumSubSamples = 1; mSubSamples[0].mNumBytesOfClearData = size; mSubSamples[0].mNumBytesOfEncryptedData = 0; } ~NativeCryptoInfo() { if (mIv != nullptr) { mEnv->ReleaseByteArrayElements(mIvObj.get(), mIv, 0); } if (mKey != nullptr) { mEnv->ReleaseByteArrayElements(mKeyObj.get(), mKey, 0); } if (mSubSamples != nullptr) { delete[] mSubSamples; } } JNIEnv *mEnv{nullptr}; ScopedLocalRef mIvObj; ScopedLocalRef mKeyObj; status_t mErr{OK}; CryptoPlugin::SubSample *mSubSamples{nullptr}; int32_t mNumSubSamples{0}; jbyte *mIv{nullptr}; jbyte *mKey{nullptr}; enum CryptoPlugin::Mode mMode; CryptoPlugin::Pattern mPattern; }; 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 || codec->initCheck() != OK) { 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(), codec->getCrypto()); } static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) { ALOGV("android_media_MediaCodec_mapHardwareBuffer"); AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer( env, bufferObj); AHardwareBuffer_Desc desc; AHardwareBuffer_describe(hardwareBuffer, &desc); if (desc.format != AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420) { ALOGI("mapHardwareBuffer: unmappable format: %d", desc.format); return nullptr; } if ((desc.usage & AHARDWAREBUFFER_USAGE_CPU_READ_MASK) == 0) { ALOGI("mapHardwareBuffer: buffer not CPU readable"); return nullptr; } bool readOnly = ((desc.usage & AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK) == 0); uint64_t cpuUsage = 0; cpuUsage |= (desc.usage & AHARDWAREBUFFER_USAGE_CPU_READ_MASK); cpuUsage |= (desc.usage & AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK); AHardwareBuffer_Planes planes; int err = AHardwareBuffer_lockPlanes( hardwareBuffer, cpuUsage, -1 /* fence */, nullptr /* rect */, &planes); if (err != 0) { ALOGI("mapHardwareBuffer: Failed to lock planes (err=%d)", err); return nullptr; } if (planes.planeCount != 3) { ALOGI("mapHardwareBuffer: planeCount expected 3, actual %u", planes.planeCount); return nullptr; } ScopedLocalRef buffersArray{ env, env->NewObjectArray(3, gByteBufferInfo.clazz, NULL)}; ScopedLocalRef rowStridesArray{env, env->NewIntArray(3)}; ScopedLocalRef pixelStridesArray{env, env->NewIntArray(3)}; jboolean isCopy = JNI_FALSE; jint *rowStrides = env->GetIntArrayElements(rowStridesArray.get(), &isCopy); jint *pixelStrides = env->GetIntArrayElements(rowStridesArray.get(), &isCopy); // For Y plane int rowSampling = 1; int colSampling = 1; // plane indices are Y-U-V. for (uint32_t i = 0; i < 3; ++i) { const AHardwareBuffer_Plane &plane = planes.planes[i]; int maxRowOffset = plane.rowStride * (desc.height / rowSampling - 1); int maxColOffset = plane.pixelStride * (desc.width / colSampling - 1); int maxOffset = maxRowOffset + maxColOffset; ScopedLocalRef byteBuffer{env, CreateByteBuffer( env, plane.data, maxOffset + 1, 0, maxOffset + 1, readOnly, true)}; env->SetObjectArrayElement(buffersArray.get(), i, byteBuffer.get()); rowStrides[i] = plane.rowStride; pixelStrides[i] = plane.pixelStride; // For U-V planes rowSampling = 2; colSampling = 2; } env->ReleaseIntArrayElements(rowStridesArray.get(), rowStrides, 0); env->ReleaseIntArrayElements(pixelStridesArray.get(), pixelStrides, 0); rowStrides = pixelStrides = nullptr; ScopedLocalRef imageClazz( env, env->FindClass("android/media/MediaCodec$MediaImage")); CHECK(imageClazz.get() != NULL); jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "", "([Ljava/nio/ByteBuffer;[I[IIIIZJIILandroid/graphics/Rect;J)V"); jobject img = env->NewObject(imageClazz.get(), imageConstructID, buffersArray.get(), rowStridesArray.get(), pixelStridesArray.get(), desc.width, desc.height, desc.format, // ??? (jboolean)readOnly /* readOnly */, (jlong)0 /* timestamp */, (jint)0 /* xOffset */, (jint)0 /* yOffset */, nullptr /* cropRect */, (jlong)hardwareBuffer); // if MediaImage creation fails, return null if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); return nullptr; } AHardwareBuffer_acquire(hardwareBuffer); return img; } static void android_media_MediaCodec_closeMediaImage(JNIEnv *, jclass, jlong context) { ALOGV("android_media_MediaCodec_closeMediaImage"); if (context == 0) { return; } AHardwareBuffer *hardwareBuffer = (AHardwareBuffer *)context; int err = AHardwareBuffer_unlock(hardwareBuffer, nullptr); if (err != 0) { ALOGI("closeMediaImage: failed to unlock (err=%d)", err); // Continue to release the hardwareBuffer } AHardwareBuffer_release(hardwareBuffer); } static status_t ConvertKeyValueListsToAMessage( JNIEnv *env, jobject keys, jobject values, sp *msg) { static struct Fields { explicit Fields(JNIEnv *env) { ScopedLocalRef clazz{env, env->FindClass("java/lang/String")}; CHECK(clazz.get() != NULL); mStringClass = (jclass)env->NewGlobalRef(clazz.get()); clazz.reset(env->FindClass("java/lang/Integer")); CHECK(clazz.get() != NULL); mIntegerClass = (jclass)env->NewGlobalRef(clazz.get()); mIntegerValueId = env->GetMethodID(clazz.get(), "intValue", "()I"); CHECK(mIntegerValueId != NULL); clazz.reset(env->FindClass("java/lang/Long")); CHECK(clazz.get() != NULL); mLongClass = (jclass)env->NewGlobalRef(clazz.get()); mLongValueId = env->GetMethodID(clazz.get(), "longValue", "()J"); CHECK(mLongValueId != NULL); clazz.reset(env->FindClass("java/lang/Float")); CHECK(clazz.get() != NULL); mFloatClass = (jclass)env->NewGlobalRef(clazz.get()); mFloatValueId = env->GetMethodID(clazz.get(), "floatValue", "()F"); CHECK(mFloatValueId != NULL); clazz.reset(env->FindClass("java/util/ArrayList")); CHECK(clazz.get() != NULL); mByteBufferArrayId = env->GetMethodID(gByteBufferInfo.clazz, "array", "()[B"); CHECK(mByteBufferArrayId != NULL); } jclass mStringClass; jclass mIntegerClass; jmethodID mIntegerValueId; jclass mLongClass; jmethodID mLongValueId; jclass mFloatClass; jmethodID mFloatValueId; jmethodID mByteBufferArrayId; } sFields{env}; jint size = env->CallIntMethod(keys, gArrayListInfo.sizeId); if (size != env->CallIntMethod(values, gArrayListInfo.sizeId)) { return BAD_VALUE; } sp result{new AMessage}; for (jint i = 0; i < size; ++i) { ScopedLocalRef jkey{ env, (jstring)env->CallObjectMethod(keys, gArrayListInfo.getId, i)}; const char *tmp = env->GetStringUTFChars(jkey.get(), nullptr); AString key; if (tmp) { key.setTo(tmp); } env->ReleaseStringUTFChars(jkey.get(), tmp); if (key.empty()) { return NO_MEMORY; } ScopedLocalRef jvalue{ env, env->CallObjectMethod(values, gArrayListInfo.getId, i)}; if (env->IsInstanceOf(jvalue.get(), sFields.mStringClass)) { const char *tmp = env->GetStringUTFChars((jstring)jvalue.get(), nullptr); AString value; if (!tmp) { return NO_MEMORY; } value.setTo(tmp); env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp); result->setString(key.c_str(), value); } else if (env->IsInstanceOf(jvalue.get(), sFields.mIntegerClass)) { jint value = env->CallIntMethod(jvalue.get(), sFields.mIntegerValueId); result->setInt32(key.c_str(), value); } else if (env->IsInstanceOf(jvalue.get(), sFields.mLongClass)) { jlong value = env->CallLongMethod(jvalue.get(), sFields.mLongValueId); result->setInt64(key.c_str(), value); } else if (env->IsInstanceOf(jvalue.get(), sFields.mFloatClass)) { jfloat value = env->CallFloatMethod(jvalue.get(), sFields.mFloatValueId); result->setFloat(key.c_str(), value); } else if (env->IsInstanceOf(jvalue.get(), gByteBufferInfo.clazz)) { jint position = env->CallIntMethod(jvalue.get(), gByteBufferInfo.getPositionId); jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.getLimitId); sp buffer{new ABuffer(limit - position)}; void *data = env->GetDirectBufferAddress(jvalue.get()); if (data != nullptr) { memcpy(buffer->data(), static_cast(data) + position, buffer->size()); } else { ScopedLocalRef byteArray{env, (jbyteArray)env->CallObjectMethod( jvalue.get(), sFields.mByteBufferArrayId)}; env->GetByteArrayRegion(byteArray.get(), position, buffer->size(), reinterpret_cast(buffer->data())); } result->setBuffer(key.c_str(), buffer); } } *msg = result; return OK; } static void android_media_MediaCodec_native_queueLinearBlock( JNIEnv *env, jobject thiz, jint index, jobject bufferObj, jint offset, jint size, jobject cryptoInfoObj, jlong presentationTimeUs, jint flags, jobject keys, jobject values) { ALOGV("android_media_MediaCodec_native_queueLinearBlock"); sp codec = getMediaCodec(env, thiz); if (codec == nullptr || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp tunings; status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); if (err != OK) { throwExceptionAsNecessary(env, err); return; } std::shared_ptr buffer; sp memory; ScopedLocalRef lock{env, env->GetObjectField(bufferObj, gLinearBlockInfo.lockId)}; if (env->MonitorEnter(lock.get()) == JNI_OK) { if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) { JMediaCodecLinearBlock *context = (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId); if (codec->hasCryptoOrDescrambler()) { memory = context->toHidlMemory(); // TODO: copy if memory is null offset += context->mHidlMemoryOffset; } else { buffer = context->toC2Buffer(offset, size); // TODO: copy if buffer is null } } env->MonitorExit(lock.get()); } else { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } AString errorDetailMsg; if (codec->hasCryptoOrDescrambler()) { if (!memory) { ALOGI("queueLinearBlock: no ashmem memory for encrypted content"); throwExceptionAsNecessary(env, BAD_VALUE); return; } NativeCryptoInfo cryptoInfo = [env, cryptoInfoObj, size]{ if (cryptoInfoObj == nullptr) { return NativeCryptoInfo{size}; } else { return NativeCryptoInfo{env, cryptoInfoObj}; } }(); err = codec->queueEncryptedLinearBlock( index, memory, offset, cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples, (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv, cryptoInfo.mMode, cryptoInfo.mPattern, presentationTimeUs, flags, tunings, &errorDetailMsg); } else { if (!buffer) { ALOGI("queueLinearBlock: no C2Buffer found"); throwExceptionAsNecessary(env, BAD_VALUE); return; } err = codec->queueBuffer( index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg); } throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str()); } static void android_media_MediaCodec_native_queueHardwareBuffer( JNIEnv *env, jobject thiz, jint index, jobject bufferObj, jlong presentationTimeUs, jint flags, jobject keys, jobject values) { ALOGV("android_media_MediaCodec_native_queueHardwareBuffer"); sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } sp tunings; status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); if (err != OK) { throwExceptionAsNecessary(env, err); return; } AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer( env, bufferObj); sp graphicBuffer{AHardwareBuffer_to_GraphicBuffer(hardwareBuffer)}; C2Handle *handle = WrapNativeCodec2GrallocHandle( graphicBuffer->handle, graphicBuffer->width, graphicBuffer->height, graphicBuffer->format, graphicBuffer->usage, graphicBuffer->stride); static std::shared_ptr sGrallocAlloc = []() -> std::shared_ptr { std::shared_ptr alloc; c2_status_t err = GetCodec2PlatformAllocatorStore()->fetchAllocator( C2PlatformAllocatorStore::GRALLOC, &alloc); if (err == C2_OK) { return alloc; } return nullptr; }(); std::shared_ptr alloc; c2_status_t c2err = sGrallocAlloc->priorGraphicAllocation(handle, &alloc); if (c2err != C2_OK) { ALOGW("Failed to wrap AHardwareBuffer into C2GraphicAllocation"); native_handle_close(handle); native_handle_delete(handle); throwExceptionAsNecessary(env, BAD_VALUE); return; } std::shared_ptr block = _C2BlockFactory::CreateGraphicBlock(alloc); std::shared_ptr buffer = C2Buffer::CreateGraphicBuffer(block->share( block->crop(), C2Fence{})); AString errorDetailMsg; err = codec->queueBuffer( index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg); throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str()); } static void android_media_MediaCodec_native_getOutputFrame( JNIEnv *env, jobject thiz, jobject frame, jint index) { ALOGV("android_media_MediaCodec_native_getOutputFrame"); sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t err = codec->getOutputFrame(env, frame, index); if (err != OK) { throwExceptionAsNecessary(env, err); } } 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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 jobject android_media_MediaCodec_getOwnCodecInfo( JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_getOwnCodecInfo"); sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject codecInfoObj; status_t err = codec->getCodecInfo(env, &codecInfoObj); if (err == OK) { return codecInfoObj; } throwExceptionAsNecessary(env, err); return NULL; } static jobject android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_native_getMetrics"); sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { jniThrowException(env, "java/lang/IllegalStateException", NULL); return 0; } // get what we have for the metrics from the codec mediametrics::Item *item = 0; status_t err = codec->getMetrics(env, item); if (err != OK) { ALOGE("getMetrics failed"); return (jobject) NULL; } jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); // housekeeping delete item; item = 0; return mybundle; } 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 || codec->initCheck() != OK) { 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 || codec->initCheck() != OK) { 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/IllegalArgumentException", NULL); return; } codec->setVideoScalingMode(mode); } static void android_media_MediaCodec_setAudioPresentation( JNIEnv *env, jobject thiz, jint presentationId, jint programId) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId); } static jobject android_media_MediaCodec_getSupportedVendorParameters( JNIEnv *env, jobject thiz) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject ret = NULL; status_t status = codec->querySupportedVendorParameters(env, &ret); if (status != OK) { throwExceptionAsNecessary(env, status); } return ret; } static jobject android_media_MediaCodec_getParameterDescriptor( JNIEnv *env, jobject thiz, jstring name) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return NULL; } jobject ret = NULL; status_t status = codec->describeParameter(env, name, &ret); if (status != OK) { ret = NULL; } return ret; } static void android_media_MediaCodec_subscribeToVendorParameters( JNIEnv *env, jobject thiz, jobject names) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t status = codec->subscribeToVendorParameters(env, names); if (status != OK) { throwExceptionAsNecessary(env, status); } return; } static void android_media_MediaCodec_unsubscribeFromVendorParameters( JNIEnv *env, jobject thiz, jobject names) { sp codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { throwExceptionAsNecessary(env, INVALID_OPERATION); return; } status_t status = codec->unsubscribeFromVendorParameters(env, names); if (status != OK) { throwExceptionAsNecessary(env, status); } return; } static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { ScopedLocalRef clazz( env, env->FindClass("android/media/MediaCodec")); CHECK(clazz.get() != NULL); gFields.postEventFromNativeID = env->GetMethodID( clazz.get(), "postEventFromNative", "(IIILjava/lang/Object;)V"); CHECK(gFields.postEventFromNativeID != NULL); gFields.lockAndGetContextID = env->GetMethodID( clazz.get(), "lockAndGetContext", "()J"); CHECK(gFields.lockAndGetContextID != NULL); gFields.setAndUnlockContextID = env->GetMethodID( clazz.get(), "setAndUnlockContext", "(J)V"); CHECK(gFields.setAndUnlockContextID != 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(), "mPattern", "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$QueueRequest")); CHECK(clazz.get() != NULL); gFields.queueRequestIndexID = env->GetFieldID(clazz.get(), "mIndex", "I"); CHECK(gFields.queueRequestIndexID != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$OutputFrame")); CHECK(clazz.get() != NULL); gFields.outputFrameLinearBlockID = env->GetFieldID(clazz.get(), "mLinearBlock", "Landroid/media/MediaCodec$LinearBlock;"); CHECK(gFields.outputFrameLinearBlockID != NULL); gFields.outputFrameHardwareBufferID = env->GetFieldID(clazz.get(), "mHardwareBuffer", "Landroid/hardware/HardwareBuffer;"); CHECK(gFields.outputFrameHardwareBufferID != NULL); gFields.outputFrameChangedKeysID = env->GetFieldID(clazz.get(), "mChangedKeys", "Ljava/util/ArrayList;"); CHECK(gFields.outputFrameChangedKeysID != NULL); gFields.outputFrameFormatID = env->GetFieldID(clazz.get(), "mFormat", "Landroid/media/MediaFormat;"); CHECK(gFields.outputFrameFormatID != 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_INSUFFICIENT_SECURITY", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorInsufficientSecurity = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_UNSUPPORTED_OPERATION", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorUnsupportedOperation = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_FRAME_TOO_LARGE", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorFrameTooLarge = env->GetStaticIntField(clazz.get(), field); field = env->GetStaticFieldID(clazz.get(), "ERROR_LOST_STATE", "I"); CHECK(field != NULL); gCryptoErrorCodes.cryptoErrorLostState = 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(), "", "()V"); CHECK(method != NULL); gPersistentSurfaceClassInfo.ctor = method; field = env->GetFieldID(clazz.get(), "mPersistentObject", "J"); CHECK(field != NULL); gPersistentSurfaceClassInfo.mPersistentObject = field; clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecCapabilities")); CHECK(clazz.get() != NULL); gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get()); method = env->GetMethodID(clazz.get(), "", "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ" "Ljava/util/Map;Ljava/util/Map;)V"); CHECK(method != NULL); gCodecInfo.capsCtorId = method; clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel")); CHECK(clazz.get() != NULL); gCodecInfo.profileLevelClazz = (jclass)env->NewGlobalRef(clazz.get()); field = env->GetFieldID(clazz.get(), "profile", "I"); CHECK(field != NULL); gCodecInfo.profileField = field; field = env->GetFieldID(clazz.get(), "level", "I"); CHECK(field != NULL); gCodecInfo.levelField = field; clazz.reset(env->FindClass("java/nio/ByteBuffer")); CHECK(clazz.get() != NULL); gByteBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); 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); ScopedLocalRef nativeByteOrderObj{ env, env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID)}; gByteBufferInfo.nativeByteOrder = env->NewGlobalRef(nativeByteOrderObj.get()); CHECK(gByteBufferInfo.nativeByteOrder != NULL); nativeByteOrderObj.reset(); gByteBufferInfo.orderId = env->GetMethodID( clazz.get(), "order", "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); CHECK(gByteBufferInfo.orderId != NULL); gByteBufferInfo.asReadOnlyBufferId = env->GetMethodID( clazz.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); CHECK(gByteBufferInfo.asReadOnlyBufferId != NULL); gByteBufferInfo.positionId = env->GetMethodID( clazz.get(), "position", "(I)Ljava/nio/Buffer;"); CHECK(gByteBufferInfo.positionId != NULL); gByteBufferInfo.limitId = env->GetMethodID( clazz.get(), "limit", "(I)Ljava/nio/Buffer;"); CHECK(gByteBufferInfo.limitId != NULL); gByteBufferInfo.getPositionId = env->GetMethodID( clazz.get(), "position", "()I"); CHECK(gByteBufferInfo.getPositionId != NULL); gByteBufferInfo.getLimitId = env->GetMethodID( clazz.get(), "limit", "()I"); CHECK(gByteBufferInfo.getLimitId != NULL); clazz.reset(env->FindClass("java/util/ArrayList")); CHECK(clazz.get() != NULL); gArrayListInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); gArrayListInfo.ctorId = env->GetMethodID(clazz.get(), "", "()V"); CHECK(gArrayListInfo.ctorId != NULL); gArrayListInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I"); CHECK(gArrayListInfo.sizeId != NULL); gArrayListInfo.getId = env->GetMethodID(clazz.get(), "get", "(I)Ljava/lang/Object;"); CHECK(gArrayListInfo.getId != NULL); gArrayListInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z"); CHECK(gArrayListInfo.addId != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$LinearBlock")); CHECK(clazz.get() != NULL); gLinearBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); gLinearBlockInfo.ctorId = env->GetMethodID(clazz.get(), "", "()V"); CHECK(gLinearBlockInfo.ctorId != NULL); gLinearBlockInfo.setInternalStateId = env->GetMethodID( clazz.get(), "setInternalStateLocked", "(JZ)V"); CHECK(gLinearBlockInfo.setInternalStateId != NULL); gLinearBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J"); CHECK(gLinearBlockInfo.contextId != NULL); gLinearBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z"); CHECK(gLinearBlockInfo.validId != NULL); gLinearBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); CHECK(gLinearBlockInfo.lockId != NULL); clazz.reset(env->FindClass("android/media/MediaCodec$ParameterDescriptor")); CHECK(clazz.get() != NULL); gDescriptorInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); gDescriptorInfo.ctorId = env->GetMethodID(clazz.get(), "", "()V"); CHECK(gDescriptorInfo.ctorId != NULL); gDescriptorInfo.nameId = env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;"); CHECK(gDescriptorInfo.nameId != NULL); gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I"); CHECK(gDescriptorInfo.typeId != NULL); } 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; } 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) { setMediaCodec(env, thiz, NULL); } // MediaCodec.LinearBlock static jobject android_media_MediaCodec_LinearBlock_native_map( JNIEnv *env, jobject thiz) { JMediaCodecLinearBlock *context = (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId); if (context->mBuffer) { std::shared_ptr buffer = context->mBuffer; if (!context->mReadonlyMapping) { const C2BufferData data = buffer->data(); if (data.type() != C2BufferData::LINEAR) { throwExceptionAsNecessary(env, INVALID_OPERATION); return nullptr; } if (data.linearBlocks().size() != 1u) { throwExceptionAsNecessary(env, INVALID_OPERATION); return nullptr; } C2ConstLinearBlock block = data.linearBlocks().front(); context->mReadonlyMapping = std::make_shared(block.map().get()); } return CreateByteBuffer( env, context->mReadonlyMapping->data(), // base context->mReadonlyMapping->capacity(), // capacity 0u, // offset context->mReadonlyMapping->capacity(), // size true, // readOnly true /* clearBuffer */); } else if (context->mBlock) { std::shared_ptr block = context->mBlock; if (!context->mReadWriteMapping) { context->mReadWriteMapping = std::make_shared(block->map().get()); } return CreateByteBuffer( env, context->mReadWriteMapping->base(), context->mReadWriteMapping->capacity(), context->mReadWriteMapping->offset(), context->mReadWriteMapping->size(), false, // readOnly true /* clearBuffer */); } else if (context->mLegacyBuffer) { return CreateByteBuffer( env, context->mLegacyBuffer->base(), context->mLegacyBuffer->capacity(), context->mLegacyBuffer->offset(), context->mLegacyBuffer->size(), true, // readOnly true /* clearBuffer */); } else if (context->mMemory) { return CreateByteBuffer( env, context->mMemory->unsecurePointer(), context->mMemory->size(), 0, context->mMemory->size(), false, // readOnly true /* clearBuffer */); } throwExceptionAsNecessary(env, INVALID_OPERATION); return nullptr; } static void android_media_MediaCodec_LinearBlock_native_recycle( JNIEnv *env, jobject thiz) { JMediaCodecLinearBlock *context = (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId); env->CallVoidMethod(thiz, gLinearBlockInfo.setInternalStateId, jlong(0), false); delete context; } static void PopulateNamesVector( JNIEnv *env, jobjectArray codecNames, std::vector *names) { jsize length = env->GetArrayLength(codecNames); for (jsize i = 0; i < length; ++i) { jstring jstr = static_cast(env->GetObjectArrayElement(codecNames, i)); if (jstr == nullptr) { // null entries are ignored continue; } const char *cstr = env->GetStringUTFChars(jstr, nullptr); if (cstr == nullptr) { throwExceptionAsNecessary(env, BAD_VALUE); return; } names->emplace_back(cstr); env->ReleaseStringUTFChars(jstr, cstr); } } static void android_media_MediaCodec_LinearBlock_native_obtain( JNIEnv *env, jobject thiz, jint capacity, jobjectArray codecNames) { std::unique_ptr context{new JMediaCodecLinearBlock}; std::vector names; PopulateNamesVector(env, codecNames, &names); bool hasSecure = false; bool hasNonSecure = false; for (const std::string &name : names) { if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") { hasSecure = true; } else { hasNonSecure = true; } } if (hasSecure && !hasNonSecure) { constexpr size_t kInitialDealerCapacity = 1048576; // 1MB thread_local sp sDealer = new MemoryDealer( kInitialDealerCapacity, "JNI(1MB)"); context->mMemory = sDealer->allocate(capacity); if (context->mMemory == nullptr) { size_t newDealerCapacity = sDealer->getMemoryHeap()->getSize() * 2; while (capacity * 2 > newDealerCapacity) { newDealerCapacity *= 2; } ALOGI("LinearBlock.native_obtain: " "Dealer capacity increasing from %zuMB to %zuMB", sDealer->getMemoryHeap()->getSize() / 1048576, newDealerCapacity / 1048576); sDealer = new MemoryDealer( newDealerCapacity, AStringPrintf("JNI(%zuMB)", newDealerCapacity).c_str()); context->mMemory = sDealer->allocate(capacity); } context->mHidlMemory = hardware::fromHeap(context->mMemory->getMemory( &context->mHidlMemoryOffset, &context->mHidlMemorySize)); } else { context->mBlock = MediaCodec::FetchLinearBlock(capacity, names); if (!context->mBlock) { jniThrowException(env, "java/io/IOException", nullptr); return; } } env->CallVoidMethod( thiz, gLinearBlockInfo.setInternalStateId, (jlong)context.release(), true /* isMappable */); } static jboolean android_media_MediaCodec_LinearBlock_checkCompatible( JNIEnv *env, jclass, jobjectArray codecNames) { std::vector names; PopulateNamesVector(env, codecNames, &names); bool isCompatible = false; bool hasSecure = false; bool hasNonSecure = false; for (const std::string &name : names) { if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") { hasSecure = true; } else { hasNonSecure = true; } } if (hasSecure && hasNonSecure) { return false; } status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible); if (err != OK) { throwExceptionAsNecessary(env, err); } return isCompatible; } 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_enableOnFirstTunnelFrameReadyListener", "(Z)V", (void *)android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener }, { "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;Landroid/os/IHwBinder;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_mapHardwareBuffer", "(Landroid/hardware/HardwareBuffer;)Landroid/media/Image;", (void *)android_media_MediaCodec_mapHardwareBuffer }, { "native_closeMediaImage", "(J)V", (void *)android_media_MediaCodec_closeMediaImage }, { "native_queueLinearBlock", "(ILandroid/media/MediaCodec$LinearBlock;IILandroid/media/MediaCodec$CryptoInfo;JI" "Ljava/util/ArrayList;Ljava/util/ArrayList;)V", (void *)android_media_MediaCodec_native_queueLinearBlock }, { "native_queueHardwareBuffer", "(ILandroid/hardware/HardwareBuffer;JILjava/util/ArrayList;Ljava/util/ArrayList;)V", (void *)android_media_MediaCodec_native_queueHardwareBuffer }, { "native_getOutputFrame", "(Landroid/media/MediaCodec$OutputFrame;I)V", (void *)android_media_MediaCodec_native_getOutputFrame }, { "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 }, { "getCanonicalName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, { "getOwnCodecInfo", "()Landroid/media/MediaCodecInfo;", (void *)android_media_MediaCodec_getOwnCodecInfo }, { "native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaCodec_native_getMetrics}, { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V", (void *)android_media_MediaCodec_setParameters }, { "setVideoScalingMode", "(I)V", (void *)android_media_MediaCodec_setVideoScalingMode }, { "native_setAudioPresentation", "(II)V", (void *)android_media_MediaCodec_setAudioPresentation }, { "native_getSupportedVendorParameters", "()Ljava/util/List;", (void *)android_media_MediaCodec_getSupportedVendorParameters }, { "native_getParameterDescriptor", "(Ljava/lang/String;)Landroid/media/MediaCodec$ParameterDescriptor;", (void *)android_media_MediaCodec_getParameterDescriptor }, { "native_subscribeToVendorParameters", "(Ljava/util/List;)V", (void *)android_media_MediaCodec_subscribeToVendorParameters}, { "native_unsubscribeFromVendorParameters", "(Ljava/util/List;)V", (void *)android_media_MediaCodec_unsubscribeFromVendorParameters}, { "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 }, }; static const JNINativeMethod gLinearBlockMethods[] = { { "native_map", "()Ljava/nio/ByteBuffer;", (void *)android_media_MediaCodec_LinearBlock_native_map }, { "native_recycle", "()V", (void *)android_media_MediaCodec_LinearBlock_native_recycle }, { "native_obtain", "(I[Ljava/lang/String;)V", (void *)android_media_MediaCodec_LinearBlock_native_obtain }, { "native_checkCompatible", "([Ljava/lang/String;)Z", (void *)android_media_MediaCodec_LinearBlock_checkCompatible }, }; int register_android_media_MediaCodec(JNIEnv *env) { int result = AndroidRuntime::registerNativeMethods(env, "android/media/MediaCodec", gMethods, NELEM(gMethods)); if (result != JNI_OK) { return result; } result = AndroidRuntime::registerNativeMethods(env, "android/media/MediaCodec$LinearBlock", gLinearBlockMethods, NELEM(gLinearBlockMethods)); return result; }