From a0b72a6cdd6e575e2ea0ec2bffc9a361c4041665 Mon Sep 17 00:00:00 2001 From: Chong Zhang Date: Wed, 28 Feb 2018 18:46:26 -0800 Subject: [PATCH] fix slow get caps -- DO NOT MERGE When get caps is called on an MediaCodec object, try to use the codec info for that MediaCodec first as it should be available during codec construction. Only when that somehow failed go and initialize the full MediaCodecList, as building the entire list could be very slow. And when cloning the CodecCapabilities from MediaCodecInfo, copy the fields directly and the fields (other than the ones modifiable by client) are immutable after construction. bug: 74073607 Test: CTS media post submit: cts-tradefed run cts-dev --module CtsMediaTestCases --compatibility:module-arg CtsMediaTestCases:include-annotation:android.platform.test.annotations.RequiresDevice Change-Id: I4ae5f2431da8528e1eca093c5cfb98abcb4a5bbf (cherry picked from commit 90d7304a83ea1758ac2f7a9293d4790a1a28eb4e) --- media/java/android/media/MediaCodec.java | 22 ++- media/java/android/media/MediaCodecInfo.java | 43 +++--- media/jni/android_media_MediaCodec.cpp | 153 +++++++++++++++++++ media/jni/android_media_MediaCodec.h | 2 + 4 files changed, 200 insertions(+), 20 deletions(-) diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 3d5f6bc9ac713..8a742b75cdf8e 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -1602,7 +1602,9 @@ final public class MediaCodec { private EventHandler mCallbackHandler; private Callback mCallback; private OnFrameRenderedListener mOnFrameRenderedListener; - private Object mListenerLock = new Object(); + private final Object mListenerLock = new Object(); + private MediaCodecInfo mCodecInfo; + private final Object mCodecInfoLock = new Object(); private static final int EVENT_CALLBACK = 1; private static final int EVENT_SET_CALLBACK = 2; @@ -3469,9 +3471,25 @@ final public class MediaCodec { */ @NonNull public MediaCodecInfo getCodecInfo() { - return MediaCodecList.getInfoFor(getName()); + // Get the codec name first. If the codec is already released, + // IllegalStateException will be thrown here. + String name = getName(); + synchronized (mCodecInfoLock) { + if (mCodecInfo == null) { + // Get the codec info for this codec itself first. Only initialize + // the full codec list if this somehow fails because it can be slow. + mCodecInfo = getOwnCodecInfo(); + if (mCodecInfo == null) { + mCodecInfo = MediaCodecList.getInfoFor(name); + } + } + return mCodecInfo; + } } + @NonNull + private native final MediaCodecInfo getOwnCodecInfo(); + @NonNull private native final ByteBuffer[] getBuffers(boolean input); diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 3dd6879e44823..2a601f9b4cdb7 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -829,14 +829,24 @@ public final class MediaCodecInfo { /** @hide */ public CodecCapabilities dup() { - return new CodecCapabilities( - // clone writable arrays - Arrays.copyOf(profileLevels, profileLevels.length), - Arrays.copyOf(colorFormats, colorFormats.length), - isEncoder(), - mFlagsVerified, - mDefaultFormat, - mCapabilitiesInfo); + CodecCapabilities caps = new CodecCapabilities(); + + // profileLevels and colorFormats may be modified by client. + caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length); + caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length); + + caps.mMime = mMime; + caps.mMaxSupportedInstances = mMaxSupportedInstances; + caps.mFlagsRequired = mFlagsRequired; + caps.mFlagsSupported = mFlagsSupported; + caps.mFlagsVerified = mFlagsVerified; + caps.mAudioCaps = mAudioCaps; + caps.mVideoCaps = mVideoCaps; + caps.mEncoderCaps = mEncoderCaps; + caps.mDefaultFormat = mDefaultFormat; + caps.mCapabilitiesInfo = mCapabilitiesInfo; + + return caps; } /** @@ -898,13 +908,13 @@ public final class MediaCodecInfo { if (mMime.toLowerCase().startsWith("audio/")) { mAudioCaps = AudioCapabilities.create(info, this); - mAudioCaps.setDefaultFormat(mDefaultFormat); + mAudioCaps.getDefaultFormat(mDefaultFormat); } else if (mMime.toLowerCase().startsWith("video/")) { mVideoCaps = VideoCapabilities.create(info, this); } if (encoder) { mEncoderCaps = EncoderCapabilities.create(info, this); - mEncoderCaps.setDefaultFormat(mDefaultFormat); + mEncoderCaps.getDefaultFormat(mDefaultFormat); } final Map global = MediaCodecList.getGlobalSettings(); @@ -990,8 +1000,7 @@ public final class MediaCodecInfo { return caps; } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { + private void init(MediaFormat info, CodecCapabilities parent) { mParent = parent; initWithPlatformLimits(); applyLevelLimits(); @@ -1171,7 +1180,7 @@ public final class MediaCodecInfo { } /** @hide */ - public void setDefaultFormat(MediaFormat format) { + public void getDefaultFormat(MediaFormat format) { // report settings that have only a single choice if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); @@ -1585,8 +1594,7 @@ public final class MediaCodecInfo { return caps; } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { + private void init(MediaFormat info, CodecCapabilities parent) { mParent = parent; initWithPlatformLimits(); applyLevelLimits(); @@ -2707,8 +2715,7 @@ public final class MediaCodecInfo { return caps; } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { + private void init(MediaFormat info, CodecCapabilities parent) { // no support for complexity or quality yet mParent = parent; mComplexityRange = Range.create(0, 0); @@ -2789,7 +2796,7 @@ public final class MediaCodecInfo { } /** @hide */ - public void setDefaultFormat(MediaFormat format) { + public void getDefaultFormat(MediaFormat format) { // don't list trivial quality/complexity as default for now if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) && mDefaultQuality != null) { diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index a1022c0829099..000317e688dab 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -29,6 +29,7 @@ #include "android_util_Binder.h" #include "jni.h" #include +#include #include @@ -98,6 +99,13 @@ static struct { jint AesCbc; } gCryptoModes; +static struct { + jclass capsClazz; + jmethodID capsCtorId; + jclass profileLevelClazz; + jfieldID profileField; + jfieldID levelField; +} gCodecInfo; struct fields_t { jfieldID context; @@ -625,6 +633,103 @@ status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { return OK; } +static jobject getCodecCapabilitiesObject( + JNIEnv *env, const char *mime, bool isEncoder, + const sp &capabilities) { + Vector profileLevels; + Vector colorFormats; + + sp defaultFormat = new AMessage(); + defaultFormat->setString("mime", mime); + + capabilities->getSupportedColorFormats(&colorFormats); + capabilities->getSupportedProfileLevels(&profileLevels); + uint32_t flags = capabilities->getFlags(); + sp details = capabilities->getDetails(); + + jobject defaultFormatObj = NULL; + if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) { + return NULL; + } + ScopedLocalRef defaultFormatRef(env, defaultFormatObj); + + jobject detailsObj = NULL; + if (ConvertMessageToMap(env, details, &detailsObj)) { + return NULL; + } + ScopedLocalRef detailsRef(env, detailsObj); + + ScopedLocalRef profileLevelArray(env, env->NewObjectArray( + profileLevels.size(), gCodecInfo.profileLevelClazz, NULL)); + + for (size_t i = 0; i < profileLevels.size(); ++i) { + const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i); + + ScopedLocalRef srcRef(env, env->AllocObject( + gCodecInfo.profileLevelClazz)); + + env->SetIntField(srcRef.get(), gCodecInfo.profileField, src.mProfile); + env->SetIntField(srcRef.get(), gCodecInfo.levelField, src.mLevel); + + env->SetObjectArrayElement(profileLevelArray.get(), i, srcRef.get()); + } + + ScopedLocalRef colorFormatsArray( + env, env->NewIntArray(colorFormats.size())); + for (size_t i = 0; i < colorFormats.size(); ++i) { + jint val = colorFormats.itemAt(i); + env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val); + } + + return env->NewObject( + gCodecInfo.capsClazz, gCodecInfo.capsCtorId, + profileLevelArray.get(), colorFormatsArray.get(), isEncoder, flags, + defaultFormatRef.get(), detailsRef.get()); +} + +status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const { + sp codecInfo; + + status_t err = mCodec->getCodecInfo(&codecInfo); + + if (err != OK) { + return err; + } + + ScopedLocalRef nameObject(env, + env->NewStringUTF(codecInfo->getCodecName())); + + bool isEncoder = codecInfo->isEncoder(); + + Vector mimes; + codecInfo->getSupportedMimes(&mimes); + + ScopedLocalRef capsArrayObj(env, + env->NewObjectArray(mimes.size(), gCodecInfo.capsClazz, NULL)); + + for (size_t i = 0; i < mimes.size(); i++) { + const sp caps = + codecInfo->getCapabilitiesFor(mimes[i].c_str()); + + ScopedLocalRef capsObj(env, getCodecCapabilitiesObject( + env, mimes[i].c_str(), isEncoder, caps)); + + env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get()); + } + + ScopedLocalRef codecInfoClazz(env, + env->FindClass("android/media/MediaCodecInfo")); + CHECK(codecInfoClazz.get() != NULL); + + jmethodID codecInfoCtorID = env->GetMethodID(codecInfoClazz.get(), "", + "(Ljava/lang/String;Z[Landroid/media/MediaCodecInfo$CodecCapabilities;)V"); + + *codecInfoObject = env->NewObject(codecInfoClazz.get(), codecInfoCtorID, + nameObject.get(), isEncoder, capsArrayObj.get()); + + return OK; +} + status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const { status_t status = mCodec->getMetrics(reply); @@ -1665,6 +1770,29 @@ static jobject android_media_MediaCodec_getName( return NULL; } +static jobject android_media_MediaCodec_getOwnCodecInfo( + JNIEnv *env, jobject thiz) { + ALOGV("android_media_MediaCodec_getOwnCodecInfo"); + + sp codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return NULL; + } + + jobject codecInfoObj; + status_t err = codec->getCodecInfo(env, &codecInfoObj); + + if (err == OK) { + return codecInfoObj; + } + + throwExceptionAsNecessary(env, err); + + return NULL; +} + static jobject android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz) { @@ -1877,6 +2005,28 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { field = env->GetFieldID(clazz.get(), "mPersistentObject", "J"); CHECK(field != NULL); gPersistentSurfaceClassInfo.mPersistentObject = field; + + clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecCapabilities")); + CHECK(clazz.get() != NULL); + gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get()); + + method = env->GetMethodID(clazz.get(), "", + "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZI" + "Ljava/util/Map;Ljava/util/Map;)V"); + CHECK(method != NULL); + gCodecInfo.capsCtorId = method; + + clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel")); + CHECK(clazz.get() != NULL); + gCodecInfo.profileLevelClazz = (jclass)env->NewGlobalRef(clazz.get()); + + field = env->GetFieldID(clazz.get(), "profile", "I"); + CHECK(field != NULL); + gCodecInfo.profileField = field; + + field = env->GetFieldID(clazz.get(), "level", "I"); + CHECK(field != NULL); + gCodecInfo.levelField = field; } static void android_media_MediaCodec_native_setup( @@ -2002,6 +2152,9 @@ static const JNINativeMethod gMethods[] = { { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, + { "getOwnCodecInfo", "()Landroid/media/MediaCodecInfo;", + (void *)android_media_MediaCodec_getOwnCodecInfo }, + { "native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaCodec_native_getMetrics}, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 2ec8703adc831..985f55a89254c 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -120,6 +120,8 @@ struct JMediaCodec : public AHandler { status_t getName(JNIEnv *env, jstring *name) const; + status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const; + status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const; status_t setParameters(const sp ¶ms);