media: MediaCodec buffer API revision
Bug: 136283874
Test: atest CtsMediaTestCases:MediaCodecBlockModelTest
Test: atest CtsMediaTestCases -- --module-arg CtsMediaTestCases:size:small
Change-Id: I3fb163be67112b28fa9998493b359f12d096e759
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 20980a9..ab6966d 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 @@
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 @@
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 @@
mClass = (jclass)env->NewGlobalRef(clazz);
mObject = env->NewWeakGlobalRef(thiz);
- cacheJavaObjects(env);
-
mLooper = new ALooper;
mLooper->setName("MediaCodec_looper");
@@ -163,45 +256,6 @@
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 @@
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 @@
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 @@
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 @@
}
*bufArray = (jobjectArray)env->NewObjectArray(
- buffers.size(), mByteBufferClass, NULL);
+ buffers.size(), gByteBufferInfo.clazz, NULL);
if (*bufArray == NULL) {
return NO_MEMORY;
}
@@ -470,6 +543,39 @@
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 @@
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 @@
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 @@
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,154 +1750,276 @@
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);
- }
- }
-
+ NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
AString errorDetailMsg;
+ status_t err = cryptoInfo.mErr;
if (err == OK) {
err = codec->queueSecureInputBuffer(
index, offset,
- subSamples, numSubSamples,
- (const uint8_t *)key, (const uint8_t *)iv,
- mode,
- pattern,
+ cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
+ (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
+ cryptoInfo.mMode,
+ cryptoInfo.mPattern,
timestampUs,
flags,
&errorDetailMsg);
}
- if (iv != NULL) {
- env->ReleaseByteArrayElements(ivObj, iv, 0);
- iv = NULL;
- }
-
- if (key != NULL) {
- env->ReleaseByteArrayElements(keyObj, key, 0);
- key = NULL;
- }
-
- delete[] subSamples;
- subSamples = NULL;
-
throwExceptionAsNecessary(
env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
}
+static 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());
+
+ 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<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;
+ }
+
+ 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);
+ }
+ }
+
+ *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;
+ }
+
+ 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;
+ }
+
+ 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 (codec == NULL) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ sp<AMessage> tunings;
+ status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
+ if (err != OK) {
+ throwExceptionAsNecessary(env, err);
+ return;
+ }
+
+ 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;
+ }
+
+ 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(
JNIEnv *env, jobject thiz, jlong timeoutUs) {
ALOGV("android_media_MediaCodec_dequeueInputBuffer");
@@ -1991,6 +2418,31 @@
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 @@
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 @@
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 @@
{ "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 @@
(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;
}