diff options
author | Wonsik Kim <wonsik@google.com> | 2019-12-27 17:12:40 -0800 |
---|---|---|
committer | Wonsik Kim <wonsik@google.com> | 2020-01-21 17:43:53 -0800 |
commit | ccb7ac6b6552c0692cf002364cdaaba676d47350 (patch) | |
tree | 5e7be7b12e6cf130d845b8e3a9404953df3e9c91 /media/jni/android_media_MediaCodec.cpp | |
parent | 2bb9f65c870626b113bd8c729d0b516aaff482cb (diff) |
media: MediaCodec buffer API revision
Bug: 136283874
Test: atest CtsMediaTestCases:MediaCodecBlockModelTest
Test: atest CtsMediaTestCases -- --module-arg CtsMediaTestCases:size:small
Change-Id: I3fb163be67112b28fa9998493b359f12d096e759
Diffstat (limited to 'media/jni/android_media_MediaCodec.cpp')
-rw-r--r-- | media/jni/android_media_MediaCodec.cpp | 1321 |
1 files changed, 1129 insertions, 192 deletions
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 20980a90e9fe..ab6966d5d1c3 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -18,6 +18,8 @@ #define LOG_TAG "MediaCodec-JNI" #include <utils/Log.h> +#include <type_traits> + #include "android_media_MediaCodec.h" #include "android_media_MediaCrypto.h" @@ -31,13 +33,20 @@ #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> +#include <C2Buffer.h> + #include <android/hardware/cas/native/1.0/IDescrambler.h> +#include <binder/MemoryHeapBase.h> + #include <cutils/compiler.h> #include <gui/Surface.h> +#include <hidlmemory/FrameworkUtils.h> + #include <media/MediaCodecBuffer.h> +#include <media/hardware/VideoAPI.h> #include <media/stagefright/MediaCodec.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> @@ -47,7 +56,6 @@ #include <media/stagefright/MediaErrors.h> #include <media/stagefright/PersistentSurface.h> #include <mediadrm/ICrypto.h> -#include <nativehelper/ScopedLocalRef.h> #include <system/window.h> @@ -110,6 +118,39 @@ static struct { jfieldID levelField; } gCodecInfo; +static struct { + jclass clazz; + jobject nativeByteOrder; + jmethodID orderId; + jmethodID asReadOnlyBufferId; + jmethodID positionId; + jmethodID limitId; +} gByteBufferInfo; + +static struct { + 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; + jmethodID setInternalStateId; + jfieldID contextId; + jfieldID validId; + jfieldID lockId; +} gGraphicBlockInfo; + struct fields_t { jmethodID postEventFromNativeID; jmethodID lockAndGetContextID; @@ -123,11 +164,65 @@ struct fields_t { jfieldID cryptoInfoPatternID; jfieldID patternEncryptBlocksID; jfieldID patternSkipBlocksID; + jfieldID queueRequestIndexID; + jfieldID outputFrameLinearBlockID; + jfieldID outputFrameGraphicBlockID; + jfieldID outputFrameChangedKeysID; + jfieldID outputFrameFormatID; }; static fields_t gFields; static const void *sRefBaseOwner; +struct JMediaCodecLinearBlock { + std::shared_ptr<C2Buffer> mBuffer; + std::shared_ptr<C2ReadView> mReadonlyMapping; + + std::shared_ptr<C2LinearBlock> mBlock; + std::shared_ptr<C2WriteView> mReadWriteMapping; + + sp<IMemoryHeap> mHeap; + sp<hardware::HidlMemory> mMemory; + + sp<MediaCodecBuffer> mLegacyBuffer; + + std::once_flag mCopyWarningFlag; + + std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) { + if (mBuffer) { + if (mBuffer->data().type() != C2BufferData::LINEAR) { + return nullptr; + } + C2ConstLinearBlock block = mBuffer->data().linearBlocks().front(); + if (offset == 0 && size == block.capacity()) { + return mBuffer; + } + return C2Buffer::CreateLinearBuffer(block.subBlock(offset, size)); + } + if (mBlock) { + return C2Buffer::CreateLinearBuffer(mBlock->share(offset, size, C2Fence{})); + } + return nullptr; + } + + sp<hardware::HidlMemory> toHidlMemory() { + if (mMemory) { + return mMemory; + } + return nullptr; + } +}; + +struct JMediaCodecGraphicBlock { + std::shared_ptr<C2Buffer> mBuffer; + std::shared_ptr<const C2GraphicView> mReadonlyMapping; + + std::shared_ptr<C2GraphicBlock> mBlock; + std::shared_ptr<C2GraphicView> mReadWriteMapping; + + sp<MediaCodecBuffer> mLegacyBuffer; +}; + //////////////////////////////////////////////////////////////////////////////// JMediaCodec::JMediaCodec( @@ -141,8 +236,6 @@ JMediaCodec::JMediaCodec( mClass = (jclass)env->NewGlobalRef(clazz); mObject = env->NewWeakGlobalRef(thiz); - cacheJavaObjects(env); - mLooper = new ALooper; mLooper->setName("MediaCodec_looper"); @@ -163,45 +256,6 @@ JMediaCodec::JMediaCodec( CHECK((mCodec != NULL) != (mInitStatus != OK)); } -void JMediaCodec::cacheJavaObjects(JNIEnv *env) { - jclass clazz = (jclass)env->FindClass("java/nio/ByteBuffer"); - mByteBufferClass = (jclass)env->NewGlobalRef(clazz); - CHECK(mByteBufferClass != NULL); - - ScopedLocalRef<jclass> byteOrderClass( - env, env->FindClass("java/nio/ByteOrder")); - CHECK(byteOrderClass.get() != NULL); - - jmethodID nativeOrderID = env->GetStaticMethodID( - byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); - CHECK(nativeOrderID != NULL); - - jobject nativeByteOrderObj = - env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID); - mNativeByteOrderObj = env->NewGlobalRef(nativeByteOrderObj); - CHECK(mNativeByteOrderObj != NULL); - env->DeleteLocalRef(nativeByteOrderObj); - nativeByteOrderObj = NULL; - - mByteBufferOrderMethodID = env->GetMethodID( - mByteBufferClass, - "order", - "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); - CHECK(mByteBufferOrderMethodID != NULL); - - mByteBufferAsReadOnlyBufferMethodID = env->GetMethodID( - mByteBufferClass, "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); - CHECK(mByteBufferAsReadOnlyBufferMethodID != NULL); - - mByteBufferPositionMethodID = env->GetMethodID( - mByteBufferClass, "position", "(I)Ljava/nio/Buffer;"); - CHECK(mByteBufferPositionMethodID != NULL); - - mByteBufferLimitMethodID = env->GetMethodID( - mByteBufferClass, "limit", "(I)Ljava/nio/Buffer;"); - CHECK(mByteBufferLimitMethodID != NULL); -} - status_t JMediaCodec::initCheck() const { return mInitStatus; } @@ -247,19 +301,6 @@ JMediaCodec::~JMediaCodec() { mObject = NULL; env->DeleteGlobalRef(mClass); mClass = NULL; - deleteJavaObjects(env); -} - -void JMediaCodec::deleteJavaObjects(JNIEnv *env) { - env->DeleteGlobalRef(mByteBufferClass); - mByteBufferClass = NULL; - env->DeleteGlobalRef(mNativeByteOrderObj); - mNativeByteOrderObj = NULL; - - mByteBufferOrderMethodID = NULL; - mByteBufferAsReadOnlyBufferMethodID = NULL; - mByteBufferPositionMethodID = NULL; - mByteBufferLimitMethodID = NULL; } status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) { @@ -300,6 +341,12 @@ status_t JMediaCodec::configure( 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); + return mCodec->configure( format, mSurfaceTextureClient, crypto, descrambler, flags); } @@ -370,6 +417,32 @@ status_t JMediaCodec::queueSecureInputBuffer( presentationTimeUs, flags, errorDetailMsg); } +status_t JMediaCodec::queueBuffer( + size_t index, const std::shared_ptr<C2Buffer> &buffer, int64_t timeUs, + uint32_t flags, const sp<AMessage> &tunings, AString *errorDetailMsg) { + return mCodec->queueBuffer( + index, buffer, timeUs, flags, tunings, errorDetailMsg); +} + +status_t JMediaCodec::queueEncryptedLinearBlock( + size_t index, + const sp<hardware::HidlMemory> &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<AMessage> &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); } @@ -444,7 +517,7 @@ status_t JMediaCodec::getBuffers( } *bufArray = (jobjectArray)env->NewObjectArray( - buffers.size(), mByteBufferClass, NULL); + buffers.size(), gByteBufferInfo.clazz, NULL); if (*bufArray == NULL) { return NO_MEMORY; } @@ -470,6 +543,39 @@ status_t JMediaCodec::getBuffers( return OK; } +template <typename T> +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<typename std::remove_const<T>::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 <typename T> status_t JMediaCodec::createByteBufferFromABuffer( @@ -488,29 +594,9 @@ status_t JMediaCodec::createByteBufferFromABuffer( return OK; } - jobject byteBuffer = - env->NewDirectByteBuffer(buffer->base(), buffer->capacity()); - if (readOnly && byteBuffer != NULL) { - jobject readOnlyBuffer = env->CallObjectMethod( - byteBuffer, mByteBufferAsReadOnlyBufferMethodID); - env->DeleteLocalRef(byteBuffer); - byteBuffer = readOnlyBuffer; - } - if (byteBuffer == NULL) { - return NO_MEMORY; - } - jobject me = env->CallObjectMethod( - byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj); - env->DeleteLocalRef(me); - me = env->CallObjectMethod( - byteBuffer, mByteBufferLimitMethodID, - clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size())); - env->DeleteLocalRef(me); - me = env->CallObjectMethod( - byteBuffer, mByteBufferPositionMethodID, - clearBuffer ? 0 : buffer->offset()); - env->DeleteLocalRef(me); - me = NULL; + jobject byteBuffer = CreateByteBuffer( + env, buffer->base(), buffer->capacity(), buffer->offset(), buffer->size(), + readOnly, clearBuffer); *buf = byteBuffer; return OK; @@ -628,6 +714,92 @@ status_t JMediaCodec::getImage( return OK; } +status_t JMediaCodec::getOutputFrame( + JNIEnv *env, jobject frame, size_t index) const { + sp<MediaCodecBuffer> buffer; + + status_t err = mCodec->getOutputBuffer(index, &buffer); + if (err != OK) { + return err; + } + + if (buffer->size() > 0) { + // asC2Buffer clears internal reference, so set the reference again. + std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer(); + buffer->copy(c2Buffer); + if (c2Buffer) { + switch (c2Buffer->data().type()) { + case C2BufferData::LINEAR: { + std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + context->mBuffer = c2Buffer; + ScopedLocalRef<jobject> linearBlock{env, env->NewObject( + gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; + env->SetLongField( + linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); + break; + } + case C2BufferData::GRAPHIC: { + std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; + context->mBuffer = c2Buffer; + ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( + gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; + env->SetLongField( + graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.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<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + context->mLegacyBuffer = buffer; + ScopedLocalRef<jobject> linearBlock{env, env->NewObject( + gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)}; + env->SetLongField( + linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get()); + } else { + std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; + context->mLegacyBuffer = buffer; + ScopedLocalRef<jobject> graphicBlock{env, env->NewObject( + gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)}; + env->SetLongField( + graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release()); + env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get()); + } + } + } + + jobject format; + err = getOutputFormat(env, index, &format); + if (err != OK) { + return err; + } + env->SetObjectField(frame, gFields.outputFrameFormatID, format); + env->DeleteLocalRef(format); + format = nullptr; + + sp<RefBase> obj; + if (buffer->meta()->findObject("changedKeys", &obj) && obj) { + sp<MediaCodec::WrapperObject<std::set<std::string>>> changedKeys{ + (decltype(changedKeys.get()))obj.get()}; + ScopedLocalRef<jobject> changedKeysObj{env, env->GetObjectField( + frame, gFields.outputFrameChangedKeysID)}; + for (const std::string &key : changedKeys->value) { + ScopedLocalRef<jstring> 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; @@ -1428,6 +1600,139 @@ static void android_media_MediaCodec_queueInputBuffer( 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<jintArray> numBytesOfClearDataObj{env, (jintArray)env->GetObjectField( + cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID)}; + + ScopedLocalRef<jintArray> 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<jobject> patternObj{ + env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)}; + + CryptoPlugin::Pattern pattern; + if (patternObj.get() == nullptr) { + pattern.mEncryptBlocks = 0; + pattern.mSkipBlocks = 0; + } else { + pattern.mEncryptBlocks = env->GetIntField( + patternObj.get(), gFields.patternEncryptBlocksID); + pattern.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); + } + } + } + + ~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<jbyteArray> mIvObj; + ScopedLocalRef<jbyteArray> 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, @@ -1445,152 +1750,274 @@ static void android_media_MediaCodec_queueSecureInputBuffer( return; } - jint numSubSamples = - env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID); - - jintArray numBytesOfClearDataObj = - (jintArray)env->GetObjectField( - cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID); + NativeCryptoInfo cryptoInfo{env, cryptoInfoObj}; + AString errorDetailMsg; - jintArray numBytesOfEncryptedDataObj = - (jintArray)env->GetObjectField( - cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID); + status_t err = cryptoInfo.mErr; + if (err == OK) { + err = codec->queueSecureInputBuffer( + index, offset, + cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples, + (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv, + cryptoInfo.mMode, + cryptoInfo.mPattern, + timestampUs, + flags, + &errorDetailMsg); + } - jbyteArray keyObj = - (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID); + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); +} - jbyteArray ivObj = - (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID); +static status_t ConvertKeyValueListsToAMessage( + JNIEnv *env, jobject keys, jobject values, sp<AMessage> *msg) { + static struct Fields { + explicit Fields(JNIEnv *env) { + ScopedLocalRef<jclass> clazz{env, env->FindClass("java/lang/String")}; + CHECK(clazz.get() != NULL); + mStringClass = (jclass)env->NewGlobalRef(clazz.get()); - 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; - } + clazz.reset(env->FindClass("java/lang/Integer")); + CHECK(clazz.get() != NULL); + mIntegerClass = (jclass)env->NewGlobalRef(clazz.get()); - jobject patternObj = env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID); + mIntegerValueId = env->GetMethodID(clazz.get(), "intValue", "()I"); + CHECK(mIntegerValueId != NULL); - 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; + clazz.reset(env->FindClass("java/lang/Long")); + CHECK(clazz.get() != NULL); + mLongClass = (jclass)env->NewGlobalRef(clazz.get()); - jint *numBytesOfClearData = - (numBytesOfClearDataObj == NULL) - ? NULL - : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy); + mLongValueId = env->GetMethodID(clazz.get(), "longValue", "()J"); + CHECK(mLongValueId != NULL); - jint *numBytesOfEncryptedData = - (numBytesOfEncryptedDataObj == NULL) - ? NULL - : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy); + clazz.reset(env->FindClass("java/lang/Float")); + CHECK(clazz.get() != NULL); + mFloatClass = (jclass)env->NewGlobalRef(clazz.get()); - subSamples = new CryptoPlugin::SubSample[numSubSamples]; + mFloatValueId = env->GetMethodID(clazz.get(), "floatValue", "()F"); + CHECK(mFloatValueId != NULL); - for (jint i = 0; i < numSubSamples; ++i) { - subSamples[i].mNumBytesOfClearData = - (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i]; + clazz.reset(env->FindClass("java/util/ArrayList")); + CHECK(clazz.get() != NULL); - subSamples[i].mNumBytesOfEncryptedData = - (numBytesOfEncryptedData == NULL) - ? 0 : numBytesOfEncryptedData[i]; + mByteBufferArrayId = env->GetMethodID(gByteBufferInfo.clazz, "array", "()[B"); + CHECK(mByteBufferArrayId != NULL); } - if (numBytesOfEncryptedData != NULL) { - env->ReleaseIntArrayElements( - numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0); - numBytesOfEncryptedData = 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<AMessage> result{new AMessage}; + for (jint i = 0; i < size; ++i) { + ScopedLocalRef<jstring> 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; } - if (numBytesOfClearData != NULL) { - env->ReleaseIntArrayElements( - numBytesOfClearDataObj, numBytesOfClearData, 0); - numBytesOfClearData = NULL; + ScopedLocalRef<jobject> 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) { + value.setTo(tmp); + } + env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp); + if (value.empty()) { + return NO_MEMORY; + } + 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.positionId); + jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.limitId); + sp<ABuffer> buffer{new ABuffer(limit - position)}; + void *data = env->GetDirectBufferAddress(jvalue.get()); + if (data != nullptr) { + memcpy(buffer->data(), + static_cast<const uint8_t *>(data) + position, + buffer->size()); + } else { + ScopedLocalRef<jbyteArray> byteArray{env, (jbyteArray)env->CallObjectMethod( + jvalue.get(), sFields.mByteBufferArrayId)}; + env->GetByteArrayRegion(byteArray.get(), position, buffer->size(), + reinterpret_cast<jbyte *>(buffer->data())); + } + result->setBuffer(key.c_str(), buffer); } } - if (err == OK && keyObj != NULL) { - if (env->GetArrayLength(keyObj) != 16) { - err = -EINVAL; - } else { - jboolean isCopy; - key = env->GetByteArrayElements(keyObj, &isCopy); - } + *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<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == nullptr) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; } - if (err == OK && ivObj != NULL) { - if (env->GetArrayLength(ivObj) != 16) { - err = -EINVAL; - } else { - jboolean isCopy; - iv = env->GetByteArrayElements(ivObj, &isCopy); + sp<AMessage> tunings; + status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); + if (err != OK) { + throwExceptionAsNecessary(env, err); + return; + } + + std::shared_ptr<C2Buffer> buffer; + sp<hardware::HidlMemory> memory; + ScopedLocalRef<jobject> 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 (cryptoInfoObj != nullptr) { + memory = context->toHidlMemory(); + } else { + buffer = context->toC2Buffer(offset, size); + } } + env->MonitorExit(lock.get()); + } else { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; } AString errorDetailMsg; + if (cryptoInfoObj != nullptr) { + if (!memory) { + throwExceptionAsNecessary(env, BAD_VALUE); + return; + } - if (err == OK) { - err = codec->queueSecureInputBuffer( - index, offset, - subSamples, numSubSamples, - (const uint8_t *)key, (const uint8_t *)iv, - mode, - pattern, - timestampUs, + NativeCryptoInfo cryptoInfo{env, cryptoInfoObj}; + size_t cryptoSize = 0; + for (int i = 0; i < cryptoInfo.mNumSubSamples; ++i) { + cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfClearData; + cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfEncryptedData; + } + 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) { + 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_queueGraphicBlock( + JNIEnv *env, jobject thiz, jint index, jobject bufferObj, + jlong presentationTimeUs, jint flags, jobject keys, jobject values) { + ALOGV("android_media_MediaCodec_native_queueGraphicBlock"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); - if (iv != NULL) { - env->ReleaseByteArrayElements(ivObj, iv, 0); - iv = NULL; + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; } - if (key != NULL) { - env->ReleaseByteArrayElements(keyObj, key, 0); - key = NULL; + sp<AMessage> tunings; + status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); + if (err != OK) { + throwExceptionAsNecessary(env, err); + return; } - delete[] subSamples; - subSamples = NULL; + std::shared_ptr<C2Buffer> buffer; + std::shared_ptr<C2GraphicBlock> block; + ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gGraphicBlockInfo.lockId)}; + if (env->MonitorEnter(lock.get()) == JNI_OK) { + if (env->GetBooleanField(bufferObj, gGraphicBlockInfo.validId)) { + JMediaCodecGraphicBlock *context = (JMediaCodecGraphicBlock *)env->GetLongField( + bufferObj, gGraphicBlockInfo.contextId); + buffer = context->mBuffer; + block = context->mBlock; + } + env->MonitorExit(lock.get()); + } else { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } - throwExceptionAsNecessary( - env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); + if (!block && !buffer) { + throwExceptionAsNecessary(env, BAD_VALUE); + return; + } + if (!buffer) { + 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<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + 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( @@ -1991,6 +2418,31 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { 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.outputFrameGraphicBlockID = + env->GetFieldID(clazz.get(), "mGraphicBlock", "Landroid/media/MediaCodec$GraphicBlock;"); + CHECK(gFields.outputFrameGraphicBlockID != 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); @@ -2105,6 +2557,96 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { 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<jclass> byteOrderClass( + env, env->FindClass("java/nio/ByteOrder")); + CHECK(byteOrderClass.get() != NULL); + + jmethodID nativeOrderID = env->GetStaticMethodID( + byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); + CHECK(nativeOrderID != NULL); + + ScopedLocalRef<jobject> 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); + + clazz.reset(env->FindClass("java/util/ArrayList")); + CHECK(clazz.get() != 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(), "<init>", "()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$GraphicBlock")); + CHECK(clazz.get() != NULL); + + gGraphicBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + gGraphicBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(gGraphicBlockInfo.ctorId != NULL); + + gGraphicBlockInfo.setInternalStateId = env->GetMethodID( + clazz.get(), "setInternalStateLocked", "(JZ)V"); + CHECK(gGraphicBlockInfo.setInternalStateId != NULL); + + gGraphicBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J"); + CHECK(gGraphicBlockInfo.contextId != NULL); + + gGraphicBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z"); + CHECK(gGraphicBlockInfo.validId != NULL); + + gGraphicBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); + CHECK(gGraphicBlockInfo.lockId != NULL); } static void android_media_MediaCodec_native_setup( @@ -2155,6 +2697,345 @@ static void android_media_MediaCodec_native_finalize( android_media_MediaCodec_release(env, thiz); } +// 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<C2Buffer> 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<C2ReadView>(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<C2LinearBlock> block = context->mBlock; + if (!context->mReadWriteMapping) { + context->mReadWriteMapping = + std::make_shared<C2WriteView>(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->mHeap) { + return CreateByteBuffer( + env, + static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(), + context->mHeap->getSize(), + 0, + context->mHeap->getSize(), + 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, 0, false); + delete context; +} + +static void PopulateNamesVector( + JNIEnv *env, jobjectArray codecNames, std::vector<std::string> *names) { + jsize length = env->GetArrayLength(codecNames); + for (jsize i = 0; i < length; ++i) { + jstring jstr = static_cast<jstring>(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<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + std::vector<std::string> 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) { + context->mHeap = new MemoryHeapBase(capacity); + context->mMemory = hardware::fromHeap(context->mHeap); + } 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, jobjectArray codecNames) { + std::vector<std::string> 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; +} + +// MediaCodec.GraphicBlock + +template <class T> +static jobject CreateImage(JNIEnv *env, const std::shared_ptr<T> &view) { + bool readOnly = std::is_const<T>::value; + const C2PlanarLayout layout = view->layout(); + jint format = HAL_PIXEL_FORMAT_YCBCR_420_888; + switch (layout.type) { + case C2PlanarLayout::TYPE_YUV: { + if (layout.numPlanes != 3) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + const C2PlaneInfo & yPlane = layout.planes[C2PlanarLayout::PLANE_Y]; + const C2PlaneInfo & uPlane = layout.planes[C2PlanarLayout::PLANE_U]; + const C2PlaneInfo & vPlane = layout.planes[C2PlanarLayout::PLANE_V]; + if (yPlane.rowSampling != 1 || yPlane.colSampling != 1) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (uPlane.rowSampling != vPlane.rowSampling + || uPlane.colSampling != vPlane.colSampling) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (uPlane.rowSampling == 2 && uPlane.colSampling == 2) { + format = HAL_PIXEL_FORMAT_YCBCR_420_888; + break; + } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 2) { + format = HAL_PIXEL_FORMAT_YCBCR_422_888; + break; + } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 1) { + format = HAL_PIXEL_FORMAT_YCBCR_444_888; + break; + } + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + case C2PlanarLayout::TYPE_RGB: { + if (layout.numPlanes != 3) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + format = HAL_PIXEL_FORMAT_FLEX_RGB_888; + break; + } + case C2PlanarLayout::TYPE_RGBA: { + if (layout.numPlanes != 4) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + format = HAL_PIXEL_FORMAT_FLEX_RGBA_8888; + break; + } + case C2PlanarLayout::TYPE_YUVA: + [[fallthrough]]; + default: + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + + ScopedLocalRef<jclass> planeClazz( + env, env->FindClass("android/media/MediaCodec$MediaImage$MediaPlane")); + ScopedLocalRef<jobjectArray> planeArray{ + env, env->NewObjectArray(layout.numPlanes, planeClazz.get(), NULL)}; + CHECK(planeClazz.get() != NULL); + jmethodID planeConstructID = env->GetMethodID(planeClazz.get(), "<init>", + "([Ljava/nio/ByteBuffer;IIIII)V"); + + // plane indices are happened to be Y-U-V and R-G-B(-A) order. + for (uint32_t i = 0; i < layout.numPlanes; ++i) { + const C2PlaneInfo &plane = layout.planes[i]; + if (plane.rowInc < 0 || plane.colInc < 0) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + ssize_t minOffset = plane.minOffset(view->width(), view->height()); + ssize_t maxOffset = plane.maxOffset(view->width(), view->height()); + ScopedLocalRef<jobject> byteBuffer{env, CreateByteBuffer( + env, + view->data()[plane.rootIx] + plane.offset + minOffset, + maxOffset - minOffset + 1, + 0, + maxOffset - minOffset + 1, + readOnly, + true)}; + + ScopedLocalRef<jobject> jPlane{env, env->NewObject( + planeClazz.get(), planeConstructID, + byteBuffer.get(), plane.rowInc, plane.colInc)}; + } + + ScopedLocalRef<jclass> imageClazz( + env, env->FindClass("android/media/MediaCodec$MediaImage")); + CHECK(imageClazz.get() != NULL); + + jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "<init>", + "([Landroid/media/Image$Plane;IIIZJIILandroid/graphics/Rect;)V"); + + jobject img = env->NewObject(imageClazz.get(), imageConstructID, + planeArray.get(), + view->width(), + view->height(), + format, + (jboolean)readOnly /* readOnly */, + (jlong)0 /* timestamp */, + (jint)0 /* xOffset */, (jint)0 /* yOffset */, nullptr /* cropRect */); + + // if MediaImage creation fails, return null + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + + return img; +} + +static jobject android_media_MediaCodec_GraphicBlock_native_map( + JNIEnv *env, jobject thiz) { + JMediaCodecGraphicBlock *context = + (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId); + if (context->mBuffer) { + std::shared_ptr<C2Buffer> buffer = context->mBuffer; + if (!context->mReadonlyMapping) { + const C2BufferData data = buffer->data(); + if (data.type() != C2BufferData::GRAPHIC) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + if (data.graphicBlocks().size() != 1u) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; + } + C2ConstGraphicBlock block = data.graphicBlocks().front(); + context->mReadonlyMapping = + std::make_shared<const C2GraphicView>(block.map().get()); + } + return CreateImage(env, context->mReadonlyMapping); + } else if (context->mBlock) { + std::shared_ptr<C2GraphicBlock> block = context->mBlock; + if (!context->mReadWriteMapping) { + context->mReadWriteMapping = + std::make_shared<C2GraphicView>(block->map().get()); + } + return CreateImage(env, context->mReadWriteMapping); + } else if (context->mLegacyBuffer) { + } + throwExceptionAsNecessary(env, INVALID_OPERATION); + return nullptr; +} + +static void android_media_MediaCodec_GraphicBlock_native_recycle( + JNIEnv *env, jobject thiz) { + JMediaCodecGraphicBlock *context = + (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId); + env->CallVoidMethod(thiz, gGraphicBlockInfo.setInternalStateId, 0, false /* isMappable */); + delete context; +} + +static void android_media_MediaCodec_GraphicBlock_native_obtain( + JNIEnv *env, jobject thiz, + jint width, jint height, jint format, jlong usage, jobjectArray codecNames) { + std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock}; + std::vector<std::string> names; + PopulateNamesVector(env, codecNames, &names); + context->mBlock = MediaCodec::FetchGraphicBlock(width, height, format, usage, names); + if (!context->mBlock) { + jniThrowException(env, "java/io/IOException", nullptr); + return; + } + env->CallVoidMethod( + thiz, + gGraphicBlockInfo.setInternalStateId, + (jlong)context.release(), + true /*isMappable */); +} + +static jboolean android_media_MediaCodec_GraphicBlock_checkCompatible( + JNIEnv *env, jobjectArray codecNames) { + std::vector<std::string> names; + PopulateNamesVector(env, codecNames, &names); + bool isCompatible = false; + status_t err = MediaCodec::CanFetchGraphicBlock(names, &isCompatible); + if (err != OK) { + throwExceptionAsNecessary(env, err); + } + return isCompatible; +} + static const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaCodec_release }, @@ -2200,6 +3081,19 @@ static const JNINativeMethod gMethods[] = { { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", (void *)android_media_MediaCodec_queueSecureInputBuffer }, + { "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_queueGraphicBlock", + "(ILandroid/media/MediaCodec$GraphicBlock;JILjava/util/ArrayList;Ljava/util/ArrayList;)V", + (void *)android_media_MediaCodec_native_queueGraphicBlock }, + + { "native_getOutputFrame", + "(Landroid/media/MediaCodec$OutputFrame;I)V", + (void *)android_media_MediaCodec_native_getOutputFrame }, + { "native_dequeueInputBuffer", "(J)I", (void *)android_media_MediaCodec_dequeueInputBuffer }, @@ -2254,7 +3148,50 @@ static const JNINativeMethod gMethods[] = { (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 }, +}; + +static const JNINativeMethod gGraphicBlockMethods[] = { + { "native_map", "()Landroid/media/Image;", + (void *)android_media_MediaCodec_GraphicBlock_native_map }, + + { "native_recycle", "()V", + (void *)android_media_MediaCodec_GraphicBlock_native_recycle }, + + { "native_obtain", "(IIIJ[Ljava/lang/String;)V", + (void *)android_media_MediaCodec_GraphicBlock_native_obtain }, + + { "native_checkCompatible", "([Ljava/lang/String;)Z", + (void *)android_media_MediaCodec_GraphicBlock_checkCompatible }, +}; + int register_android_media_MediaCodec(JNIEnv *env) { - return AndroidRuntime::registerNativeMethods(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)); + if (result != JNI_OK) { + return result; + } + result = AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodec$GraphicBlock", + gGraphicBlockMethods, + NELEM(gGraphicBlockMethods)); + return result; } |