Merge changes I34c10bf7,I39ba5949,I40b8ac00,Ie0864e21,Ie96de170 into main

* changes:
  Java and JNI support for native CodecCapabilities.
  Java and JNI support for native EncoderCapabilities.
  Java and JNI support for native VideoCapabilities.
  Java and JNI support for native PerformancePoint.
  Java and JNI support for native AudioCapabilities.
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 782db35..69b26fd 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -244,12 +244,7 @@
      * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
      */
     public static final class CodecCapabilities {
-        public CodecCapabilities() {
-        }
-
-        // CLASSIFICATION
-        private String mMime;
-        private int mMaxSupportedInstances;
+        private static final String TAG = "CodecCapabilities";
 
         // LEGACY FIELDS
 
@@ -627,12 +622,6 @@
          */
         public int[] colorFormats; // NOTE this array is modifiable by user
 
-        // FEATURES
-
-        private int mFlagsSupported;
-        private int mFlagsRequired;
-        private int mFlagsVerified;
-
         /**
          * <b>video decoder only</b>: codec supports seamless resolution changes.
          */
@@ -822,6 +811,656 @@
         @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
         public static final String FEATURE_DetachedSurface = "detached-surface";
 
+        /** package private */ interface CodecCapsIntf {
+            public CodecCapsIntf dup();
+
+            public boolean isFeatureSupported(String name);
+
+            public boolean isFeatureRequired(String name);
+
+            public boolean isFormatSupported(MediaFormat format);
+
+            public MediaFormat getDefaultFormat();
+
+            public String getMimeType();
+
+            public int getMaxSupportedInstances();
+
+            public AudioCapabilities getAudioCapabilities();
+
+            public VideoCapabilities getVideoCapabilities();
+
+            public EncoderCapabilities getEncoderCapabilities();
+
+            public boolean isRegular();
+
+            public CodecProfileLevel[] getProfileLevels();
+
+            public int[] getColorFormats();
+        }
+
+        /* package private */ static final class CodecCapsLegacyImpl implements CodecCapsIntf {
+            // errors while reading profile levels - accessed from sister capabilities
+            int mError;
+
+            private CodecProfileLevel[] mProfileLevels;
+            private int[] mColorFormats;
+
+            // CLASSIFICATION
+            private String mMime;
+            private int mMaxSupportedInstances;
+
+            // FEATURES
+            private int mFlagsSupported;
+            private int mFlagsRequired;
+            private int mFlagsVerified;
+
+            // NEW-STYLE CAPABILITIES
+            private AudioCapabilities mAudioCaps;
+            private VideoCapabilities mVideoCaps;
+            private EncoderCapabilities mEncoderCaps;
+            private MediaFormat mDefaultFormat;
+
+            private MediaFormat mCapabilitiesInfo;
+
+            public CodecProfileLevel[] getProfileLevels() {
+                return mProfileLevels;
+            }
+
+            public int[] getColorFormats() {
+                return mColorFormats;
+            }
+
+            public CodecCapsLegacyImpl() {}
+
+            public CodecCapsLegacyImpl dup() {
+                CodecCapsLegacyImpl caps = new CodecCapsLegacyImpl();
+
+                caps.mProfileLevels = Arrays.copyOf(mProfileLevels, mProfileLevels.length);
+                caps.mColorFormats = Arrays.copyOf(mColorFormats, mColorFormats.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;
+            }
+
+            public final boolean isFeatureSupported(String name) {
+                return checkFeature(name, mFlagsSupported);
+            }
+
+            public final boolean isFeatureRequired(String name) {
+                return checkFeature(name, mFlagsRequired);
+            }
+
+            // Flags are used for feature list creation so separate this into a private
+            // static class to delay reading the flags only when constructing the list.
+            private static class FeatureList {
+                private static Feature[] getDecoderFeatures() {
+                    ArrayList<Feature> features = new ArrayList();
+                    features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
+                    features.add(new Feature(FEATURE_SecurePlayback,   (1 << 1), false));
+                    features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
+                    features.add(new Feature(FEATURE_PartialFrame,     (1 << 3), false));
+                    features.add(new Feature(FEATURE_FrameParsing,     (1 << 4), false));
+                    features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
+                    features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
+                    features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
+                    if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
+                        features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
+                    }
+                    if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
+                        features.add(new Feature(FEATURE_DetachedSurface,     (1 << 9), true));
+                    }
+
+                    // feature to exclude codec from REGULAR codec list
+                    features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
+
+                    return features.toArray(new Feature[0]);
+                };
+
+                private static Feature[] decoderFeatures = getDecoderFeatures();
+
+                private static Feature[] getEncoderFeatures() {
+                    ArrayList<Feature> features = new ArrayList();
+
+                    features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
+                    features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
+                    features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
+                    features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
+                    features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
+                    features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
+                    if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
+                        features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
+                    }
+                    if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
+                        features.add(new Feature(FEATURE_Roi, (1 << 7), true));
+                    }
+
+                    // feature to exclude codec from REGULAR codec list
+                    features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
+
+                    return features.toArray(new Feature[0]);
+                };
+
+                private static Feature[] encoderFeatures = getEncoderFeatures();
+
+                public static Feature[] getFeatures(boolean isEncoder) {
+                    if (isEncoder) {
+                        return encoderFeatures;
+                    } else {
+                        return decoderFeatures;
+                    }
+                }
+            }
+
+            /** @hide */
+            public String[] validFeatures() {
+                Feature[] features = getValidFeatures();
+                String[] res = new String[features.length];
+                for (int i = 0; i < res.length; i++) {
+                    if (!features[i].mInternal) {
+                        res[i] = features[i].mName;
+                    }
+                }
+                return res;
+            }
+
+            private Feature[] getValidFeatures() {
+                return FeatureList.getFeatures(isEncoder());
+            }
+
+            private boolean checkFeature(String name, int flags) {
+                for (Feature feat: getValidFeatures()) {
+                    if (feat.mName.equals(name)) {
+                        return (flags & feat.mValue) != 0;
+                    }
+                }
+                return false;
+            }
+
+            public boolean isRegular() {
+                // regular codecs only require default features
+                for (Feature feat: getValidFeatures()) {
+                    if (!feat.mDefault && isFeatureRequired(feat.mName)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            public final boolean isFormatSupported(MediaFormat format) {
+                final Map<String, Object> map = format.getMap();
+                final String mime = (String) map.get(MediaFormat.KEY_MIME);
+
+                // mime must match if present
+                if (mime != null && !mMime.equalsIgnoreCase(mime)) {
+                    return false;
+                }
+
+                // check feature support
+                for (Feature feat: getValidFeatures()) {
+                    if (feat.mInternal) {
+                        continue;
+                    }
+
+                    Integer yesNo = (Integer) map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+                    if (yesNo == null) {
+                        continue;
+                    }
+                    if ((yesNo == 1 && !isFeatureSupported(feat.mName))
+                            || (yesNo == 0 && isFeatureRequired(feat.mName))) {
+                        return false;
+                    }
+                }
+
+                Integer profile = (Integer) map.get(MediaFormat.KEY_PROFILE);
+                Integer level = (Integer) map.get(MediaFormat.KEY_LEVEL);
+
+                if (profile != null) {
+                    if (!supportsProfileLevel(profile, level)) {
+                        return false;
+                    }
+
+                    // If we recognize this profile, check that this format is supported by the
+                    // highest level supported by the codec for that profile. (Ignore specified
+                    // level beyond the above profile/level check as level is only used as a
+                    // guidance. E.g. AVC Level 1 CIF format is supported if codec supports
+                    // level 1.1 even though max size for Level 1 is QCIF. However, MPEG2 Simple
+                    // Profile 1080p format is not supported even if codec supports Main Profile
+                    // Level High, as Simple Profile does not support 1080p.
+                    CodecCapsLegacyImpl levelCaps = null;
+                    int maxLevel = 0;
+                    for (CodecProfileLevel pl : mProfileLevels) {
+                        if (pl.profile == profile && pl.level > maxLevel) {
+                            // H.263 levels are not completely ordered:
+                            // Level45 support only implies Level10 support
+                            if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)
+                                    || pl.level != CodecProfileLevel.H263Level45
+                                    || maxLevel == CodecProfileLevel.H263Level10) {
+                                maxLevel = pl.level;
+                            }
+                        }
+                    }
+                    levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
+                    // We must remove the profile from this format otherwise
+                    // levelCaps.isFormatSupported will get into this same condition and loop
+                    // forever. Furthermore, since levelCaps does not contain features and bitrate
+                    // specific keys, keep only keys relevant for a level check.
+                    Map<String, Object> levelCriticalFormatMap = new HashMap<>(map);
+                    final Set<String> criticalKeys = isVideo()
+                        ? VideoCapabilities.VideoCapsLegacyImpl.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS
+                        : isAudio()
+                        ? AudioCapabilities.AudioCapsLegacyImpl.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS
+                        : null;
+
+                    // critical keys will always contain KEY_MIME, but should also contain others
+                    // to be meaningful
+                    if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) {
+                        levelCriticalFormatMap.keySet().retainAll(criticalKeys);
+
+                        MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap);
+                        if (!levelCaps.isFormatSupported(levelCriticalFormat)) {
+                            return false;
+                        }
+                    }
+                }
+                if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
+                    return false;
+                }
+                if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
+                    return false;
+                }
+                if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
+                    return false;
+                }
+                return true;
+            }
+
+            private static boolean supportsBitrate(
+                    Range<Integer> bitrateRange, MediaFormat format) {
+                Map<String, Object> map = format.getMap();
+
+                // consider max bitrate over average bitrate for support
+                Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
+                Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
+                if (bitrate == null) {
+                    bitrate = maxBitrate;
+                } else if (maxBitrate != null) {
+                    bitrate = Math.max(bitrate, maxBitrate);
+                }
+
+                if (bitrate != null && bitrate > 0) {
+                    return bitrateRange.contains(bitrate);
+                }
+
+                return true;
+            }
+
+            private boolean supportsProfileLevel(int profile, Integer level) {
+                for (CodecProfileLevel pl: mProfileLevels) {
+                    if (pl.profile != profile) {
+                        continue;
+                    }
+
+                    // No specific level requested
+                    if (level == null) {
+                        return true;
+                    }
+
+                    // AAC doesn't use levels
+                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+                        return true;
+                    }
+
+                    // DTS doesn't use levels
+                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
+                            || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
+                            || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+                        return true;
+                    }
+
+                    // H.263 levels are not completely ordered:
+                    // Level45 support only implies Level10 support
+                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+                        if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
+                                && level > CodecProfileLevel.H263Level10) {
+                            continue;
+                        }
+                    }
+
+                    // MPEG4 levels are not completely ordered:
+                    // Level1 support only implies Level0 (and not Level0b) support
+                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+                        if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
+                                && level > CodecProfileLevel.MPEG4Level0) {
+                            continue;
+                        }
+                    }
+
+                    // HEVC levels incorporate both tiers and levels. Verify tier support.
+                    if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                        boolean supportsHighTier =
+                            (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+                        boolean checkingHighTier
+                                = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+                        // high tier levels are only supported by other high tier levels
+                        if (checkingHighTier && !supportsHighTier) {
+                            continue;
+                        }
+                    }
+
+                    if (pl.level >= level) {
+                        // if we recognize the listed profile/level, we must also recognize the
+                        // profile/level arguments.
+                        if (createFromProfileLevel(mMime, profile, pl.level) != null) {
+                            return createFromProfileLevel(mMime, profile, level) != null;
+                        }
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            public MediaFormat getDefaultFormat() {
+                return mDefaultFormat;
+            }
+
+            public String getMimeType() {
+                return mMime;
+            }
+
+            public int getMaxSupportedInstances() {
+                return mMaxSupportedInstances;
+            }
+
+            private boolean isAudio() {
+                return mAudioCaps != null;
+            }
+
+            public AudioCapabilities getAudioCapabilities() {
+                return mAudioCaps;
+            }
+
+            private boolean isEncoder() {
+                return mEncoderCaps != null;
+            }
+
+            public EncoderCapabilities getEncoderCapabilities() {
+                return mEncoderCaps;
+            }
+
+            private boolean isVideo() {
+                return mVideoCaps != null;
+            }
+
+            public VideoCapabilities getVideoCapabilities() {
+                return mVideoCaps;
+            }
+
+            public static CodecCapsLegacyImpl createFromProfileLevel(
+                    String mime, int profile, int level) {
+                CodecProfileLevel pl = new CodecProfileLevel();
+                pl.profile = profile;
+                pl.level = level;
+                MediaFormat defaultFormat = new MediaFormat();
+                defaultFormat.setString(MediaFormat.KEY_MIME, mime);
+
+                CodecCapsLegacyImpl ret = new CodecCapsLegacyImpl(
+                        new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
+                        defaultFormat, new MediaFormat() /* info */);
+                if (ret.mError != 0) {
+                    return null;
+                }
+                return ret;
+            }
+
+            /* package private */ CodecCapsLegacyImpl(
+                    CodecProfileLevel[] profLevs, int[] colFmts,
+                    boolean encoder,
+                    Map<String, Object>defaultFormatMap,
+                    Map<String, Object>capabilitiesMap) {
+                this(profLevs, colFmts, encoder,
+                        new MediaFormat(defaultFormatMap),
+                        new MediaFormat(capabilitiesMap));
+            }
+
+            /* package private */ CodecCapsLegacyImpl(
+                    CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
+                    MediaFormat defaultFormat, MediaFormat info) {
+                final Map<String, Object> map = info.getMap();
+                mColorFormats = colFmts;
+                mFlagsVerified = 0; // TODO: remove as it is unused
+                mDefaultFormat = defaultFormat;
+                mCapabilitiesInfo = info;
+                mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
+
+                /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
+                supported profiles. Determine the level for them using the info they provide. */
+                if (profLevs.length == 0
+                        && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                    CodecProfileLevel profLev = new CodecProfileLevel();
+                    profLev.profile = CodecProfileLevel.VP9Profile0;
+                    profLev.level = VideoCapabilities.VideoCapsLegacyImpl.equivalentVP9Level(info);
+                    profLevs = new CodecProfileLevel[] { profLev };
+                }
+                mProfileLevels = profLevs;
+
+                if (mMime.toLowerCase().startsWith("audio/")) {
+                    mAudioCaps = AudioCapabilities.create(info, this);
+                    mAudioCaps.getDefaultFormat(mDefaultFormat);
+                } else if (mMime.toLowerCase().startsWith("video/")
+                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
+                    mVideoCaps = VideoCapabilities.create(info, this);
+                }
+                if (encoder) {
+                    mEncoderCaps = EncoderCapabilities.create(info, this);
+                    mEncoderCaps.getDefaultFormat(mDefaultFormat);
+                }
+
+                final Map<String, Object> global = MediaCodecList.getGlobalSettings();
+                mMaxSupportedInstances = Utils.parseIntSafely(
+                        global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
+
+                int maxInstances = Utils.parseIntSafely(
+                        map.get("max-concurrent-instances"), mMaxSupportedInstances);
+                mMaxSupportedInstances =
+                        Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
+
+                for (Feature feat: getValidFeatures()) {
+                    String key = MediaFormat.KEY_FEATURE_ + feat.mName;
+                    Integer yesNo = (Integer)map.get(key);
+                    if (yesNo == null) {
+                        continue;
+                    }
+                    if (yesNo > 0) {
+                        mFlagsRequired |= feat.mValue;
+                    }
+                    mFlagsSupported |= feat.mValue;
+                    if (!feat.mInternal) {
+                        mDefaultFormat.setInteger(key, 1);
+                    }
+                    // TODO restrict features by mFlagsVerified once all codecs reliably verify them
+                }
+            }
+        }
+
+        /* package private */ static final class CodecCapsNativeImpl implements CodecCapsIntf {
+            private long mNativeContext; // accessed by native methods
+
+            private CodecProfileLevel[] mProfileLevels;
+            private int[] mColorFormats;
+
+            private MediaFormat mDefaultFormat;
+            private AudioCapabilities mAudioCaps;
+            private VideoCapabilities mVideoCaps;
+            private EncoderCapabilities mEncoderCaps;
+
+            public static CodecCapsNativeImpl createFromProfileLevel(
+                    String mime, int profile, int level) {
+                return native_createFromProfileLevel(mime, profile, level);
+            }
+
+            /**
+             * Constructor used by JNI.
+             *
+             * The Java CodecCapabilities object keeps these subobjects to avoid recontructing.
+             */
+            /* package private */ CodecCapsNativeImpl(CodecProfileLevel[] profLevs, int[] colFmts,
+                    MediaFormat defaultFormat, AudioCapabilities audioCaps,
+                    VideoCapabilities videoCaps, EncoderCapabilities encoderCaps) {
+                mProfileLevels = profLevs;
+                mColorFormats = colFmts;
+                mDefaultFormat = defaultFormat;
+                mAudioCaps = audioCaps;
+                mVideoCaps = videoCaps;
+                mEncoderCaps = encoderCaps;
+            }
+
+            public CodecCapsNativeImpl dup() {
+                CodecCapsNativeImpl impl = native_dup();
+                return impl;
+            }
+
+            @Override
+            protected void finalize() {
+                native_finalize();
+            }
+
+            public CodecProfileLevel[] getProfileLevels() {
+                return mProfileLevels;
+            }
+
+            public int[] getColorFormats() {
+                return mColorFormats;
+            }
+
+            public final boolean isFeatureSupported(String name) {
+                return native_isFeatureSupported(name);
+            }
+
+            public final boolean isFeatureRequired(String name) {
+                return native_isFeatureRequired(name);
+            }
+
+            public boolean isRegular() {
+                return native_isRegular();
+            }
+
+            public final boolean isFormatSupported(MediaFormat format) {
+                if (format == null) {
+                    throw new NullPointerException();
+                }
+
+                Map<String, Object> formatMap = format.getMap();
+                String[] keys = new String[formatMap.size()];
+                Object[] values = new Object[formatMap.size()];
+
+                int i = 0;
+                for (Map.Entry<String, Object> entry: formatMap.entrySet()) {
+                    keys[i] = entry.getKey();
+                    values[i] = entry.getValue();
+                    ++i;
+                }
+
+                return native_isFormatSupported(keys, values);
+            }
+
+            public MediaFormat getDefaultFormat() {
+                return mDefaultFormat;
+            }
+
+            public String getMimeType() {
+                return native_getMimeType();
+            }
+
+            public int getMaxSupportedInstances() {
+                return native_getMaxSupportedInstances();
+            }
+
+            public AudioCapabilities getAudioCapabilities() {
+                return mAudioCaps;
+            }
+
+            public EncoderCapabilities getEncoderCapabilities() {
+                return mEncoderCaps;
+            }
+
+            public VideoCapabilities getVideoCapabilities() {
+                return mVideoCaps;
+            }
+
+            private static native void native_init();
+            private static native CodecCapsNativeImpl native_createFromProfileLevel(
+                    String mime, int profile, int level);
+            private native CodecCapsNativeImpl native_dup();
+            private native void native_finalize();
+            private native int native_getMaxSupportedInstances();
+            private native String native_getMimeType();
+            private native final boolean native_isFeatureRequired(String name);
+            private native final boolean native_isFeatureSupported(String name);
+            private native final boolean native_isFormatSupported(@Nullable String[] keys,
+                    @Nullable Object[] values);
+            private native boolean native_isRegular();
+
+            static {
+                System.loadLibrary("media_jni");
+                native_init();
+            }
+        }
+
+        private CodecCapsIntf mImpl;
+
+        /**
+         * Retrieve the codec capabilities for a certain {@code mime type}, {@code
+         * profile} and {@code level}.  If the type, or profile-level combination
+         * is not understood by the framework, it returns null.
+         * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
+         * method without calling any method of the {@link MediaCodecList} class beforehand
+         * results in a {@link NullPointerException}.</p>
+         */
+        public static CodecCapabilities createFromProfileLevel(
+                String mime, int profile, int level) {
+            CodecCapsIntf impl;
+            if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+                impl = CodecCapsNativeImpl.createFromProfileLevel(mime, profile, level);
+            } else {
+                impl = CodecCapsLegacyImpl.createFromProfileLevel(mime, profile, level);
+            }
+            return new CodecCapabilities(impl);
+        }
+
+        public CodecCapabilities() {
+            mImpl = new CodecCapsLegacyImpl();
+        }
+
+        /** package private */ CodecCapabilities(CodecCapsIntf impl) {
+            mImpl = impl;
+            profileLevels = mImpl.getProfileLevels();
+            colorFormats = mImpl.getColorFormats();
+        }
+
+        /** @hide */
+        public CodecCapabilities dup() {
+            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.mImpl = mImpl.dup();
+
+            return caps;
+        }
+
         /**
          * Query codec feature capabilities.
          * <p>
@@ -830,7 +1469,7 @@
          * features that are always on.
          */
         public final boolean isFeatureSupported(String name) {
-            return checkFeature(name, mFlagsSupported);
+            return mImpl.isFeatureSupported(name);
         }
 
         /**
@@ -840,104 +1479,12 @@
          * they are always turned on.
          */
         public final boolean isFeatureRequired(String name) {
-            return checkFeature(name, mFlagsRequired);
-        }
-
-        // Flags are used for feature list creation so separate this into a private
-        // static class to delay reading the flags only when constructing the list.
-        private static class FeatureList {
-            private static Feature[] getDecoderFeatures() {
-                ArrayList<Feature> features = new ArrayList();
-                features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true));
-                features.add(new Feature(FEATURE_SecurePlayback,   (1 << 1), false));
-                features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false));
-                features.add(new Feature(FEATURE_PartialFrame,     (1 << 3), false));
-                features.add(new Feature(FEATURE_FrameParsing,     (1 << 4), false));
-                features.add(new Feature(FEATURE_MultipleFrames,   (1 << 5), false));
-                features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false));
-                features.add(new Feature(FEATURE_LowLatency,       (1 << 7), true));
-                if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) {
-                    features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true));
-                }
-                if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
-                    features.add(new Feature(FEATURE_DetachedSurface,     (1 << 9), true));
-                }
-
-                // feature to exclude codec from REGULAR codec list
-                features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
-
-                return features.toArray(new Feature[0]);
-            };
-
-            private static Feature[] decoderFeatures = getDecoderFeatures();
-
-            private static Feature[] getEncoderFeatures() {
-                ArrayList<Feature> features = new ArrayList();
-
-                features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false));
-                features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false));
-                features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false));
-                features.add(new Feature(FEATURE_QpBounds, (1 << 3), false));
-                features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false));
-                features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false));
-                if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) {
-                    features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true));
-                }
-                if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
-                    features.add(new Feature(FEATURE_Roi, (1 << 7), true));
-                }
-
-                // feature to exclude codec from REGULAR codec list
-                features.add(new Feature(FEATURE_SpecialCodec,     (1 << 30), false, true));
-
-                return features.toArray(new Feature[0]);
-            };
-
-            private static Feature[] encoderFeatures = getEncoderFeatures();
-
-            public static Feature[] getFeatures(boolean isEncoder) {
-                if (isEncoder) {
-                    return encoderFeatures;
-                } else {
-                    return decoderFeatures;
-                }
-            }
-        }
-
-        /** @hide */
-        public String[] validFeatures() {
-            Feature[] features = getValidFeatures();
-            String[] res = new String[features.length];
-            for (int i = 0; i < res.length; i++) {
-                if (!features[i].mInternal) {
-                    res[i] = features[i].mName;
-                }
-            }
-            return res;
-        }
-
-        private Feature[] getValidFeatures() {
-            return FeatureList.getFeatures(isEncoder());
-        }
-
-        private boolean checkFeature(String name, int flags) {
-            for (Feature feat: getValidFeatures()) {
-                if (feat.mName.equals(name)) {
-                    return (flags & feat.mValue) != 0;
-                }
-            }
-            return false;
+            return mImpl.isFeatureRequired(name);
         }
 
         /** @hide */
         public boolean isRegular() {
-            // regular codecs only require default features
-            for (Feature feat: getValidFeatures()) {
-                if (!feat.mDefault && isFeatureRequired(feat.mName)) {
-                    return false;
-                }
-            }
-            return true;
+            return mImpl.isRegular();
         }
 
         /**
@@ -1046,200 +1593,22 @@
          *         and feature requests.
          */
         public final boolean isFormatSupported(MediaFormat format) {
-            final Map<String, Object> map = format.getMap();
-            final String mime = (String)map.get(MediaFormat.KEY_MIME);
-
-            // mime must match if present
-            if (mime != null && !mMime.equalsIgnoreCase(mime)) {
-                return false;
-            }
-
-            // check feature support
-            for (Feature feat: getValidFeatures()) {
-                if (feat.mInternal) {
-                    continue;
-                }
-
-                Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
-                if (yesNo == null) {
-                    continue;
-                }
-                if ((yesNo == 1 && !isFeatureSupported(feat.mName)) ||
-                        (yesNo == 0 && isFeatureRequired(feat.mName))) {
-                    return false;
-                }
-            }
-
-            Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
-            Integer level = (Integer)map.get(MediaFormat.KEY_LEVEL);
-
-            if (profile != null) {
-                if (!supportsProfileLevel(profile, level)) {
-                    return false;
-                }
-
-                // If we recognize this profile, check that this format is supported by the
-                // highest level supported by the codec for that profile. (Ignore specified
-                // level beyond the above profile/level check as level is only used as a
-                // guidance. E.g. AVC Level 1 CIF format is supported if codec supports level 1.1
-                // even though max size for Level 1 is QCIF. However, MPEG2 Simple Profile
-                // 1080p format is not supported even if codec supports Main Profile Level High,
-                // as Simple Profile does not support 1080p.
-                CodecCapabilities levelCaps = null;
-                int maxLevel = 0;
-                for (CodecProfileLevel pl : profileLevels) {
-                    if (pl.profile == profile && pl.level > maxLevel) {
-                        // H.263 levels are not completely ordered:
-                        // Level45 support only implies Level10 support
-                        if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)
-                                || pl.level != CodecProfileLevel.H263Level45
-                                || maxLevel == CodecProfileLevel.H263Level10) {
-                            maxLevel = pl.level;
-                        }
-                    }
-                }
-                levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
-                // We must remove the profile from this format otherwise levelCaps.isFormatSupported
-                // will get into this same condition and loop forever. Furthermore, since levelCaps
-                // does not contain features and bitrate specific keys, keep only keys relevant for
-                // a level check.
-                Map<String, Object> levelCriticalFormatMap = new HashMap<>(map);
-                final Set<String> criticalKeys =
-                    isVideo() ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS :
-                    isAudio() ? AudioCapabilities.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS :
-                    null;
-
-                // critical keys will always contain KEY_MIME, but should also contain others to be
-                // meaningful
-                if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) {
-                    levelCriticalFormatMap.keySet().retainAll(criticalKeys);
-
-                    MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap);
-                    if (!levelCaps.isFormatSupported(levelCriticalFormat)) {
-                        return false;
-                    }
-                }
-            }
-            if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
-                return false;
-            }
-            if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
-                return false;
-            }
-            if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
-                return false;
-            }
-            return true;
+            return mImpl.isFormatSupported(format);
         }
 
-        private static boolean supportsBitrate(
-                Range<Integer> bitrateRange, MediaFormat format) {
-            Map<String, Object> map = format.getMap();
-
-            // consider max bitrate over average bitrate for support
-            Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
-            Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
-            if (bitrate == null) {
-                bitrate = maxBitrate;
-            } else if (maxBitrate != null) {
-                bitrate = Math.max(bitrate, maxBitrate);
-            }
-
-            if (bitrate != null && bitrate > 0) {
-                return bitrateRange.contains(bitrate);
-            }
-
-            return true;
-        }
-
-        private boolean supportsProfileLevel(int profile, Integer level) {
-            for (CodecProfileLevel pl: profileLevels) {
-                if (pl.profile != profile) {
-                    continue;
-                }
-
-                // No specific level requested
-                if (level == null) {
-                    return true;
-                }
-
-                // AAC doesn't use levels
-                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-                    return true;
-                }
-
-                // DTS doesn't use levels
-                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)
-                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)
-                        || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
-                    return true;
-                }
-
-                // H.263 levels are not completely ordered:
-                // Level45 support only implies Level10 support
-                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
-                    if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
-                            && level > CodecProfileLevel.H263Level10) {
-                        continue;
-                    }
-                }
-
-                // MPEG4 levels are not completely ordered:
-                // Level1 support only implies Level0 (and not Level0b) support
-                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-                    if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
-                            && level > CodecProfileLevel.MPEG4Level0) {
-                        continue;
-                    }
-                }
-
-                // HEVC levels incorporate both tiers and levels. Verify tier support.
-                if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-                    boolean supportsHighTier =
-                        (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
-                    boolean checkingHighTier = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
-                    // high tier levels are only supported by other high tier levels
-                    if (checkingHighTier && !supportsHighTier) {
-                        continue;
-                    }
-                }
-
-                if (pl.level >= level) {
-                    // if we recognize the listed profile/level, we must also recognize the
-                    // profile/level arguments.
-                    if (createFromProfileLevel(mMime, profile, pl.level) != null) {
-                        return createFromProfileLevel(mMime, profile, level) != null;
-                    }
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        // errors while reading profile levels - accessed from sister capabilities
-        int mError;
-
-        private static final String TAG = "CodecCapabilities";
-
-        // NEW-STYLE CAPABILITIES
-        private AudioCapabilities mAudioCaps;
-        private VideoCapabilities mVideoCaps;
-        private EncoderCapabilities mEncoderCaps;
-        private MediaFormat mDefaultFormat;
-
         /**
          * Returns a MediaFormat object with default values for configurations that have
          * defaults.
          */
         public MediaFormat getDefaultFormat() {
-            return mDefaultFormat;
+            return mImpl.getDefaultFormat();
         }
 
         /**
          * Returns the mime type for which this codec-capability object was created.
          */
         public String getMimeType() {
-            return mMime;
+            return mImpl.getMimeType();
         }
 
         /**
@@ -1251,157 +1620,28 @@
          * resources at time of use.
          */
         public int getMaxSupportedInstances() {
-            return mMaxSupportedInstances;
-        }
-
-        private boolean isAudio() {
-            return mAudioCaps != null;
+            return mImpl.getMaxSupportedInstances();
         }
 
         /**
          * Returns the audio capabilities or {@code null} if this is not an audio codec.
          */
         public AudioCapabilities getAudioCapabilities() {
-            return mAudioCaps;
-        }
-
-        private boolean isEncoder() {
-            return mEncoderCaps != null;
+            return mImpl.getAudioCapabilities();
         }
 
         /**
          * Returns the encoding capabilities or {@code null} if this is not an encoder.
          */
         public EncoderCapabilities getEncoderCapabilities() {
-            return mEncoderCaps;
-        }
-
-        private boolean isVideo() {
-            return mVideoCaps != null;
+            return mImpl.getEncoderCapabilities();
         }
 
         /**
          * Returns the video capabilities or {@code null} if this is not a video codec.
          */
         public VideoCapabilities getVideoCapabilities() {
-            return mVideoCaps;
-        }
-
-        /** @hide */
-        public CodecCapabilities dup() {
-            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;
-        }
-
-        /**
-         * Retrieve the codec capabilities for a certain {@code mime type}, {@code
-         * profile} and {@code level}.  If the type, or profile-level combination
-         * is not understood by the framework, it returns null.
-         * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
-         * method without calling any method of the {@link MediaCodecList} class beforehand
-         * results in a {@link NullPointerException}.</p>
-         */
-        public static CodecCapabilities createFromProfileLevel(
-                String mime, int profile, int level) {
-            CodecProfileLevel pl = new CodecProfileLevel();
-            pl.profile = profile;
-            pl.level = level;
-            MediaFormat defaultFormat = new MediaFormat();
-            defaultFormat.setString(MediaFormat.KEY_MIME, mime);
-
-            CodecCapabilities ret = new CodecCapabilities(
-                new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
-                defaultFormat, new MediaFormat() /* info */);
-            if (ret.mError != 0) {
-                return null;
-            }
-            return ret;
-        }
-
-        /* package private */ CodecCapabilities(
-                CodecProfileLevel[] profLevs, int[] colFmts,
-                boolean encoder,
-                Map<String, Object>defaultFormatMap,
-                Map<String, Object>capabilitiesMap) {
-            this(profLevs, colFmts, encoder,
-                    new MediaFormat(defaultFormatMap),
-                    new MediaFormat(capabilitiesMap));
-        }
-
-        private MediaFormat mCapabilitiesInfo;
-
-        /* package private */ CodecCapabilities(
-                CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
-                MediaFormat defaultFormat, MediaFormat info) {
-            final Map<String, Object> map = info.getMap();
-            colorFormats = colFmts;
-            mFlagsVerified = 0; // TODO: remove as it is unused
-            mDefaultFormat = defaultFormat;
-            mCapabilitiesInfo = info;
-            mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
-
-            /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
-               supported profiles. Determine the level for them using the info they provide. */
-            if (profLevs.length == 0 && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                CodecProfileLevel profLev = new CodecProfileLevel();
-                profLev.profile = CodecProfileLevel.VP9Profile0;
-                profLev.level = VideoCapabilities.equivalentVP9Level(info);
-                profLevs = new CodecProfileLevel[] { profLev };
-            }
-            profileLevels = profLevs;
-
-            if (mMime.toLowerCase().startsWith("audio/")) {
-                mAudioCaps = AudioCapabilities.create(info, this);
-                mAudioCaps.getDefaultFormat(mDefaultFormat);
-            } else if (mMime.toLowerCase().startsWith("video/")
-                    || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
-                mVideoCaps = VideoCapabilities.create(info, this);
-            }
-            if (encoder) {
-                mEncoderCaps = EncoderCapabilities.create(info, this);
-                mEncoderCaps.getDefaultFormat(mDefaultFormat);
-            }
-
-            final Map<String, Object> global = MediaCodecList.getGlobalSettings();
-            mMaxSupportedInstances = Utils.parseIntSafely(
-                    global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
-
-            int maxInstances = Utils.parseIntSafely(
-                    map.get("max-concurrent-instances"), mMaxSupportedInstances);
-            mMaxSupportedInstances =
-                    Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
-
-            for (Feature feat: getValidFeatures()) {
-                String key = MediaFormat.KEY_FEATURE_ + feat.mName;
-                Integer yesNo = (Integer)map.get(key);
-                if (yesNo == null) {
-                    continue;
-                }
-                if (yesNo > 0) {
-                    mFlagsRequired |= feat.mValue;
-                }
-                mFlagsSupported |= feat.mValue;
-                if (!feat.mInternal) {
-                    mDefaultFormat.setInteger(key, 1);
-                }
-                // TODO restrict features by mFlagsVerified once all codecs reliably verify them
-            }
+            return mImpl.getVideoCapabilities();
         }
     }
 
@@ -1410,20 +1650,516 @@
      */
     public static final class AudioCapabilities {
         private static final String TAG = "AudioCapabilities";
-        private CodecCapabilities mParent;
-        private Range<Integer> mBitrateRange;
 
-        private int[] mSampleRates;
-        private Range<Integer>[] mSampleRateRanges;
-        private Range<Integer>[] mInputChannelRanges;
+        /* package private */ interface AudioCapsIntf {
+            public Range<Integer> getBitrateRange();
 
-        private static final int MAX_INPUT_CHANNEL_COUNT = 30;
+            public int[] getSupportedSampleRates();
+
+            public Range<Integer>[] getSupportedSampleRateRanges();
+
+            public int getMaxInputChannelCount();
+
+            public int getMinInputChannelCount();
+
+            public Range<Integer>[] getInputChannelCountRanges();
+
+            public boolean isSampleRateSupported(int sampleRate);
+
+            public void getDefaultFormat(MediaFormat format);
+
+            public boolean supportsFormat(MediaFormat format);
+        }
+
+        /* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf {
+            private CodecCapabilities.CodecCapsLegacyImpl mParent;
+            private Range<Integer> mBitrateRange;
+
+            private int[] mSampleRates;
+            private Range<Integer>[] mSampleRateRanges;
+            private Range<Integer>[] mInputChannelRanges;
+
+            private static final int MAX_INPUT_CHANNEL_COUNT = 30;
+
+            public Range<Integer> getBitrateRange() {
+                return mBitrateRange;
+            }
+
+            public int[] getSupportedSampleRates() {
+                return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
+                        : null;
+            }
+
+            public Range<Integer>[] getSupportedSampleRateRanges() {
+                return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+            }
+
+            public int getMaxInputChannelCount() {
+                int overall_max = 0;
+                for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                    int lmax = mInputChannelRanges[i].getUpper();
+                    if (lmax > overall_max) {
+                        overall_max = lmax;
+                    }
+                }
+                return overall_max;
+            }
+
+            public int getMinInputChannelCount() {
+                int overall_min = MAX_INPUT_CHANNEL_COUNT;
+                for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                    int lmin = mInputChannelRanges[i].getLower();
+                    if (lmin < overall_min) {
+                        overall_min = lmin;
+                    }
+                }
+                return overall_min;
+            }
+
+            public Range<Integer>[] getInputChannelCountRanges() {
+                return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
+            }
+
+            /* no public constructor */
+            private AudioCapsLegacyImpl() { }
+
+            public static AudioCapsLegacyImpl create(
+                    MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
+                }
+
+                AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl();
+                caps.init(info, parent);
+                return caps;
+            }
+
+            private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+                mParent = parent;
+                initWithPlatformLimits();
+                applyLevelLimits();
+                parseFromInfo(info);
+            }
+
+            private void initWithPlatformLimits() {
+                mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+                mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
+                // mBitrateRange = Range.create(1, 320000);
+                final int minSampleRate = SystemProperties.
+                    getInt("ro.mediacodec.min_sample_rate", 7350);
+                final int maxSampleRate = SystemProperties.
+                    getInt("ro.mediacodec.max_sample_rate", 192000);
+                mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
+                mSampleRates = null;
+            }
+
+            private boolean supports(Integer sampleRate, Integer inputChannels) {
+                // channels and sample rates are checked orthogonally
+                if (inputChannels != null) {
+                    int ix = Utils.binarySearchDistinctRanges(
+                            mInputChannelRanges, inputChannels);
+                    if (ix < 0) {
+                        return false;
+                    }
+                }
+                if (sampleRate != null) {
+                    int ix = Utils.binarySearchDistinctRanges(
+                            mSampleRateRanges, sampleRate);
+                    if (ix < 0) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            public boolean isSampleRateSupported(int sampleRate) {
+                return supports(sampleRate, null);
+            }
+
+            /** modifies rates */
+            private void limitSampleRates(int[] rates) {
+                Arrays.sort(rates);
+                ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
+                for (int rate: rates) {
+                    if (supports(rate, null /* channels */)) {
+                        ranges.add(Range.create(rate, rate));
+                    }
+                }
+                mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
+                createDiscreteSampleRates();
+            }
+
+            private void createDiscreteSampleRates() {
+                mSampleRates = new int[mSampleRateRanges.length];
+                for (int i = 0; i < mSampleRateRanges.length; i++) {
+                    mSampleRates[i] = mSampleRateRanges[i].getLower();
+                }
+            }
+
+            /** modifies rateRanges */
+            private void limitSampleRates(Range<Integer>[] rateRanges) {
+                sortDistinctRanges(rateRanges);
+                mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
+
+                // check if all values are discrete
+                for (Range<Integer> range: mSampleRateRanges) {
+                    if (!range.getLower().equals(range.getUpper())) {
+                        mSampleRates = null;
+                        return;
+                    }
+                }
+                createDiscreteSampleRates();
+            }
+
+            private void applyLevelLimits() {
+                int[] sampleRates = null;
+                Range<Integer> sampleRateRange = null, bitRates = null;
+                int maxChannels = MAX_INPUT_CHANNEL_COUNT;
+                CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
+                String mime = mParent.getMimeType();
+
+                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
+                    sampleRates = new int[] {
+                            8000, 11025, 12000,
+                            16000, 22050, 24000,
+                            32000, 44100, 48000 };
+                    bitRates = Range.create(8000, 320000);
+                    maxChannels = 2;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+                    sampleRates = new int[] { 8000 };
+                    bitRates = Range.create(4750, 12200);
+                    maxChannels = 1;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+                    sampleRates = new int[] { 16000 };
+                    bitRates = Range.create(6600, 23850);
+                    maxChannels = 1;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+                    sampleRates = new int[] {
+                            7350, 8000,
+                            11025, 12000, 16000,
+                            22050, 24000, 32000,
+                            44100, 48000, 64000,
+                            88200, 96000 };
+                    bitRates = Range.create(8000, 510000);
+                    maxChannels = 48;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
+                    bitRates = Range.create(32000, 500000);
+                    sampleRateRange = Range.create(8000, 192000);
+                    maxChannels = 255;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
+                    bitRates = Range.create(6000, 510000);
+                    sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
+                    maxChannels = 255;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
+                    sampleRateRange = Range.create(1, 192000);
+                    bitRates = Range.create(1, 10000000);
+                    maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                    sampleRateRange = Range.create(1, 655350);
+                    // lossless codec, so bitrate is ignored
+                    maxChannels = 255;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
+                    sampleRates = new int[] { 8000 };
+                    bitRates = Range.create(64000, 64000);
+                    // platform allows multiple channels for this format
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+                    sampleRates = new int[] { 8000 };
+                    bitRates = Range.create(13000, 13000);
+                    maxChannels = 1;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
+                    maxChannels = 6;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
+                    maxChannels = 16;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
+                    sampleRates = new int[] { 48000 };
+                    bitRates = Range.create(32000, 6144000);
+                    maxChannels = 16;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
+                    sampleRates = new int[] { 44100, 48000, 96000, 192000 };
+                    bitRates = Range.create(16000, 2688000);
+                    maxChannels = 24;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
+                    sampleRates = new int[] { 44100, 48000 };
+                    bitRates = Range.create(96000, 1524000);
+                    maxChannels = 6;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.DTS_HDProfileLBR:
+                                sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
+                                bitRates = Range.create(32000, 768000);
+                                break;
+                            case CodecProfileLevel.DTS_HDProfileHRA:
+                            case CodecProfileLevel.DTS_HDProfileMA:
+                                sampleRates
+                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                                bitRates = Range.create(96000, 24500000);
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                mParent.mError |= ERROR_UNRECOGNIZED;
+                                sampleRates
+                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                                bitRates = Range.create(96000, 24500000);
+                        }
+                    }
+                    maxChannels = 8;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.DTS_UHDProfileP2:
+                                sampleRates = new int[]{ 48000 };
+                                bitRates = Range.create(96000, 768000);
+                                maxChannels = 10;
+                                break;
+                            case CodecProfileLevel.DTS_UHDProfileP1:
+                                sampleRates
+                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                                bitRates = Range.create(96000, 24500000);
+                                maxChannels = 32;
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                mParent.mError |= ERROR_UNRECOGNIZED;
+                                sampleRates
+                                        = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
+                                bitRates = Range.create(96000, 24500000);
+                                maxChannels = 32;
+                        }
+                    }
+                } else {
+                    Log.w(TAG, "Unsupported mime " + mime);
+                    mParent.mError |= ERROR_UNSUPPORTED;
+                }
+
+                // restrict ranges
+                if (sampleRates != null) {
+                    limitSampleRates(sampleRates);
+                } else if (sampleRateRange != null) {
+                    limitSampleRates(new Range[] { sampleRateRange });
+                }
+
+                Range<Integer> channelRange = Range.create(1, maxChannels);
+
+                applyLimits(new Range[] { channelRange }, bitRates);
+            }
+
+            private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
+
+                // clamp & make a local copy
+                Range<Integer>[] myInputChannels = new Range[inputChannels.length];
+                for (int i = 0; i < inputChannels.length; i++) {
+                    int lower = inputChannels[i].clamp(1);
+                    int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
+                    myInputChannels[i] = Range.create(lower, upper);
+                }
+
+                // sort, intersect with existing, & save channel list
+                sortDistinctRanges(myInputChannels);
+                Range<Integer>[] joinedChannelList =
+                                intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
+                mInputChannelRanges = joinedChannelList;
+
+                if (bitRates != null) {
+                    mBitrateRange = mBitrateRange.intersect(bitRates);
+                }
+            }
+
+            private void parseFromInfo(MediaFormat info) {
+                int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+                Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
+                Range<Integer> bitRates = POSITIVE_INTEGERS;
+
+                if (info.containsKey("sample-rate-ranges")) {
+                    String[] rateStrings = info.getString("sample-rate-ranges").split(",");
+                    Range<Integer>[] rateRanges = new Range[rateStrings.length];
+                    for (int i = 0; i < rateStrings.length; i++) {
+                        rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
+                    }
+                    limitSampleRates(rateRanges);
+                }
+
+                // we will prefer channel-ranges over max-channel-count
+                if (info.containsKey("channel-ranges")) {
+                    String[] channelStrings = info.getString("channel-ranges").split(",");
+                    Range<Integer>[] channelRanges = new Range[channelStrings.length];
+                    for (int i = 0; i < channelStrings.length; i++) {
+                        channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
+                    }
+                    channels = channelRanges;
+                } else if (info.containsKey("channel-range")) {
+                    Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
+                                                                null);
+                    channels = new Range[] { oneRange };
+                } else if (info.containsKey("max-channel-count")) {
+                    maxInputChannels = Utils.parseIntSafely(
+                            info.getString("max-channel-count"), maxInputChannels);
+                    if (maxInputChannels == 0) {
+                        channels = new Range[] {Range.create(0, 0)};
+                    } else {
+                        channels = new Range[] {Range.create(1, maxInputChannels)};
+                    }
+                } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+                    maxInputChannels = 0;
+                    channels = new Range[] {Range.create(0, 0)};
+                }
+
+                if (info.containsKey("bitrate-range")) {
+                    bitRates = bitRates.intersect(
+                            Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
+                }
+
+                applyLimits(channels, bitRates);
+            }
+
+            /** @hide */
+            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());
+                }
+                if (getMaxInputChannelCount() == 1) {
+                    // mono-only format
+                    format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+                }
+                if (mSampleRates != null && mSampleRates.length == 1) {
+                    format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
+                }
+            }
+
+            /* package private */
+            // must not contain KEY_PROFILE
+            static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
+                    // We don't set level-specific limits for audio codecs today. Key candidates
+                    // would be sample rate, bit rate or channel count.
+                    // MediaFormat.KEY_SAMPLE_RATE,
+                    // MediaFormat.KEY_CHANNEL_COUNT,
+                    // MediaFormat.KEY_BIT_RATE,
+                    MediaFormat.KEY_MIME);
+
+            /** @hide */
+            public boolean supportsFormat(MediaFormat format) {
+                Map<String, Object> map = format.getMap();
+                Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
+                Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
+
+                if (!supports(sampleRate, channels)) {
+                    return false;
+                }
+
+                if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
+                    return false;
+                }
+
+                // nothing to do for:
+                // KEY_CHANNEL_MASK: codecs don't get this
+                // KEY_IS_ADTS:      required feature for all AAC decoders
+                return true;
+            }
+        }
+
+        /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf {
+            private long mNativeContext; // accessed by native methods
+
+            private Range<Integer> mBitrateRange;
+            private int[] mSampleRates;
+            private Range<Integer>[] mSampleRateRanges;
+            private Range<Integer>[] mInputChannelRanges;
+
+            /**
+             * Constructor used by JNI.
+             *
+             * The Java AudioCapabilities object keeps these subobjects to avoid recontruction.
+             */
+            /* package private */ AudioCapsNativeImpl(Range<Integer> bitrateRange,
+                    int[] sampleRates, Range<Integer>[] sampleRateRanges,
+                    Range<Integer>[] inputChannelRanges) {
+                mBitrateRange = bitrateRange;
+                mSampleRates = sampleRates;
+                mSampleRateRanges = sampleRateRanges;
+                mInputChannelRanges = inputChannelRanges;
+            }
+
+            /* no public constructor */
+            private AudioCapsNativeImpl() { }
+
+            public Range<Integer> getBitrateRange() {
+                return mBitrateRange;
+            }
+
+            public int[] getSupportedSampleRates() {
+                return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length)
+                        : null;
+            }
+
+            public Range<Integer>[] getSupportedSampleRateRanges() {
+                return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+            }
+
+            public Range<Integer>[] getInputChannelCountRanges() {
+                return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
+            }
+
+            public int getMaxInputChannelCount() {
+                return native_getMaxInputChannelCount();
+            }
+
+            public int getMinInputChannelCount() {
+                return native_getMinInputChannelCount();
+            }
+
+            public boolean isSampleRateSupported(int sampleRate) {
+                return native_isSampleRateSupported(sampleRate);
+            }
+
+            // This API is for internal Java implementation only. Should not be called.
+            public void getDefaultFormat(MediaFormat format) {
+                throw new UnsupportedOperationException(
+                    "Java Implementation should not call native implemenatation");
+            }
+
+            // This API is for internal Java implementation only. Should not be called.
+            public boolean supportsFormat(MediaFormat format) {
+                throw new UnsupportedOperationException(
+                    "Java Implementation should not call native implemenatation");
+            }
+
+            private native int native_getMaxInputChannelCount();
+            private native int native_getMinInputChannelCount();
+            private native boolean native_isSampleRateSupported(int sampleRate);
+            private static native void native_init();
+
+            static {
+                System.loadLibrary("media_jni");
+                native_init();
+            }
+        }
+
+        private AudioCapsIntf mImpl;
+
+        /** @hide */
+        public static AudioCapabilities create(
+                MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+            AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent);
+            AudioCapabilities caps = new AudioCapabilities(impl);
+            return caps;
+        }
+
+        /* package private */ AudioCapabilities(AudioCapsIntf impl) {
+            mImpl = impl;
+        }
+
+        /* no public constructor */
+        private AudioCapabilities() { }
 
         /**
          * Returns the range of supported bitrates in bits/second.
          */
         public Range<Integer> getBitrateRange() {
-            return mBitrateRange;
+            return mImpl.getBitrateRange();
         }
 
         /**
@@ -1432,7 +2168,7 @@
          * {@code null}.  The array is sorted in ascending order.
          */
         public int[] getSupportedSampleRates() {
-            return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) : null;
+            return mImpl.getSupportedSampleRates();
         }
 
         /**
@@ -1441,7 +2177,21 @@
          * distinct.
          */
         public Range<Integer>[] getSupportedSampleRateRanges() {
-            return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+            return mImpl.getSupportedSampleRateRanges();
+        }
+
+        /*
+         * Returns an array of ranges representing the number of input channels supported.
+         * The codec supports any number of input channels within this range.
+         *
+         * This supersedes the {@link #getMaxInputChannelCount} method.
+         *
+         * For many codecs, this will be a single range [1..N], for some N.
+         */
+        @SuppressLint("ArrayReturn")
+        @NonNull
+        public Range<Integer>[] getInputChannelCountRanges() {
+            return mImpl.getInputChannelCountRanges();
         }
 
         /**
@@ -1461,14 +2211,7 @@
          */
         @IntRange(from = 1, to = 255)
         public int getMaxInputChannelCount() {
-            int overall_max = 0;
-            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
-                int lmax = mInputChannelRanges[i].getUpper();
-                if (lmax > overall_max) {
-                    overall_max = lmax;
-                }
-            }
-            return overall_max;
+            return mImpl.getMaxInputChannelCount();
         }
 
         /**
@@ -1480,364 +2223,24 @@
          */
         @IntRange(from = 1, to = 255)
         public int getMinInputChannelCount() {
-            int overall_min = MAX_INPUT_CHANNEL_COUNT;
-            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
-                int lmin = mInputChannelRanges[i].getLower();
-                if (lmin < overall_min) {
-                    overall_min = lmin;
-                }
-            }
-            return overall_min;
-        }
-
-        /*
-         * Returns an array of ranges representing the number of input channels supported.
-         * The codec supports any number of input channels within this range.
-         *
-         * This supersedes the {@link #getMaxInputChannelCount} method.
-         *
-         * For many codecs, this will be a single range [1..N], for some N.
-         */
-        @SuppressLint("ArrayReturn")
-        @NonNull
-        public Range<Integer>[] getInputChannelCountRanges() {
-            return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
-        }
-
-        /* no public constructor */
-        private AudioCapabilities() { }
-
-        /** @hide */
-        public static AudioCapabilities create(
-                MediaFormat info, CodecCapabilities parent) {
-            AudioCapabilities caps = new AudioCapabilities();
-            caps.init(info, parent);
-            return caps;
-        }
-
-        private void init(MediaFormat info, CodecCapabilities parent) {
-            mParent = parent;
-            initWithPlatformLimits();
-            applyLevelLimits();
-            parseFromInfo(info);
-        }
-
-        private void initWithPlatformLimits() {
-            mBitrateRange = Range.create(0, Integer.MAX_VALUE);
-            mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
-            // mBitrateRange = Range.create(1, 320000);
-            final int minSampleRate = SystemProperties.
-                getInt("ro.mediacodec.min_sample_rate", 7350);
-            final int maxSampleRate = SystemProperties.
-                getInt("ro.mediacodec.max_sample_rate", 192000);
-            mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
-            mSampleRates = null;
-        }
-
-        private boolean supports(Integer sampleRate, Integer inputChannels) {
-            // channels and sample rates are checked orthogonally
-            if (inputChannels != null) {
-                int ix = Utils.binarySearchDistinctRanges(
-                        mInputChannelRanges, inputChannels);
-                if (ix < 0) {
-                    return false;
-                }
-            }
-            if (sampleRate != null) {
-                int ix = Utils.binarySearchDistinctRanges(
-                        mSampleRateRanges, sampleRate);
-                if (ix < 0) {
-                    return false;
-                }
-            }
-            return true;
+            return mImpl.getMinInputChannelCount();
         }
 
         /**
          * Query whether the sample rate is supported by the codec.
          */
         public boolean isSampleRateSupported(int sampleRate) {
-            return supports(sampleRate, null);
-        }
-
-        /** modifies rates */
-        private void limitSampleRates(int[] rates) {
-            Arrays.sort(rates);
-            ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
-            for (int rate: rates) {
-                if (supports(rate, null /* channels */)) {
-                    ranges.add(Range.create(rate, rate));
-                }
-            }
-            mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
-            createDiscreteSampleRates();
-        }
-
-        private void createDiscreteSampleRates() {
-            mSampleRates = new int[mSampleRateRanges.length];
-            for (int i = 0; i < mSampleRateRanges.length; i++) {
-                mSampleRates[i] = mSampleRateRanges[i].getLower();
-            }
-        }
-
-        /** modifies rateRanges */
-        private void limitSampleRates(Range<Integer>[] rateRanges) {
-            sortDistinctRanges(rateRanges);
-            mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
-
-            // check if all values are discrete
-            for (Range<Integer> range: mSampleRateRanges) {
-                if (!range.getLower().equals(range.getUpper())) {
-                    mSampleRates = null;
-                    return;
-                }
-            }
-            createDiscreteSampleRates();
-        }
-
-        private void applyLevelLimits() {
-            int[] sampleRates = null;
-            Range<Integer> sampleRateRange = null, bitRates = null;
-            int maxChannels = MAX_INPUT_CHANNEL_COUNT;
-            CodecProfileLevel[] profileLevels = mParent.profileLevels;
-            String mime = mParent.getMimeType();
-
-            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
-                sampleRates = new int[] {
-                        8000, 11025, 12000,
-                        16000, 22050, 24000,
-                        32000, 44100, 48000 };
-                bitRates = Range.create(8000, 320000);
-                maxChannels = 2;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-                sampleRates = new int[] { 8000 };
-                bitRates = Range.create(4750, 12200);
-                maxChannels = 1;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
-                sampleRates = new int[] { 16000 };
-                bitRates = Range.create(6600, 23850);
-                maxChannels = 1;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-                sampleRates = new int[] {
-                        7350, 8000,
-                        11025, 12000, 16000,
-                        22050, 24000, 32000,
-                        44100, 48000, 64000,
-                        88200, 96000 };
-                bitRates = Range.create(8000, 510000);
-                maxChannels = 48;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
-                bitRates = Range.create(32000, 500000);
-                sampleRateRange = Range.create(8000, 192000);
-                maxChannels = 255;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
-                bitRates = Range.create(6000, 510000);
-                sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
-                maxChannels = 255;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
-                sampleRateRange = Range.create(1, 192000);
-                bitRates = Range.create(1, 10000000);
-                maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
-                sampleRateRange = Range.create(1, 655350);
-                // lossless codec, so bitrate is ignored
-                maxChannels = 255;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
-                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
-                sampleRates = new int[] { 8000 };
-                bitRates = Range.create(64000, 64000);
-                // platform allows multiple channels for this format
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
-                sampleRates = new int[] { 8000 };
-                bitRates = Range.create(13000, 13000);
-                maxChannels = 1;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
-                maxChannels = 6;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
-                maxChannels = 16;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
-                sampleRates = new int[] { 48000 };
-                bitRates = Range.create(32000, 6144000);
-                maxChannels = 16;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
-                sampleRates = new int[] { 44100, 48000, 96000, 192000 };
-                bitRates = Range.create(16000, 2688000);
-                maxChannels = 24;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) {
-                sampleRates = new int[] { 44100, 48000 };
-                bitRates = Range.create(96000, 1524000);
-                maxChannels = 6;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) {
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.DTS_HDProfileLBR:
-                            sampleRates = new int[]{ 22050, 24000, 44100, 48000 };
-                            bitRates = Range.create(32000, 768000);
-                            break;
-                        case CodecProfileLevel.DTS_HDProfileHRA:
-                        case CodecProfileLevel.DTS_HDProfileMA:
-                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                            bitRates = Range.create(96000, 24500000);
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            mParent.mError |= ERROR_UNRECOGNIZED;
-                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                            bitRates = Range.create(96000, 24500000);
-                    }
-                }
-                maxChannels = 8;
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) {
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.DTS_UHDProfileP2:
-                            sampleRates = new int[]{ 48000 };
-                            bitRates = Range.create(96000, 768000);
-                            maxChannels = 10;
-                            break;
-                        case CodecProfileLevel.DTS_UHDProfileP1:
-                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                            bitRates = Range.create(96000, 24500000);
-                            maxChannels = 32;
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            mParent.mError |= ERROR_UNRECOGNIZED;
-                            sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 };
-                            bitRates = Range.create(96000, 24500000);
-                            maxChannels = 32;
-                    }
-                }
-            } else {
-                Log.w(TAG, "Unsupported mime " + mime);
-                mParent.mError |= ERROR_UNSUPPORTED;
-            }
-
-            // restrict ranges
-            if (sampleRates != null) {
-                limitSampleRates(sampleRates);
-            } else if (sampleRateRange != null) {
-                limitSampleRates(new Range[] { sampleRateRange });
-            }
-
-            Range<Integer> channelRange = Range.create(1, maxChannels);
-
-            applyLimits(new Range[] { channelRange }, bitRates);
-        }
-
-        private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
-
-            // clamp & make a local copy
-            Range<Integer>[] myInputChannels = new Range[inputChannels.length];
-            for (int i = 0; i < inputChannels.length; i++) {
-                int lower = inputChannels[i].clamp(1);
-                int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
-                myInputChannels[i] = Range.create(lower, upper);
-            }
-
-            // sort, intersect with existing, & save channel list
-            sortDistinctRanges(myInputChannels);
-            Range<Integer>[] joinedChannelList =
-                            intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
-            mInputChannelRanges = joinedChannelList;
-
-            if (bitRates != null) {
-                mBitrateRange = mBitrateRange.intersect(bitRates);
-            }
-        }
-
-        private void parseFromInfo(MediaFormat info) {
-            int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
-            Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
-            Range<Integer> bitRates = POSITIVE_INTEGERS;
-
-            if (info.containsKey("sample-rate-ranges")) {
-                String[] rateStrings = info.getString("sample-rate-ranges").split(",");
-                Range<Integer>[] rateRanges = new Range[rateStrings.length];
-                for (int i = 0; i < rateStrings.length; i++) {
-                    rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
-                }
-                limitSampleRates(rateRanges);
-            }
-
-            // we will prefer channel-ranges over max-channel-count
-            if (info.containsKey("channel-ranges")) {
-                String[] channelStrings = info.getString("channel-ranges").split(",");
-                Range<Integer>[] channelRanges = new Range[channelStrings.length];
-                for (int i = 0; i < channelStrings.length; i++) {
-                    channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
-                }
-                channels = channelRanges;
-            } else if (info.containsKey("channel-range")) {
-                Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
-                                                              null);
-                channels = new Range[] { oneRange };
-            } else if (info.containsKey("max-channel-count")) {
-                maxInputChannels = Utils.parseIntSafely(
-                        info.getString("max-channel-count"), maxInputChannels);
-                if (maxInputChannels == 0) {
-                    channels = new Range[] {Range.create(0, 0)};
-                } else {
-                    channels = new Range[] {Range.create(1, maxInputChannels)};
-                }
-            } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
-                maxInputChannels = 0;
-                channels = new Range[] {Range.create(0, 0)};
-            }
-
-            if (info.containsKey("bitrate-range")) {
-                bitRates = bitRates.intersect(
-                        Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
-            }
-
-            applyLimits(channels, bitRates);
+            return mImpl.isSampleRateSupported(sampleRate);
         }
 
         /** @hide */
         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());
-            }
-            if (getMaxInputChannelCount() == 1) {
-                // mono-only format
-                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-            }
-            if (mSampleRates != null && mSampleRates.length == 1) {
-                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
-            }
+            mImpl.getDefaultFormat(format);
         }
 
-        /* package private */
-        // must not contain KEY_PROFILE
-        static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
-                // We don't set level-specific limits for audio codecs today. Key candidates would
-                // be sample rate, bit rate or channel count.
-                // MediaFormat.KEY_SAMPLE_RATE,
-                // MediaFormat.KEY_CHANNEL_COUNT,
-                // MediaFormat.KEY_BIT_RATE,
-                MediaFormat.KEY_MIME);
-
         /** @hide */
         public boolean supportsFormat(MediaFormat format) {
-            Map<String, Object> map = format.getMap();
-            Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
-            Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
-
-            if (!supports(sampleRate, channels)) {
-                return false;
-            }
-
-            if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
-                return false;
-            }
-
-            // nothing to do for:
-            // KEY_CHANNEL_MASK: codecs don't get this
-            // KEY_IS_ADTS:      required feature for all AAC decoders
-            return true;
+            return mImpl.supportsFormat(format);
         }
     }
 
@@ -1897,304 +2300,6 @@
      */
     public static final class VideoCapabilities {
         private static final String TAG = "VideoCapabilities";
-        private CodecCapabilities mParent;
-        private Range<Integer> mBitrateRange;
-
-        private Range<Integer> mHeightRange;
-        private Range<Integer> mWidthRange;
-        private Range<Integer> mBlockCountRange;
-        private Range<Integer> mHorizontalBlockRange;
-        private Range<Integer> mVerticalBlockRange;
-        private Range<Rational> mAspectRatioRange;
-        private Range<Rational> mBlockAspectRatioRange;
-        private Range<Long> mBlocksPerSecondRange;
-        private Map<Size, Range<Long>> mMeasuredFrameRates;
-        private List<PerformancePoint> mPerformancePoints;
-        private Range<Integer> mFrameRateRange;
-
-        private int mBlockWidth;
-        private int mBlockHeight;
-        private int mWidthAlignment;
-        private int mHeightAlignment;
-        private int mSmallerDimensionUpperLimit;
-
-        private boolean mAllowMbOverride; // allow XML to override calculated limits
-
-        /**
-         * Returns the range of supported bitrates in bits/second.
-         */
-        public Range<Integer> getBitrateRange() {
-            return mBitrateRange;
-        }
-
-        /**
-         * Returns the range of supported video widths.
-         * <p class=note>
-         * 32-bit processes will not support resolutions larger than 4096x4096 due to
-         * the limited address space.
-         */
-        public Range<Integer> getSupportedWidths() {
-            return mWidthRange;
-        }
-
-        /**
-         * Returns the range of supported video heights.
-         * <p class=note>
-         * 32-bit processes will not support resolutions larger than 4096x4096 due to
-         * the limited address space.
-         */
-        public Range<Integer> getSupportedHeights() {
-            return mHeightRange;
-        }
-
-        /**
-         * Returns the alignment requirement for video width (in pixels).
-         *
-         * This is a power-of-2 value that video width must be a
-         * multiple of.
-         */
-        public int getWidthAlignment() {
-            return mWidthAlignment;
-        }
-
-        /**
-         * Returns the alignment requirement for video height (in pixels).
-         *
-         * This is a power-of-2 value that video height must be a
-         * multiple of.
-         */
-        public int getHeightAlignment() {
-            return mHeightAlignment;
-        }
-
-        /**
-         * Return the upper limit on the smaller dimension of width or height.
-         * <p></p>
-         * Some codecs have a limit on the smaller dimension, whether it be
-         * the width or the height.  E.g. a codec may only be able to handle
-         * up to 1920x1080 both in landscape and portrait mode (1080x1920).
-         * In this case the maximum width and height are both 1920, but the
-         * smaller dimension limit will be 1080. For other codecs, this is
-         * {@code Math.min(getSupportedWidths().getUpper(),
-         * getSupportedHeights().getUpper())}.
-         *
-         * @hide
-         */
-        public int getSmallerDimensionUpperLimit() {
-            return mSmallerDimensionUpperLimit;
-        }
-
-        /**
-         * Returns the range of supported frame rates.
-         * <p>
-         * This is not a performance indicator.  Rather, it expresses the
-         * limits specified in the coding standard, based on the complexities
-         * of encoding material for later playback at a certain frame rate,
-         * or the decoding of such material in non-realtime.
-         */
-        public Range<Integer> getSupportedFrameRates() {
-            return mFrameRateRange;
-        }
-
-        /**
-         * Returns the range of supported video widths for a video height.
-         * @param height the height of the video
-         */
-        public Range<Integer> getSupportedWidthsFor(int height) {
-            try {
-                Range<Integer> range = mWidthRange;
-                if (!mHeightRange.contains(height)
-                        || (height % mHeightAlignment) != 0) {
-                    throw new IllegalArgumentException("unsupported height");
-                }
-                final int heightInBlocks = Utils.divUp(height, mBlockHeight);
-
-                // constrain by block count and by block aspect ratio
-                final int minWidthInBlocks = Math.max(
-                        Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
-                        (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
-                                * heightInBlocks));
-                final int maxWidthInBlocks = Math.min(
-                        mBlockCountRange.getUpper() / heightInBlocks,
-                        (int)(mBlockAspectRatioRange.getUpper().doubleValue()
-                                * heightInBlocks));
-                range = range.intersect(
-                        (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
-                        maxWidthInBlocks * mBlockWidth);
-
-                // constrain by smaller dimension limit
-                if (height > mSmallerDimensionUpperLimit) {
-                    range = range.intersect(1, mSmallerDimensionUpperLimit);
-                }
-
-                // constrain by aspect ratio
-                range = range.intersect(
-                        (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
-                                * height),
-                        (int)(mAspectRatioRange.getUpper().doubleValue() * height));
-                return range;
-            } catch (IllegalArgumentException e) {
-                // height is not supported because there are no suitable widths
-                Log.v(TAG, "could not get supported widths for " + height);
-                throw new IllegalArgumentException("unsupported height");
-            }
-        }
-
-        /**
-         * Returns the range of supported video heights for a video width
-         * @param width the width of the video
-         */
-        public Range<Integer> getSupportedHeightsFor(int width) {
-            try {
-                Range<Integer> range = mHeightRange;
-                if (!mWidthRange.contains(width)
-                        || (width % mWidthAlignment) != 0) {
-                    throw new IllegalArgumentException("unsupported width");
-                }
-                final int widthInBlocks = Utils.divUp(width, mBlockWidth);
-
-                // constrain by block count and by block aspect ratio
-                final int minHeightInBlocks = Math.max(
-                        Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
-                        (int)Math.ceil(widthInBlocks /
-                                mBlockAspectRatioRange.getUpper().doubleValue()));
-                final int maxHeightInBlocks = Math.min(
-                        mBlockCountRange.getUpper() / widthInBlocks,
-                        (int)(widthInBlocks /
-                                mBlockAspectRatioRange.getLower().doubleValue()));
-                range = range.intersect(
-                        (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
-                        maxHeightInBlocks * mBlockHeight);
-
-                // constrain by smaller dimension limit
-                if (width > mSmallerDimensionUpperLimit) {
-                    range = range.intersect(1, mSmallerDimensionUpperLimit);
-                }
-
-                // constrain by aspect ratio
-                range = range.intersect(
-                        (int)Math.ceil(width /
-                                mAspectRatioRange.getUpper().doubleValue()),
-                        (int)(width / mAspectRatioRange.getLower().doubleValue()));
-                return range;
-            } catch (IllegalArgumentException e) {
-                // width is not supported because there are no suitable heights
-                Log.v(TAG, "could not get supported heights for " + width);
-                throw new IllegalArgumentException("unsupported width");
-            }
-        }
-
-        /**
-         * Returns the range of supported video frame rates for a video size.
-         * <p>
-         * This is not a performance indicator.  Rather, it expresses the limits specified in
-         * the coding standard, based on the complexities of encoding material of a given
-         * size for later playback at a certain frame rate, or the decoding of such material
-         * in non-realtime.
-
-         * @param width the width of the video
-         * @param height the height of the video
-         */
-        public Range<Double> getSupportedFrameRatesFor(int width, int height) {
-            Range<Integer> range = mHeightRange;
-            if (!supports(width, height, null)) {
-                throw new IllegalArgumentException("unsupported size");
-            }
-            final int blockCount =
-                Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
-
-            return Range.create(
-                    Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
-                            (double) mFrameRateRange.getLower()),
-                    Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
-                            (double) mFrameRateRange.getUpper()));
-        }
-
-        private int getBlockCount(int width, int height) {
-            return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
-        }
-
-        @NonNull
-        private Size findClosestSize(int width, int height) {
-            int targetBlockCount = getBlockCount(width, height);
-            Size closestSize = null;
-            int minDiff = Integer.MAX_VALUE;
-            for (Size size : mMeasuredFrameRates.keySet()) {
-                int diff = Math.abs(targetBlockCount -
-                        getBlockCount(size.getWidth(), size.getHeight()));
-                if (diff < minDiff) {
-                    minDiff = diff;
-                    closestSize = size;
-                }
-            }
-            return closestSize;
-        }
-
-        private Range<Double> estimateFrameRatesFor(int width, int height) {
-            Size size = findClosestSize(width, height);
-            Range<Long> range = mMeasuredFrameRates.get(size);
-            Double ratio = getBlockCount(size.getWidth(), size.getHeight())
-                    / (double)Math.max(getBlockCount(width, height), 1);
-            return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
-        }
-
-        /**
-         * Returns the range of achievable video frame rates for a video size.
-         * May return {@code null}, if the codec did not publish any measurement
-         * data.
-         * <p>
-         * This is a performance estimate provided by the device manufacturer based on statistical
-         * sampling of full-speed decoding and encoding measurements in various configurations
-         * of common video sizes supported by the codec. As such it should only be used to
-         * compare individual codecs on the device. The value is not suitable for comparing
-         * different devices or even different android releases for the same device.
-         * <p>
-         * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
-         * corresponds to the fastest frame rates achieved in the tested configurations. As
-         * such, it should not be used to gauge guaranteed or even average codec performance
-         * on the device.
-         * <p>
-         * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
-         * corresponds closer to sustained performance <em>in tested configurations</em>.
-         * One can expect to achieve sustained performance higher than the lower limit more than
-         * 50% of the time, and higher than half of the lower limit at least 90% of the time
-         * <em>in tested configurations</em>.
-         * Conversely, one can expect performance lower than twice the upper limit at least
-         * 90% of the time.
-         * <p class=note>
-         * Tested configurations use a single active codec. For use cases where multiple
-         * codecs are active, applications can expect lower and in most cases significantly lower
-         * performance.
-         * <p class=note>
-         * The returned range value is interpolated from the nearest frame size(s) tested.
-         * Codec performance is severely impacted by other activity on the device as well
-         * as environmental factors (such as battery level, temperature or power source), and can
-         * vary significantly even in a steady environment.
-         * <p class=note>
-         * Use this method in cases where only codec performance matters, e.g. to evaluate if
-         * a codec has any chance of meeting a performance target. Codecs are listed
-         * in {@link MediaCodecList} in the preferred order as defined by the device
-         * manufacturer. As such, applications should use the first suitable codec in the
-         * list to achieve the best balance between power use and performance.
-         *
-         * @param width the width of the video
-         * @param height the height of the video
-         *
-         * @throws IllegalArgumentException if the video size is not supported.
-         */
-        @Nullable
-        public Range<Double> getAchievableFrameRatesFor(int width, int height) {
-            if (!supports(width, height, null)) {
-                throw new IllegalArgumentException("unsupported size");
-            }
-
-            if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
-                Log.w(TAG, "Codec did not publish any measurement data.");
-                return null;
-            }
-
-            return estimateFrameRatesFor(width, height);
-        }
 
         /**
          * Video performance points are a set of standard performance points defined by number of
@@ -2224,6 +2329,24 @@
             }
 
             /**
+             * Width in macroblocks.
+             *
+             * @hide
+             */
+            /** package private */ int getWidth() {
+                return mWidth;
+            }
+
+            /**
+             * Height in macroblocks.
+             *
+             * @hide
+             */
+            /** package private */ int getHeight() {
+                return mHeight;
+            }
+
+            /**
              * Maximum frame rate in frames per second.
              *
              * @hide
@@ -2243,6 +2366,24 @@
                 return mMaxMacroBlockRate;
             }
 
+            /**
+             * Codec block width in macroblocks.
+             *
+             * @hide
+             */
+            /** package private */ int getBlockWidth() {
+                return mBlockSize.getWidth();
+            }
+
+            /**
+             * Codec block height in macroblocks.
+             *
+             * @hide
+             */
+            /** package private */ int getBlockHeight() {
+                return mBlockSize.getHeight();
+            }
+
             /** Convert to a debug string */
             public String toString() {
                 int blockWidth = 16 * mBlockSize.getWidth();
@@ -2330,6 +2471,20 @@
                 this(width, height, frameRate, frameRate /* maxFrameRate */, new Size(16, 16));
             }
 
+            /* package private */ PerformancePoint(int width, int height, int maxFrameRate,
+                    long maxMacroBlockRate, int blockSizeWidth, int blockSizeHeight) {
+                mWidth = width;
+                mHeight = height;
+                mMaxFrameRate = maxFrameRate;
+                mMaxMacroBlockRate = maxMacroBlockRate;
+                mBlockSize = new Size(blockSizeWidth, blockSizeHeight);
+            }
+
+            private PerformancePoint(PerformancePoint pp) {
+                this(pp.mWidth, pp.mHeight, pp.mMaxFrameRate, pp.mMaxMacroBlockRate,
+                        pp.mBlockSize.getWidth(), pp.mBlockSize.getHeight());
+            }
+
             /** Saturates a long value to int */
             private int saturateLongToInt(long value) {
                 if (value < Integer.MIN_VALUE) {
@@ -2383,14 +2538,18 @@
              * @return {@code true} if the performance point covers the other.
              */
             public boolean covers(@NonNull PerformancePoint other) {
-                // convert performance points to common block size
-                Size commonSize = getCommonBlockSize(other);
-                PerformancePoint aligned = new PerformancePoint(this, commonSize);
-                PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
+                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+                    return native_covers(other);
+                } else {
+                    // convert performance points to common block size
+                    Size commonSize = getCommonBlockSize(other);
+                    PerformancePoint aligned = new PerformancePoint(this, commonSize);
+                    PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
 
-                return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
-                        && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
-                        && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
+                    return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
+                            && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
+                            && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
+                }
             }
 
             private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) {
@@ -2404,17 +2563,28 @@
                 if (o instanceof PerformancePoint) {
                     // convert performance points to common block size
                     PerformancePoint other = (PerformancePoint)o;
-                    Size commonSize = getCommonBlockSize(other);
-                    PerformancePoint aligned = new PerformancePoint(this, commonSize);
-                    PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
+                    if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+                        return native_equals(other);
+                    } else {
+                        Size commonSize = getCommonBlockSize(other);
+                        PerformancePoint aligned = new PerformancePoint(this, commonSize);
+                        PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
 
-                    return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
-                            && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
-                            && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
+                        return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
+                                && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
+                                && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
+                    }
                 }
                 return false;
             }
 
+            private native boolean native_covers(PerformancePoint other);
+            private native boolean native_equals(PerformancePoint other);
+
+            static {
+                System.loadLibrary("media_jni");
+            }
+
             /** 480p 24fps */
             @NonNull
             public static final PerformancePoint SD_24 = new PerformancePoint(720, 480, 24);
@@ -2519,6 +2689,1812 @@
             public static final PerformancePoint UHD_240 = new PerformancePoint(3840, 2160, 240);
         }
 
+        /* package private */ interface VideoCapsIntf {
+            public Range<Integer> getBitrateRange();
+
+            public Range<Integer> getSupportedWidths();
+
+            public Range<Integer> getSupportedHeights();
+
+            public int getWidthAlignment();
+
+            public int getHeightAlignment();
+
+            public int getSmallerDimensionUpperLimit();
+
+            public Range<Integer> getSupportedFrameRates();
+
+            public Range<Integer> getSupportedWidthsFor(int height);
+
+            public Range<Integer> getSupportedHeightsFor(int width);
+
+            public Range<Double> getSupportedFrameRatesFor(int width, int height);
+
+            public Range<Double> getAchievableFrameRatesFor(int width, int height);
+
+            public boolean areSizeAndRateSupported(int width, int height, double frameRate);
+
+            public boolean isSizeSupported(int width, int height);
+
+            public boolean supportsFormat(MediaFormat format);
+
+            public List<PerformancePoint> getSupportedPerformancePoints();
+        }
+
+        /* package private */ static final class VideoCapsLegacyImpl implements VideoCapsIntf {
+            /* package private */
+            // must not contain KEY_PROFILE
+            static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
+                    MediaFormat.KEY_WIDTH,
+                    MediaFormat.KEY_HEIGHT,
+                    MediaFormat.KEY_FRAME_RATE,
+                    MediaFormat.KEY_BIT_RATE,
+                    MediaFormat.KEY_MIME);
+
+            private CodecCapabilities.CodecCapsLegacyImpl mParent;
+            private Range<Integer> mBitrateRange;
+
+            private Range<Integer> mHeightRange;
+            private Range<Integer> mWidthRange;
+            private Range<Integer> mBlockCountRange;
+            private Range<Integer> mHorizontalBlockRange;
+            private Range<Integer> mVerticalBlockRange;
+            private Range<Rational> mAspectRatioRange;
+            private Range<Rational> mBlockAspectRatioRange;
+            private Range<Long> mBlocksPerSecondRange;
+            private Map<Size, Range<Long>> mMeasuredFrameRates;
+            private List<PerformancePoint> mPerformancePoints;
+            private Range<Integer> mFrameRateRange;
+
+            private int mBlockWidth;
+            private int mBlockHeight;
+            private int mWidthAlignment;
+            private int mHeightAlignment;
+            private int mSmallerDimensionUpperLimit;
+
+            private boolean mAllowMbOverride; // allow XML to override calculated limits
+
+            /* no public constructor */
+            private VideoCapsLegacyImpl() { }
+
+            @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+            public static VideoCapsLegacyImpl create(
+                    MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
+                }
+
+                VideoCapsLegacyImpl caps = new VideoCapsLegacyImpl();
+                caps.init(info, parent);
+                return caps;
+            }
+
+            private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+                mParent = parent;
+                initWithPlatformLimits();
+                applyLevelLimits();
+                parseFromInfo(info);
+                updateLimits();
+            }
+
+            public Range<Integer> getBitrateRange() {
+                return mBitrateRange;
+            }
+
+            public Range<Integer> getSupportedWidths() {
+                return mWidthRange;
+            }
+
+            public Range<Integer> getSupportedHeights() {
+                return mHeightRange;
+            }
+
+            public int getWidthAlignment() {
+                return mWidthAlignment;
+            }
+
+            public int getHeightAlignment() {
+                return mHeightAlignment;
+            }
+
+            /** @hide */
+            public int getSmallerDimensionUpperLimit() {
+                return mSmallerDimensionUpperLimit;
+            }
+
+            public Range<Integer> getSupportedFrameRates() {
+                return mFrameRateRange;
+            }
+
+            public Range<Integer> getSupportedWidthsFor(int height) {
+                try {
+                    Range<Integer> range = mWidthRange;
+                    if (!mHeightRange.contains(height)
+                            || (height % mHeightAlignment) != 0) {
+                        throw new IllegalArgumentException("unsupported height");
+                    }
+                    final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+
+                    // constrain by block count and by block aspect ratio
+                    final int minWidthInBlocks = Math.max(
+                            Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
+                            (int) Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
+                                    * heightInBlocks));
+                    final int maxWidthInBlocks = Math.min(
+                            mBlockCountRange.getUpper() / heightInBlocks,
+                            (int) (mBlockAspectRatioRange.getUpper().doubleValue()
+                                    * heightInBlocks));
+                    range = range.intersect(
+                            (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
+                            maxWidthInBlocks * mBlockWidth);
+
+                    // constrain by smaller dimension limit
+                    if (height > mSmallerDimensionUpperLimit) {
+                        range = range.intersect(1, mSmallerDimensionUpperLimit);
+                    }
+
+                    // constrain by aspect ratio
+                    range = range.intersect(
+                            (int) Math.ceil(mAspectRatioRange.getLower().doubleValue()
+                                    * height),
+                            (int) (mAspectRatioRange.getUpper().doubleValue() * height));
+                    return range;
+                } catch (IllegalArgumentException e) {
+                    // height is not supported because there are no suitable widths
+                    Log.v(TAG, "could not get supported widths for " + height);
+                    throw new IllegalArgumentException("unsupported height");
+                }
+            }
+
+            public Range<Integer> getSupportedHeightsFor(int width) {
+                try {
+                    Range<Integer> range = mHeightRange;
+                    if (!mWidthRange.contains(width)
+                            || (width % mWidthAlignment) != 0) {
+                        throw new IllegalArgumentException("unsupported width");
+                    }
+                    final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+
+                    // constrain by block count and by block aspect ratio
+                    final int minHeightInBlocks = Math.max(
+                            Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
+                            (int) Math.ceil(widthInBlocks
+                                    / mBlockAspectRatioRange.getUpper().doubleValue()));
+                    final int maxHeightInBlocks = Math.min(
+                            mBlockCountRange.getUpper() / widthInBlocks,
+                            (int) (widthInBlocks
+                                    / mBlockAspectRatioRange.getLower().doubleValue()));
+                    range = range.intersect(
+                            (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
+                            maxHeightInBlocks * mBlockHeight);
+
+                    // constrain by smaller dimension limit
+                    if (width > mSmallerDimensionUpperLimit) {
+                        range = range.intersect(1, mSmallerDimensionUpperLimit);
+                    }
+
+                    // constrain by aspect ratio
+                    range = range.intersect(
+                            (int) Math.ceil(width
+                                    / mAspectRatioRange.getUpper().doubleValue()),
+                            (int) (width / mAspectRatioRange.getLower().doubleValue()));
+                    return range;
+                } catch (IllegalArgumentException e) {
+                    // width is not supported because there are no suitable heights
+                    Log.v(TAG, "could not get supported heights for " + width);
+                    throw new IllegalArgumentException("unsupported width");
+                }
+            }
+
+            public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+                Range<Integer> range = mHeightRange;
+                if (!supports(width, height, null)) {
+                    throw new IllegalArgumentException("unsupported size");
+                }
+                final int blockCount =
+                        Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+
+                return Range.create(
+                        Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
+                                (double) mFrameRateRange.getLower()),
+                        Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
+                                (double) mFrameRateRange.getUpper()));
+            }
+
+            private int getBlockCount(int width, int height) {
+                return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+            }
+
+            @NonNull
+            private Size findClosestSize(int width, int height) {
+                int targetBlockCount = getBlockCount(width, height);
+                Size closestSize = null;
+                int minDiff = Integer.MAX_VALUE;
+                for (Size size : mMeasuredFrameRates.keySet()) {
+                    int diff = Math.abs(targetBlockCount -
+                            getBlockCount(size.getWidth(), size.getHeight()));
+                    if (diff < minDiff) {
+                        minDiff = diff;
+                        closestSize = size;
+                    }
+                }
+                return closestSize;
+            }
+
+            private Range<Double> estimateFrameRatesFor(int width, int height) {
+                Size size = findClosestSize(width, height);
+                Range<Long> range = mMeasuredFrameRates.get(size);
+                Double ratio = getBlockCount(size.getWidth(), size.getHeight())
+                        / (double)Math.max(getBlockCount(width, height), 1);
+                return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
+            }
+
+            /** @throws IllegalArgumentException if the video size is not supported. */
+            @Nullable
+            public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+                if (!supports(width, height, null)) {
+                    throw new IllegalArgumentException("unsupported size");
+                }
+
+                if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
+                    Log.w(TAG, "Codec did not publish any measurement data.");
+                    return null;
+                }
+
+                return estimateFrameRatesFor(width, height);
+            }
+
+            @Nullable
+            public List<PerformancePoint> getSupportedPerformancePoints() {
+                return mPerformancePoints;
+            }
+
+            public boolean areSizeAndRateSupported(
+                    int width, int height, double frameRate) {
+                return supports(width, height, frameRate);
+            }
+
+            public boolean isSizeSupported(int width, int height) {
+                return supports(width, height, null);
+            }
+
+            private boolean supports(Integer width, Integer height, Number rate) {
+                boolean ok = true;
+
+                if (ok && width != null) {
+                    ok = mWidthRange.contains(width)
+                            && (width % mWidthAlignment == 0);
+                }
+                if (ok && height != null) {
+                    ok = mHeightRange.contains(height)
+                            && (height % mHeightAlignment == 0);
+                }
+                if (ok && rate != null) {
+                    ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
+                }
+                if (ok && height != null && width != null) {
+                    ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+
+                    final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+                    final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+                    final int blockCount = widthInBlocks * heightInBlocks;
+                    ok = ok && mBlockCountRange.contains(blockCount)
+                            && mBlockAspectRatioRange.contains(
+                                    new Rational(widthInBlocks, heightInBlocks))
+                            && mAspectRatioRange.contains(new Rational(width, height));
+                    if (ok && rate != null) {
+                        double blocksPerSec = blockCount * rate.doubleValue();
+                        ok = mBlocksPerSecondRange.contains(
+                                Utils.longRangeFor(blocksPerSec));
+                    }
+                }
+                return ok;
+            }
+
+            /**
+             * @hide
+             * @throws java.lang.ClassCastException */
+            public boolean supportsFormat(MediaFormat format) {
+                final Map<String, Object> map = format.getMap();
+                Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+                Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+                Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
+
+                if (!supports(width, height, rate)) {
+                    return false;
+                }
+
+                if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
+                    return false;
+                }
+
+                // we ignore color-format for now as it is not reliably reported by codec
+                return true;
+            }
+
+            /** @hide */
+            public Size getBlockSize() {
+                return new Size(mBlockWidth, mBlockHeight);
+            }
+
+            /** @hide */
+            public Range<Integer> getBlockCountRange() {
+                return mBlockCountRange;
+            }
+
+            /** @hide */
+            public Range<Long> getBlocksPerSecondRange() {
+                return mBlocksPerSecondRange;
+            }
+
+            /** @hide */
+            public Range<Rational> getAspectRatioRange(boolean blocks) {
+                return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
+            }
+
+            private void initWithPlatformLimits() {
+                mBitrateRange = BITRATE_RANGE;
+
+                mWidthRange  = getSizeRange();
+                mHeightRange = getSizeRange();
+                mFrameRateRange = FRAME_RATE_RANGE;
+
+                mHorizontalBlockRange = getSizeRange();
+                mVerticalBlockRange   = getSizeRange();
+
+                // full positive ranges are supported as these get calculated
+                mBlockCountRange      = POSITIVE_INTEGERS;
+                mBlocksPerSecondRange = POSITIVE_LONGS;
+
+                mBlockAspectRatioRange = POSITIVE_RATIONALS;
+                mAspectRatioRange      = POSITIVE_RATIONALS;
+
+                mWidthAlignment = 1;
+                mHeightAlignment = 1;
+                mBlockWidth = 1;
+                mBlockHeight = 1;
+                mSmallerDimensionUpperLimit = getSizeRange().getUpper();
+            }
+
+            private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
+                Vector<PerformancePoint> ret = new Vector<>();
+                final String prefix = "performance-point-";
+                Set<String> keys = map.keySet();
+                for (String key : keys) {
+                    // looking for: performance-point-WIDTHxHEIGHT-range
+                    if (!key.startsWith(prefix)) {
+                        continue;
+                    }
+                    String subKey = key.substring(prefix.length());
+                    if (subKey.equals("none") && ret.size() == 0) {
+                        // This means that component knowingly did not publish performance points.
+                        // This is different from when the component forgot to publish performance
+                        // points.
+                        return Collections.unmodifiableList(ret);
+                    }
+                    String[] temp = key.split("-");
+                    if (temp.length != 4) {
+                        continue;
+                    }
+                    String sizeStr = temp[2];
+                    Size size = Utils.parseSize(sizeStr, null);
+                    if (size == null || size.getWidth() * size.getHeight() <= 0) {
+                        continue;
+                    }
+                    Range<Long> range = Utils.parseLongRange(map.get(key), null);
+                    if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+                        continue;
+                    }
+                    PerformancePoint given = new PerformancePoint(
+                            size.getWidth(), size.getHeight(), range.getLower().intValue(),
+                            range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+                    PerformancePoint rotated = new PerformancePoint(
+                            size.getHeight(), size.getWidth(), range.getLower().intValue(),
+                            range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+                    ret.add(given);
+                    if (!given.covers(rotated)) {
+                        ret.add(rotated);
+                    }
+                }
+
+                // check if the component specified no performance point indication
+                if (ret.size() == 0) {
+                    return null;
+                }
+
+                // sort reversed by area first, then by frame rate
+                ret.sort((a, b) ->
+                        -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
+                                (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
+                        (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
+                                (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
+                        (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
+                                (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
+
+                return Collections.unmodifiableList(ret);
+            }
+
+            private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
+                Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
+                final String prefix = "measured-frame-rate-";
+                Set<String> keys = map.keySet();
+                for (String key : keys) {
+                    // looking for: measured-frame-rate-WIDTHxHEIGHT-range
+                    if (!key.startsWith(prefix)) {
+                        continue;
+                    }
+                    String subKey = key.substring(prefix.length());
+                    String[] temp = key.split("-");
+                    if (temp.length != 5) {
+                        continue;
+                    }
+                    String sizeStr = temp[3];
+                    Size size = Utils.parseSize(sizeStr, null);
+                    if (size == null || size.getWidth() * size.getHeight() <= 0) {
+                        continue;
+                    }
+                    Range<Long> range = Utils.parseLongRange(map.get(key), null);
+                    if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+                        continue;
+                    }
+                    ret.put(size, range);
+                }
+                return ret;
+            }
+
+            private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
+                Pair<Size, Size> range = Utils.parseSizeRange(o);
+                if (range != null) {
+                    try {
+                        return Pair.create(
+                                Range.create(range.first.getWidth(), range.second.getWidth()),
+                                Range.create(range.first.getHeight(), range.second.getHeight()));
+                    } catch (IllegalArgumentException e) {
+                        Log.w(TAG, "could not parse size range '" + o + "'");
+                    }
+                }
+                return null;
+            }
+
+            /** @hide */
+            public static int equivalentVP9Level(MediaFormat info) {
+                final Map<String, Object> map = info.getMap();
+
+                Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
+                int BS = blockSize.getWidth() * blockSize.getHeight();
+
+                Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
+                int FS = counts == null ? 0 : BS * counts.getUpper();
+
+                Range<Long> blockRates =
+                    Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+                long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
+
+                Pair<Range<Integer>, Range<Integer>> dimensionRanges =
+                    parseWidthHeightRanges(map.get("size-range"));
+                int D = dimensionRanges == null ? 0 : Math.max(
+                        dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
+
+                Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+                int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
+
+                if (SR <=      829440 && FS <=    36864 && BR <=    200 && D <=   512)
+                    return CodecProfileLevel.VP9Level1;
+                if (SR <=     2764800 && FS <=    73728 && BR <=    800 && D <=   768)
+                    return CodecProfileLevel.VP9Level11;
+                if (SR <=     4608000 && FS <=   122880 && BR <=   1800 && D <=   960)
+                    return CodecProfileLevel.VP9Level2;
+                if (SR <=     9216000 && FS <=   245760 && BR <=   3600 && D <=  1344)
+                    return CodecProfileLevel.VP9Level21;
+                if (SR <=    20736000 && FS <=   552960 && BR <=   7200 && D <=  2048)
+                    return CodecProfileLevel.VP9Level3;
+                if (SR <=    36864000 && FS <=   983040 && BR <=  12000 && D <=  2752)
+                    return CodecProfileLevel.VP9Level31;
+                if (SR <=    83558400 && FS <=  2228224 && BR <=  18000 && D <=  4160)
+                    return CodecProfileLevel.VP9Level4;
+                if (SR <=   160432128 && FS <=  2228224 && BR <=  30000 && D <=  4160)
+                    return CodecProfileLevel.VP9Level41;
+                if (SR <=   311951360 && FS <=  8912896 && BR <=  60000 && D <=  8384)
+                    return CodecProfileLevel.VP9Level5;
+                if (SR <=   588251136 && FS <=  8912896 && BR <= 120000 && D <=  8384)
+                    return CodecProfileLevel.VP9Level51;
+                if (SR <=  1176502272 && FS <=  8912896 && BR <= 180000 && D <=  8384)
+                    return CodecProfileLevel.VP9Level52;
+                if (SR <=  1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
+                    return CodecProfileLevel.VP9Level6;
+                if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
+                    return CodecProfileLevel.VP9Level61;
+                if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
+                    return CodecProfileLevel.VP9Level62;
+                // returning largest level
+                return CodecProfileLevel.VP9Level62;
+            }
+
+            private void parseFromInfo(MediaFormat info) {
+                final Map<String, Object> map = info.getMap();
+                Size blockSize = new Size(mBlockWidth, mBlockHeight);
+                Size alignment = new Size(mWidthAlignment, mHeightAlignment);
+                Range<Integer> counts = null, widths = null, heights = null;
+                Range<Integer> frameRates = null, bitRates = null;
+                Range<Long> blockRates = null;
+                Range<Rational> ratios = null, blockRatios = null;
+
+                blockSize = Utils.parseSize(map.get("block-size"), blockSize);
+                alignment = Utils.parseSize(map.get("alignment"), alignment);
+                counts = Utils.parseIntRange(map.get("block-count-range"), null);
+                blockRates =
+                    Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+                mMeasuredFrameRates = getMeasuredFrameRates(map);
+                mPerformancePoints = getPerformancePoints(map);
+                Pair<Range<Integer>, Range<Integer>> sizeRanges =
+                    parseWidthHeightRanges(map.get("size-range"));
+                if (sizeRanges != null) {
+                    widths = sizeRanges.first;
+                    heights = sizeRanges.second;
+                }
+                // for now this just means using the smaller max size as 2nd
+                // upper limit.
+                // for now we are keeping the profile specific "width/height
+                // in macroblocks" limits.
+                if (map.containsKey("feature-can-swap-width-height")) {
+                    if (widths != null) {
+                        mSmallerDimensionUpperLimit =
+                            Math.min(widths.getUpper(), heights.getUpper());
+                        widths = heights = widths.extend(heights);
+                    } else {
+                        Log.w(TAG, "feature can-swap-width-height is best used with size-range");
+                        mSmallerDimensionUpperLimit =
+                            Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
+                        mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
+                    }
+                }
+
+                ratios = Utils.parseRationalRange(
+                        map.get("block-aspect-ratio-range"), null);
+                blockRatios = Utils.parseRationalRange(
+                        map.get("pixel-aspect-ratio-range"), null);
+                frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
+                if (frameRates != null) {
+                    try {
+                        frameRates = frameRates.intersect(FRAME_RATE_RANGE);
+                    } catch (IllegalArgumentException e) {
+                        Log.w(TAG, "frame rate range (" + frameRates
+                                + ") is out of limits: " + FRAME_RATE_RANGE);
+                        frameRates = null;
+                    }
+                }
+                bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+                if (bitRates != null) {
+                    try {
+                        bitRates = bitRates.intersect(BITRATE_RANGE);
+                    } catch (IllegalArgumentException e) {
+                        Log.w(TAG,  "bitrate range (" + bitRates
+                                + ") is out of limits: " + BITRATE_RANGE);
+                        bitRates = null;
+                    }
+                }
+
+                checkPowerOfTwo(
+                        blockSize.getWidth(), "block-size width must be power of two");
+                checkPowerOfTwo(
+                        blockSize.getHeight(), "block-size height must be power of two");
+
+                checkPowerOfTwo(
+                        alignment.getWidth(), "alignment width must be power of two");
+                checkPowerOfTwo(
+                        alignment.getHeight(), "alignment height must be power of two");
+
+                // update block-size and alignment
+                applyMacroBlockLimits(
+                        Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+                        Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
+                        alignment.getWidth(), alignment.getHeight());
+
+                if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
+                    // codec supports profiles that we don't know.
+                    // Use supplied values clipped to platform limits
+                    if (widths != null) {
+                        mWidthRange = getSizeRange().intersect(widths);
+                    }
+                    if (heights != null) {
+                        mHeightRange = getSizeRange().intersect(heights);
+                    }
+                    if (counts != null) {
+                        mBlockCountRange = POSITIVE_INTEGERS.intersect(
+                                Utils.factorRange(counts, mBlockWidth * mBlockHeight
+                                        / blockSize.getWidth() / blockSize.getHeight()));
+                    }
+                    if (blockRates != null) {
+                        mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
+                                Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+                                        / blockSize.getWidth() / blockSize.getHeight()));
+                    }
+                    if (blockRatios != null) {
+                        mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
+                                Utils.scaleRange(blockRatios,
+                                        mBlockHeight / blockSize.getHeight(),
+                                        mBlockWidth / blockSize.getWidth()));
+                    }
+                    if (ratios != null) {
+                        mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
+                    }
+                    if (frameRates != null) {
+                        mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
+                    }
+                    if (bitRates != null) {
+                        // only allow bitrate override if unsupported profiles were encountered
+                        if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+                            mBitrateRange = BITRATE_RANGE.intersect(bitRates);
+                        } else {
+                            mBitrateRange = mBitrateRange.intersect(bitRates);
+                        }
+                    }
+                } else {
+                    // no unsupported profile/levels, so restrict values to known limits
+                    if (widths != null) {
+                        mWidthRange = mWidthRange.intersect(widths);
+                    }
+                    if (heights != null) {
+                        mHeightRange = mHeightRange.intersect(heights);
+                    }
+                    if (counts != null) {
+                        mBlockCountRange = mBlockCountRange.intersect(
+                                Utils.factorRange(counts, mBlockWidth * mBlockHeight
+                                        / blockSize.getWidth() / blockSize.getHeight()));
+                    }
+                    if (blockRates != null) {
+                        mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+                                Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+                                        / blockSize.getWidth() / blockSize.getHeight()));
+                    }
+                    if (blockRatios != null) {
+                        mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+                                Utils.scaleRange(blockRatios,
+                                        mBlockHeight / blockSize.getHeight(),
+                                        mBlockWidth / blockSize.getWidth()));
+                    }
+                    if (ratios != null) {
+                        mAspectRatioRange = mAspectRatioRange.intersect(ratios);
+                    }
+                    if (frameRates != null) {
+                        mFrameRateRange = mFrameRateRange.intersect(frameRates);
+                    }
+                    if (bitRates != null) {
+                        mBitrateRange = mBitrateRange.intersect(bitRates);
+                    }
+                }
+                updateLimits();
+            }
+
+            private void applyBlockLimits(
+                    int blockWidth, int blockHeight,
+                    Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
+                checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
+                checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
+
+                final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
+                final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
+
+                // factor will always be a power-of-2
+                int factor =
+                    newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
+                if (factor != 1) {
+                    mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
+                    mBlocksPerSecondRange = Utils.factorRange(
+                            mBlocksPerSecondRange, factor);
+                    mBlockAspectRatioRange = Utils.scaleRange(
+                            mBlockAspectRatioRange,
+                            newBlockHeight / mBlockHeight,
+                            newBlockWidth / mBlockWidth);
+                    mHorizontalBlockRange = Utils.factorRange(
+                            mHorizontalBlockRange, newBlockWidth / mBlockWidth);
+                    mVerticalBlockRange = Utils.factorRange(
+                            mVerticalBlockRange, newBlockHeight / mBlockHeight);
+                }
+                factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
+                if (factor != 1) {
+                    counts = Utils.factorRange(counts, factor);
+                    rates = Utils.factorRange(rates, factor);
+                    ratios = Utils.scaleRange(
+                            ratios, newBlockHeight / blockHeight,
+                            newBlockWidth / blockWidth);
+                }
+                mBlockCountRange = mBlockCountRange.intersect(counts);
+                mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
+                mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
+                mBlockWidth = newBlockWidth;
+                mBlockHeight = newBlockHeight;
+            }
+
+            private void applyAlignment(int widthAlignment, int heightAlignment) {
+                checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
+                checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
+
+                if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
+                    // maintain assumption that 0 < alignment <= block-size
+                    applyBlockLimits(
+                            Math.max(widthAlignment, mBlockWidth),
+                            Math.max(heightAlignment, mBlockHeight),
+                            POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+                }
+
+                mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+                mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+
+                mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+                mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+            }
+
+            private void updateLimits() {
+                // pixels -> blocks <- counts
+                mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+                        Utils.factorRange(mWidthRange, mBlockWidth));
+                mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+                        Range.create(
+                                mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
+                                mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
+                mVerticalBlockRange = mVerticalBlockRange.intersect(
+                        Utils.factorRange(mHeightRange, mBlockHeight));
+                mVerticalBlockRange = mVerticalBlockRange.intersect(
+                        Range.create(
+                                mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
+                                mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
+                mBlockCountRange = mBlockCountRange.intersect(
+                        Range.create(
+                                mHorizontalBlockRange.getLower()
+                                        * mVerticalBlockRange.getLower(),
+                                mHorizontalBlockRange.getUpper()
+                                        * mVerticalBlockRange.getUpper()));
+                mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+                        new Rational(
+                                mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
+                        new Rational(
+                                mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
+
+                // blocks -> pixels
+                mWidthRange = mWidthRange.intersect(
+                        (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
+                        mHorizontalBlockRange.getUpper() * mBlockWidth);
+                mHeightRange = mHeightRange.intersect(
+                        (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
+                        mVerticalBlockRange.getUpper() * mBlockHeight);
+                mAspectRatioRange = mAspectRatioRange.intersect(
+                        new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
+                        new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
+
+                mSmallerDimensionUpperLimit = Math.min(
+                        mSmallerDimensionUpperLimit,
+                        Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
+
+                // blocks -> rate
+                mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+                        mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
+                        mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
+                mFrameRateRange = mFrameRateRange.intersect(
+                        (int)(mBlocksPerSecondRange.getLower()
+                                / mBlockCountRange.getUpper()),
+                        (int)(mBlocksPerSecondRange.getUpper()
+                                / (double)mBlockCountRange.getLower()));
+            }
+
+            private void applyMacroBlockLimits(
+                    int maxHorizontalBlocks, int maxVerticalBlocks,
+                    int maxBlocks, long maxBlocksPerSecond,
+                    int blockWidth, int blockHeight,
+                    int widthAlignment, int heightAlignment) {
+                applyMacroBlockLimits(
+                        1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
+                        maxHorizontalBlocks, maxVerticalBlocks,
+                        maxBlocks, maxBlocksPerSecond,
+                        blockWidth, blockHeight, widthAlignment, heightAlignment);
+            }
+
+            private void applyMacroBlockLimits(
+                    int minHorizontalBlocks, int minVerticalBlocks,
+                    int maxHorizontalBlocks, int maxVerticalBlocks,
+                    int maxBlocks, long maxBlocksPerSecond,
+                    int blockWidth, int blockHeight,
+                    int widthAlignment, int heightAlignment) {
+                applyAlignment(widthAlignment, heightAlignment);
+                applyBlockLimits(
+                        blockWidth, blockHeight, Range.create(1, maxBlocks),
+                        Range.create(1L, maxBlocksPerSecond),
+                        Range.create(
+                                new Rational(1, maxVerticalBlocks),
+                                new Rational(maxHorizontalBlocks, 1)));
+                mHorizontalBlockRange =
+                        mHorizontalBlockRange.intersect(
+                                Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
+                                maxHorizontalBlocks / (mBlockWidth / blockWidth));
+                mVerticalBlockRange =
+                        mVerticalBlockRange.intersect(
+                                Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
+                                maxVerticalBlocks / (mBlockHeight / blockHeight));
+            }
+
+            private void applyLevelLimits() {
+                long maxBlocksPerSecond = 0;
+                int maxBlocks = 0;
+                int maxBps = 0;
+                int maxDPBBlocks = 0;
+
+                int errors = ERROR_NONE_SUPPORTED;
+                CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
+                String mime = mParent.getMimeType();
+
+                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+                    maxBlocks = 99;
+                    maxBlocksPerSecond = 1485;
+                    maxBps = 64000;
+                    maxDPBBlocks = 396;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        int MBPS = 0, FS = 0, BR = 0, DPB = 0;
+                        boolean supported = true;
+                        switch (profileLevel.level) {
+                            case CodecProfileLevel.AVCLevel1:
+                                MBPS =     1485; FS =     99; BR =     64; DPB =    396; break;
+                            case CodecProfileLevel.AVCLevel1b:
+                                MBPS =     1485; FS =     99; BR =    128; DPB =    396; break;
+                            case CodecProfileLevel.AVCLevel11:
+                                MBPS =     3000; FS =    396; BR =    192; DPB =    900; break;
+                            case CodecProfileLevel.AVCLevel12:
+                                MBPS =     6000; FS =    396; BR =    384; DPB =   2376; break;
+                            case CodecProfileLevel.AVCLevel13:
+                                MBPS =    11880; FS =    396; BR =    768; DPB =   2376; break;
+                            case CodecProfileLevel.AVCLevel2:
+                                MBPS =    11880; FS =    396; BR =   2000; DPB =   2376; break;
+                            case CodecProfileLevel.AVCLevel21:
+                                MBPS =    19800; FS =    792; BR =   4000; DPB =   4752; break;
+                            case CodecProfileLevel.AVCLevel22:
+                                MBPS =    20250; FS =   1620; BR =   4000; DPB =   8100; break;
+                            case CodecProfileLevel.AVCLevel3:
+                                MBPS =    40500; FS =   1620; BR =  10000; DPB =   8100; break;
+                            case CodecProfileLevel.AVCLevel31:
+                                MBPS =   108000; FS =   3600; BR =  14000; DPB =  18000; break;
+                            case CodecProfileLevel.AVCLevel32:
+                                MBPS =   216000; FS =   5120; BR =  20000; DPB =  20480; break;
+                            case CodecProfileLevel.AVCLevel4:
+                                MBPS =   245760; FS =   8192; BR =  20000; DPB =  32768; break;
+                            case CodecProfileLevel.AVCLevel41:
+                                MBPS =   245760; FS =   8192; BR =  50000; DPB =  32768; break;
+                            case CodecProfileLevel.AVCLevel42:
+                                MBPS =   522240; FS =   8704; BR =  50000; DPB =  34816; break;
+                            case CodecProfileLevel.AVCLevel5:
+                                MBPS =   589824; FS =  22080; BR = 135000; DPB = 110400; break;
+                            case CodecProfileLevel.AVCLevel51:
+                                MBPS =   983040; FS =  36864; BR = 240000; DPB = 184320; break;
+                            case CodecProfileLevel.AVCLevel52:
+                                MBPS =  2073600; FS =  36864; BR = 240000; DPB = 184320; break;
+                            case CodecProfileLevel.AVCLevel6:
+                                MBPS =  4177920; FS = 139264; BR = 240000; DPB = 696320; break;
+                            case CodecProfileLevel.AVCLevel61:
+                                MBPS =  8355840; FS = 139264; BR = 480000; DPB = 696320; break;
+                            case CodecProfileLevel.AVCLevel62:
+                                MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
+                            default:
+                                Log.w(TAG, "Unrecognized level "
+                                        + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.AVCProfileConstrainedHigh:
+                            case CodecProfileLevel.AVCProfileHigh:
+                                BR *= 1250; break;
+                            case CodecProfileLevel.AVCProfileHigh10:
+                                BR *= 3000; break;
+                            case CodecProfileLevel.AVCProfileExtended:
+                            case CodecProfileLevel.AVCProfileHigh422:
+                            case CodecProfileLevel.AVCProfileHigh444:
+                                Log.w(TAG, "Unsupported profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNSUPPORTED;
+                                supported = false;
+                                // fall through - treat as base profile
+                            case CodecProfileLevel.AVCProfileConstrainedBaseline:
+                            case CodecProfileLevel.AVCProfileBaseline:
+                            case CodecProfileLevel.AVCProfileMain:
+                                BR *= 1000; break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                                BR *= 1000;
+                        }
+                        if (supported) {
+                            errors &= ~ERROR_NONE_SUPPORTED;
+                        }
+                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                        maxBlocks = Math.max(FS, maxBlocks);
+                        maxBps = Math.max(BR, maxBps);
+                        maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
+                    }
+
+                    int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+                    applyMacroBlockLimits(
+                            maxLengthInBlocks, maxLengthInBlocks,
+                            maxBlocks, maxBlocksPerSecond,
+                            16 /* blockWidth */, 16 /* blockHeight */,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
+                    int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                    maxBlocks = 99;
+                    maxBlocksPerSecond = 1485;
+                    maxBps = 64000;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+                        boolean supported = true;
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.MPEG2ProfileSimple:
+                                switch (profileLevel.level) {
+                                    case CodecProfileLevel.MPEG2LevelML:
+                                        FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR =  15000; break;
+                                    default:
+                                        Log.w(TAG, "Unrecognized profile/level "
+                                                + profileLevel.profile + "/"
+                                                + profileLevel.level + " for " + mime);
+                                        errors |= ERROR_UNRECOGNIZED;
+                                }
+                                break;
+                            case CodecProfileLevel.MPEG2ProfileMain:
+                                switch (profileLevel.level) {
+                                    case CodecProfileLevel.MPEG2LevelLL:
+                                        FR = 30; W = 22; H =  18; MBPS =  11880; FS =   396; BR =  4000; break;
+                                    case CodecProfileLevel.MPEG2LevelML:
+                                        FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR = 15000; break;
+                                    case CodecProfileLevel.MPEG2LevelH14:
+                                        FR = 60; W = 90; H =  68; MBPS = 183600; FS =  6120; BR = 60000; break;
+                                    case CodecProfileLevel.MPEG2LevelHL:
+                                        FR = 60; W = 120; H = 68; MBPS = 244800; FS =  8160; BR = 80000; break;
+                                    case CodecProfileLevel.MPEG2LevelHP:
+                                        FR = 60; W = 120; H = 68; MBPS = 489600; FS =  8160; BR = 80000; break;
+                                    default:
+                                        Log.w(TAG, "Unrecognized profile/level "
+                                                + profileLevel.profile + "/"
+                                                + profileLevel.level + " for " + mime);
+                                        errors |= ERROR_UNRECOGNIZED;
+                                }
+                                break;
+                            case CodecProfileLevel.MPEG2Profile422:
+                            case CodecProfileLevel.MPEG2ProfileSNR:
+                            case CodecProfileLevel.MPEG2ProfileSpatial:
+                            case CodecProfileLevel.MPEG2ProfileHigh:
+                                Log.i(TAG, "Unsupported profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNSUPPORTED;
+                                supported = false;
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        if (supported) {
+                            errors &= ~ERROR_NONE_SUPPORTED;
+                        }
+                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                        maxBlocks = Math.max(FS, maxBlocks);
+                        maxBps = Math.max(BR * 1000, maxBps);
+                        maxWidth = Math.max(W, maxWidth);
+                        maxHeight = Math.max(H, maxHeight);
+                        maxRate = Math.max(FR, maxRate);
+                    }
+                    applyMacroBlockLimits(maxWidth, maxHeight,
+                            maxBlocks, maxBlocksPerSecond,
+                            16 /* blockWidth */, 16 /* blockHeight */,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                    mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+                    int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                    maxBlocks = 99;
+                    maxBlocksPerSecond = 1485;
+                    maxBps = 64000;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+                        boolean strict = false; // true: W, H and FR are individual max limits
+                        boolean supported = true;
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.MPEG4ProfileSimple:
+                                switch (profileLevel.level) {
+                                    case CodecProfileLevel.MPEG4Level0:
+                                        strict = true;
+                                        FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
+                                    case CodecProfileLevel.MPEG4Level1:
+                                        FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
+                                    case CodecProfileLevel.MPEG4Level0b:
+                                        strict = true;
+                                        FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR = 128; break;
+                                    case CodecProfileLevel.MPEG4Level2:
+                                        FR = 30; W = 22; H = 18; MBPS =  5940; FS = 396; BR = 128; break;
+                                    case CodecProfileLevel.MPEG4Level3:
+                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
+                                    case CodecProfileLevel.MPEG4Level4a:
+                                        FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
+                                    case CodecProfileLevel.MPEG4Level5:
+                                        FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
+                                    case CodecProfileLevel.MPEG4Level6:
+                                        FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
+                                    default:
+                                        Log.w(TAG, "Unrecognized profile/level "
+                                                + profileLevel.profile + "/"
+                                                + profileLevel.level + " for " + mime);
+                                        errors |= ERROR_UNRECOGNIZED;
+                                }
+                                break;
+                            case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
+                                switch (profileLevel.level) {
+                                    case CodecProfileLevel.MPEG4Level0:
+                                    case CodecProfileLevel.MPEG4Level1:
+                                        FR = 30; W = 11; H =  9; MBPS =  2970; FS =   99; BR =  128; break;
+                                    case CodecProfileLevel.MPEG4Level2:
+                                        FR = 30; W = 22; H = 18; MBPS =  5940; FS =  396; BR =  384; break;
+                                    case CodecProfileLevel.MPEG4Level3:
+                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR =  768; break;
+                                    case CodecProfileLevel.MPEG4Level3b:
+                                        FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR = 1500; break;
+                                    case CodecProfileLevel.MPEG4Level4:
+                                        FR = 30; W = 44; H = 36; MBPS = 23760; FS =  792; BR = 3000; break;
+                                    case CodecProfileLevel.MPEG4Level5:
+                                        FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
+                                    default:
+                                        Log.w(TAG, "Unrecognized profile/level "
+                                                + profileLevel.profile + "/"
+                                                + profileLevel.level + " for " + mime);
+                                        errors |= ERROR_UNRECOGNIZED;
+                                }
+                                break;
+                            case CodecProfileLevel.MPEG4ProfileMain:             // 2-4
+                            case CodecProfileLevel.MPEG4ProfileNbit:             // 2
+                            case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
+                            case CodecProfileLevel.MPEG4ProfileCoreScalable:     // 1-3
+                            case CodecProfileLevel.MPEG4ProfileAdvancedCoding:   // 1-4
+                            case CodecProfileLevel.MPEG4ProfileCore:             // 1-2
+                            case CodecProfileLevel.MPEG4ProfileAdvancedCore:     // 1-4
+                            case CodecProfileLevel.MPEG4ProfileSimpleScalable:   // 0-2
+                            case CodecProfileLevel.MPEG4ProfileHybrid:           // 1-2
+
+                            // Studio profiles are not supported by our codecs.
+
+                            // Only profiles that can decode simple object types are considered.
+                            // The following profiles are not able to.
+                            case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
+                            case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
+                            case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
+                            case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+                            case CodecProfileLevel.MPEG4ProfileSimpleFBA:        // 1-2
+                                Log.i(TAG, "Unsupported profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNSUPPORTED;
+                                supported = false;
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        if (supported) {
+                            errors &= ~ERROR_NONE_SUPPORTED;
+                        }
+                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                        maxBlocks = Math.max(FS, maxBlocks);
+                        maxBps = Math.max(BR * 1000, maxBps);
+                        if (strict) {
+                            maxWidth = Math.max(W, maxWidth);
+                            maxHeight = Math.max(H, maxHeight);
+                            maxRate = Math.max(FR, maxRate);
+                        } else {
+                            // assuming max 60 fps frame rate and 1:2 aspect ratio
+                            int maxDim = (int)Math.sqrt(FS * 2);
+                            maxWidth = Math.max(maxDim, maxWidth);
+                            maxHeight = Math.max(maxDim, maxHeight);
+                            maxRate = Math.max(Math.max(FR, 60), maxRate);
+                        }
+                    }
+                    applyMacroBlockLimits(maxWidth, maxHeight,
+                            maxBlocks, maxBlocksPerSecond,
+                            16 /* blockWidth */, 16 /* blockHeight */,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                    mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+                    int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                    int minWidth = maxWidth, minHeight = maxHeight;
+                    int minAlignment = 16;
+                    maxBlocks = 99;
+                    maxBlocksPerSecond = 1485;
+                    maxBps = 64000;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
+                        boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
+                        switch (profileLevel.level) {
+                            case CodecProfileLevel.H263Level10:
+                                strict = true; // only supports sQCIF & QCIF
+                                FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level20:
+                                strict = true; // only supports sQCIF, QCIF & CIF
+                                FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * 15; break;
+                            case CodecProfileLevel.H263Level30:
+                                strict = true; // only supports sQCIF, QCIF & CIF
+                                FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level40:
+                                strict = true; // only supports sQCIF, QCIF & CIF
+                                FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level45:
+                                // only implies level 10 support
+                                strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
+                                        || profileLevel.profile ==
+                                                CodecProfileLevel.H263ProfileBackwardCompatible;
+                                if (!strict) {
+                                    minW = 1; minH = 1; minAlignment = 4;
+                                }
+                                FR = 15; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level50:
+                                // only supports 50fps for H > 15
+                                minW = 1; minH = 1; minAlignment = 4;
+                                FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
+                            case CodecProfileLevel.H263Level60:
+                                // only supports 50fps for H > 15
+                                minW = 1; minH = 1; minAlignment = 4;
+                                FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
+                            case CodecProfileLevel.H263Level70:
+                                // only supports 50fps for H > 30
+                                minW = 1; minH = 1; minAlignment = 4;
+                                FR = 60; W = 45; H = 36; BR = 256; MBPS =  W * H * 50; break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
+                                        + "/" + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.H263ProfileBackwardCompatible:
+                            case CodecProfileLevel.H263ProfileBaseline:
+                            case CodecProfileLevel.H263ProfileH320Coding:
+                            case CodecProfileLevel.H263ProfileHighCompression:
+                            case CodecProfileLevel.H263ProfileHighLatency:
+                            case CodecProfileLevel.H263ProfileInterlace:
+                            case CodecProfileLevel.H263ProfileInternet:
+                            case CodecProfileLevel.H263ProfileISWV2:
+                            case CodecProfileLevel.H263ProfileISWV3:
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        if (strict) {
+                            // Strict levels define sub-QCIF min size and enumerated sizes. We
+                            // cannot express support for "only sQCIF & QCIF (& CIF)" using
+                            // VideoCapabilities but we can express "only QCIF (& CIF)", so set
+                            // minimume size at QCIF.minW = 8; minH = 6;
+                            minW = 11; minH = 9;
+                        } else {
+                            // any support for non-strict levels (including unrecognized profiles or
+                            // levels) allow custom frame size support beyond supported limits
+                            // (other than bitrate)
+                            mAllowMbOverride = true;
+                        }
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                        maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+                        maxBlocks = Math.max(W * H, maxBlocks);
+                        maxBps = Math.max(BR * 64000, maxBps);
+                        maxWidth = Math.max(W, maxWidth);
+                        maxHeight = Math.max(H, maxHeight);
+                        maxRate = Math.max(FR, maxRate);
+                        minWidth = Math.min(minW, minWidth);
+                        minHeight = Math.min(minH, minHeight);
+                    }
+                    // unless we encountered custom frame size support, limit size to QCIF and CIF
+                    // using aspect ratio.
+                    if (!mAllowMbOverride) {
+                        mBlockAspectRatioRange =
+                            Range.create(new Rational(11, 9), new Rational(11, 9));
+                    }
+                    applyMacroBlockLimits(
+                            minWidth, minHeight,
+                            maxWidth, maxHeight,
+                            maxBlocks, maxBlocksPerSecond,
+                            16 /* blockWidth */, 16 /* blockHeight */,
+                            minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
+                    mFrameRateRange = Range.create(1, maxRate);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
+                    maxBlocks = Integer.MAX_VALUE;
+                    maxBlocksPerSecond = Integer.MAX_VALUE;
+
+                    // TODO: set to 100Mbps for now, need a number for VP8
+                    maxBps = 100000000;
+
+                    // profile levels are not indicative for VPx, but verify
+                    // them nonetheless
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        switch (profileLevel.level) {
+                            case CodecProfileLevel.VP8Level_Version0:
+                            case CodecProfileLevel.VP8Level_Version1:
+                            case CodecProfileLevel.VP8Level_Version2:
+                            case CodecProfileLevel.VP8Level_Version3:
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized level "
+                                        + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.VP8ProfileMain:
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                    }
+
+                    final int blockSize = 16;
+                    applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
+                            maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                    maxBlocksPerSecond = 829440;
+                    maxBlocks = 36864;
+                    maxBps = 200000;
+                    int maxDim = 512;
+
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        long SR = 0; // luma sample rate
+                        int FS = 0;  // luma picture size
+                        int BR = 0;  // bit rate kbps
+                        int D = 0;   // luma dimension
+                        switch (profileLevel.level) {
+                            case CodecProfileLevel.VP9Level1:
+                                SR =      829440; FS =    36864; BR =    200; D =   512; break;
+                            case CodecProfileLevel.VP9Level11:
+                                SR =     2764800; FS =    73728; BR =    800; D =   768; break;
+                            case CodecProfileLevel.VP9Level2:
+                                SR =     4608000; FS =   122880; BR =   1800; D =   960; break;
+                            case CodecProfileLevel.VP9Level21:
+                                SR =     9216000; FS =   245760; BR =   3600; D =  1344; break;
+                            case CodecProfileLevel.VP9Level3:
+                                SR =    20736000; FS =   552960; BR =   7200; D =  2048; break;
+                            case CodecProfileLevel.VP9Level31:
+                                SR =    36864000; FS =   983040; BR =  12000; D =  2752; break;
+                            case CodecProfileLevel.VP9Level4:
+                                SR =    83558400; FS =  2228224; BR =  18000; D =  4160; break;
+                            case CodecProfileLevel.VP9Level41:
+                                SR =   160432128; FS =  2228224; BR =  30000; D =  4160; break;
+                            case CodecProfileLevel.VP9Level5:
+                                SR =   311951360; FS =  8912896; BR =  60000; D =  8384; break;
+                            case CodecProfileLevel.VP9Level51:
+                                SR =   588251136; FS =  8912896; BR = 120000; D =  8384; break;
+                            case CodecProfileLevel.VP9Level52:
+                                SR =  1176502272; FS =  8912896; BR = 180000; D =  8384; break;
+                            case CodecProfileLevel.VP9Level6:
+                                SR =  1176502272; FS = 35651584; BR = 180000; D = 16832; break;
+                            case CodecProfileLevel.VP9Level61:
+                                SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
+                            case CodecProfileLevel.VP9Level62:
+                                SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
+                            default:
+                                Log.w(TAG, "Unrecognized level "
+                                        + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.VP9Profile0:
+                            case CodecProfileLevel.VP9Profile1:
+                            case CodecProfileLevel.VP9Profile2:
+                            case CodecProfileLevel.VP9Profile3:
+                            case CodecProfileLevel.VP9Profile2HDR:
+                            case CodecProfileLevel.VP9Profile3HDR:
+                            case CodecProfileLevel.VP9Profile2HDR10Plus:
+                            case CodecProfileLevel.VP9Profile3HDR10Plus:
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                        maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+                        maxBlocks = Math.max(FS, maxBlocks);
+                        maxBps = Math.max(BR * 1000, maxBps);
+                        maxDim = Math.max(D, maxDim);
+                    }
+
+                    final int blockSize = 8;
+                    int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+                    maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+                    maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+
+                    applyMacroBlockLimits(
+                            maxLengthInBlocks, maxLengthInBlocks,
+                            maxBlocks, maxBlocksPerSecond,
+                            blockSize, blockSize,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                    // CTBs are at least 8x8 so use 8x8 block size
+                    maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
+                    maxBlocksPerSecond = maxBlocks * 15;
+                    maxBps = 128000;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        double FR = 0;
+                        int FS = 0;
+                        int BR = 0;
+                        switch (profileLevel.level) {
+                            /* The HEVC spec talks only in a very convoluted manner about the
+                            existence of levels 1-3.1 for High tier, which could also be
+                            understood as 'decoders and encoders should treat these levels
+                            as if they were Main tier', so we do that. */
+                            case CodecProfileLevel.HEVCMainTierLevel1:
+                            case CodecProfileLevel.HEVCHighTierLevel1:
+                                FR =    15; FS =    36864; BR =    128; break;
+                            case CodecProfileLevel.HEVCMainTierLevel2:
+                            case CodecProfileLevel.HEVCHighTierLevel2:
+                                FR =    30; FS =   122880; BR =   1500; break;
+                            case CodecProfileLevel.HEVCMainTierLevel21:
+                            case CodecProfileLevel.HEVCHighTierLevel21:
+                                FR =    30; FS =   245760; BR =   3000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel3:
+                            case CodecProfileLevel.HEVCHighTierLevel3:
+                                FR =    30; FS =   552960; BR =   6000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel31:
+                            case CodecProfileLevel.HEVCHighTierLevel31:
+                                FR = 33.75; FS =   983040; BR =  10000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel4:
+                                FR =    30; FS =  2228224; BR =  12000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel4:
+                                FR =    30; FS =  2228224; BR =  30000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel41:
+                                FR =    60; FS =  2228224; BR =  20000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel41:
+                                FR =    60; FS =  2228224; BR =  50000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel5:
+                                FR =    30; FS =  8912896; BR =  25000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel5:
+                                FR =    30; FS =  8912896; BR = 100000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel51:
+                                FR =    60; FS =  8912896; BR =  40000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel51:
+                                FR =    60; FS =  8912896; BR = 160000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel52:
+                                FR =   120; FS =  8912896; BR =  60000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel52:
+                                FR =   120; FS =  8912896; BR = 240000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel6:
+                                FR =    30; FS = 35651584; BR =  60000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel6:
+                                FR =    30; FS = 35651584; BR = 240000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel61:
+                                FR =    60; FS = 35651584; BR = 120000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel61:
+                                FR =    60; FS = 35651584; BR = 480000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel62:
+                                FR =   120; FS = 35651584; BR = 240000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel62:
+                                FR =   120; FS = 35651584; BR = 800000; break;
+                            default:
+                                Log.w(TAG, "Unrecognized level "
+                                        + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.HEVCProfileMain:
+                            case CodecProfileLevel.HEVCProfileMain10:
+                            case CodecProfileLevel.HEVCProfileMainStill:
+                            case CodecProfileLevel.HEVCProfileMain10HDR10:
+                            case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+
+                        /* DPB logic:
+                        if      (width * height <= FS / 4)    DPB = 16;
+                        else if (width * height <= FS / 2)    DPB = 12;
+                        else if (width * height <= FS * 0.75) DPB = 8;
+                        else                                  DPB = 6;
+                        */
+
+                        FS >>= 6; // convert pixels to blocks
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                        maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
+                        maxBlocks = Math.max(FS, maxBlocks);
+                        maxBps = Math.max(BR * 1000, maxBps);
+                    }
+
+                    int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+                    applyMacroBlockLimits(
+                            maxLengthInBlocks, maxLengthInBlocks,
+                            maxBlocks, maxBlocksPerSecond,
+                            8 /* blockWidth */, 8 /* blockHeight */,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
+                    maxBlocksPerSecond = 829440;
+                    maxBlocks = 36864;
+                    maxBps = 200000;
+                    int maxDim = 512;
+
+                    // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
+                    // corresponding to the definitions in
+                    // "AV1 Bitstream & Decoding Process Specification", Annex A
+                    // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        long SR = 0; // luma sample rate
+                        int FS = 0;  // luma picture size
+                        int BR = 0;  // bit rate kbps
+                        int D = 0;   // luma D
+                        switch (profileLevel.level) {
+                            case CodecProfileLevel.AV1Level2:
+                                SR =     5529600; FS =   147456; BR =   1500; D =  2048; break;
+                            case CodecProfileLevel.AV1Level21:
+                            case CodecProfileLevel.AV1Level22:
+                            case CodecProfileLevel.AV1Level23:
+                                SR =    10454400; FS =   278784; BR =   3000; D =  2816; break;
+
+                            case CodecProfileLevel.AV1Level3:
+                                SR =    24969600; FS =   665856; BR =   6000; D =  4352; break;
+                            case CodecProfileLevel.AV1Level31:
+                            case CodecProfileLevel.AV1Level32:
+                            case CodecProfileLevel.AV1Level33:
+                                SR =    39938400; FS =  1065024; BR =  10000; D =  5504; break;
+
+                            case CodecProfileLevel.AV1Level4:
+                                SR =    77856768; FS =  2359296; BR =  12000; D =  6144; break;
+                            case CodecProfileLevel.AV1Level41:
+                            case CodecProfileLevel.AV1Level42:
+                            case CodecProfileLevel.AV1Level43:
+                                SR =   155713536; FS =  2359296; BR =  20000; D =  6144; break;
+
+                            case CodecProfileLevel.AV1Level5:
+                                SR =   273715200; FS =  8912896; BR =  30000; D =  8192; break;
+                            case CodecProfileLevel.AV1Level51:
+                                SR =   547430400; FS =  8912896; BR =  40000; D =  8192; break;
+                            case CodecProfileLevel.AV1Level52:
+                                SR =  1094860800; FS =  8912896; BR =  60000; D =  8192; break;
+                            case CodecProfileLevel.AV1Level53:
+                                SR =  1176502272; FS =  8912896; BR =  60000; D =  8192; break;
+
+                            case CodecProfileLevel.AV1Level6:
+                                SR =  1176502272; FS = 35651584; BR =  60000; D = 16384; break;
+                            case CodecProfileLevel.AV1Level61:
+                                SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
+                            case CodecProfileLevel.AV1Level62:
+                                SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
+                            case CodecProfileLevel.AV1Level63:
+                                SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
+
+                            default:
+                                Log.w(TAG, "Unrecognized level "
+                                        + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.AV1ProfileMain8:
+                            case CodecProfileLevel.AV1ProfileMain10:
+                            case CodecProfileLevel.AV1ProfileMain10HDR10:
+                            case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
+                                break;
+                            default:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                        }
+                        errors &= ~ERROR_NONE_SUPPORTED;
+                        maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
+                        maxBlocks = Math.max(FS, maxBlocks);
+                        maxBps = Math.max(BR * 1000, maxBps);
+                        maxDim = Math.max(D, maxDim);
+                    }
+
+                    final int blockSize = 8;
+                    int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+                    maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+                    maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
+                    applyMacroBlockLimits(
+                            maxLengthInBlocks, maxLengthInBlocks,
+                            maxBlocks, maxBlocksPerSecond,
+                            blockSize, blockSize,
+                            1 /* widthAlignment */, 1 /* heightAlignment */);
+                } else {
+                    Log.w(TAG, "Unsupported mime " + mime);
+                    // using minimal bitrate here.  should be overridden by
+                    // info from media_codecs.xml
+                    maxBps = 64000;
+                    errors |= ERROR_UNSUPPORTED;
+                }
+                mBitrateRange = Range.create(1, maxBps);
+                mParent.mError |= errors;
+            }
+        }
+
+        /* package private */ static final class VideoCapsNativeImpl implements VideoCapsIntf {
+            private long mNativeContext; // accessed by native methods
+
+            private Range<Integer> mBitrateRange;
+            private Range<Integer> mHeightRange;
+            private Range<Integer> mWidthRange;
+            private Range<Integer> mFrameRateRange;
+            private List<PerformancePoint> mPerformancePoints;
+
+            private int mWidthAlignment;
+            private int mHeightAlignment;
+
+            // Used by JNI to construct Java VideoCapsNativeImpl
+            /** package private */ VideoCapsNativeImpl(Range<Integer> bitrateRange,
+                    Range<Integer> widthRange, Range<Integer> heightRange,
+                    Range<Integer> frameRateRange, List<PerformancePoint> performancePoints,
+                    int widthAlignment, int heightAlignment) {
+                mBitrateRange = new Range<Integer>(bitrateRange.getLower(),
+                        bitrateRange.getUpper());
+                mWidthRange = new Range<Integer>(widthRange.getLower(), widthRange.getUpper());
+                mHeightRange = new Range<Integer>(heightRange.getLower(), heightRange.getUpper());
+                mFrameRateRange = new Range<Integer>(frameRateRange.getLower(),
+                        frameRateRange.getUpper());
+                mPerformancePoints = new ArrayList<PerformancePoint>();
+                for (PerformancePoint pp : performancePoints) {
+                    mPerformancePoints.add(new PerformancePoint(pp));
+                }
+                mWidthAlignment = widthAlignment;
+                mHeightAlignment = heightAlignment;
+            }
+
+            /* no public constructor */
+            private VideoCapsNativeImpl() { }
+
+            public Range<Integer> getBitrateRange() {
+                return mBitrateRange;
+            }
+
+            public Range<Integer> getSupportedWidths() {
+                return mWidthRange;
+            }
+
+            public Range<Integer> getSupportedHeights() {
+                return mHeightRange;
+            }
+
+            public int getWidthAlignment() {
+                return mWidthAlignment;
+            }
+
+            public int getHeightAlignment() {
+                return mHeightAlignment;
+            }
+
+            /** @hide */
+            public int getSmallerDimensionUpperLimit() {
+                return native_getSmallerDimensionUpperLimit();
+            }
+
+            public Range<Integer> getSupportedFrameRates() {
+                return mFrameRateRange;
+            }
+
+            @Nullable
+            public List<PerformancePoint> getSupportedPerformancePoints() {
+                return mPerformancePoints;
+            }
+
+            public Range<Integer> getSupportedWidthsFor(int height) {
+                return native_getSupportedWidthsFor(height);
+            }
+
+            public Range<Integer> getSupportedHeightsFor(int width) {
+                return native_getSupportedHeightsFor(width);
+            }
+
+            public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+                return native_getSupportedFrameRatesFor(width, height);
+            }
+
+            /** @throws IllegalArgumentException if the video size is not supported. */
+            @Nullable
+            public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+                return native_getAchievableFrameRatesFor(width, height);
+            }
+
+            public boolean areSizeAndRateSupported(int width, int height, double frameRate) {
+                return native_areSizeAndRateSupported(width, height, frameRate);
+            }
+
+            public boolean isSizeSupported(int width, int height) {
+                return native_isSizeSupported(width, height);
+            }
+
+            /** @hide */
+            public boolean supportsFormat(MediaFormat format) {
+                throw new UnsupportedOperationException(
+                    "Java Implementation should not call native implemenatation");
+            }
+
+            private native Range<Integer> native_getSupportedWidthsFor(int height);
+            private native Range<Integer> native_getSupportedHeightsFor(int width);
+            private native Range<Double> native_getSupportedFrameRatesFor(int width, int height);
+            private native Range<Double> native_getAchievableFrameRatesFor(int width, int height);
+            private native boolean native_areSizeAndRateSupported(
+                    int width, int height, double frameRate);
+            private native boolean native_isSizeSupported(int width, int height);
+            private native int native_getSmallerDimensionUpperLimit();
+
+            private static native void native_init();
+
+            static {
+                System.loadLibrary("media_jni");
+                native_init();
+            }
+        }
+
+        private VideoCapsIntf mImpl;
+
+        /** @hide */
+        public static VideoCapabilities create(
+                MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+            VideoCapsLegacyImpl impl = VideoCapsLegacyImpl.create(info, parent);
+            VideoCapabilities caps = new VideoCapabilities(impl);
+            return caps;
+        }
+
+        /* package private */ VideoCapabilities(VideoCapsIntf impl) {
+            mImpl = impl;
+        }
+
+        /* no public constructor */
+        private VideoCapabilities() { }
+
+        /**
+         * Returns the range of supported bitrates in bits/second.
+         */
+        public Range<Integer> getBitrateRange() {
+            return mImpl.getBitrateRange();
+        }
+
+        /**
+         * Returns the range of supported video widths.
+         * <p class=note>
+         * 32-bit processes will not support resolutions larger than 4096x4096 due to
+         * the limited address space.
+         */
+        public Range<Integer> getSupportedWidths() {
+            return mImpl.getSupportedWidths();
+        }
+
+        /**
+         * Returns the range of supported video heights.
+         * <p class=note>
+         * 32-bit processes will not support resolutions larger than 4096x4096 due to
+         * the limited address space.
+         */
+        public Range<Integer> getSupportedHeights() {
+            return mImpl.getSupportedHeights();
+        }
+
+        /**
+         * Returns the alignment requirement for video width (in pixels).
+         *
+         * This is a power-of-2 value that video width must be a
+         * multiple of.
+         */
+        public int getWidthAlignment() {
+            return mImpl.getWidthAlignment();
+        }
+
+        /**
+         * Returns the alignment requirement for video height (in pixels).
+         *
+         * This is a power-of-2 value that video height must be a
+         * multiple of.
+         */
+        public int getHeightAlignment() {
+            return mImpl.getWidthAlignment();
+        }
+
+        /**
+         * Return the upper limit on the smaller dimension of width or height.
+         * <p></p>
+         * Some codecs have a limit on the smaller dimension, whether it be
+         * the width or the height.  E.g. a codec may only be able to handle
+         * up to 1920x1080 both in landscape and portrait mode (1080x1920).
+         * In this case the maximum width and height are both 1920, but the
+         * smaller dimension limit will be 1080. For other codecs, this is
+         * {@code Math.min(getSupportedWidths().getUpper(),
+         * getSupportedHeights().getUpper())}.
+         *
+         * @hide
+         */
+        public int getSmallerDimensionUpperLimit() {
+            return mImpl.getSmallerDimensionUpperLimit();
+        }
+
+        /**
+         * Returns the range of supported frame rates.
+         * <p>
+         * This is not a performance indicator.  Rather, it expresses the
+         * limits specified in the coding standard, based on the complexities
+         * of encoding material for later playback at a certain frame rate,
+         * or the decoding of such material in non-realtime.
+         */
+        public Range<Integer> getSupportedFrameRates() {
+            return mImpl.getSupportedFrameRates();
+        }
+
+        /**
+         * Returns the range of supported video widths for a video height.
+         * @param height the height of the video
+         */
+        public Range<Integer> getSupportedWidthsFor(int height) {
+            return mImpl.getSupportedWidthsFor(height);
+        }
+
+        /**
+         * Returns the range of supported video heights for a video width
+         * @param width the width of the video
+         */
+        public Range<Integer> getSupportedHeightsFor(int width) {
+            return mImpl.getSupportedHeightsFor(width);
+        }
+
+        /**
+         * Returns the range of supported video frame rates for a video size.
+         * <p>
+         * This is not a performance indicator.  Rather, it expresses the limits specified in
+         * the coding standard, based on the complexities of encoding material of a given
+         * size for later playback at a certain frame rate, or the decoding of such material
+         * in non-realtime.
+
+         * @param width the width of the video
+         * @param height the height of the video
+         */
+        public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+            return mImpl.getSupportedFrameRatesFor(width, height);
+        }
+
+        /**
+         * Returns the range of achievable video frame rates for a video size.
+         * May return {@code null}, if the codec did not publish any measurement
+         * data.
+         * <p>
+         * This is a performance estimate provided by the device manufacturer based on statistical
+         * sampling of full-speed decoding and encoding measurements in various configurations
+         * of common video sizes supported by the codec. As such it should only be used to
+         * compare individual codecs on the device. The value is not suitable for comparing
+         * different devices or even different android releases for the same device.
+         * <p>
+         * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
+         * corresponds to the fastest frame rates achieved in the tested configurations. As
+         * such, it should not be used to gauge guaranteed or even average codec performance
+         * on the device.
+         * <p>
+         * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
+         * corresponds closer to sustained performance <em>in tested configurations</em>.
+         * One can expect to achieve sustained performance higher than the lower limit more than
+         * 50% of the time, and higher than half of the lower limit at least 90% of the time
+         * <em>in tested configurations</em>.
+         * Conversely, one can expect performance lower than twice the upper limit at least
+         * 90% of the time.
+         * <p class=note>
+         * Tested configurations use a single active codec. For use cases where multiple
+         * codecs are active, applications can expect lower and in most cases significantly lower
+         * performance.
+         * <p class=note>
+         * The returned range value is interpolated from the nearest frame size(s) tested.
+         * Codec performance is severely impacted by other activity on the device as well
+         * as environmental factors (such as battery level, temperature or power source), and can
+         * vary significantly even in a steady environment.
+         * <p class=note>
+         * Use this method in cases where only codec performance matters, e.g. to evaluate if
+         * a codec has any chance of meeting a performance target. Codecs are listed
+         * in {@link MediaCodecList} in the preferred order as defined by the device
+         * manufacturer. As such, applications should use the first suitable codec in the
+         * list to achieve the best balance between power use and performance.
+         *
+         * @param width the width of the video
+         * @param height the height of the video
+         *
+         * @throws IllegalArgumentException if the video size is not supported.
+         */
+        @Nullable
+        public Range<Double> getAchievableFrameRatesFor(int width, int height) {
+            return mImpl.getAchievableFrameRatesFor(width, height);
+        }
+
         /**
          * Returns the supported performance points. May return {@code null} if the codec did not
          * publish any performance point information (e.g. the vendor codecs have not been updated
@@ -2542,16 +4518,15 @@
          */
         @Nullable
         public List<PerformancePoint> getSupportedPerformancePoints() {
-            return mPerformancePoints;
+            return mImpl.getSupportedPerformancePoints();
         }
 
         /**
          * Returns whether a given video size ({@code width} and
          * {@code height}) and {@code frameRate} combination is supported.
          */
-        public boolean areSizeAndRateSupported(
-                int width, int height, double frameRate) {
-            return supports(width, height, frameRate);
+        public boolean areSizeAndRateSupported(int width, int height, double frameRate) {
+            return mImpl.areSizeAndRateSupported(width, height, frameRate);
         }
 
         /**
@@ -2559,1282 +4534,16 @@
          * {@code height}) is supported.
          */
         public boolean isSizeSupported(int width, int height) {
-            return supports(width, height, null);
+            return mImpl.isSizeSupported(width, height);
         }
 
-        private boolean supports(Integer width, Integer height, Number rate) {
-            boolean ok = true;
-
-            if (ok && width != null) {
-                ok = mWidthRange.contains(width)
-                        && (width % mWidthAlignment == 0);
-            }
-            if (ok && height != null) {
-                ok = mHeightRange.contains(height)
-                        && (height % mHeightAlignment == 0);
-            }
-            if (ok && rate != null) {
-                ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
-            }
-            if (ok && height != null && width != null) {
-                ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
-
-                final int widthInBlocks = Utils.divUp(width, mBlockWidth);
-                final int heightInBlocks = Utils.divUp(height, mBlockHeight);
-                final int blockCount = widthInBlocks * heightInBlocks;
-                ok = ok && mBlockCountRange.contains(blockCount)
-                        && mBlockAspectRatioRange.contains(
-                                new Rational(widthInBlocks, heightInBlocks))
-                        && mAspectRatioRange.contains(new Rational(width, height));
-                if (ok && rate != null) {
-                    double blocksPerSec = blockCount * rate.doubleValue();
-                    ok = mBlocksPerSecondRange.contains(
-                            Utils.longRangeFor(blocksPerSec));
-                }
-            }
-            return ok;
-        }
-
-        /* package private */
-        // must not contain KEY_PROFILE
-        static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of(
-                MediaFormat.KEY_WIDTH,
-                MediaFormat.KEY_HEIGHT,
-                MediaFormat.KEY_FRAME_RATE,
-                MediaFormat.KEY_BIT_RATE,
-                MediaFormat.KEY_MIME);
-
         /**
          * @hide
-         * @throws java.lang.ClassCastException */
+         * @throws java.lang.ClassCastException
+         * @throws java.lang.UnsupportedOperationException
+         */
         public boolean supportsFormat(MediaFormat format) {
-            final Map<String, Object> map = format.getMap();
-            Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
-            Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
-            Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
-
-            if (!supports(width, height, rate)) {
-                return false;
-            }
-
-            if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
-                return false;
-            }
-
-            // we ignore color-format for now as it is not reliably reported by codec
-            return true;
-        }
-
-        /* no public constructor */
-        private VideoCapabilities() { }
-
-        /** @hide */
-        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-        public static VideoCapabilities create(
-                MediaFormat info, CodecCapabilities parent) {
-            VideoCapabilities caps = new VideoCapabilities();
-            caps.init(info, parent);
-            return caps;
-        }
-
-        private void init(MediaFormat info, CodecCapabilities parent) {
-            mParent = parent;
-            initWithPlatformLimits();
-            applyLevelLimits();
-            parseFromInfo(info);
-            updateLimits();
-        }
-
-        /** @hide */
-        public Size getBlockSize() {
-            return new Size(mBlockWidth, mBlockHeight);
-        }
-
-        /** @hide */
-        public Range<Integer> getBlockCountRange() {
-            return mBlockCountRange;
-        }
-
-        /** @hide */
-        public Range<Long> getBlocksPerSecondRange() {
-            return mBlocksPerSecondRange;
-        }
-
-        /** @hide */
-        public Range<Rational> getAspectRatioRange(boolean blocks) {
-            return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
-        }
-
-        private void initWithPlatformLimits() {
-            mBitrateRange = BITRATE_RANGE;
-
-            mWidthRange  = getSizeRange();
-            mHeightRange = getSizeRange();
-            mFrameRateRange = FRAME_RATE_RANGE;
-
-            mHorizontalBlockRange = getSizeRange();
-            mVerticalBlockRange   = getSizeRange();
-
-            // full positive ranges are supported as these get calculated
-            mBlockCountRange      = POSITIVE_INTEGERS;
-            mBlocksPerSecondRange = POSITIVE_LONGS;
-
-            mBlockAspectRatioRange = POSITIVE_RATIONALS;
-            mAspectRatioRange      = POSITIVE_RATIONALS;
-
-            mWidthAlignment = 1;
-            mHeightAlignment = 1;
-            mBlockWidth = 1;
-            mBlockHeight = 1;
-            mSmallerDimensionUpperLimit = getSizeRange().getUpper();
-        }
-
-        private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
-            Vector<PerformancePoint> ret = new Vector<>();
-            final String prefix = "performance-point-";
-            Set<String> keys = map.keySet();
-            for (String key : keys) {
-                // looking for: performance-point-WIDTHxHEIGHT-range
-                if (!key.startsWith(prefix)) {
-                    continue;
-                }
-                String subKey = key.substring(prefix.length());
-                if (subKey.equals("none") && ret.size() == 0) {
-                    // This means that component knowingly did not publish performance points.
-                    // This is different from when the component forgot to publish performance
-                    // points.
-                    return Collections.unmodifiableList(ret);
-                }
-                String[] temp = key.split("-");
-                if (temp.length != 4) {
-                    continue;
-                }
-                String sizeStr = temp[2];
-                Size size = Utils.parseSize(sizeStr, null);
-                if (size == null || size.getWidth() * size.getHeight() <= 0) {
-                    continue;
-                }
-                Range<Long> range = Utils.parseLongRange(map.get(key), null);
-                if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
-                    continue;
-                }
-                PerformancePoint given = new PerformancePoint(
-                        size.getWidth(), size.getHeight(), range.getLower().intValue(),
-                        range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
-                PerformancePoint rotated = new PerformancePoint(
-                        size.getHeight(), size.getWidth(), range.getLower().intValue(),
-                        range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
-                ret.add(given);
-                if (!given.covers(rotated)) {
-                    ret.add(rotated);
-                }
-            }
-
-            // check if the component specified no performance point indication
-            if (ret.size() == 0) {
-                return null;
-            }
-
-            // sort reversed by area first, then by frame rate
-            ret.sort((a, b) ->
-                     -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
-                               (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
-                       (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
-                               (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
-                       (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
-                               (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
-
-            return Collections.unmodifiableList(ret);
-        }
-
-        private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
-            Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
-            final String prefix = "measured-frame-rate-";
-            Set<String> keys = map.keySet();
-            for (String key : keys) {
-                // looking for: measured-frame-rate-WIDTHxHEIGHT-range
-                if (!key.startsWith(prefix)) {
-                    continue;
-                }
-                String subKey = key.substring(prefix.length());
-                String[] temp = key.split("-");
-                if (temp.length != 5) {
-                    continue;
-                }
-                String sizeStr = temp[3];
-                Size size = Utils.parseSize(sizeStr, null);
-                if (size == null || size.getWidth() * size.getHeight() <= 0) {
-                    continue;
-                }
-                Range<Long> range = Utils.parseLongRange(map.get(key), null);
-                if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
-                    continue;
-                }
-                ret.put(size, range);
-            }
-            return ret;
-        }
-
-        private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
-            Pair<Size, Size> range = Utils.parseSizeRange(o);
-            if (range != null) {
-                try {
-                    return Pair.create(
-                            Range.create(range.first.getWidth(), range.second.getWidth()),
-                            Range.create(range.first.getHeight(), range.second.getHeight()));
-                } catch (IllegalArgumentException e) {
-                    Log.w(TAG, "could not parse size range '" + o + "'");
-                }
-            }
-            return null;
-        }
-
-        /** @hide */
-        public static int equivalentVP9Level(MediaFormat info) {
-            final Map<String, Object> map = info.getMap();
-
-            Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
-            int BS = blockSize.getWidth() * blockSize.getHeight();
-
-            Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
-            int FS = counts == null ? 0 : BS * counts.getUpper();
-
-            Range<Long> blockRates =
-                Utils.parseLongRange(map.get("blocks-per-second-range"), null);
-            long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
-
-            Pair<Range<Integer>, Range<Integer>> dimensionRanges =
-                parseWidthHeightRanges(map.get("size-range"));
-            int D = dimensionRanges == null ? 0 : Math.max(
-                    dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
-
-            Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
-            int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
-
-            if (SR <=      829440 && FS <=    36864 && BR <=    200 && D <=   512)
-                return CodecProfileLevel.VP9Level1;
-            if (SR <=     2764800 && FS <=    73728 && BR <=    800 && D <=   768)
-                return CodecProfileLevel.VP9Level11;
-            if (SR <=     4608000 && FS <=   122880 && BR <=   1800 && D <=   960)
-                return CodecProfileLevel.VP9Level2;
-            if (SR <=     9216000 && FS <=   245760 && BR <=   3600 && D <=  1344)
-                return CodecProfileLevel.VP9Level21;
-            if (SR <=    20736000 && FS <=   552960 && BR <=   7200 && D <=  2048)
-                return CodecProfileLevel.VP9Level3;
-            if (SR <=    36864000 && FS <=   983040 && BR <=  12000 && D <=  2752)
-                return CodecProfileLevel.VP9Level31;
-            if (SR <=    83558400 && FS <=  2228224 && BR <=  18000 && D <=  4160)
-                return CodecProfileLevel.VP9Level4;
-            if (SR <=   160432128 && FS <=  2228224 && BR <=  30000 && D <=  4160)
-                return CodecProfileLevel.VP9Level41;
-            if (SR <=   311951360 && FS <=  8912896 && BR <=  60000 && D <=  8384)
-                return CodecProfileLevel.VP9Level5;
-            if (SR <=   588251136 && FS <=  8912896 && BR <= 120000 && D <=  8384)
-                return CodecProfileLevel.VP9Level51;
-            if (SR <=  1176502272 && FS <=  8912896 && BR <= 180000 && D <=  8384)
-                return CodecProfileLevel.VP9Level52;
-            if (SR <=  1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
-                return CodecProfileLevel.VP9Level6;
-            if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
-                return CodecProfileLevel.VP9Level61;
-            if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
-                return CodecProfileLevel.VP9Level62;
-            // returning largest level
-            return CodecProfileLevel.VP9Level62;
-        }
-
-        private void parseFromInfo(MediaFormat info) {
-            final Map<String, Object> map = info.getMap();
-            Size blockSize = new Size(mBlockWidth, mBlockHeight);
-            Size alignment = new Size(mWidthAlignment, mHeightAlignment);
-            Range<Integer> counts = null, widths = null, heights = null;
-            Range<Integer> frameRates = null, bitRates = null;
-            Range<Long> blockRates = null;
-            Range<Rational> ratios = null, blockRatios = null;
-
-            blockSize = Utils.parseSize(map.get("block-size"), blockSize);
-            alignment = Utils.parseSize(map.get("alignment"), alignment);
-            counts = Utils.parseIntRange(map.get("block-count-range"), null);
-            blockRates =
-                Utils.parseLongRange(map.get("blocks-per-second-range"), null);
-            mMeasuredFrameRates = getMeasuredFrameRates(map);
-            mPerformancePoints = getPerformancePoints(map);
-            Pair<Range<Integer>, Range<Integer>> sizeRanges =
-                parseWidthHeightRanges(map.get("size-range"));
-            if (sizeRanges != null) {
-                widths = sizeRanges.first;
-                heights = sizeRanges.second;
-            }
-            // for now this just means using the smaller max size as 2nd
-            // upper limit.
-            // for now we are keeping the profile specific "width/height
-            // in macroblocks" limits.
-            if (map.containsKey("feature-can-swap-width-height")) {
-                if (widths != null) {
-                    mSmallerDimensionUpperLimit =
-                        Math.min(widths.getUpper(), heights.getUpper());
-                    widths = heights = widths.extend(heights);
-                } else {
-                    Log.w(TAG, "feature can-swap-width-height is best used with size-range");
-                    mSmallerDimensionUpperLimit =
-                        Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
-                    mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
-                }
-            }
-
-            ratios = Utils.parseRationalRange(
-                    map.get("block-aspect-ratio-range"), null);
-            blockRatios = Utils.parseRationalRange(
-                    map.get("pixel-aspect-ratio-range"), null);
-            frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
-            if (frameRates != null) {
-                try {
-                    frameRates = frameRates.intersect(FRAME_RATE_RANGE);
-                } catch (IllegalArgumentException e) {
-                    Log.w(TAG, "frame rate range (" + frameRates
-                            + ") is out of limits: " + FRAME_RATE_RANGE);
-                    frameRates = null;
-                }
-            }
-            bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
-            if (bitRates != null) {
-                try {
-                    bitRates = bitRates.intersect(BITRATE_RANGE);
-                } catch (IllegalArgumentException e) {
-                    Log.w(TAG,  "bitrate range (" + bitRates
-                            + ") is out of limits: " + BITRATE_RANGE);
-                    bitRates = null;
-                }
-            }
-
-            checkPowerOfTwo(
-                    blockSize.getWidth(), "block-size width must be power of two");
-            checkPowerOfTwo(
-                    blockSize.getHeight(), "block-size height must be power of two");
-
-            checkPowerOfTwo(
-                    alignment.getWidth(), "alignment width must be power of two");
-            checkPowerOfTwo(
-                    alignment.getHeight(), "alignment height must be power of two");
-
-            // update block-size and alignment
-            applyMacroBlockLimits(
-                    Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
-                    Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
-                    alignment.getWidth(), alignment.getHeight());
-
-            if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
-                // codec supports profiles that we don't know.
-                // Use supplied values clipped to platform limits
-                if (widths != null) {
-                    mWidthRange = getSizeRange().intersect(widths);
-                }
-                if (heights != null) {
-                    mHeightRange = getSizeRange().intersect(heights);
-                }
-                if (counts != null) {
-                    mBlockCountRange = POSITIVE_INTEGERS.intersect(
-                            Utils.factorRange(counts, mBlockWidth * mBlockHeight
-                                    / blockSize.getWidth() / blockSize.getHeight()));
-                }
-                if (blockRates != null) {
-                    mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
-                            Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
-                                    / blockSize.getWidth() / blockSize.getHeight()));
-                }
-                if (blockRatios != null) {
-                    mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
-                            Utils.scaleRange(blockRatios,
-                                    mBlockHeight / blockSize.getHeight(),
-                                    mBlockWidth / blockSize.getWidth()));
-                }
-                if (ratios != null) {
-                    mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
-                }
-                if (frameRates != null) {
-                    mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
-                }
-                if (bitRates != null) {
-                    // only allow bitrate override if unsupported profiles were encountered
-                    if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
-                        mBitrateRange = BITRATE_RANGE.intersect(bitRates);
-                    } else {
-                        mBitrateRange = mBitrateRange.intersect(bitRates);
-                    }
-                }
-            } else {
-                // no unsupported profile/levels, so restrict values to known limits
-                if (widths != null) {
-                    mWidthRange = mWidthRange.intersect(widths);
-                }
-                if (heights != null) {
-                    mHeightRange = mHeightRange.intersect(heights);
-                }
-                if (counts != null) {
-                    mBlockCountRange = mBlockCountRange.intersect(
-                            Utils.factorRange(counts, mBlockWidth * mBlockHeight
-                                    / blockSize.getWidth() / blockSize.getHeight()));
-                }
-                if (blockRates != null) {
-                    mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
-                            Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
-                                    / blockSize.getWidth() / blockSize.getHeight()));
-                }
-                if (blockRatios != null) {
-                    mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
-                            Utils.scaleRange(blockRatios,
-                                    mBlockHeight / blockSize.getHeight(),
-                                    mBlockWidth / blockSize.getWidth()));
-                }
-                if (ratios != null) {
-                    mAspectRatioRange = mAspectRatioRange.intersect(ratios);
-                }
-                if (frameRates != null) {
-                    mFrameRateRange = mFrameRateRange.intersect(frameRates);
-                }
-                if (bitRates != null) {
-                    mBitrateRange = mBitrateRange.intersect(bitRates);
-                }
-            }
-            updateLimits();
-        }
-
-        private void applyBlockLimits(
-                int blockWidth, int blockHeight,
-                Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
-            checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
-            checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
-
-            final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
-            final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
-
-            // factor will always be a power-of-2
-            int factor =
-                newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
-            if (factor != 1) {
-                mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
-                mBlocksPerSecondRange = Utils.factorRange(
-                        mBlocksPerSecondRange, factor);
-                mBlockAspectRatioRange = Utils.scaleRange(
-                        mBlockAspectRatioRange,
-                        newBlockHeight / mBlockHeight,
-                        newBlockWidth / mBlockWidth);
-                mHorizontalBlockRange = Utils.factorRange(
-                        mHorizontalBlockRange, newBlockWidth / mBlockWidth);
-                mVerticalBlockRange = Utils.factorRange(
-                        mVerticalBlockRange, newBlockHeight / mBlockHeight);
-            }
-            factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
-            if (factor != 1) {
-                counts = Utils.factorRange(counts, factor);
-                rates = Utils.factorRange(rates, factor);
-                ratios = Utils.scaleRange(
-                        ratios, newBlockHeight / blockHeight,
-                        newBlockWidth / blockWidth);
-            }
-            mBlockCountRange = mBlockCountRange.intersect(counts);
-            mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
-            mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
-            mBlockWidth = newBlockWidth;
-            mBlockHeight = newBlockHeight;
-        }
-
-        private void applyAlignment(int widthAlignment, int heightAlignment) {
-            checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
-            checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
-
-            if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
-                // maintain assumption that 0 < alignment <= block-size
-                applyBlockLimits(
-                        Math.max(widthAlignment, mBlockWidth),
-                        Math.max(heightAlignment, mBlockHeight),
-                        POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
-            }
-
-            mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
-            mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
-
-            mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
-            mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
-        }
-
-        private void updateLimits() {
-            // pixels -> blocks <- counts
-            mHorizontalBlockRange = mHorizontalBlockRange.intersect(
-                    Utils.factorRange(mWidthRange, mBlockWidth));
-            mHorizontalBlockRange = mHorizontalBlockRange.intersect(
-                    Range.create(
-                            mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
-                            mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
-            mVerticalBlockRange = mVerticalBlockRange.intersect(
-                    Utils.factorRange(mHeightRange, mBlockHeight));
-            mVerticalBlockRange = mVerticalBlockRange.intersect(
-                    Range.create(
-                            mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
-                            mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
-            mBlockCountRange = mBlockCountRange.intersect(
-                    Range.create(
-                            mHorizontalBlockRange.getLower()
-                                    * mVerticalBlockRange.getLower(),
-                            mHorizontalBlockRange.getUpper()
-                                    * mVerticalBlockRange.getUpper()));
-            mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
-                    new Rational(
-                            mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
-                    new Rational(
-                            mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
-
-            // blocks -> pixels
-            mWidthRange = mWidthRange.intersect(
-                    (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
-                    mHorizontalBlockRange.getUpper() * mBlockWidth);
-            mHeightRange = mHeightRange.intersect(
-                    (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
-                    mVerticalBlockRange.getUpper() * mBlockHeight);
-            mAspectRatioRange = mAspectRatioRange.intersect(
-                    new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
-                    new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
-
-            mSmallerDimensionUpperLimit = Math.min(
-                    mSmallerDimensionUpperLimit,
-                    Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
-
-            // blocks -> rate
-            mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
-                    mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
-                    mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
-            mFrameRateRange = mFrameRateRange.intersect(
-                    (int)(mBlocksPerSecondRange.getLower()
-                            / mBlockCountRange.getUpper()),
-                    (int)(mBlocksPerSecondRange.getUpper()
-                            / (double)mBlockCountRange.getLower()));
-        }
-
-        private void applyMacroBlockLimits(
-                int maxHorizontalBlocks, int maxVerticalBlocks,
-                int maxBlocks, long maxBlocksPerSecond,
-                int blockWidth, int blockHeight,
-                int widthAlignment, int heightAlignment) {
-            applyMacroBlockLimits(
-                    1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
-                    maxHorizontalBlocks, maxVerticalBlocks,
-                    maxBlocks, maxBlocksPerSecond,
-                    blockWidth, blockHeight, widthAlignment, heightAlignment);
-        }
-
-        private void applyMacroBlockLimits(
-                int minHorizontalBlocks, int minVerticalBlocks,
-                int maxHorizontalBlocks, int maxVerticalBlocks,
-                int maxBlocks, long maxBlocksPerSecond,
-                int blockWidth, int blockHeight,
-                int widthAlignment, int heightAlignment) {
-            applyAlignment(widthAlignment, heightAlignment);
-            applyBlockLimits(
-                    blockWidth, blockHeight, Range.create(1, maxBlocks),
-                    Range.create(1L, maxBlocksPerSecond),
-                    Range.create(
-                            new Rational(1, maxVerticalBlocks),
-                            new Rational(maxHorizontalBlocks, 1)));
-            mHorizontalBlockRange =
-                    mHorizontalBlockRange.intersect(
-                            Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
-                            maxHorizontalBlocks / (mBlockWidth / blockWidth));
-            mVerticalBlockRange =
-                    mVerticalBlockRange.intersect(
-                            Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
-                            maxVerticalBlocks / (mBlockHeight / blockHeight));
-        }
-
-        private void applyLevelLimits() {
-            long maxBlocksPerSecond = 0;
-            int maxBlocks = 0;
-            int maxBps = 0;
-            int maxDPBBlocks = 0;
-
-            int errors = ERROR_NONE_SUPPORTED;
-            CodecProfileLevel[] profileLevels = mParent.profileLevels;
-            String mime = mParent.getMimeType();
-
-            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-                maxBlocks = 99;
-                maxBlocksPerSecond = 1485;
-                maxBps = 64000;
-                maxDPBBlocks = 396;
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    int MBPS = 0, FS = 0, BR = 0, DPB = 0;
-                    boolean supported = true;
-                    switch (profileLevel.level) {
-                        case CodecProfileLevel.AVCLevel1:
-                            MBPS =     1485; FS =     99; BR =     64; DPB =    396; break;
-                        case CodecProfileLevel.AVCLevel1b:
-                            MBPS =     1485; FS =     99; BR =    128; DPB =    396; break;
-                        case CodecProfileLevel.AVCLevel11:
-                            MBPS =     3000; FS =    396; BR =    192; DPB =    900; break;
-                        case CodecProfileLevel.AVCLevel12:
-                            MBPS =     6000; FS =    396; BR =    384; DPB =   2376; break;
-                        case CodecProfileLevel.AVCLevel13:
-                            MBPS =    11880; FS =    396; BR =    768; DPB =   2376; break;
-                        case CodecProfileLevel.AVCLevel2:
-                            MBPS =    11880; FS =    396; BR =   2000; DPB =   2376; break;
-                        case CodecProfileLevel.AVCLevel21:
-                            MBPS =    19800; FS =    792; BR =   4000; DPB =   4752; break;
-                        case CodecProfileLevel.AVCLevel22:
-                            MBPS =    20250; FS =   1620; BR =   4000; DPB =   8100; break;
-                        case CodecProfileLevel.AVCLevel3:
-                            MBPS =    40500; FS =   1620; BR =  10000; DPB =   8100; break;
-                        case CodecProfileLevel.AVCLevel31:
-                            MBPS =   108000; FS =   3600; BR =  14000; DPB =  18000; break;
-                        case CodecProfileLevel.AVCLevel32:
-                            MBPS =   216000; FS =   5120; BR =  20000; DPB =  20480; break;
-                        case CodecProfileLevel.AVCLevel4:
-                            MBPS =   245760; FS =   8192; BR =  20000; DPB =  32768; break;
-                        case CodecProfileLevel.AVCLevel41:
-                            MBPS =   245760; FS =   8192; BR =  50000; DPB =  32768; break;
-                        case CodecProfileLevel.AVCLevel42:
-                            MBPS =   522240; FS =   8704; BR =  50000; DPB =  34816; break;
-                        case CodecProfileLevel.AVCLevel5:
-                            MBPS =   589824; FS =  22080; BR = 135000; DPB = 110400; break;
-                        case CodecProfileLevel.AVCLevel51:
-                            MBPS =   983040; FS =  36864; BR = 240000; DPB = 184320; break;
-                        case CodecProfileLevel.AVCLevel52:
-                            MBPS =  2073600; FS =  36864; BR = 240000; DPB = 184320; break;
-                        case CodecProfileLevel.AVCLevel6:
-                            MBPS =  4177920; FS = 139264; BR = 240000; DPB = 696320; break;
-                        case CodecProfileLevel.AVCLevel61:
-                            MBPS =  8355840; FS = 139264; BR = 480000; DPB = 696320; break;
-                        case CodecProfileLevel.AVCLevel62:
-                            MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
-                        default:
-                            Log.w(TAG, "Unrecognized level "
-                                    + profileLevel.level + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.AVCProfileConstrainedHigh:
-                        case CodecProfileLevel.AVCProfileHigh:
-                            BR *= 1250; break;
-                        case CodecProfileLevel.AVCProfileHigh10:
-                            BR *= 3000; break;
-                        case CodecProfileLevel.AVCProfileExtended:
-                        case CodecProfileLevel.AVCProfileHigh422:
-                        case CodecProfileLevel.AVCProfileHigh444:
-                            Log.w(TAG, "Unsupported profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNSUPPORTED;
-                            supported = false;
-                            // fall through - treat as base profile
-                        case CodecProfileLevel.AVCProfileConstrainedBaseline:
-                        case CodecProfileLevel.AVCProfileBaseline:
-                        case CodecProfileLevel.AVCProfileMain:
-                            BR *= 1000; break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                            BR *= 1000;
-                    }
-                    if (supported) {
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                    }
-                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                    maxBlocks = Math.max(FS, maxBlocks);
-                    maxBps = Math.max(BR, maxBps);
-                    maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
-                }
-
-                int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
-                applyMacroBlockLimits(
-                        maxLengthInBlocks, maxLengthInBlocks,
-                        maxBlocks, maxBlocksPerSecond,
-                        16 /* blockWidth */, 16 /* blockHeight */,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
-                int maxWidth = 11, maxHeight = 9, maxRate = 15;
-                maxBlocks = 99;
-                maxBlocksPerSecond = 1485;
-                maxBps = 64000;
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
-                    boolean supported = true;
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.MPEG2ProfileSimple:
-                            switch (profileLevel.level) {
-                                case CodecProfileLevel.MPEG2LevelML:
-                                    FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR =  15000; break;
-                                default:
-                                    Log.w(TAG, "Unrecognized profile/level "
-                                            + profileLevel.profile + "/"
-                                            + profileLevel.level + " for " + mime);
-                                    errors |= ERROR_UNRECOGNIZED;
-                            }
-                            break;
-                        case CodecProfileLevel.MPEG2ProfileMain:
-                            switch (profileLevel.level) {
-                                case CodecProfileLevel.MPEG2LevelLL:
-                                    FR = 30; W = 22; H =  18; MBPS =  11880; FS =   396; BR =  4000; break;
-                                case CodecProfileLevel.MPEG2LevelML:
-                                    FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR = 15000; break;
-                                case CodecProfileLevel.MPEG2LevelH14:
-                                    FR = 60; W = 90; H =  68; MBPS = 183600; FS =  6120; BR = 60000; break;
-                                case CodecProfileLevel.MPEG2LevelHL:
-                                    FR = 60; W = 120; H = 68; MBPS = 244800; FS =  8160; BR = 80000; break;
-                                case CodecProfileLevel.MPEG2LevelHP:
-                                    FR = 60; W = 120; H = 68; MBPS = 489600; FS =  8160; BR = 80000; break;
-                                default:
-                                    Log.w(TAG, "Unrecognized profile/level "
-                                            + profileLevel.profile + "/"
-                                            + profileLevel.level + " for " + mime);
-                                    errors |= ERROR_UNRECOGNIZED;
-                            }
-                            break;
-                        case CodecProfileLevel.MPEG2Profile422:
-                        case CodecProfileLevel.MPEG2ProfileSNR:
-                        case CodecProfileLevel.MPEG2ProfileSpatial:
-                        case CodecProfileLevel.MPEG2ProfileHigh:
-                            Log.i(TAG, "Unsupported profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNSUPPORTED;
-                            supported = false;
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    if (supported) {
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                    }
-                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                    maxBlocks = Math.max(FS, maxBlocks);
-                    maxBps = Math.max(BR * 1000, maxBps);
-                    maxWidth = Math.max(W, maxWidth);
-                    maxHeight = Math.max(H, maxHeight);
-                    maxRate = Math.max(FR, maxRate);
-                }
-                applyMacroBlockLimits(maxWidth, maxHeight,
-                        maxBlocks, maxBlocksPerSecond,
-                        16 /* blockWidth */, 16 /* blockHeight */,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-                mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-                int maxWidth = 11, maxHeight = 9, maxRate = 15;
-                maxBlocks = 99;
-                maxBlocksPerSecond = 1485;
-                maxBps = 64000;
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
-                    boolean strict = false; // true: W, H and FR are individual max limits
-                    boolean supported = true;
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.MPEG4ProfileSimple:
-                            switch (profileLevel.level) {
-                                case CodecProfileLevel.MPEG4Level0:
-                                    strict = true;
-                                    FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
-                                case CodecProfileLevel.MPEG4Level1:
-                                    FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
-                                case CodecProfileLevel.MPEG4Level0b:
-                                    strict = true;
-                                    FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR = 128; break;
-                                case CodecProfileLevel.MPEG4Level2:
-                                    FR = 30; W = 22; H = 18; MBPS =  5940; FS = 396; BR = 128; break;
-                                case CodecProfileLevel.MPEG4Level3:
-                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
-                                case CodecProfileLevel.MPEG4Level4a:
-                                    FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
-                                case CodecProfileLevel.MPEG4Level5:
-                                    FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
-                                case CodecProfileLevel.MPEG4Level6:
-                                    FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
-                                default:
-                                    Log.w(TAG, "Unrecognized profile/level "
-                                            + profileLevel.profile + "/"
-                                            + profileLevel.level + " for " + mime);
-                                    errors |= ERROR_UNRECOGNIZED;
-                            }
-                            break;
-                        case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
-                            switch (profileLevel.level) {
-                                case CodecProfileLevel.MPEG4Level0:
-                                case CodecProfileLevel.MPEG4Level1:
-                                    FR = 30; W = 11; H =  9; MBPS =  2970; FS =   99; BR =  128; break;
-                                case CodecProfileLevel.MPEG4Level2:
-                                    FR = 30; W = 22; H = 18; MBPS =  5940; FS =  396; BR =  384; break;
-                                case CodecProfileLevel.MPEG4Level3:
-                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR =  768; break;
-                                case CodecProfileLevel.MPEG4Level3b:
-                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR = 1500; break;
-                                case CodecProfileLevel.MPEG4Level4:
-                                    FR = 30; W = 44; H = 36; MBPS = 23760; FS =  792; BR = 3000; break;
-                                case CodecProfileLevel.MPEG4Level5:
-                                    FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
-                                default:
-                                    Log.w(TAG, "Unrecognized profile/level "
-                                            + profileLevel.profile + "/"
-                                            + profileLevel.level + " for " + mime);
-                                    errors |= ERROR_UNRECOGNIZED;
-                            }
-                            break;
-                        case CodecProfileLevel.MPEG4ProfileMain:             // 2-4
-                        case CodecProfileLevel.MPEG4ProfileNbit:             // 2
-                        case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
-                        case CodecProfileLevel.MPEG4ProfileCoreScalable:     // 1-3
-                        case CodecProfileLevel.MPEG4ProfileAdvancedCoding:   // 1-4
-                        case CodecProfileLevel.MPEG4ProfileCore:             // 1-2
-                        case CodecProfileLevel.MPEG4ProfileAdvancedCore:     // 1-4
-                        case CodecProfileLevel.MPEG4ProfileSimpleScalable:   // 0-2
-                        case CodecProfileLevel.MPEG4ProfileHybrid:           // 1-2
-
-                        // Studio profiles are not supported by our codecs.
-
-                        // Only profiles that can decode simple object types are considered.
-                        // The following profiles are not able to.
-                        case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
-                        case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
-                        case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
-                        case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
-                        case CodecProfileLevel.MPEG4ProfileSimpleFBA:        // 1-2
-                            Log.i(TAG, "Unsupported profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNSUPPORTED;
-                            supported = false;
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    if (supported) {
-                        errors &= ~ERROR_NONE_SUPPORTED;
-                    }
-                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                    maxBlocks = Math.max(FS, maxBlocks);
-                    maxBps = Math.max(BR * 1000, maxBps);
-                    if (strict) {
-                        maxWidth = Math.max(W, maxWidth);
-                        maxHeight = Math.max(H, maxHeight);
-                        maxRate = Math.max(FR, maxRate);
-                    } else {
-                        // assuming max 60 fps frame rate and 1:2 aspect ratio
-                        int maxDim = (int)Math.sqrt(FS * 2);
-                        maxWidth = Math.max(maxDim, maxWidth);
-                        maxHeight = Math.max(maxDim, maxHeight);
-                        maxRate = Math.max(Math.max(FR, 60), maxRate);
-                    }
-                }
-                applyMacroBlockLimits(maxWidth, maxHeight,
-                        maxBlocks, maxBlocksPerSecond,
-                        16 /* blockWidth */, 16 /* blockHeight */,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-                mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
-                int maxWidth = 11, maxHeight = 9, maxRate = 15;
-                int minWidth = maxWidth, minHeight = maxHeight;
-                int minAlignment = 16;
-                maxBlocks = 99;
-                maxBlocksPerSecond = 1485;
-                maxBps = 64000;
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
-                    boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
-                    switch (profileLevel.level) {
-                        case CodecProfileLevel.H263Level10:
-                            strict = true; // only supports sQCIF & QCIF
-                            FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
-                        case CodecProfileLevel.H263Level20:
-                            strict = true; // only supports sQCIF, QCIF & CIF
-                            FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * 15; break;
-                        case CodecProfileLevel.H263Level30:
-                            strict = true; // only supports sQCIF, QCIF & CIF
-                            FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
-                        case CodecProfileLevel.H263Level40:
-                            strict = true; // only supports sQCIF, QCIF & CIF
-                            FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
-                        case CodecProfileLevel.H263Level45:
-                            // only implies level 10 support
-                            strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
-                                    || profileLevel.profile ==
-                                            CodecProfileLevel.H263ProfileBackwardCompatible;
-                            if (!strict) {
-                                minW = 1; minH = 1; minAlignment = 4;
-                            }
-                            FR = 15; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
-                        case CodecProfileLevel.H263Level50:
-                            // only supports 50fps for H > 15
-                            minW = 1; minH = 1; minAlignment = 4;
-                            FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
-                        case CodecProfileLevel.H263Level60:
-                            // only supports 50fps for H > 15
-                            minW = 1; minH = 1; minAlignment = 4;
-                            FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
-                        case CodecProfileLevel.H263Level70:
-                            // only supports 50fps for H > 30
-                            minW = 1; minH = 1; minAlignment = 4;
-                            FR = 60; W = 45; H = 36; BR = 256; MBPS =  W * H * 50; break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
-                                    + "/" + profileLevel.level + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.H263ProfileBackwardCompatible:
-                        case CodecProfileLevel.H263ProfileBaseline:
-                        case CodecProfileLevel.H263ProfileH320Coding:
-                        case CodecProfileLevel.H263ProfileHighCompression:
-                        case CodecProfileLevel.H263ProfileHighLatency:
-                        case CodecProfileLevel.H263ProfileInterlace:
-                        case CodecProfileLevel.H263ProfileInternet:
-                        case CodecProfileLevel.H263ProfileISWV2:
-                        case CodecProfileLevel.H263ProfileISWV3:
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    if (strict) {
-                        // Strict levels define sub-QCIF min size and enumerated sizes. We cannot
-                        // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities
-                        // but we can express "only QCIF (& CIF)", so set minimume size at QCIF.
-                        // minW = 8; minH = 6;
-                        minW = 11; minH = 9;
-                    } else {
-                        // any support for non-strict levels (including unrecognized profiles or
-                        // levels) allow custom frame size support beyond supported limits
-                        // (other than bitrate)
-                        mAllowMbOverride = true;
-                    }
-                    errors &= ~ERROR_NONE_SUPPORTED;
-                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
-                    maxBlocks = Math.max(W * H, maxBlocks);
-                    maxBps = Math.max(BR * 64000, maxBps);
-                    maxWidth = Math.max(W, maxWidth);
-                    maxHeight = Math.max(H, maxHeight);
-                    maxRate = Math.max(FR, maxRate);
-                    minWidth = Math.min(minW, minWidth);
-                    minHeight = Math.min(minH, minHeight);
-                }
-                // unless we encountered custom frame size support, limit size to QCIF and CIF
-                // using aspect ratio.
-                if (!mAllowMbOverride) {
-                    mBlockAspectRatioRange =
-                        Range.create(new Rational(11, 9), new Rational(11, 9));
-                }
-                applyMacroBlockLimits(
-                        minWidth, minHeight,
-                        maxWidth, maxHeight,
-                        maxBlocks, maxBlocksPerSecond,
-                        16 /* blockWidth */, 16 /* blockHeight */,
-                        minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
-                mFrameRateRange = Range.create(1, maxRate);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
-                maxBlocks = Integer.MAX_VALUE;
-                maxBlocksPerSecond = Integer.MAX_VALUE;
-
-                // TODO: set to 100Mbps for now, need a number for VP8
-                maxBps = 100000000;
-
-                // profile levels are not indicative for VPx, but verify
-                // them nonetheless
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    switch (profileLevel.level) {
-                        case CodecProfileLevel.VP8Level_Version0:
-                        case CodecProfileLevel.VP8Level_Version1:
-                        case CodecProfileLevel.VP8Level_Version2:
-                        case CodecProfileLevel.VP8Level_Version3:
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized level "
-                                    + profileLevel.level + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.VP8ProfileMain:
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    errors &= ~ERROR_NONE_SUPPORTED;
-                }
-
-                final int blockSize = 16;
-                applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
-                        maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                maxBlocksPerSecond = 829440;
-                maxBlocks = 36864;
-                maxBps = 200000;
-                int maxDim = 512;
-
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    long SR = 0; // luma sample rate
-                    int FS = 0;  // luma picture size
-                    int BR = 0;  // bit rate kbps
-                    int D = 0;   // luma dimension
-                    switch (profileLevel.level) {
-                        case CodecProfileLevel.VP9Level1:
-                            SR =      829440; FS =    36864; BR =    200; D =   512; break;
-                        case CodecProfileLevel.VP9Level11:
-                            SR =     2764800; FS =    73728; BR =    800; D =   768; break;
-                        case CodecProfileLevel.VP9Level2:
-                            SR =     4608000; FS =   122880; BR =   1800; D =   960; break;
-                        case CodecProfileLevel.VP9Level21:
-                            SR =     9216000; FS =   245760; BR =   3600; D =  1344; break;
-                        case CodecProfileLevel.VP9Level3:
-                            SR =    20736000; FS =   552960; BR =   7200; D =  2048; break;
-                        case CodecProfileLevel.VP9Level31:
-                            SR =    36864000; FS =   983040; BR =  12000; D =  2752; break;
-                        case CodecProfileLevel.VP9Level4:
-                            SR =    83558400; FS =  2228224; BR =  18000; D =  4160; break;
-                        case CodecProfileLevel.VP9Level41:
-                            SR =   160432128; FS =  2228224; BR =  30000; D =  4160; break;
-                        case CodecProfileLevel.VP9Level5:
-                            SR =   311951360; FS =  8912896; BR =  60000; D =  8384; break;
-                        case CodecProfileLevel.VP9Level51:
-                            SR =   588251136; FS =  8912896; BR = 120000; D =  8384; break;
-                        case CodecProfileLevel.VP9Level52:
-                            SR =  1176502272; FS =  8912896; BR = 180000; D =  8384; break;
-                        case CodecProfileLevel.VP9Level6:
-                            SR =  1176502272; FS = 35651584; BR = 180000; D = 16832; break;
-                        case CodecProfileLevel.VP9Level61:
-                            SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
-                        case CodecProfileLevel.VP9Level62:
-                            SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
-                        default:
-                            Log.w(TAG, "Unrecognized level "
-                                    + profileLevel.level + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.VP9Profile0:
-                        case CodecProfileLevel.VP9Profile1:
-                        case CodecProfileLevel.VP9Profile2:
-                        case CodecProfileLevel.VP9Profile3:
-                        case CodecProfileLevel.VP9Profile2HDR:
-                        case CodecProfileLevel.VP9Profile3HDR:
-                        case CodecProfileLevel.VP9Profile2HDR10Plus:
-                        case CodecProfileLevel.VP9Profile3HDR10Plus:
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    errors &= ~ERROR_NONE_SUPPORTED;
-                    maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
-                    maxBlocks = Math.max(FS, maxBlocks);
-                    maxBps = Math.max(BR * 1000, maxBps);
-                    maxDim = Math.max(D, maxDim);
-                }
-
-                final int blockSize = 8;
-                int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
-                maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
-                maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
-
-                applyMacroBlockLimits(
-                        maxLengthInBlocks, maxLengthInBlocks,
-                        maxBlocks, maxBlocksPerSecond,
-                        blockSize, blockSize,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-                // CTBs are at least 8x8 so use 8x8 block size
-                maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
-                maxBlocksPerSecond = maxBlocks * 15;
-                maxBps = 128000;
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    double FR = 0;
-                    int FS = 0;
-                    int BR = 0;
-                    switch (profileLevel.level) {
-                        /* The HEVC spec talks only in a very convoluted manner about the
-                           existence of levels 1-3.1 for High tier, which could also be
-                           understood as 'decoders and encoders should treat these levels
-                           as if they were Main tier', so we do that. */
-                        case CodecProfileLevel.HEVCMainTierLevel1:
-                        case CodecProfileLevel.HEVCHighTierLevel1:
-                            FR =    15; FS =    36864; BR =    128; break;
-                        case CodecProfileLevel.HEVCMainTierLevel2:
-                        case CodecProfileLevel.HEVCHighTierLevel2:
-                            FR =    30; FS =   122880; BR =   1500; break;
-                        case CodecProfileLevel.HEVCMainTierLevel21:
-                        case CodecProfileLevel.HEVCHighTierLevel21:
-                            FR =    30; FS =   245760; BR =   3000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel3:
-                        case CodecProfileLevel.HEVCHighTierLevel3:
-                            FR =    30; FS =   552960; BR =   6000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel31:
-                        case CodecProfileLevel.HEVCHighTierLevel31:
-                            FR = 33.75; FS =   983040; BR =  10000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel4:
-                            FR =    30; FS =  2228224; BR =  12000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel4:
-                            FR =    30; FS =  2228224; BR =  30000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel41:
-                            FR =    60; FS =  2228224; BR =  20000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel41:
-                            FR =    60; FS =  2228224; BR =  50000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel5:
-                            FR =    30; FS =  8912896; BR =  25000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel5:
-                            FR =    30; FS =  8912896; BR = 100000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel51:
-                            FR =    60; FS =  8912896; BR =  40000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel51:
-                            FR =    60; FS =  8912896; BR = 160000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel52:
-                            FR =   120; FS =  8912896; BR =  60000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel52:
-                            FR =   120; FS =  8912896; BR = 240000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel6:
-                            FR =    30; FS = 35651584; BR =  60000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel6:
-                            FR =    30; FS = 35651584; BR = 240000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel61:
-                            FR =    60; FS = 35651584; BR = 120000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel61:
-                            FR =    60; FS = 35651584; BR = 480000; break;
-                        case CodecProfileLevel.HEVCMainTierLevel62:
-                            FR =   120; FS = 35651584; BR = 240000; break;
-                        case CodecProfileLevel.HEVCHighTierLevel62:
-                            FR =   120; FS = 35651584; BR = 800000; break;
-                        default:
-                            Log.w(TAG, "Unrecognized level "
-                                    + profileLevel.level + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.HEVCProfileMain:
-                        case CodecProfileLevel.HEVCProfileMain10:
-                        case CodecProfileLevel.HEVCProfileMainStill:
-                        case CodecProfileLevel.HEVCProfileMain10HDR10:
-                        case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-
-                    /* DPB logic:
-                    if      (width * height <= FS / 4)    DPB = 16;
-                    else if (width * height <= FS / 2)    DPB = 12;
-                    else if (width * height <= FS * 0.75) DPB = 8;
-                    else                                  DPB = 6;
-                    */
-
-                    FS >>= 6; // convert pixels to blocks
-                    errors &= ~ERROR_NONE_SUPPORTED;
-                    maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
-                    maxBlocks = Math.max(FS, maxBlocks);
-                    maxBps = Math.max(BR * 1000, maxBps);
-                }
-
-                int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
-                applyMacroBlockLimits(
-                        maxLengthInBlocks, maxLengthInBlocks,
-                        maxBlocks, maxBlocksPerSecond,
-                        8 /* blockWidth */, 8 /* blockHeight */,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
-                maxBlocksPerSecond = 829440;
-                maxBlocks = 36864;
-                maxBps = 200000;
-                int maxDim = 512;
-
-                // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
-                // corresponding to the definitions in
-                // "AV1 Bitstream & Decoding Process Specification", Annex A
-                // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
-                for (CodecProfileLevel profileLevel: profileLevels) {
-                    long SR = 0; // luma sample rate
-                    int FS = 0;  // luma picture size
-                    int BR = 0;  // bit rate kbps
-                    int D = 0;   // luma D
-                    switch (profileLevel.level) {
-                        case CodecProfileLevel.AV1Level2:
-                            SR =     5529600; FS =   147456; BR =   1500; D =  2048; break;
-                        case CodecProfileLevel.AV1Level21:
-                        case CodecProfileLevel.AV1Level22:
-                        case CodecProfileLevel.AV1Level23:
-                            SR =    10454400; FS =   278784; BR =   3000; D =  2816; break;
-
-                        case CodecProfileLevel.AV1Level3:
-                            SR =    24969600; FS =   665856; BR =   6000; D =  4352; break;
-                        case CodecProfileLevel.AV1Level31:
-                        case CodecProfileLevel.AV1Level32:
-                        case CodecProfileLevel.AV1Level33:
-                            SR =    39938400; FS =  1065024; BR =  10000; D =  5504; break;
-
-                        case CodecProfileLevel.AV1Level4:
-                            SR =    77856768; FS =  2359296; BR =  12000; D =  6144; break;
-                        case CodecProfileLevel.AV1Level41:
-                        case CodecProfileLevel.AV1Level42:
-                        case CodecProfileLevel.AV1Level43:
-                            SR =   155713536; FS =  2359296; BR =  20000; D =  6144; break;
-
-                        case CodecProfileLevel.AV1Level5:
-                            SR =   273715200; FS =  8912896; BR =  30000; D =  8192; break;
-                        case CodecProfileLevel.AV1Level51:
-                            SR =   547430400; FS =  8912896; BR =  40000; D =  8192; break;
-                        case CodecProfileLevel.AV1Level52:
-                            SR =  1094860800; FS =  8912896; BR =  60000; D =  8192; break;
-                        case CodecProfileLevel.AV1Level53:
-                            SR =  1176502272; FS =  8912896; BR =  60000; D =  8192; break;
-
-                        case CodecProfileLevel.AV1Level6:
-                            SR =  1176502272; FS = 35651584; BR =  60000; D = 16384; break;
-                        case CodecProfileLevel.AV1Level61:
-                            SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
-                        case CodecProfileLevel.AV1Level62:
-                            SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
-                        case CodecProfileLevel.AV1Level63:
-                            SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
-
-                        default:
-                            Log.w(TAG, "Unrecognized level "
-                                    + profileLevel.level + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    switch (profileLevel.profile) {
-                        case CodecProfileLevel.AV1ProfileMain8:
-                        case CodecProfileLevel.AV1ProfileMain10:
-                        case CodecProfileLevel.AV1ProfileMain10HDR10:
-                        case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
-                            break;
-                        default:
-                            Log.w(TAG, "Unrecognized profile "
-                                    + profileLevel.profile + " for " + mime);
-                            errors |= ERROR_UNRECOGNIZED;
-                    }
-                    errors &= ~ERROR_NONE_SUPPORTED;
-                    maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
-                    maxBlocks = Math.max(FS, maxBlocks);
-                    maxBps = Math.max(BR * 1000, maxBps);
-                    maxDim = Math.max(D, maxDim);
-                }
-
-                final int blockSize = 8;
-                int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
-                maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
-                maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
-                applyMacroBlockLimits(
-                        maxLengthInBlocks, maxLengthInBlocks,
-                        maxBlocks, maxBlocksPerSecond,
-                        blockSize, blockSize,
-                        1 /* widthAlignment */, 1 /* heightAlignment */);
-            } else {
-                Log.w(TAG, "Unsupported mime " + mime);
-                // using minimal bitrate here.  should be overriden by
-                // info from media_codecs.xml
-                maxBps = 64000;
-                errors |= ERROR_UNSUPPORTED;
-            }
-            mBitrateRange = Range.create(1, maxBps);
-            mParent.mError |= errors;
+            return mImpl.supportsFormat(format);
         }
     }
 
@@ -3842,6 +4551,288 @@
      * A class that supports querying the encoding capabilities of a codec.
      */
     public static final class EncoderCapabilities {
+        private static final String TAG = "EncoderCapabilities";
+
+        /** Constant quality mode */
+        public static final int BITRATE_MODE_CQ = 0;
+        /** Variable bitrate mode */
+        public static final int BITRATE_MODE_VBR = 1;
+        /** Constant bitrate mode */
+        public static final int BITRATE_MODE_CBR = 2;
+        /** Constant bitrate mode with frame drops */
+        public static final int BITRATE_MODE_CBR_FD =  3;
+
+        /* package private */ interface EncoderCapsIntf {
+            public Range<Integer> getQualityRange();
+
+            public Range<Integer> getComplexityRange();
+
+            public boolean isBitrateModeSupported(int mode);
+
+            public void getDefaultFormat(MediaFormat format);
+
+            public boolean supportsFormat(MediaFormat format);
+        }
+
+        /* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf {
+            private CodecCapabilities.CodecCapsLegacyImpl mParent;
+
+            private Range<Integer> mQualityRange;
+            private Range<Integer> mComplexityRange;
+
+            public Range<Integer> getQualityRange() {
+                return mQualityRange;
+            }
+
+            public Range<Integer> getComplexityRange() {
+                return mComplexityRange;
+            }
+
+            private static final Feature[] bitrates = new Feature[] {
+                new Feature("VBR", BITRATE_MODE_VBR, true),
+                new Feature("CBR", BITRATE_MODE_CBR, false),
+                new Feature("CQ",  BITRATE_MODE_CQ,  false),
+                new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false)
+            };
+
+            private static int parseBitrateMode(String mode) {
+                for (Feature feat: bitrates) {
+                    if (feat.mName.equalsIgnoreCase(mode)) {
+                        return feat.mValue;
+                    }
+                }
+                return 0;
+            }
+
+            public boolean isBitrateModeSupported(int mode) {
+                for (Feature feat: bitrates) {
+                    if (mode == feat.mValue) {
+                        return (mBitControl & (1 << mode)) != 0;
+                    }
+                }
+                return false;
+            }
+
+            /* no public constructor */
+            private EncoderCapsLegacyImpl() { }
+
+            /** @hide */
+            public static EncoderCapsLegacyImpl create(
+                    MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+                if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
+                    Log.d(TAG, "Legacy implementation is called while native flag is on.");
+                }
+
+                EncoderCapsLegacyImpl caps = new EncoderCapsLegacyImpl();
+                caps.init(info, parent);
+                return caps;
+            }
+
+            private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+                // no support for complexity or quality yet
+                mParent = parent;
+                mComplexityRange = Range.create(0, 0);
+                mQualityRange = Range.create(0, 0);
+                mBitControl = (1 << BITRATE_MODE_VBR);
+
+                applyLevelLimits();
+                parseFromInfo(info);
+            }
+
+            private void applyLevelLimits() {
+                String mime = mParent.getMimeType();
+                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                    mComplexityRange = Range.create(0, 8);
+                    mBitControl = (1 << BITRATE_MODE_CQ);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
+                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
+                        || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+                    mBitControl = (1 << BITRATE_MODE_CBR);
+                }
+            }
+
+            private int mBitControl;
+            private Integer mDefaultComplexity;
+            private Integer mDefaultQuality;
+            private String mQualityScale;
+
+            private void parseFromInfo(MediaFormat info) {
+                Map<String, Object> map = info.getMap();
+
+                if (info.containsKey("complexity-range")) {
+                    mComplexityRange = Utils
+                            .parseIntRange(info.getString("complexity-range"), mComplexityRange);
+                    // TODO should we limit this to level limits?
+                }
+                if (info.containsKey("quality-range")) {
+                    mQualityRange = Utils
+                            .parseIntRange(info.getString("quality-range"), mQualityRange);
+                }
+                if (info.containsKey("feature-bitrate-modes")) {
+                    mBitControl = 0;
+                    for (String mode: info.getString("feature-bitrate-modes").split(",")) {
+                        mBitControl |= (1 << parseBitrateMode(mode));
+                    }
+                }
+
+                try {
+                    mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+                } catch (NumberFormatException e) { }
+
+                try {
+                    mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+                } catch (NumberFormatException e) { }
+
+                mQualityScale = (String)map.get("quality-scale");
+            }
+
+            private boolean supports(
+                    Integer complexity, Integer quality, Integer profile) {
+                boolean ok = true;
+                if (ok && complexity != null) {
+                    ok = mComplexityRange.contains(complexity);
+                }
+                if (ok && quality != null) {
+                    ok = mQualityRange.contains(quality);
+                }
+                if (ok && profile != null) {
+                    for (CodecProfileLevel pl: mParent.getProfileLevels()) {
+                        if (pl.profile == profile) {
+                            profile = null;
+                            break;
+                        }
+                    }
+                    ok = profile == null;
+                }
+                return ok;
+            }
+
+            /** @hide */
+            public void getDefaultFormat(MediaFormat format) {
+                // don't list trivial quality/complexity as default for now
+                if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
+                        && mDefaultQuality != null) {
+                    format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
+                }
+                if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
+                        && mDefaultComplexity != null) {
+                    format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
+                }
+                // bitrates are listed in order of preference
+                for (Feature feat: bitrates) {
+                    if ((mBitControl & (1 << feat.mValue)) != 0) {
+                        format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
+                        break;
+                    }
+                }
+            }
+
+            /** @hide */
+            public boolean supportsFormat(MediaFormat format) {
+                final Map<String, Object> map = format.getMap();
+                final String mime = mParent.getMimeType();
+
+                Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
+                if (mode != null && !isBitrateModeSupported(mode)) {
+                    return false;
+                }
+
+                Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
+                if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
+                    Integer flacComplexity =
+                        (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
+                    if (complexity == null) {
+                        complexity = flacComplexity;
+                    } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
+                        throw new IllegalArgumentException(
+                                "conflicting values for complexity and " +
+                                "flac-compression-level");
+                    }
+                }
+
+                // other audio parameters
+                Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
+                if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
+                    Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
+                    if (profile == null) {
+                        profile = aacProfile;
+                    } else if (aacProfile != null && !aacProfile.equals(profile)) {
+                        throw new IllegalArgumentException(
+                                "conflicting values for profile and aac-profile");
+                    }
+                }
+
+                Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+                return supports(complexity, quality, profile);
+            }
+        }
+
+        /* package private */ static final class EncoderCapsNativeImpl implements EncoderCapsIntf {
+            private long mNativeContext; // accessed by native methods
+
+            private Range<Integer> mQualityRange;
+            private Range<Integer> mComplexityRange;
+
+            /* no public constructor */
+            private EncoderCapsNativeImpl() { }
+
+            // Constructor called from native
+            /* package private */ EncoderCapsNativeImpl(Range<Integer> qualityRange,
+                    Range<Integer> complexityRange) {
+                mQualityRange = qualityRange;
+                mComplexityRange = complexityRange;
+            }
+
+            public Range<Integer> getQualityRange() {
+                return mQualityRange;
+            }
+
+            public Range<Integer> getComplexityRange() {
+                return mComplexityRange;
+            }
+
+            public boolean isBitrateModeSupported(int mode) {
+                return native_isBitrateModeSupported(mode);
+            }
+
+            // This API is for internal Java implementation only. Should not be called.
+            public void getDefaultFormat(MediaFormat format) {
+                throw new UnsupportedOperationException(
+                    "Java Implementation should not call native implemenatation");
+            }
+
+            // This API is for internal Java implementation only. Should not be called.
+            public boolean supportsFormat(MediaFormat format) {
+                throw new UnsupportedOperationException(
+                    "Java Implementation should not call native implemenatation");
+            }
+
+            private native boolean native_isBitrateModeSupported(int mode);
+            private static native void native_init();
+
+            static {
+                System.loadLibrary("media_jni");
+                native_init();
+            }
+        }
+
+        private EncoderCapsIntf mImpl;
+
+        /** @hide */
+        public static EncoderCapabilities create(
+                MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
+            EncoderCapsLegacyImpl impl = EncoderCapsLegacyImpl.create(info, parent);
+            EncoderCapabilities caps = new EncoderCapabilities(impl);
+            return caps;
+        }
+
+        /* package private */ EncoderCapabilities(EncoderCapsIntf impl) {
+            mImpl = impl;
+        }
+
         /**
          * Returns the supported range of quality values.
          *
@@ -3849,7 +4840,7 @@
          * setting results in a better image quality and a lower compression ratio.
          */
         public Range<Integer> getQualityRange() {
-            return mQualityRange;
+            return mImpl.getQualityRange();
         }
 
         /**
@@ -3861,200 +4852,24 @@
          * ratio.  Use a lower value to save power and/or time.
          */
         public Range<Integer> getComplexityRange() {
-            return mComplexityRange;
-        }
-
-        /** Constant quality mode */
-        public static final int BITRATE_MODE_CQ = 0;
-        /** Variable bitrate mode */
-        public static final int BITRATE_MODE_VBR = 1;
-        /** Constant bitrate mode */
-        public static final int BITRATE_MODE_CBR = 2;
-        /** Constant bitrate mode with frame drops */
-        public static final int BITRATE_MODE_CBR_FD =  3;
-
-        private static final Feature[] bitrates = new Feature[] {
-            new Feature("VBR", BITRATE_MODE_VBR, true),
-            new Feature("CBR", BITRATE_MODE_CBR, false),
-            new Feature("CQ",  BITRATE_MODE_CQ,  false),
-            new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false)
-        };
-
-        private static int parseBitrateMode(String mode) {
-            for (Feature feat: bitrates) {
-                if (feat.mName.equalsIgnoreCase(mode)) {
-                    return feat.mValue;
-                }
-            }
-            return 0;
+            return mImpl.getComplexityRange();
         }
 
         /**
          * Query whether a bitrate mode is supported.
          */
         public boolean isBitrateModeSupported(int mode) {
-            for (Feature feat: bitrates) {
-                if (mode == feat.mValue) {
-                    return (mBitControl & (1 << mode)) != 0;
-                }
-            }
-            return false;
-        }
-
-        private Range<Integer> mQualityRange;
-        private Range<Integer> mComplexityRange;
-        private CodecCapabilities mParent;
-
-        /* no public constructor */
-        private EncoderCapabilities() { }
-
-        /** @hide */
-        public static EncoderCapabilities create(
-                MediaFormat info, CodecCapabilities parent) {
-            EncoderCapabilities caps = new EncoderCapabilities();
-            caps.init(info, parent);
-            return caps;
-        }
-
-        private void init(MediaFormat info, CodecCapabilities parent) {
-            // no support for complexity or quality yet
-            mParent = parent;
-            mComplexityRange = Range.create(0, 0);
-            mQualityRange = Range.create(0, 0);
-            mBitControl = (1 << BITRATE_MODE_VBR);
-
-            applyLevelLimits();
-            parseFromInfo(info);
-        }
-
-        private void applyLevelLimits() {
-            String mime = mParent.getMimeType();
-            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
-                mComplexityRange = Range.create(0, 8);
-                mBitControl = (1 << BITRATE_MODE_CQ);
-            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
-                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
-                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
-                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
-                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
-                mBitControl = (1 << BITRATE_MODE_CBR);
-            }
-        }
-
-        private int mBitControl;
-        private Integer mDefaultComplexity;
-        private Integer mDefaultQuality;
-        private String mQualityScale;
-
-        private void parseFromInfo(MediaFormat info) {
-            Map<String, Object> map = info.getMap();
-
-            if (info.containsKey("complexity-range")) {
-                mComplexityRange = Utils
-                        .parseIntRange(info.getString("complexity-range"), mComplexityRange);
-                // TODO should we limit this to level limits?
-            }
-            if (info.containsKey("quality-range")) {
-                mQualityRange = Utils
-                        .parseIntRange(info.getString("quality-range"), mQualityRange);
-            }
-            if (info.containsKey("feature-bitrate-modes")) {
-                mBitControl = 0;
-                for (String mode: info.getString("feature-bitrate-modes").split(",")) {
-                    mBitControl |= (1 << parseBitrateMode(mode));
-                }
-            }
-
-            try {
-                mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
-            } catch (NumberFormatException e) { }
-
-            try {
-                mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
-            } catch (NumberFormatException e) { }
-
-            mQualityScale = (String)map.get("quality-scale");
-        }
-
-        private boolean supports(
-                Integer complexity, Integer quality, Integer profile) {
-            boolean ok = true;
-            if (ok && complexity != null) {
-                ok = mComplexityRange.contains(complexity);
-            }
-            if (ok && quality != null) {
-                ok = mQualityRange.contains(quality);
-            }
-            if (ok && profile != null) {
-                for (CodecProfileLevel pl: mParent.profileLevels) {
-                    if (pl.profile == profile) {
-                        profile = null;
-                        break;
-                    }
-                }
-                ok = profile == null;
-            }
-            return ok;
+            return mImpl.isBitrateModeSupported(mode);
         }
 
         /** @hide */
         public void getDefaultFormat(MediaFormat format) {
-            // don't list trivial quality/complexity as default for now
-            if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
-                    && mDefaultQuality != null) {
-                format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
-            }
-            if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
-                    && mDefaultComplexity != null) {
-                format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
-            }
-            // bitrates are listed in order of preference
-            for (Feature feat: bitrates) {
-                if ((mBitControl & (1 << feat.mValue)) != 0) {
-                    format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
-                    break;
-                }
-            }
+            mImpl.getDefaultFormat(format);
         }
 
         /** @hide */
         public boolean supportsFormat(MediaFormat format) {
-            final Map<String, Object> map = format.getMap();
-            final String mime = mParent.getMimeType();
-
-            Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
-            if (mode != null && !isBitrateModeSupported(mode)) {
-                return false;
-            }
-
-            Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
-            if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
-                Integer flacComplexity =
-                    (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
-                if (complexity == null) {
-                    complexity = flacComplexity;
-                } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
-                    throw new IllegalArgumentException(
-                            "conflicting values for complexity and " +
-                            "flac-compression-level");
-                }
-            }
-
-            // other audio parameters
-            Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
-            if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
-                Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
-                if (profile == null) {
-                    profile = aacProfile;
-                } else if (aacProfile != null && !aacProfile.equals(profile)) {
-                    throw new IllegalArgumentException(
-                            "conflicting values for profile and aac-profile");
-                }
-            }
-
-            Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
-
-            return supports(complexity, quality, profile);
+            return mImpl.supportsFormat(format);
         }
     };
 
@@ -4826,4 +5641,19 @@
                 mName, mCanonicalName, mFlags,
                 caps.toArray(new CodecCapabilities[caps.size()]));
     }
+
+    /* package private */ class GenericHelper {
+        private static Range<Integer> constructIntegerRange(int lower, int upper) {
+            return Range.create(Integer.valueOf(lower), Integer.valueOf(upper));
+        }
+
+        private static Range<Double> constructDoubleRange(double lower, double upper) {
+            return Range.create(Double.valueOf(lower), Double.valueOf(upper));
+        }
+
+        private static List<VideoCapabilities.PerformancePoint>
+                constructPerformancePointList(VideoCapabilities.PerformancePoint[] array) {
+            return Arrays.asList(array);
+        }
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index f09dc72..af545d5 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -25,6 +25,7 @@
     min_sdk_version: "",
 
     srcs: [
+        "android_media_CodecCapabilities.cpp",
         "android_media_ImageWriter.cpp",
         "android_media_ImageReader.cpp",
         "android_media_JetPlayer.cpp",
@@ -64,6 +65,7 @@
         "libbinder",
         "libmedia",
         "libmedia_codeclist",
+        "libmedia_codeclist_capabilities",
         "libmedia_jni_utils",
         "libmedia_omx",
         "libmediametrics",
diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp
new file mode 100644
index 0000000..df0c826
--- /dev/null
+++ b/media/jni/android_media_CodecCapabilities.cpp
@@ -0,0 +1,1015 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaCodec-JNI"
+
+#include "android_media_CodecCapabilities.h"
+#include "android_media_Streams.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+
+#include <media/AudioCapabilities.h>
+#include <media/CodecCapabilities.h>
+#include <media/EncoderCapabilities.h>
+#include <media/VideoCapabilities.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Log.h>
+
+namespace android {
+
+struct fields_t {
+    jfieldID audioCapsContext;
+    jfieldID videoCapsContext;
+    jfieldID encoderCapsContext;
+    jfieldID codecCapsContext;
+};
+static fields_t fields;
+
+// JCodecCapabilities
+
+JCodecCapabilities::JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps)
+        : mCodecCaps(codecCaps) {}
+
+std::shared_ptr<CodecCapabilities> JCodecCapabilities::getCodecCaps() const {
+    return mCodecCaps;
+}
+
+int32_t JCodecCapabilities::getMaxSupportedInstances() const {
+    return mCodecCaps->getMaxSupportedInstances();
+}
+
+std::string JCodecCapabilities::getMediaType() const {
+    return mCodecCaps->getMediaType();
+}
+
+bool JCodecCapabilities::isFeatureRequired(const std::string& name) const {
+    return mCodecCaps->isFeatureRequired(name);
+}
+
+bool JCodecCapabilities::isFeatureSupported(const std::string& name) const {
+    return mCodecCaps->isFeatureSupported(name);
+}
+
+bool JCodecCapabilities::isFormatSupported(const sp<AMessage> &format) const {
+    return mCodecCaps->isFormatSupported(format);
+}
+
+bool JCodecCapabilities::isRegular() const {
+    return mCodecCaps->isRegular();
+}
+
+// Setter
+
+static sp<JCodecCapabilities> setCodecCapabilities(JNIEnv *env, jobject thiz,
+        const sp<JCodecCapabilities>& jCodecCaps) {
+    sp<JCodecCapabilities> old
+            = (JCodecCapabilities*)env->GetLongField(thiz, fields.codecCapsContext);
+    if (jCodecCaps != NULL) {
+        jCodecCaps->incStrong(thiz);
+    }
+    if (old != NULL) {
+        old->decStrong(thiz);
+    }
+    env->SetLongField(thiz, fields.codecCapsContext, (jlong)jCodecCaps.get());
+    return old;
+}
+
+// Getters
+
+static AudioCapabilities* getAudioCapabilities(JNIEnv *env, jobject thiz) {
+    AudioCapabilities* const p = (AudioCapabilities*)env->GetLongField(
+            thiz, fields.audioCapsContext);
+    return p;
+}
+
+static VideoCapabilities* getVideoCapabilities(JNIEnv *env, jobject thiz) {
+    VideoCapabilities* const p = (VideoCapabilities*)env->GetLongField(
+            thiz, fields.videoCapsContext);
+    return p;
+}
+
+static EncoderCapabilities* getEncoderCapabilities(JNIEnv *env, jobject thiz) {
+    EncoderCapabilities* const p = (EncoderCapabilities*)env->GetLongField(
+            thiz, fields.encoderCapsContext);
+    return p;
+}
+
+static sp<JCodecCapabilities> getCodecCapabilities(JNIEnv *env, jobject thiz) {
+    JCodecCapabilities* const p = (JCodecCapabilities*)env->GetLongField(
+            thiz, fields.codecCapsContext);
+    return sp<JCodecCapabilities>(p);
+}
+
+// Utils
+
+static jobject convertToJavaIntRange(JNIEnv *env, const Range<int32_t>& range) {
+    jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
+    jmethodID constructIntegerRangeID = env->GetStaticMethodID(helperClazz, "constructIntegerRange",
+            "(II)Landroid/util/Range;");
+    jobject jRange = env->CallStaticObjectMethod(helperClazz, constructIntegerRangeID,
+            range.lower(), range.upper());
+
+    return jRange;
+}
+
+static jobject convertToJavaDoubleRange(JNIEnv *env, const Range<double>& range) {
+    jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
+    jmethodID constructDoubleRangeID = env->GetStaticMethodID(helperClazz, "constructDoubleRange",
+            "(DD)Landroid/util/Range;");
+    jobject jRange = env->CallStaticObjectMethod(helperClazz, constructDoubleRangeID,
+            range.lower(), range.upper());
+    return jRange;
+}
+
+static jobjectArray convertToJavaIntRangeArray(JNIEnv *env,
+        const std::vector<Range<int32_t>>& ranges) {
+    jclass rangeClazz = env->FindClass("android/util/Range");
+    CHECK(rangeClazz != NULL);
+    jobjectArray jRanges = env->NewObjectArray(ranges.size(), rangeClazz, NULL);
+    for (int i = 0; i < ranges.size(); i++) {
+        Range<int32_t> range = ranges.at(i);
+        jobject jRange = convertToJavaIntRange(env, range);
+        env->SetObjectArrayElement(jRanges, i, jRange);
+        env->DeleteLocalRef(jRange);
+        jRange = NULL;
+    }
+    return jRanges;
+}
+
+// Converters between Java objects and native instances
+
+// The Java AudioCapabilities object keep bitrateRange, sampleRates, sampleRateRanges
+// and inputChannelRanges in it to prevent reconstruction when called the getters functions.
+static jobject convertToJavaAudioCapabilities(
+        JNIEnv *env, std::shared_ptr<AudioCapabilities> audioCaps) {
+    if (audioCaps == nullptr) {
+        return NULL;
+    }
+
+    // construct Java bitrateRange
+    const Range<int32_t>& bitrateRange = audioCaps->getBitrateRange();
+    jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange);
+
+    // construct Java sampleRates array
+    const std::vector<int32_t>& sampleRates = audioCaps->getSupportedSampleRates();
+    jintArray jSampleRates = env->NewIntArray(sampleRates.size());
+    for (size_t i = 0; i < sampleRates.size(); ++i) {
+        jint val = sampleRates.at(i);
+        env->SetIntArrayRegion(jSampleRates, i, 1, &val);
+    }
+
+    // construct Java sampleRateRanges
+    const std::vector<Range<int32_t>>& sampleRateRanges = audioCaps->getSupportedSampleRateRanges();
+    jobjectArray jSampleRateRanges = convertToJavaIntRangeArray(env, sampleRateRanges);
+
+    // construct Java inputChannelRanges
+    const std::vector<Range<int32_t>>& inputChannelRanges = audioCaps->getInputChannelCountRanges();
+    jobjectArray jInputChannelRanges = convertToJavaIntRangeArray(env, inputChannelRanges);
+
+    // construct Java AudioCapsNativeImpl
+    jclass audioCapsImplClazz
+            = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl");
+    CHECK(audioCapsImplClazz != NULL);
+    jmethodID audioCapsImplConstructID = env->GetMethodID(audioCapsImplClazz, "<init>",
+            "(Landroid/util/Range;"
+            "[I"
+            "[Landroid/util/Range;"
+            "[Landroid/util/Range;)V");
+    jobject jAudioCapsImpl = env->NewObject(audioCapsImplClazz, audioCapsImplConstructID,
+            jBitrateRange, jSampleRates, jSampleRateRanges, jInputChannelRanges);
+    // The native AudioCapabilities won't be destructed until process ends.
+    env->SetLongField(jAudioCapsImpl, fields.audioCapsContext, (jlong)audioCaps.get());
+
+    // construct Java AudioCapabilities
+    jclass audioCapsClazz
+            = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities");
+    CHECK(audioCapsClazz != NULL);
+    jmethodID audioCapsConstructID = env->GetMethodID(audioCapsClazz, "<init>",
+            "(Landroid/media/MediaCodecInfo$AudioCapabilities$AudioCapsIntf;)V");
+    jobject jAudioCaps = env->NewObject(audioCapsClazz, audioCapsConstructID, jAudioCapsImpl);
+
+    env->DeleteLocalRef(jBitrateRange);
+    jBitrateRange = NULL;
+
+    env->DeleteLocalRef(jSampleRates);
+    jSampleRates = NULL;
+
+    env->DeleteLocalRef(jSampleRateRanges);
+    jSampleRateRanges = NULL;
+
+    env->DeleteLocalRef(jInputChannelRanges);
+    jInputChannelRanges = NULL;
+
+    env->DeleteLocalRef(jAudioCapsImpl);
+    jAudioCapsImpl = NULL;
+
+    return jAudioCaps;
+}
+
+// convert native PerformancePoints to Java objects
+static jobject convertToJavaPerformancePoints(JNIEnv *env,
+        const std::vector<VideoCapabilities::PerformancePoint>& performancePoints) {
+    jclass performancePointClazz = env->FindClass(
+            "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint");
+    CHECK(performancePointClazz != NULL);
+    jmethodID performancePointConstructID = env->GetMethodID(performancePointClazz, "<init>",
+            "(IIIJII)V");
+
+    jobjectArray jPerformancePoints = env->NewObjectArray(performancePoints.size(),
+            performancePointClazz, NULL);
+    int i = 0;
+    for (auto it = performancePoints.begin(); it != performancePoints.end(); ++it, ++i) {
+        jobject jPerformancePoint = env->NewObject(performancePointClazz,
+                performancePointConstructID, it->getWidth(),
+                it->getHeight(), it->getMaxFrameRate(),
+                it->getMaxMacroBlockRate(), it->getBlockSize().getWidth(),
+                it->getBlockSize().getHeight());
+
+        env->SetObjectArrayElement(jPerformancePoints, i, jPerformancePoint);
+
+        env->DeleteLocalRef(jPerformancePoint);
+    }
+
+    jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper");
+    CHECK(helperClazz != NULL);
+    jmethodID asListID = env->GetStaticMethodID(helperClazz, "constructPerformancePointList",
+            "([Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Ljava/util/List;");
+    CHECK(asListID != NULL);
+    jobject jList = env->CallStaticObjectMethod(helperClazz, asListID, jPerformancePoints);
+
+    return jList;
+}
+
+static VideoCapabilities::PerformancePoint convertToNativePerformancePoint(
+        JNIEnv *env, jobject pp) {
+    if (pp == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+    }
+
+    jclass clazz = env->FindClass(
+            "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint");
+    CHECK(clazz != NULL);
+    CHECK(env->IsInstanceOf(pp, clazz));
+
+    jmethodID getWidthID = env->GetMethodID(clazz, "getWidth", "()I");
+    CHECK(getWidthID != NULL);
+    jint width = env->CallIntMethod(pp, getWidthID);
+
+    jmethodID getHeightID = env->GetMethodID(clazz, "getHeight", "()I");
+    CHECK(getHeightID != NULL);
+    jint height = env->CallIntMethod(pp, getHeightID);
+
+    jmethodID getMaxFrameRateID = env->GetMethodID(clazz, "getMaxFrameRate", "()I");
+    CHECK(getMaxFrameRateID != NULL);
+    jint maxFrameRate = env->CallIntMethod(pp, getMaxFrameRateID);
+
+    jmethodID getMaxMacroBlockRateID = env->GetMethodID(clazz, "getMaxMacroBlockRate", "()J");
+    CHECK(getMaxMacroBlockRateID != NULL);
+    jlong maxMacroBlockRate = env->CallLongMethod(pp, getMaxMacroBlockRateID);
+
+    jmethodID getBlockWidthID = env->GetMethodID(clazz, "getBlockWidth", "()I");
+    CHECK(getBlockWidthID != NULL);
+    jint blockWidth = env->CallIntMethod(pp, getBlockWidthID);
+
+    jmethodID getBlockHeightID = env->GetMethodID(clazz, "getBlockHeight", "()I");
+    CHECK(getBlockHeightID != NULL);
+    jint blockHeight = env->CallIntMethod(pp, getBlockHeightID);
+
+    return VideoCapabilities::PerformancePoint(VideoSize(blockWidth, blockHeight),
+            width, height, maxFrameRate, maxMacroBlockRate);
+}
+
+static jobject convertToJavaVideoCapabilities(JNIEnv *env,
+        std::shared_ptr<VideoCapabilities> videoCaps) {
+    if (videoCaps == nullptr) {
+        return NULL;
+    }
+
+    // get Java bitrateRange
+    const Range<int32_t>& bitrateRange = videoCaps->getBitrateRange();
+    jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange);
+
+    // get Java widthRange
+    const Range<int32_t>& widthRange = videoCaps->getSupportedWidths();
+    jobject jWidthRange = convertToJavaIntRange(env, widthRange);
+
+    // get Java heightRange
+    const Range<int32_t>& heightRange = videoCaps->getSupportedHeights();
+    jobject jHeightRange = convertToJavaIntRange(env, heightRange);
+
+    // get Java frameRateRange
+    const Range<int32_t>& frameRateRange = videoCaps->getSupportedFrameRates();
+    jobject jFrameRateRange = convertToJavaIntRange(env, frameRateRange);
+
+    // get Java performancePoints
+    const std::vector<VideoCapabilities::PerformancePoint>& performancePoints
+            = videoCaps->getSupportedPerformancePoints();
+    jobject jPerformancePoints = convertToJavaPerformancePoints(env, performancePoints);
+
+    // get width alignment
+    int32_t widthAlignment = videoCaps->getWidthAlignment();
+
+    // get height alignment
+    int32_t heightAlignment = videoCaps->getHeightAlignment();
+
+    // get Java VideoCapsNativeImpl
+    jclass videoCapsImplClazz = env->FindClass(
+            "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl");
+    CHECK(videoCapsImplClazz != NULL);
+    jmethodID videoCapsImplConstructID = env->GetMethodID(videoCapsImplClazz, "<init>",
+            "(Landroid/util/Range;"
+            "Landroid/util/Range;"
+            "Landroid/util/Range;"
+            "Landroid/util/Range;"
+            "Ljava/util/List;II)V");
+    jobject jVideoCapsImpl = env->NewObject(videoCapsImplClazz, videoCapsImplConstructID,
+            jBitrateRange, jWidthRange, jHeightRange, jFrameRateRange, jPerformancePoints,
+            widthAlignment, heightAlignment);
+    // The native VideoCapabilities won't be destructed until process ends.
+    env->SetLongField(jVideoCapsImpl, fields.videoCapsContext, (jlong)videoCaps.get());
+
+    // get Java VideoCapabilities
+    jclass videoCapsClazz
+            = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities");
+    CHECK(videoCapsClazz != NULL);
+    jmethodID videoCapsConstructID = env->GetMethodID(videoCapsClazz, "<init>",
+            "(Landroid/media/MediaCodecInfo$VideoCapabilities$VideoCapsIntf;)V");
+    jobject jVideoCaps = env->NewObject(videoCapsClazz, videoCapsConstructID, jVideoCapsImpl);
+
+    env->DeleteLocalRef(jBitrateRange);
+    jBitrateRange = NULL;
+
+    env->DeleteLocalRef(jWidthRange);
+    jWidthRange = NULL;
+
+    env->DeleteLocalRef(jHeightRange);
+    jHeightRange = NULL;
+
+    env->DeleteLocalRef(jFrameRateRange);
+    jFrameRateRange = NULL;
+
+    env->DeleteLocalRef(jPerformancePoints);
+    jPerformancePoints = NULL;
+
+    env->DeleteLocalRef(jVideoCapsImpl);
+    jVideoCapsImpl = NULL;
+
+    return jVideoCaps;
+}
+
+static jobject convertToJavaEncoderCapabilities(JNIEnv *env,
+        std::shared_ptr<EncoderCapabilities> encoderCaps) {
+    if (encoderCaps == nullptr) {
+        return NULL;
+    }
+
+    // get quality range
+    const Range<int>& qualityRange = encoderCaps->getQualityRange();
+    jobject jQualityRange = convertToJavaIntRange(env, qualityRange);
+
+    // get complexity range
+    const Range<int>& complexityRange = encoderCaps->getComplexityRange();
+    jobject jComplexityRange = convertToJavaIntRange(env, complexityRange);
+
+    // construct java EncoderCapsNativeImpl
+    jclass encoderCapsImplClazz = env->FindClass(
+            "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl");
+    CHECK(encoderCapsImplClazz != NULL);
+    jmethodID encoderCapsImplConstructID = env->GetMethodID(encoderCapsImplClazz, "<init>",
+            "(Landroid/util/Range;Landroid/util/Range;)V");
+    jobject jEncoderCapsImpl = env->NewObject(encoderCapsImplClazz, encoderCapsImplConstructID,
+            jQualityRange, jComplexityRange);
+    // The native EncoderCapabilities won't be destructed until process ends.
+    env->SetLongField(jEncoderCapsImpl, fields.encoderCapsContext, (jlong)encoderCaps.get());
+
+    // construct java EncoderCapabilities object
+    jclass encoderCapsClazz
+            = env->FindClass("android/media/MediaCodecInfo$EncoderCapabilities");
+    CHECK(encoderCapsClazz != NULL);
+    jmethodID encoderCapsConstructID = env->GetMethodID(encoderCapsClazz, "<init>",
+            "(Landroid/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsIntf;)V");
+    jobject jEncoderCaps = env->NewObject(encoderCapsClazz, encoderCapsConstructID,
+            jEncoderCapsImpl);
+
+    env->DeleteLocalRef(jQualityRange);
+    jQualityRange = NULL;
+
+    env->DeleteLocalRef(jComplexityRange);
+    jComplexityRange = NULL;
+
+    env->DeleteLocalRef(jEncoderCapsImpl);
+    jEncoderCapsImpl = NULL;
+
+    return jEncoderCaps;
+}
+
+// Java CodecCapsNativeImpl keeps the defaultFormat, profileLevels, colorFormats, audioCapabilities,
+// videoCapabilities and encoderCapabilities in it to prevent reconsturction when called by getter.
+static jobject convertToJavaCodecCapsNativeImpl(
+            JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) {
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
+    }
+
+    // Construct defaultFormat
+    sp<AMessage> defaultFormat = codecCaps->getDefaultFormat();
+
+    jobject formatMap = NULL;
+    if (ConvertMessageToMap(env, defaultFormat, &formatMap)) {
+        return NULL;
+    }
+
+    ScopedLocalRef<jclass> mediaFormatClass{env, env->FindClass("android/media/MediaFormat")};
+    ScopedLocalRef<jobject> jDefaultFormat{env, env->NewObject(
+            mediaFormatClass.get(),
+            env->GetMethodID(mediaFormatClass.get(), "<init>", "(Ljava/util/Map;)V"),
+            formatMap)};
+
+    env->DeleteLocalRef(formatMap);
+    formatMap = NULL;
+
+    // Construct Java ProfileLevelArray
+    std::vector<ProfileLevel> profileLevels = codecCaps->getProfileLevels();
+
+    jclass profileLevelClazz =
+        env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
+    CHECK(profileLevelClazz != NULL);
+
+    jobjectArray profileLevelArray =
+            env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
+
+    jfieldID profileField =
+            env->GetFieldID(profileLevelClazz, "profile", "I");
+    jfieldID levelField =
+            env->GetFieldID(profileLevelClazz, "level", "I");
+
+    for (size_t i = 0; i < profileLevels.size(); ++i) {
+        const ProfileLevel &src = profileLevels.at(i);
+
+        jobject profileLevelObj = env->AllocObject(profileLevelClazz);
+
+        env->SetIntField(profileLevelObj, profileField, src.mProfile);
+        env->SetIntField(profileLevelObj, levelField, src.mLevel);
+
+        env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
+
+        env->DeleteLocalRef(profileLevelObj);
+        profileLevelObj = NULL;
+    }
+
+    // Construct ColorFormatArray
+    std::vector<uint32_t> colorFormats = codecCaps->getColorFormats();
+
+    jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+    env->SetIntArrayRegion(colorFormatsArray, 0, colorFormats.size(),
+            reinterpret_cast<jint*>(colorFormats.data()));
+
+    // Construct and set AudioCapabilities
+    std::shared_ptr<AudioCapabilities> audioCaps = codecCaps->getAudioCapabilities();
+    jobject jAudioCaps = convertToJavaAudioCapabilities(env, audioCaps);
+
+    // Set VideoCapabilities
+    std::shared_ptr<VideoCapabilities> videoCaps = codecCaps->getVideoCapabilities();
+    jobject jVideoCaps = convertToJavaVideoCapabilities(env, videoCaps);
+
+    // Set EncoderCapabilities
+    std::shared_ptr<EncoderCapabilities> encoderCaps = codecCaps->getEncoderCapabilities();
+    jobject jEncoderCaps = convertToJavaEncoderCapabilities(env, encoderCaps);
+
+    // Construct CodecCapsNativeImpl
+    jclass codecCapsImplClazz =
+        env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl");
+    CHECK(codecCapsImplClazz != NULL);
+    jmethodID codecCapsImplConstructID = env->GetMethodID(codecCapsImplClazz, "<init>",
+                "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[I"
+                "Landroid/media/MediaFormat;"
+                "Landroid/media/MediaCodecInfo$AudioCapabilities;"
+                "Landroid/media/MediaCodecInfo$VideoCapabilities;"
+                "Landroid/media/MediaCodecInfo$EncoderCapabilities;)V");
+    jobject javaCodecCapsImpl = env->NewObject(codecCapsImplClazz, codecCapsImplConstructID,
+            profileLevelArray, colorFormatsArray, jDefaultFormat.get(),
+            jAudioCaps, jVideoCaps, jEncoderCaps);
+
+    // Construct JCodecCapabilities and hold the codecCaps in it
+    sp<JCodecCapabilities> jCodecCaps = sp<JCodecCapabilities>::make(codecCaps);
+    setCodecCapabilities(env, javaCodecCapsImpl, jCodecCaps);
+
+    env->DeleteLocalRef(profileLevelArray);
+    profileLevelArray = NULL;
+
+    env->DeleteLocalRef(colorFormatsArray);
+    colorFormatsArray = NULL;
+
+    env->DeleteLocalRef(jAudioCaps);
+    jAudioCaps = NULL;
+
+    env->DeleteLocalRef(jVideoCaps);
+    jVideoCaps = NULL;
+
+    env->DeleteLocalRef(jEncoderCaps);
+    jEncoderCaps = NULL;
+
+    return javaCodecCapsImpl;
+}
+
+jobject convertToJavaCodecCapabiliites(
+        JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) {
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
+    }
+
+    jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps);
+
+    // Construct CodecCapabilities
+    jclass codecCapsClazz = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
+    CHECK(codecCapsClazz != NULL);
+
+    jmethodID codecCapsConstructID = env->GetMethodID(codecCapsClazz, "<init>",
+            "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
+    jobject javaCodecCaps = env->NewObject(codecCapsClazz, codecCapsConstructID, javaCodecCapsImpl);
+
+    return javaCodecCaps;
+}
+
+}  // namespace android
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+// AudioCapabilities
+
+static void android_media_AudioCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+    jclass audioCapsImplClazz
+            = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl");
+    if (audioCapsImplClazz == NULL) {
+        return;
+    }
+
+    fields.audioCapsContext = env->GetFieldID(audioCapsImplClazz, "mNativeContext", "J");
+    if (fields.audioCapsContext == NULL) {
+        return;
+    }
+
+    env->DeleteLocalRef(audioCapsImplClazz);
+}
+
+static jint android_media_AudioCapabilities_getMaxInputChannelCount(JNIEnv *env, jobject thiz) {
+    AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
+    if (audioCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    int32_t maxInputChannelCount = audioCaps->getMaxInputChannelCount();
+    return maxInputChannelCount;
+}
+
+static jint android_media_AudioCapabilities_getMinInputChannelCount(JNIEnv *env, jobject thiz) {
+    AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
+    if (audioCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    int32_t minInputChannelCount = audioCaps->getMinInputChannelCount();
+    return minInputChannelCount;
+}
+
+static jboolean android_media_AudioCapabilities_isSampleRateSupported(JNIEnv *env, jobject thiz,
+        int sampleRate) {
+    AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz);
+    if (audioCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    bool res = audioCaps->isSampleRateSupported(sampleRate);
+    return res;
+}
+
+// PerformancePoint
+
+static jboolean android_media_VideoCapabilities_PerformancePoint_covers(JNIEnv *env, jobject thiz,
+        jobject other) {
+    VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz);
+    VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other);
+
+    bool res = pp0.covers(pp1);
+    return res;
+}
+
+static jboolean android_media_VideoCapabilities_PerformancePoint_equals(JNIEnv *env, jobject thiz,
+        jobject other) {
+    VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz);
+    VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other);
+
+    bool res = pp0.equals(pp1);
+    return res;
+}
+
+// VideoCapabilities
+
+static void android_media_VideoCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+    jclass clazz
+            = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl");
+    if (clazz == NULL) {
+        return;
+    }
+
+    fields.videoCapsContext = env->GetFieldID(clazz, "mNativeContext", "J");
+    if (fields.videoCapsContext == NULL) {
+        return;
+    }
+
+    env->DeleteLocalRef(clazz);
+}
+
+static jboolean android_media_VideoCapabilities_areSizeAndRateSupported(JNIEnv *env, jobject thiz,
+        int32_t width, int32_t height, double frameRate) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    bool res = videoCaps->areSizeAndRateSupported(width, height, frameRate);
+    return res;
+}
+
+static jboolean android_media_VideoCapabilities_isSizeSupported(JNIEnv *env, jobject thiz,
+        int32_t width, int32_t height) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    bool res = videoCaps->isSizeSupported(width, height);
+    return res;
+}
+
+static jobject android_media_VideoCapabilities_getAchievableFrameRatesFor(JNIEnv *env, jobject thiz,
+        int32_t width, int32_t height) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    std::optional<Range<double>> frameRates = videoCaps->getAchievableFrameRatesFor(width, height);
+    if (!frameRates) {
+        return NULL;
+    }
+    jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value());
+    return jFrameRates;
+}
+
+static jobject android_media_VideoCapabilities_getSupportedFrameRatesFor(JNIEnv *env, jobject thiz,
+        int32_t width, int32_t height) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    std::optional<Range<double>> frameRates = videoCaps->getSupportedFrameRatesFor(width, height);
+    if (!frameRates) {
+        return NULL;
+    }
+    jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value());
+    return jFrameRates;
+}
+
+static jobject android_media_VideoCapabilities_getSupportedWidthsFor(JNIEnv *env, jobject thiz,
+        int32_t height) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    std::optional<Range<int32_t>> supportedWidths = videoCaps->getSupportedWidthsFor(height);
+    if (!supportedWidths) {
+        return NULL;
+    }
+    jobject jSupportedWidths = convertToJavaIntRange(env, supportedWidths.value());
+
+    return jSupportedWidths;
+}
+
+static jobject android_media_VideoCapabilities_getSupportedHeightsFor(JNIEnv *env, jobject thiz,
+        int32_t width) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    std::optional<Range<int32_t>> supportedHeights = videoCaps->getSupportedHeightsFor(width);
+    if (!supportedHeights) {
+        return NULL;
+    }
+    jobject jSupportedHeights = convertToJavaIntRange(env, supportedHeights.value());
+
+    return jSupportedHeights;
+}
+
+static jint android_media_VideoCapabilities_getSmallerDimensionUpperLimit(JNIEnv *env,
+        jobject thiz) {
+    VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz);
+    if (videoCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    int smallerDimensionUpperLimit = videoCaps->getSmallerDimensionUpperLimit();
+    return smallerDimensionUpperLimit;
+}
+
+// EncoderCapabilities
+
+static void android_media_EncoderCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+    jclass clazz = env->FindClass(
+            "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl");
+    if (clazz == NULL) {
+        return;
+    }
+
+    fields.encoderCapsContext = env->GetFieldID(clazz, "mNativeContext", "J");
+    if (fields.encoderCapsContext == NULL) {
+        return;
+    }
+
+    env->DeleteLocalRef(clazz);
+}
+
+static jboolean android_media_EncoderCapabilities_isBitrateModeSupported(JNIEnv *env, jobject thiz,
+        int mode) {
+    EncoderCapabilities* const encoderCaps = getEncoderCapabilities(env, thiz);
+    if (encoderCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    bool res = encoderCaps->isBitrateModeSupported(mode);
+    return res;
+}
+
+// CodecCapabilities
+
+static void android_media_CodecCapabilities_native_init(JNIEnv *env, jobject /* thiz */) {
+    jclass codecCapsClazz
+            = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl");
+    if (codecCapsClazz == NULL) {
+        return;
+    }
+
+    fields.codecCapsContext = env->GetFieldID(codecCapsClazz, "mNativeContext", "J");
+    if (fields.codecCapsContext == NULL) {
+        return;
+    }
+
+    env->DeleteLocalRef(codecCapsClazz);
+}
+
+static jobject android_media_CodecCapabilities_createFromProfileLevel(JNIEnv *env,
+        jobject /* thiz */, jstring mediaType, jint profile, jint level) {
+    if (mediaType == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return NULL;
+    }
+
+    const char *mediaTypeStr = env->GetStringUTFChars(mediaType, nullptr);
+    if (mediaTypeStr == nullptr) {
+        return NULL;
+    }
+
+    std::shared_ptr<CodecCapabilities> codecCaps = CodecCapabilities::CreateFromProfileLevel(
+            mediaTypeStr, profile, level);
+
+    jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps);
+
+    env->ReleaseStringUTFChars(mediaType, mediaTypeStr);
+
+    return javaCodecCapsImpl;
+}
+
+static jobject android_media_CodecCapabilities_native_dup(JNIEnv *env, jobject thiz) {
+    sp<JCodecCapabilities> jCodecCaps = getCodecCapabilities(env, thiz);
+
+    // As the CodecCaps objects are ready ony, it is ok to use the default copy constructor.
+    // The duplicate CodecCaps will share the same subobjects with the existing one.
+    // The lifetime of subobjects are managed by the shared pointer and sp.
+    std::shared_ptr<CodecCapabilities> duplicate
+            = std::make_shared<CodecCapabilities>(*(jCodecCaps->getCodecCaps()));
+
+    jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, duplicate);
+
+    return javaCodecCapsImpl;
+}
+
+static void android_media_CodecCapabilities_native_finalize(JNIEnv *env, jobject thiz) {
+    ALOGV("native_finalize");
+    setCodecCapabilities(env, thiz, NULL);
+}
+
+static jint android_media_CodecCapabilities_getMaxSupportedInstances(JNIEnv *env, jobject thiz) {
+    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+
+    int maxSupportedInstances = codecCaps->getMaxSupportedInstances();
+    return maxSupportedInstances;
+}
+
+static jstring android_media_CodecCapabilities_getMimeType(JNIEnv *env, jobject thiz) {
+    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    std::string mediaType = codecCaps->getMediaType();
+    return env->NewStringUTF(mediaType.c_str());
+}
+
+static jboolean android_media_CodecCapabilities_isFeatureRequired(
+        JNIEnv *env, jobject thiz, jstring name) {
+    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return -ENOENT;
+    }
+
+    const char *nameStr = env->GetStringUTFChars(name, NULL);
+    if (nameStr == NULL) {
+        // Out of memory exception already pending.
+        return -ENOENT;
+    }
+
+    bool isFeatureRequired = codecCaps->isFeatureRequired(nameStr);
+
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    return isFeatureRequired;
+}
+
+static jboolean android_media_CodecCapabilities_isFeatureSupported(
+        JNIEnv *env, jobject thiz, jstring name) {
+    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    if (name == NULL) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return -ENOENT;
+    }
+
+    const char *nameStr = env->GetStringUTFChars(name, NULL);
+    if (nameStr == NULL) {
+        // Out of memory exception already pending.
+        return -ENOENT;
+    }
+
+    bool isFeatureSupported = codecCaps->isFeatureSupported(nameStr);
+
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    return isFeatureSupported;
+}
+
+static jboolean android_media_CodecCapabilities_isFormatSupported(JNIEnv *env, jobject thiz,
+        jobjectArray keys, jobjectArray values) {
+    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    sp<AMessage> format;
+    status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format);
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return -ENOENT;;
+    }
+
+    return codecCaps->isFormatSupported(format);
+}
+
+static jboolean android_media_CodecCapabilities_isRegular(JNIEnv *env, jobject thiz) {
+    sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz);
+    if (codecCaps == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return false;
+    }
+
+    bool res = codecCaps->isRegular();
+    return res;
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gAudioCapsMethods[] = {
+    {"native_init", "()V", (void *)android_media_AudioCapabilities_native_init},
+    {"native_getMaxInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMaxInputChannelCount},
+    {"native_getMinInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMinInputChannelCount},
+    {"native_isSampleRateSupported", "(I)Z", (void *)android_media_AudioCapabilities_isSampleRateSupported}
+};
+
+static const JNINativeMethod gPerformancePointMethods[] = {
+    {"native_covers", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_covers},
+    {"native_equals", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_equals},
+};
+
+static const JNINativeMethod gVideoCapsMethods[] = {
+    {"native_init", "()V", (void *)android_media_VideoCapabilities_native_init},
+    {"native_areSizeAndRateSupported", "(IID)Z", (void *)android_media_VideoCapabilities_areSizeAndRateSupported},
+    {"native_isSizeSupported", "(II)Z", (void *)android_media_VideoCapabilities_isSizeSupported},
+    {"native_getAchievableFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getAchievableFrameRatesFor},
+    {"native_getSupportedFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedFrameRatesFor},
+    {"native_getSupportedWidthsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedWidthsFor},
+    {"native_getSupportedHeightsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedHeightsFor},
+    {"native_getSmallerDimensionUpperLimit", "()I", (void *)android_media_VideoCapabilities_getSmallerDimensionUpperLimit}
+};
+
+static const JNINativeMethod gEncoderCapsMethods[] = {
+    {"native_init", "()V", (void *)android_media_EncoderCapabilities_native_init},
+    {"native_isBitrateModeSupported", "(I)Z", (void *)android_media_EncoderCapabilities_isBitrateModeSupported}
+};
+
+static const JNINativeMethod gCodecCapsMethods[] = {
+    { "native_init", "()V", (void *)android_media_CodecCapabilities_native_init },
+    { "native_createFromProfileLevel", "(Ljava/lang/String;II)Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_createFromProfileLevel },
+    { "native_dup", "()Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_native_dup },
+    { "native_finalize", "()V", (void *)android_media_CodecCapabilities_native_finalize },
+    { "native_getMaxSupportedInstances", "()I", (void *)android_media_CodecCapabilities_getMaxSupportedInstances },
+    { "native_getMimeType", "()Ljava/lang/String;", (void *)android_media_CodecCapabilities_getMimeType },
+    { "native_isFeatureRequired", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureRequired },
+    { "native_isFeatureSupported", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureSupported },
+    { "native_isFormatSupported", "([Ljava/lang/String;[Ljava/lang/Object;)Z", (void *)android_media_CodecCapabilities_isFormatSupported },
+    { "native_isRegular", "()Z", (void *)android_media_CodecCapabilities_isRegular },
+};
+
+int register_android_media_CodecCapabilities(JNIEnv *env) {
+    int result = AndroidRuntime::registerNativeMethods(env,
+            "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl",
+            gAudioCapsMethods, NELEM(gAudioCapsMethods));
+    if (result != JNI_OK) {
+        return result;
+    }
+
+    result = AndroidRuntime::registerNativeMethods(env,
+            "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint",
+            gPerformancePointMethods, NELEM(gPerformancePointMethods));
+    if (result != JNI_OK) {
+        return result;
+    }
+
+    result = AndroidRuntime::registerNativeMethods(env,
+            "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl",
+            gVideoCapsMethods, NELEM(gVideoCapsMethods));
+    if (result != JNI_OK) {
+        return result;
+    }
+
+    result = AndroidRuntime::registerNativeMethods(env,
+            "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl",
+            gEncoderCapsMethods, NELEM(gEncoderCapsMethods));
+    if (result != JNI_OK) {
+        return result;
+    }
+
+    result = AndroidRuntime::registerNativeMethods(env,
+            "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl",
+            gCodecCapsMethods, NELEM(gCodecCapsMethods));
+    return result;
+}
\ No newline at end of file
diff --git a/media/jni/android_media_CodecCapabilities.h b/media/jni/android_media_CodecCapabilities.h
new file mode 100644
index 0000000..5cca0b5
--- /dev/null
+++ b/media/jni/android_media_CodecCapabilities.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_MEDIA_CODECCAPABILITIES_H_
+#define _ANDROID_MEDIA_CODECCAPABILITIES_H_
+
+#include "jni.h"
+
+#include <media/CodecCapabilities.h>
+
+namespace android {
+
+struct JCodecCapabilities : public RefBase {
+    JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps);
+
+    std::shared_ptr<CodecCapabilities> getCodecCaps() const;
+
+    int32_t getMaxSupportedInstances() const;
+    std::string getMediaType() const;
+    bool isFeatureRequired(const std::string& name) const;
+    bool isFeatureSupported(const std::string& name) const;
+    bool isFormatSupported(const sp<AMessage> &format) const;
+    bool isRegular() const;
+
+private:
+    std::shared_ptr<CodecCapabilities> mCodecCaps;
+};
+
+jobject convertToJavaCodecCapabiliites(
+        JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps);
+
+}
+
+#endif  // _ANDROID_MEDIA_CODECCAPABILITIES_H_
\ No newline at end of file
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8419ce7..1790670 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,12 +16,14 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaCodec-JNI"
+#include <android_media_codec.h>
 #include <utils/Log.h>
 
 #include <type_traits>
 
 #include "android_media_MediaCodec.h"
 
+#include "android_media_CodecCapabilities.h"
 #include "android_media_MediaCodecLinearBlock.h"
 #include "android_media_MediaCrypto.h"
 #include "android_media_MediaDescrambler.h"
@@ -138,6 +140,8 @@
 static struct {
     jclass capsClazz;
     jmethodID capsCtorId;
+    jclass cpasImplClazz;
+    jmethodID capsImplCtorId;
     jclass profileLevelClazz;
     jfieldID profileField;
     jfieldID levelField;
@@ -996,10 +1000,12 @@
         env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val);
     }
 
-    return env->NewObject(
-            gCodecInfo.capsClazz, gCodecInfo.capsCtorId,
+    jobject javaCodecCapsImpl = env->NewObject(
+            gCodecInfo.cpasImplClazz, gCodecInfo.capsImplCtorId,
             profileLevelArray.get(), colorFormatsArray.get(), isEncoder,
             defaultFormatRef.get(), detailsRef.get());
+
+    return env->NewObject(gCodecInfo.capsClazz, gCodecInfo.capsCtorId, javaCodecCapsImpl);
 }
 
 status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const {
@@ -1027,11 +1033,18 @@
         env->NewObjectArray(mediaTypes.size(), gCodecInfo.capsClazz, NULL));
 
     for (size_t i = 0; i < mediaTypes.size(); i++) {
-        const sp<MediaCodecInfo::Capabilities> caps =
-                codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
-
-        ScopedLocalRef<jobject> capsObj(env, getCodecCapabilitiesObject(
-                env, mediaTypes[i].c_str(), isEncoder, caps));
+        jobject jCodecCaps = NULL;
+        if (android::media::codec::provider_->native_capabilites()) {
+            const std::shared_ptr<CodecCapabilities> codecCaps
+                    = codecInfo->getCodecCapsFor(mediaTypes[i].c_str());
+            jCodecCaps = convertToJavaCodecCapabiliites(env, codecCaps);
+        } else {
+            const sp<MediaCodecInfo::Capabilities> caps =
+                    codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
+            jCodecCaps = getCodecCapabilitiesObject(
+                    env, mediaTypes[i].c_str(), isEncoder, caps);
+        }
+        ScopedLocalRef<jobject> capsObj(env, jCodecCaps);
 
         env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get());
     }
@@ -3877,10 +3890,20 @@
     gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get());
 
     method = env->GetMethodID(clazz.get(), "<init>",
+            "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
+    CHECK(method != NULL);
+    gCodecInfo.capsCtorId = method;
+
+    clazz.reset(env->FindClass(
+            "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl"));
+    CHECK(clazz.get() != NULL);
+    gCodecInfo.cpasImplClazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    method = env->GetMethodID(clazz.get(), "<init>",
             "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
             "Ljava/util/Map;Ljava/util/Map;)V");
     CHECK(method != NULL);
-    gCodecInfo.capsCtorId = method;
+    gCodecInfo.capsImplCtorId = method;
 
     clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel"));
     CHECK(clazz.get() != NULL);
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index 07866ac..3522b35 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -16,6 +16,9 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "MediaCodec-JNI"
+
+#include <android_media_codec.h>
+
 #include <utils/Log.h>
 
 #include <media/stagefright/foundation/ADebug.h>
@@ -32,6 +35,7 @@
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include "android_media_CodecCapabilities.h"
 #include "android_media_Streams.h"
 
 using namespace android;
@@ -245,96 +249,114 @@
         return NULL;
     }
 
-    Vector<MediaCodecInfo::ProfileLevel> profileLevels;
-    Vector<uint32_t> colorFormats;
+    jobject caps;
+    if (android::media::codec::provider_->native_capabilites()) {
+        std::shared_ptr<CodecCapabilities> codecCaps = info.info->getCodecCapsFor(typeStr);
+        caps = android::convertToJavaCodecCapabiliites(env, codecCaps);
+    } else {
+        Vector<MediaCodecInfo::ProfileLevel> profileLevels;
+        Vector<uint32_t> colorFormats;
 
-    sp<AMessage> defaultFormat = new AMessage();
-    defaultFormat->setString("mime", typeStr);
+        sp<AMessage> defaultFormat = new AMessage();
+        defaultFormat->setString("mime", typeStr);
 
-    // TODO query default-format also from codec/codec list
-    const sp<MediaCodecInfo::Capabilities> &capabilities =
-        info.info->getCapabilitiesFor(typeStr);
-    env->ReleaseStringUTFChars(type, typeStr);
-    typeStr = NULL;
-    if (capabilities == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return NULL;
-    }
+        // TODO query default-format also from codec/codec list
+        const sp<MediaCodecInfo::Capabilities> &capabilities =
+            info.info->getCapabilitiesFor(typeStr);
+        env->ReleaseStringUTFChars(type, typeStr);
+        typeStr = NULL;
+        if (capabilities == NULL) {
+            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+            return NULL;
+        }
 
-    capabilities->getSupportedColorFormats(&colorFormats);
-    capabilities->getSupportedProfileLevels(&profileLevels);
-    sp<AMessage> details = capabilities->getDetails();
-    bool isEncoder = info.info->isEncoder();
+        capabilities->getSupportedColorFormats(&colorFormats);
+        capabilities->getSupportedProfileLevels(&profileLevels);
+        sp<AMessage> details = capabilities->getDetails();
+        bool isEncoder = info.info->isEncoder();
 
-    jobject defaultFormatObj = NULL;
-    if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
-        return NULL;
-    }
+        jobject defaultFormatObj = NULL;
+        if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
+            return NULL;
+        }
 
-    jobject infoObj = NULL;
-    if (ConvertMessageToMap(env, details, &infoObj)) {
+        jobject infoObj = NULL;
+        if (ConvertMessageToMap(env, details, &infoObj)) {
+            env->DeleteLocalRef(defaultFormatObj);
+            return NULL;
+        }
+
+        jclass capsImplClazz = env->FindClass(
+                "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl");
+        CHECK(capsImplClazz != NULL);
+
+        jclass profileLevelClazz =
+            env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
+        CHECK(profileLevelClazz != NULL);
+
+        jobjectArray profileLevelArray =
+            env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
+
+        jfieldID profileField =
+            env->GetFieldID(profileLevelClazz, "profile", "I");
+
+        jfieldID levelField =
+            env->GetFieldID(profileLevelClazz, "level", "I");
+
+        for (size_t i = 0; i < profileLevels.size(); ++i) {
+            const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
+
+            jobject profileLevelObj = env->AllocObject(profileLevelClazz);
+
+            env->SetIntField(profileLevelObj, profileField, src.mProfile);
+            env->SetIntField(profileLevelObj, levelField, src.mLevel);
+
+            env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
+
+            env->DeleteLocalRef(profileLevelObj);
+            profileLevelObj = NULL;
+        }
+
+        jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+
+        for (size_t i = 0; i < colorFormats.size(); ++i) {
+            jint val = colorFormats.itemAt(i);
+            env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
+        }
+
+        jmethodID capsImplConstructID = env->GetMethodID(capsImplClazz, "<init>",
+                "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
+                "Ljava/util/Map;Ljava/util/Map;)V");
+
+        jobject capsImpl = env->NewObject(capsImplClazz, capsImplConstructID,
+                profileLevelArray, colorFormatsArray, isEncoder,
+                defaultFormatObj, infoObj);
+
+        jclass capsClazz = env->FindClass(
+                "android/media/MediaCodecInfo$CodecCapabilities");
+        CHECK(capsClazz != NULL);
+
+        jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+                "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
+
+        caps = env->NewObject(capsClazz, capsConstructID, capsImpl);
+
+        env->DeleteLocalRef(profileLevelArray);
+        profileLevelArray = NULL;
+
+        env->DeleteLocalRef(colorFormatsArray);
+        colorFormatsArray = NULL;
+
         env->DeleteLocalRef(defaultFormatObj);
-        return NULL;
+        defaultFormatObj = NULL;
+
+        env->DeleteLocalRef(infoObj);
+        infoObj = NULL;
+
+        env->DeleteLocalRef(capsImpl);
+        capsImpl = NULL;
     }
 
-    jclass capsClazz =
-        env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
-    CHECK(capsClazz != NULL);
-
-    jclass profileLevelClazz =
-        env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
-    CHECK(profileLevelClazz != NULL);
-
-    jobjectArray profileLevelArray =
-        env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
-
-    jfieldID profileField =
-        env->GetFieldID(profileLevelClazz, "profile", "I");
-
-    jfieldID levelField =
-        env->GetFieldID(profileLevelClazz, "level", "I");
-
-    for (size_t i = 0; i < profileLevels.size(); ++i) {
-        const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
-
-        jobject profileLevelObj = env->AllocObject(profileLevelClazz);
-
-        env->SetIntField(profileLevelObj, profileField, src.mProfile);
-        env->SetIntField(profileLevelObj, levelField, src.mLevel);
-
-        env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
-
-        env->DeleteLocalRef(profileLevelObj);
-        profileLevelObj = NULL;
-    }
-
-    jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
-
-    for (size_t i = 0; i < colorFormats.size(); ++i) {
-        jint val = colorFormats.itemAt(i);
-        env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
-    }
-
-    jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
-            "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
-            "Ljava/util/Map;Ljava/util/Map;)V");
-
-    jobject caps = env->NewObject(capsClazz, capsConstructID,
-            profileLevelArray, colorFormatsArray, isEncoder,
-            defaultFormatObj, infoObj);
-
-    env->DeleteLocalRef(profileLevelArray);
-    profileLevelArray = NULL;
-
-    env->DeleteLocalRef(colorFormatsArray);
-    colorFormatsArray = NULL;
-
-    env->DeleteLocalRef(defaultFormatObj);
-    defaultFormatObj = NULL;
-
-    env->DeleteLocalRef(infoObj);
-    infoObj = NULL;
-
     return caps;
 }
 
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index d05ee55..fcba150 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1465,6 +1465,7 @@
 extern int register_android_media_ImageReader(JNIEnv *env);
 extern int register_android_media_ImageWriter(JNIEnv *env);
 extern int register_android_media_JetPlayer(JNIEnv *env);
+extern int register_android_media_CodecCapabilities(JNIEnv *env);
 extern int register_android_media_Crypto(JNIEnv *env);
 extern int register_android_media_Drm(JNIEnv *env);
 extern int register_android_media_Descrambler(JNIEnv *env);
@@ -1579,6 +1580,11 @@
         goto bail;
     }
 
+    if (register_android_media_CodecCapabilities(env) < 0) {
+        ALOGE("ERROR: CodecCapabilities native registration failed");
+        goto bail;
+    }
+
     if (register_android_media_Crypto(env) < 0) {
         ALOGE("ERROR: MediaCodec native registration failed");
         goto bail;