Extend MediaCodecInfo to describe usable codec limits and features

Bug: 11990470
Bug: 12065651
Bug: 16131974
Change-Id: I841b8507e823f1ddf14754e34029a9bed4f402d8
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index b5d0a57..2a68510 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -16,6 +16,24 @@
 
 package android.media;
 
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static android.media.Utils.intersectSortedDistinctRanges;
+import static android.media.Utils.sortDistinctRanges;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 /**
  * Provides information about a given media codec available on the device. You can
  * iterate through all codecs available by querying {@link MediaCodecList}. For example,
@@ -42,31 +60,42 @@
  *
  */
 public final class MediaCodecInfo {
-    private int mIndex;
+    private boolean mIsEncoder;
+    private String mName;
+    private Map<String, CodecCapabilities> mCaps;
 
-    /* package private */ MediaCodecInfo(int index) {
-        mIndex = index;
+    /* package private */ MediaCodecInfo(
+            String name, boolean isEncoder, CodecCapabilities[] caps) {
+        mName = name;
+        mIsEncoder = isEncoder;
+        mCaps = new HashMap<String, CodecCapabilities>();
+        for (CodecCapabilities c: caps) {
+            mCaps.put(c.getMime(), c);
+        }
     }
 
     /**
      * Retrieve the codec name.
      */
     public final String getName() {
-        return MediaCodecList.getCodecName(mIndex);
+        return mName;
     }
 
     /**
      * Query if the codec is an encoder.
      */
     public final boolean isEncoder() {
-        return MediaCodecList.isEncoder(mIndex);
+        return mIsEncoder;
     }
 
     /**
      * Query the media types supported by the codec.
      */
     public final String[] getSupportedTypes() {
-        return MediaCodecList.getSupportedTypes(mIndex);
+        Set<String> typeSet = mCaps.keySet();
+        String[] types = typeSet.toArray(new String[typeSet.size()]);
+        Arrays.sort(types);
+        return types;
     }
 
     /**
@@ -78,86 +107,1779 @@
      * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
      */
     public static final class CodecCapabilities {
+        public CodecCapabilities() {
+        }
+
+        // CLASSIFICATION
+        private String mMime;
+
+        // LEGACY FIELDS
+
         // Enumerates supported profile/level combinations as defined
         // by the type of encoded data. These combinations impose restrictions
         // on video resolution, bitrate... and limit the available encoder tools
         // such as B-frame support, arithmetic coding...
-        public CodecProfileLevel[] profileLevels;
+        public CodecProfileLevel[] profileLevels;  // NOTE this array is modifiable by user
 
         // from OMX_COLOR_FORMATTYPE
-        public final static int COLOR_FormatMonochrome              = 1;
-        public final static int COLOR_Format8bitRGB332              = 2;
-        public final static int COLOR_Format12bitRGB444             = 3;
-        public final static int COLOR_Format16bitARGB4444           = 4;
-        public final static int COLOR_Format16bitARGB1555           = 5;
-        public final static int COLOR_Format16bitRGB565             = 6;
-        public final static int COLOR_Format16bitBGR565             = 7;
-        public final static int COLOR_Format18bitRGB666             = 8;
-        public final static int COLOR_Format18bitARGB1665           = 9;
-        public final static int COLOR_Format19bitARGB1666           = 10;
-        public final static int COLOR_Format24bitRGB888             = 11;
-        public final static int COLOR_Format24bitBGR888             = 12;
-        public final static int COLOR_Format24bitARGB1887           = 13;
-        public final static int COLOR_Format25bitARGB1888           = 14;
-        public final static int COLOR_Format32bitBGRA8888           = 15;
-        public final static int COLOR_Format32bitARGB8888           = 16;
-        public final static int COLOR_FormatYUV411Planar            = 17;
-        public final static int COLOR_FormatYUV411PackedPlanar      = 18;
-        public final static int COLOR_FormatYUV420Planar            = 19;
-        public final static int COLOR_FormatYUV420PackedPlanar      = 20;
-        public final static int COLOR_FormatYUV420SemiPlanar        = 21;
-        public final static int COLOR_FormatYUV422Planar            = 22;
-        public final static int COLOR_FormatYUV422PackedPlanar      = 23;
-        public final static int COLOR_FormatYUV422SemiPlanar        = 24;
-        public final static int COLOR_FormatYCbYCr                  = 25;
-        public final static int COLOR_FormatYCrYCb                  = 26;
-        public final static int COLOR_FormatCbYCrY                  = 27;
-        public final static int COLOR_FormatCrYCbY                  = 28;
-        public final static int COLOR_FormatYUV444Interleaved       = 29;
-        public final static int COLOR_FormatRawBayer8bit            = 30;
-        public final static int COLOR_FormatRawBayer10bit           = 31;
-        public final static int COLOR_FormatRawBayer8bitcompressed  = 32;
-        public final static int COLOR_FormatL2                      = 33;
-        public final static int COLOR_FormatL4                      = 34;
-        public final static int COLOR_FormatL8                      = 35;
-        public final static int COLOR_FormatL16                     = 36;
-        public final static int COLOR_FormatL24                     = 37;
-        public final static int COLOR_FormatL32                     = 38;
-        public final static int COLOR_FormatYUV420PackedSemiPlanar  = 39;
-        public final static int COLOR_FormatYUV422PackedSemiPlanar  = 40;
-        public final static int COLOR_Format18BitBGR666             = 41;
-        public final static int COLOR_Format24BitARGB6666           = 42;
-        public final static int COLOR_Format24BitABGR6666           = 43;
+        public static final int COLOR_FormatMonochrome              = 1;
+        public static final int COLOR_Format8bitRGB332              = 2;
+        public static final int COLOR_Format12bitRGB444             = 3;
+        public static final int COLOR_Format16bitARGB4444           = 4;
+        public static final int COLOR_Format16bitARGB1555           = 5;
+        public static final int COLOR_Format16bitRGB565             = 6;
+        public static final int COLOR_Format16bitBGR565             = 7;
+        public static final int COLOR_Format18bitRGB666             = 8;
+        public static final int COLOR_Format18bitARGB1665           = 9;
+        public static final int COLOR_Format19bitARGB1666           = 10;
+        public static final int COLOR_Format24bitRGB888             = 11;
+        public static final int COLOR_Format24bitBGR888             = 12;
+        public static final int COLOR_Format24bitARGB1887           = 13;
+        public static final int COLOR_Format25bitARGB1888           = 14;
+        public static final int COLOR_Format32bitBGRA8888           = 15;
+        public static final int COLOR_Format32bitARGB8888           = 16;
+        public static final int COLOR_FormatYUV411Planar            = 17;
+        public static final int COLOR_FormatYUV411PackedPlanar      = 18;
+        public static final int COLOR_FormatYUV420Planar            = 19;
+        public static final int COLOR_FormatYUV420PackedPlanar      = 20;
+        public static final int COLOR_FormatYUV420SemiPlanar        = 21;
+        public static final int COLOR_FormatYUV422Planar            = 22;
+        public static final int COLOR_FormatYUV422PackedPlanar      = 23;
+        public static final int COLOR_FormatYUV422SemiPlanar        = 24;
+        public static final int COLOR_FormatYCbYCr                  = 25;
+        public static final int COLOR_FormatYCrYCb                  = 26;
+        public static final int COLOR_FormatCbYCrY                  = 27;
+        public static final int COLOR_FormatCrYCbY                  = 28;
+        public static final int COLOR_FormatYUV444Interleaved       = 29;
+        public static final int COLOR_FormatRawBayer8bit            = 30;
+        public static final int COLOR_FormatRawBayer10bit           = 31;
+        public static final int COLOR_FormatRawBayer8bitcompressed  = 32;
+        public static final int COLOR_FormatL2                      = 33;
+        public static final int COLOR_FormatL4                      = 34;
+        public static final int COLOR_FormatL8                      = 35;
+        public static final int COLOR_FormatL16                     = 36;
+        public static final int COLOR_FormatL24                     = 37;
+        public static final int COLOR_FormatL32                     = 38;
+        public static final int COLOR_FormatYUV420PackedSemiPlanar  = 39;
+        public static final int COLOR_FormatYUV422PackedSemiPlanar  = 40;
+        public static final int COLOR_Format18BitBGR666             = 41;
+        public static final int COLOR_Format24BitARGB6666           = 42;
+        public static final int COLOR_Format24BitABGR6666           = 43;
 
-        public final static int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100;
+        public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100;
         // COLOR_FormatSurface indicates that the data will be a GraphicBuffer metadata reference.
         // In OMX this is called OMX_COLOR_FormatAndroidOpaque.
-        public final static int COLOR_FormatSurface                   = 0x7F000789;
-        public final static int COLOR_QCOM_FormatYUV420SemiPlanar     = 0x7fa30c00;
+        public static final int COLOR_FormatSurface                   = 0x7F000789;
+        // This corresponds to YUV_420_888 format
+        public static final int COLOR_FormatYUV420Flexible            = 0x7F420888;
+        public static final int COLOR_QCOM_FormatYUV420SemiPlanar     = 0x7fa30c00;
 
         /**
          * Defined in the OpenMAX IL specs, color format values are drawn from
          * OMX_COLOR_FORMATTYPE.
          */
-        public int[] colorFormats;
+        public int[] colorFormats; // NOTE this array is modifiable by user
 
-        private final static int FLAG_SupportsAdaptivePlayback       = (1 << 0);
-        private int flags;
+        // FEATURES
+
+        private int mFlagsSupported;
+        private int mFlagsRequired;
+        private int mFlagsVerified;
 
         /**
          * <b>video decoder only</b>: codec supports seamless resolution changes.
          */
-        public final static String FEATURE_AdaptivePlayback       = "adaptive-playback";
+        public static final String FEATURE_AdaptivePlayback       = "adaptive-playback";
+
+        /**
+         * <b>video decoder only</b>: codec supports secure decryption.
+         */
+        public static final String FEATURE_SecurePlayback         = "secure-playback";
+
+        /**
+         * <b>video or audio decoder only</b>: codec supports tunneled playback.
+         */
+        public static final String FEATURE_TunneledPlayback       = "tunneled-playback";
 
         /**
          * Query codec feature capabilities.
+         * <p>
+         * These features are supported to be used by the codec.  These
+         * include optional features that can be turned on, as well as
+         * features that are always on.
          */
         public final boolean isFeatureSupported(String name) {
-            if (name.equals(FEATURE_AdaptivePlayback)) {
-                return (flags & FLAG_SupportsAdaptivePlayback) != 0;
+            return checkFeature(name, mFlagsSupported);
+        }
+
+        /**
+         * Query codec feature requirements.
+         * <p>
+         * These features are required to be used by the codec, and as such,
+         * they are always turned on.
+         */
+        public final boolean isFeatureRequired(String name) {
+            return checkFeature(name, mFlagsRequired);
+        }
+
+        private static class Feature {
+            public String mName;
+            public int mValue;
+            public boolean mDefault;
+            public Feature(String name, int value, boolean def) {
+                mName = name;
+                mValue = value;
+                mDefault = def;
+            }
+        }
+
+        private static final Feature[] decoderFeatures = {
+            new Feature(FEATURE_AdaptivePlayback, (1 << 0), true),
+            new Feature(FEATURE_SecurePlayback,   (1 << 1), false),
+            new Feature(FEATURE_TunneledPlayback, (1 << 2), false),
+        };
+
+        /** @hide */
+        public String[] validFeatures() {
+            Feature[] features = getValidFeatures();
+            String[] res = new String[features.length];
+            for (int i = 0; i < res.length; i++) {
+                res[i] = features[i].mName;
+            }
+            return res;
+        }
+
+        private Feature[] getValidFeatures() {
+            if (!isEncoder()) {
+                return decoderFeatures;
+            }
+            return new Feature[] {};
+        }
+
+        private boolean checkFeature(String name, int flags) {
+            for (Feature feat: getValidFeatures()) {
+                if (feat.mName.equals(name)) {
+                    return (flags & feat.mValue) != 0;
+                }
             }
             return false;
         }
+
+        /** @hide */
+        public boolean isRegular() {
+            // regular codecs only require default features
+            for (Feature feat: getValidFeatures()) {
+                if (!feat.mDefault && isFeatureRequired(feat.mName)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Query whether codec supports a given {@link MediaFormat}.
+         * @param format media format with optional feature directives.
+         * @throws IllegalArgumentException if format is not a valid media format.
+         * @return whether the codec capabilities support the given format
+         *         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()) {
+                Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+                if ((yesNo == 1 && !isFeatureSupported(feat.mName)) ||
+                        (yesNo == 0 && isFeatureRequired(feat.mName))) {
+                    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;
+        }
+
+        // errors while reading profile levels
+        private int mError;
+        // found stuff that is not supported by framework (=> this should not happen)
+        private static final int ERROR_UNRECOGNIZED   = (1 << 0);
+        // found profile/level for which we don't have capability estimates
+        private static final int ERROR_UNSUPPORTED    = (1 << 1);
+        // have not found any profile/level for which we don't have capability estimate
+        private static final int ERROR_NONE_SUPPORTED = (1 << 2);
+
+
+        // UTILITY METHODS
+        private static final Range<Integer> POSITIVE_INTEGERS =
+            Range.create(1, Integer.MAX_VALUE);
+        private static final Range<Long> POSITIVE_LONGS =
+            Range.create(1l, Long.MAX_VALUE);
+        private static final Range<Rational> POSITIVE_RATIONALS =
+            Range.create(new Rational(1, Integer.MAX_VALUE),
+                         new Rational(Integer.MAX_VALUE, 1));
+        private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
+        private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+
+        private static final String TAG = "CodecCapabilities";
+
+        // NEW-STYLE CAPABILITIES
+
+        /**
+         * Returns a MediaFormat object with default values for configurations that have
+         * defaults.
+         */
+        public final MediaFormat getDefaultFormat() {
+            return mDefaultFormat;
+        }
+        private MediaFormat mDefaultFormat;
+
+        /**
+         * Returns the mime type for which this codec-capability object was created.
+         */
+        public final String getMime() {
+            return mMime;
+        }
+
+        /**
+         * Returns the encoding capabilities or {@code null} if this is not an encoder.
+         */
+        public final EncoderCapabilities getEncoderCapabilities() {
+            return mEncoderCaps;
+        }
+        private EncoderCapabilities mEncoderCaps;
+
+        private boolean isEncoder() {
+            return mEncoderCaps != null;
+        }
+
+        /**
+         * A class that supports querying the encoding capabilities of a codec.
+         */
+        public static final class EncoderCapabilities {
+            /**
+             * Returns the supported range of quality values.
+             */
+            public final Range<Integer> getQualityRange() {
+                return mQualityRange;
+            }
+
+            /**
+             * Returns the supported range of encoder complexity values.
+             * <p>
+             * Some codecs may support multiple complexity levels, where higher
+             * complexity values use more encoder tools (e.g. perform more
+             * intensive calculations) to improve the quality or the compression
+             * ratio.  Use a lower value to save power and/or time.
+             */
+            public final 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;
+
+            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)
+            };
+
+            private static int parseBitrateMode(String mode) {
+                for (Feature feat: bitrates) {
+                    if (feat.mName.equalsIgnoreCase(mode)) {
+                        return feat.mValue;
+                    }
+                }
+                return 0;
+            }
+
+            /**
+             * Query whether a bitrate mode is supported.
+             */
+            public final 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;
+            }
+
+            /** @hide */
+            public 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() {
+                if (mParent.getMime().equalsIgnoreCase(
+                        MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                    mComplexityRange = Range.create(0, 8);
+                    mBitControl = (1 << BITRATE_MODE_CQ);
+                }
+            }
+
+            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-control")) {
+                    for (String mode: info.getString("feature-bitrate-control").split(",")) {
+                        mBitControl |= 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;
+            }
+
+            /** @hide */
+            public void setDefaultFormat(MediaFormat format) {
+                if (mQualityRange.getUpper() != mQualityRange.getLower()
+                        && mDefaultQuality != null) {
+                    format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
+                }
+                if (mComplexityRange.getUpper() != 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.getMime();
+
+                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 != 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 != profile) {
+                        throw new IllegalArgumentException(
+                                "conflicting values for profile and aac-profile");
+                    }
+                }
+
+                Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+                return supports(complexity, quality, profile);
+            }
+        };
+
+        /**
+         * A class that supports querying basic capabilities of a codec.
+         */
+        public static class BaseCapabilities {
+            /**
+             * Returns the range of supported bitrates in bits/second.
+             */
+            public final Range<Integer> getBitrateRange() {
+                return mBitrateRange;
+            }
+
+            /** @hide */
+            protected Range<Integer> mBitrateRange;
+
+            /** @hide */
+            protected CodecCapabilities mParent;
+
+            /** @hide */
+            protected BaseCapabilities() {
+            }
+
+            /** @hide */
+            protected void init(MediaFormat info, CodecCapabilities parent) {
+                mParent = parent;
+                mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+            }
+        }
+
+        /**
+         * A class that supports querying the video capabilities of a codec.
+         */
+        public static final class VideoCapabilities extends BaseCapabilities {
+            private static final String TAG = "VideoCapabilities";
+            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 Range<Integer> mFrameRateRange;
+
+            private int mBlockWidth;
+            private int mBlockHeight;
+            private int mWidthAlignment;
+            private int mHeightAlignment;
+            private int mSmallerDimensionUpperLimit;
+
+            /**
+             * Returns the range of supported video widths.
+             */
+            public final Range<Integer> getSupportedWidths() {
+                return mWidthRange;
+            }
+
+            /**
+             * Returns the range of supported video heights.
+             */
+            public final Range<Integer> getSupportedHeights() {
+                return mHeightRange;
+            }
+
+            /**
+             * Returns the alignment requirement for video width.
+             */
+            public final int getWidthAlignment() {
+                return mWidthAlignment;
+            }
+
+            /**
+             * Returns the alignment requirement for video height.
+             */
+            public final 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 final Range<Integer> getSupportedFrameRates() {
+                return mFrameRateRange;
+            }
+
+            /**
+             * Returns the range of supported video widths for a video height.
+             * @param height the height of the video
+             */
+            public final 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) {
+                    // should not be here
+                    Log.w(TAG, "could not get supported widths for " + height , e);
+                    throw new IllegalArgumentException("unsupported height");
+                }
+            }
+
+            /**
+             * Returns the range of supported video heights for a video width
+             * @param width the width of the video
+             */
+            public final 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) {
+                    // should not be here
+                    Log.w(TAG, "could not get supported heights for " + width , e);
+                    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 final 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()));
+            }
+
+            /**
+             * Returns whether a given video size ({@code width} and
+             * {@code height}) and {@code frameRate} combination is supported.
+             */
+            public final boolean areSizeAndRateSupported(
+                    int width, int height, double frameRate) {
+                return supports(width, height, frameRate);
+            }
+
+            /**
+             * Returns whether a given video size ({@code width} and
+             * {@code height}) is supported.
+             */
+            public final boolean isSizeSupported(int width, int height) {
+                return supports(width, height, null);
+            }
+
+            private final boolean supports(
+                    Integer width, Integer height, Double 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));
+                }
+                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;
+                        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);
+                Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE);
+
+                // we ignore color-format for now as it is not reliably reported by codec
+
+                return supports(width, height, rate);
+            }
+
+            /* no public constructor */
+            private VideoCapabilities() { }
+
+            /** @hide */
+            public static VideoCapabilities create(
+                    MediaFormat info, CodecCapabilities parent) {
+                VideoCapabilities caps = new VideoCapabilities();
+                caps.init(info, parent);
+                return caps;
+            }
+
+            /** @hide */
+            public void init(MediaFormat info, CodecCapabilities parent) {
+                super.init(info, 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() {
+                mWidthRange  = SIZE_RANGE;
+                mHeightRange = SIZE_RANGE;
+                mFrameRateRange = FRAME_RATE_RANGE;
+
+                mHorizontalBlockRange = SIZE_RANGE;
+                mVerticalBlockRange   = SIZE_RANGE;
+
+                // full positive ranges are supported as these get calculated
+                mBlockCountRange      = POSITIVE_INTEGERS;
+                mBlocksPerSecondRange = POSITIVE_LONGS;
+
+                mBlockAspectRatioRange = POSITIVE_RATIONALS;
+                mAspectRatioRange      = POSITIVE_RATIONALS;
+
+                // YUV 4:2:0 requires 2:2 alignment
+                mWidthAlignment = 2;
+                mHeightAlignment = 2;
+                mBlockWidth = 2;
+                mBlockHeight = 2;
+                mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
+            }
+
+            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;
+                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);
+                {
+                    Object o = map.get("size-range");
+                    Pair<Size, Size> sizeRange = Utils.parseSizeRange(o);
+                    if (sizeRange != null) {
+                        try {
+                            widths = Range.create(
+                                    sizeRange.first.getWidth(),
+                                    sizeRange.second.getWidth());
+                            heights = Range.create(
+                                    sizeRange.first.getHeight(),
+                                    sizeRange.second.getHeight());
+                        } catch (IllegalArgumentException e) {
+                            Log.w(TAG, "could not parse size range '" + o + "'");
+                            widths = null;
+                            heights = null;
+                        }
+                    }
+                }
+                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;
+                    }
+                }
+
+                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) {
+                    // codec supports profiles that we don't know.
+                    // Extend ranges clipped to platform limits.
+                    if (widths != null) {
+                        mWidthRange = mWidthRange.extend(widths);
+                    }
+                    if (heights != null) {
+                        mHeightRange = mHeightRange.extend(heights);
+                    }
+                    if (counts != null) {
+                        mBlockCountRange = mBlockCountRange.extend(
+                                Utils.factorRange(counts, mBlockWidth * mBlockHeight
+                                        / blockSize.getWidth() / blockSize.getHeight()));
+                    }
+                    if (blockRates != null) {
+                        mBlocksPerSecondRange = mBlocksPerSecondRange.extend(
+                                Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+                                        / blockSize.getWidth() / blockSize.getHeight()));
+                    }
+                    if (blockRatios != null) {
+                        mBlockAspectRatioRange = mBlockAspectRatioRange.extend(
+                                Utils.scaleRange(blockRatios,
+                                        mBlockHeight / blockSize.getHeight(),
+                                        mBlockWidth / blockSize.getWidth()));
+                    }
+                    if (ratios != null) {
+                        mAspectRatioRange = mAspectRatioRange.extend(ratios);
+                    }
+                    if (frameRates != null) {
+                        mFrameRateRange = mFrameRateRange.extend(frameRates);
+                    }
+                } 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);
+                    }
+                }
+                updateLimits();
+            }
+
+            private int checkPowerOfTwo(int value, String message) {
+                if ((value & (value - 1)) != 0) {
+                    throw new IllegalArgumentException(message);
+                }
+                return value;
+            }
+
+            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)).intersect(mBlockCountRange);
+                mVerticalBlockRange = mVerticalBlockRange.intersect(
+                        Utils.factorRange(mHeightRange, mBlockHeight)).intersect(mBlockCountRange);
+                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) {
+                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(
+                                1, maxHorizontalBlocks / (mBlockWidth / blockWidth));
+                mVerticalBlockRange =
+                        mVerticalBlockRange.intersect(
+                                1, maxVerticalBlocks / (mBlockHeight / blockHeight));
+            }
+
+            private void applyLevelLimits() {
+                int maxBlocksPerSecond = 0;
+                int maxBlocks = 0;
+                int maxBps = 0;
+                int maxDPBBlocks = 0;
+
+                int errors = ERROR_NONE_SUPPORTED;
+                CodecProfileLevel[] profileLevels = mParent.profileLevels;
+                String mime = mParent.getMime();
+
+                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;
+                            default:
+                                Log.w(TAG, "Unrecognized level "
+                                        + profileLevel.level + " for " + mime);
+                                errors |= ERROR_UNRECOGNIZED;
+                                supported = false;
+                        }
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.AVCProfileHigh:
+                                BR *= 1250; break;
+                            case CodecProfileLevel.AVCProfileHigh10:
+                                BR *= 3000; break;
+                            case CodecProfileLevel.AVCProfileExtended:
+                            case CodecProfileLevel.AVCProfileHigh422:
+                            case CodecProfileLevel.AVCProfileHigh444:
+                                Log.w(TAG, "Unrecognized profile "
+                                        + profileLevel.profile + " for " + mime);
+                                errors |= ERROR_UNSUPPORTED;
+                                supported = false;
+                                // fall through - treat as base profile
+                            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, 16, 1, 1);
+                } 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 supported = true;
+                        switch (profileLevel.profile) {
+                            case CodecProfileLevel.MPEG4ProfileSimple:
+                                switch (profileLevel.level) {
+                                    case CodecProfileLevel.MPEG4Level0:
+                                        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:
+                                        FR = 30; 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;
+                                    // TODO while MPEG4 SP does not have level 4 or 5,
+                                    // some vendors report it
+                                    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:
+                                    // TODO: MPEG4 level 3b is not defined in OMX
+                                    //  MBPS = 11880; FS =  396; BR = 1500; break;
+                                    case CodecProfileLevel.MPEG4Level4:
+                                    case CodecProfileLevel.MPEG4Level4a:
+                                        // TODO: MPEG4 level 4a is not defined in spec
+                                        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.MPEG4ProfileAdvancedScalable: // 1-3
+                            case CodecProfileLevel.MPEG4ProfileHybrid:           // 1-2
+                            case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
+                            case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
+                            case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
+                            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);
+                        maxWidth = Math.max(W, maxWidth);
+                        maxHeight = Math.max(H, maxHeight);
+                        maxRate = Math.max(FR, maxRate);
+                    }
+                    applyMacroBlockLimits(maxWidth, maxHeight,
+                            maxBlocks, maxBlocksPerSecond, 16, 16, 1, 1);
+                    mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+                    int maxWidth = 11, maxHeight = 9, maxRate = 15;
+                    maxBlocks = 99;
+                    maxBlocksPerSecond = 1485;
+                    maxBps = 64000;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0;
+                        switch (profileLevel.level) {
+                            case CodecProfileLevel.H263Level10:
+                                FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level20:
+                                // only supports CIF, 0..QCIF
+                                FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level30:
+                                // only supports CIF, 0..QCIF
+                                FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level40:
+                                // only supports CIF, 0..QCIF
+                                FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level45:
+                                // only implies level 10 support
+                                FR = 30; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
+                            case CodecProfileLevel.H263Level50:
+                                // only supports 50fps for H > 15
+                                FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
+                            case CodecProfileLevel.H263Level60:
+                                // only supports 50fps for H > 15
+                                FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
+                            case CodecProfileLevel.H263Level70:
+                                // only supports 50fps for H > 30
+                                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;
+                        }
+                        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);
+                    }
+                    applyMacroBlockLimits(maxWidth, maxHeight,
+                            maxBlocks, maxBlocksPerSecond, 16, 16, 1, 1);
+                    mFrameRateRange = Range.create(1, maxRate);
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+                        mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                    maxBlocks = maxBlocksPerSecond = maxBps = Integer.MAX_VALUE;
+                    // 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;
+                    }
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                    maxBlocks = 36864;
+                    maxBlocksPerSecond = maxBlocks * 15;
+                    maxBps = 128000;
+                    for (CodecProfileLevel profileLevel: profileLevels) {
+                        double FR = 0;
+                        int FS = 0;
+                        int BR = 0;
+                        switch (profileLevel.level) {
+                            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.HEVCHighTierLevel41:
+                                FR =    60; FS =  2228224; BR =  20000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel41:
+                                FR =    60; FS =  2228224; BR =  50000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel5:
+                                FR =    30; FS =  8912896; BR =  25000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel5:
+                                FR =    30; FS =  8912896; BR = 100000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel51:
+                                FR =    60; FS =  8912896; BR =  40000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel51:
+                                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.HEVCHighTierLevel61:
+                                FR =    60; FS = 35651584; BR = 120000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel61:
+                                FR =    60; FS = 35651584; BR = 480000; break;
+                            case CodecProfileLevel.HEVCHighTierLevel62:
+                                FR =   120; FS = 35651584; BR = 240000; break;
+                            case CodecProfileLevel.HEVCMainTierLevel62:
+                                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:
+                                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;
+                        */
+
+                        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));
+                    // CTBs are at least 8x8
+                    maxBlocks = Utils.divUp(maxBlocks, 8 * 8);
+                    maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8);
+                    maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8);
+
+                    applyMacroBlockLimits(
+                            maxLengthInBlocks, maxLengthInBlocks,
+                            maxBlocks, maxBlocksPerSecond, 8, 8, 1, 1);
+                } else {
+                    Log.w(TAG, "Unsupported mime " + mime);
+                    errors |= ERROR_UNSUPPORTED;
+                }
+                mBitrateRange = Range.create(1, maxBps);
+                mParent.mError |= errors;
+            }
+        };
+
+        VideoCapabilities mVideoCaps;
+
+        private boolean isVideo() {
+            return mVideoCaps != null;
+        }
+
+        /**
+         * Returns the video capabilities or {@code null} if this is not a video codec.
+         */
+        public final VideoCapabilities getVideoCapabilities() {
+            return mVideoCaps;
+        }
+
+        /**
+         * 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.
+         */
+        public static final 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 */,
+                0 /* flags */, defaultFormat, new MediaFormat() /* info */);
+            if (ret.mError != 0) {
+                return null;
+            }
+            return ret;
+        }
+
+        /* package private */ CodecCapabilities(
+                CodecProfileLevel[] profLevs, int[] colFmts,
+                boolean encoder, int flags,
+                Map<String, Object>defaultFormatMap,
+                Map<String, Object>capabilitiesMap) {
+            this(profLevs, colFmts, encoder, flags,
+                    new MediaFormat(defaultFormatMap),
+                    new MediaFormat(capabilitiesMap));
+        }
+
+        private MediaFormat mCapabilitiesInfo;
+
+        /* package private */ CodecCapabilities(
+                CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags,
+                MediaFormat defaultFormat, MediaFormat info) {
+            final Map<String, Object> map = info.getMap();
+            profileLevels = profLevs;
+            colorFormats = colFmts;
+            mFlagsVerified = flags;
+            mDefaultFormat = defaultFormat;
+            mCapabilitiesInfo = info;
+            mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
+
+            if (mMime.toLowerCase().startsWith("audio/")) {
+                mAudioCaps = AudioCapabilities.create(info, this);
+            } else if (mMime.toLowerCase().startsWith("video/")) {
+                mVideoCaps = VideoCapabilities.create(info, this);
+            }
+            if (encoder) {
+                mEncoderCaps = EncoderCapabilities.create(info, this);
+            }
+
+            for (Feature feat: getValidFeatures()) {
+                Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+                if (yesNo == null) {
+                    continue;
+                } else if (yesNo > 0) {
+                    mFlagsRequired |= feat.mValue;
+                } else {
+                    mFlagsSupported |= feat.mValue;
+                }
+                // TODO restrict features by mFlagsVerified once all codecs reliably verify them
+            }
+        }
+
+        /**
+         * A class that supports querying the audio capabilities of a codec.
+         */
+        public static final class AudioCapabilities extends BaseCapabilities {
+            private int[] mSampleRates;
+            private Range<Integer>[] mSampleRateRanges;
+            private int mMaxInputChannelCount;
+
+            /**
+             * Returns the array of supported sample rates if the codec
+             * supports only discrete values.  Otherwise, it returns
+             * {@code null}.  The array is sorted in ascending order.
+             */
+            public final int[] getSupportedSampleRates() {
+                return Arrays.copyOf(mSampleRates, mSampleRates.length);
+            }
+
+            /**
+             * Returns the array of supported sample rate ranges.  The
+             * array is sorted in ascending order, and the ranges are
+             * distinct.
+             */
+            public final Range<Integer>[] getSupportedSampleRateRanges() {
+                return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+            }
+
+            /**
+             * Returns the maximum number of input channels supported.  The codec
+             * supports any number of channels between 1 and this maximum value.
+             */
+            public final int getMaxInputChannelCount() {
+                return mMaxInputChannelCount;
+            }
+
+            /* no public constructor */
+            private AudioCapabilities() { }
+
+            /** @hide */
+            public static AudioCapabilities create(
+                    MediaFormat info, CodecCapabilities parent) {
+                AudioCapabilities caps = new AudioCapabilities();
+                caps.init(info, parent);
+                return caps;
+            }
+
+            /** @hide */
+            public void init(MediaFormat info, CodecCapabilities parent) {
+                super.init(info, parent);
+                initWithPlatformLimits();
+                applyLevelLimits();
+                parseFromInfo(info);
+            }
+
+            private void initWithPlatformLimits() {
+                mMaxInputChannelCount = 30;
+                // mBitrateRange = Range.create(1, 320000);
+                mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
+                mSampleRates = null;
+            }
+
+            private boolean supports(Integer sampleRate, Integer inputChannels) {
+                // channels and sample rates are checked orthogonally
+                if (inputChannels != null &&
+                        (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
+                    return false;
+                }
+                if (sampleRate != null) {
+                    int ix = Utils.binarySearchDistinctRanges(
+                            mSampleRateRanges, sampleRate);
+                    if (ix < 0) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            /**
+             * Query whether the sample rate is supported by the codec.
+             */
+            public final 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() != range.getUpper()) {
+                        mSampleRates = null;
+                        return;
+                    }
+                }
+                createDiscreteSampleRates();
+            }
+
+            private void applyLevelLimits() {
+                int[] sampleRates = null;
+                Range<Integer> sampleRateRange = null, bitRates = null;
+                int maxChannels = 0;
+                String mime = mParent.getMime();
+
+                if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
+                    sampleRates = new int[] {
+                            8000, 11025, 12000,
+                            16000, 22050, 24000,
+                            32000, 44100, 48000 };
+                    bitRates = Range.create(8192, 327680);
+                    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);
+                    sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 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, 96000);
+                    bitRates = Range.create(1, 10000000);
+                    maxChannels = 6;
+                } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                    sampleRateRange = Range.create(1, 655350);
+                    // lossless codec, so bitrate is ignored
+                    maxChannels = 255;
+                }
+
+                // restrict ranges
+                if (sampleRates != null) {
+                    limitSampleRates(sampleRates);
+                } else if (sampleRateRange != null) {
+                    limitSampleRates(new Range[] { sampleRateRange });
+                }
+                applyLimits(maxChannels, bitRates);
+            }
+
+            private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
+                mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
+                        .clamp(maxInputChannels);
+                if (bitRates != null) {
+                    mBitrateRange = mBitrateRange.intersect(bitRates);
+                }
+            }
+
+            private void parseFromInfo(MediaFormat info) {
+                int maxInputChannels = 1;
+                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);
+                }
+                if (info.containsKey("max-channel-count")) {
+                    maxInputChannels = info.getInteger("max-channel-count");
+                }
+                if (info.containsKey("bitrate-range")) {
+                    bitRates = bitRates.intersect(
+                            Utils.parseIntRange(info.getString("bitrate"), bitRates));
+                }
+                applyLimits(maxInputChannels, bitRates);
+            }
+
+            /** @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;
+                }
+
+                // nothing to do for:
+                // KEY_CHANNEL_MASK: codecs don't get this
+                // KEY_IS_ADTS:      required feature for all AAC decoders
+                return true;
+            }
+        };
+
+        AudioCapabilities mAudioCaps;
+        private boolean isAudio() {
+            return mAudioCaps != null;
+        }
+
+        /**
+         * Returns the audio capabilities or {@code null} if this is not an audio codec.
+         */
+        public final AudioCapabilities getAudioCapabilities() {
+            return mAudioCaps;
+        }
+
+        /** @hide */
+        public CodecCapabilities dup() {
+            return new CodecCapabilities(
+                // clone writable arrays
+                Arrays.copyOf(profileLevels, profileLevels.length),
+                Arrays.copyOf(colorFormats, colorFormats.length),
+                isEncoder(),
+                mFlagsVerified,
+                mDefaultFormat,
+                mCapabilitiesInfo);
+        }
     };
 
     /**
@@ -193,6 +1915,7 @@
         public static final int AVCLevel42      = 0x2000;
         public static final int AVCLevel5       = 0x4000;
         public static final int AVCLevel51      = 0x8000;
+        public static final int AVCLevel52      = 0x10000;
 
         // from OMX_VIDEO_H263PROFILETYPE
         public static final int H263ProfileBaseline             = 0x01;
@@ -319,6 +2042,30 @@
      */
     public final CodecCapabilities getCapabilitiesForType(
             String type) {
-        return MediaCodecList.getCodecCapabilities(mIndex, type);
+        CodecCapabilities caps = mCaps.get(type);
+        if (caps == null) {
+            throw new IllegalArgumentException("codec does not support type");
+        }
+        // clone writable object
+        return caps.dup();
+    }
+
+    /** @hide */
+    public MediaCodecInfo makeRegular() {
+        ArrayList<CodecCapabilities> caps = new ArrayList<CodecCapabilities>();
+        for (CodecCapabilities c: mCaps.values()) {
+            if (c.isRegular()) {
+                caps.add(c);
+            }
+        }
+        if (caps.size() == 0) {
+            return null;
+        } else if (caps.size() == mCaps.size()) {
+            return this;
+        }
+
+        return new MediaCodecInfo(
+                mName, mIsEncoder,
+                caps.toArray(new CodecCapabilities[caps.size()]));
     }
 }
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index 817ca31..f5d99e4 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -16,7 +16,13 @@
 
 package android.media;
 
+import android.util.Log;
+
 import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Allows you to enumerate available codecs, each specified as a {@link MediaCodecInfo} object,
@@ -25,17 +31,74 @@
  * <p>See {@link MediaCodecInfo} for sample usage.
  */
 final public class MediaCodecList {
-    /**
-     * Count the number of available codecs.
-     */
-    public static native final int getCodecCount();
+    private static final String TAG = "MediaCodecList";
 
+    /**
+     * Count the number of available (regular) codecs.
+     *
+     * @see #REGULAR_CODECS
+     */
+    public static final int getCodecCount() {
+        initCodecList();
+        return sRegularCodecInfos.length;
+    }
+
+    private static native final int native_getCodecCount();
+
+    /**
+     * Return the {@link MediaCodecInfo} object for the codec at
+     * the given {@code index} in the regular list.
+     *
+     * @see #REGULAR_CODECS
+     */
     public static final MediaCodecInfo getCodecInfoAt(int index) {
-        if (index < 0 || index > getCodecCount()) {
+        initCodecList();
+        if (index < 0 || index > sRegularCodecInfos.length) {
             throw new IllegalArgumentException();
         }
+        return sRegularCodecInfos[index];
+    }
 
-        return new MediaCodecInfo(index);
+    private static Object sInitLock = new Object();
+    private static MediaCodecInfo[] sAllCodecInfos;
+    private static MediaCodecInfo[] sRegularCodecInfos;
+
+    private static final void initCodecList() {
+        synchronized (sInitLock) {
+            if (sRegularCodecInfos == null) {
+                int count = native_getCodecCount();
+                ArrayList<MediaCodecInfo> regulars = new ArrayList<MediaCodecInfo>();
+                ArrayList<MediaCodecInfo> all = new ArrayList<MediaCodecInfo>();
+                for (int index = 0; index < count; index++) {
+                    try {
+                        MediaCodecInfo info = getNewCodecInfoAt(index);
+                        all.add(info);
+                        info = info.makeRegular();
+                        if (info != null) {
+                            regulars.add(info);
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "Could not get codec capabilities", e);
+                    }
+                }
+                sRegularCodecInfos =
+                    regulars.toArray(new MediaCodecInfo[regulars.size()]);
+                sAllCodecInfos =
+                    all.toArray(new MediaCodecInfo[all.size()]);
+            }
+        }
+    }
+
+    private static MediaCodecInfo getNewCodecInfoAt(int index) {
+        String[] supportedTypes = getSupportedTypes(index);
+        MediaCodecInfo.CodecCapabilities[] caps =
+            new MediaCodecInfo.CodecCapabilities[supportedTypes.length];
+        int typeIx = 0;
+        for (String type: supportedTypes) {
+            caps[typeIx] = getCodecCapabilities(index, type);
+        }
+        return new MediaCodecInfo(
+                getCodecName(index), isEncoder(index), caps);
     }
 
     /* package private */ static native final String getCodecName(int index);
@@ -51,10 +114,95 @@
 
     private static native final void native_init();
 
-    private MediaCodecList() {}
+    /**
+     * Use in {@link #MediaCodecList} to enumerate only codecs that are suitable
+     * for normal playback and recording.
+     */
+    public static final int REGULAR_CODECS = 0;
+
+    /**
+     * Use in {@link #MediaCodecList} to enumerate all codecs, even ones that are
+     * not suitable for normal playback or recording.
+     */
+    public static final int ALL_CODECS = 1;
+
+    private MediaCodecList() {
+        this(REGULAR_CODECS);
+    }
+
+    private MediaCodecInfo[] mCodecInfos;
+
+    /**
+     * Create a list of media-codecs of a specific kind.
+     * @param kind Either {@code REGULAR_CODECS} or {@code ALL_CODECS}.
+     */
+    public MediaCodecList(int kind) {
+        initCodecList();
+        if (kind == REGULAR_CODECS) {
+            mCodecInfos = sRegularCodecInfos;
+        } else {
+            mCodecInfos = sAllCodecInfos;
+        }
+    }
+
+    /**
+     * Returns the list of {@link MediaCodecInfo} objects for the list
+     * of media-codecs.
+     */
+    public final MediaCodecInfo[] getCodecInfos() {
+        return Arrays.copyOf(mCodecInfos, mCodecInfos.length);
+    }
 
     static {
         System.loadLibrary("media_jni");
         native_init();
+
+        // mediaserver is not yet alive here
+    }
+
+    /**
+     * Find a decoder supporting a given {@link MediaFormat} in the list
+     * of media-codecs.
+     *
+     * @param format A decoder media format with optional feature directives.
+     * @throws IllegalArgumentException if format is not a valid media format.
+     * @throws NullPointerException if format is null.
+     * @return the name of a decoder that supports the given format and feature
+     *         requests, or {@code null} if no such codec has been found.
+     */
+    public final String findDecoderForFormat(MediaFormat format) {
+        return findCodecForFormat(false /* encoder */, format);
+    }
+
+    /**
+     * Find an encoder supporting a given {@link MediaFormat} in the list
+     * of media-codecs.
+     *
+     * @param format An encoder media format with optional feature directives.
+     * @throws IllegalArgumentException if format is not a valid media format.
+     * @throws NullPointerException if format is null.
+     * @return the name of an encoder that supports the given format and feature
+     *         requests, or {@code null} if no such codec has been found.
+     */
+    public final String findEncoderForFormat(MediaFormat format) {
+        return findCodecForFormat(false /* encoder */, format);
+    }
+
+    private String findCodecForFormat(boolean encoder, MediaFormat format) {
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        for (MediaCodecInfo info: mCodecInfos) {
+            if (info.isEncoder() != encoder) {
+                continue;
+            }
+            try {
+                MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                if (caps != null && caps.isFormatSupported(format)) {
+                    return info.getName();
+                }
+            } catch (IllegalArgumentException e) {
+                // type is not supported
+            }
+        }
+        return null;
     }
 }
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 0f7906e..16a2235 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -76,9 +76,55 @@
  * </table>
  */
 public final class MediaFormat {
+    public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
+    public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+    public static final String MIMETYPE_VIDEO_AVC = "video/avc";
+    public static final String MIMETYPE_VIDEO_HEVC = "video/hevc";
+    public static final String MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
+    public static final String MIMETYPE_VIDEO_H263 = "video/3gpp";
+    public static final String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
+    public static final String MIMETYPE_VIDEO_RAW = "video/raw";
+
+    public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
+    public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
+    public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
+    public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
+    public static final String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
+    public static final String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+    public static final String MIMETYPE_AUDIO_OPUS = "audio/opus";
+    public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
+    public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
+    public static final String MIMETYPE_AUDIO_RAW = "audio/raw";
+    public static final String MIMETYPE_AUDIO_FLAC = "audio/flac";
+    public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
+    public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
+
+    /**
+     * MIME type for WebVTT subtitle data.
+     */
+    public static final String MIMETYPE_TEXT_VTT = "text/vtt";
+
+    /**
+     * MIME type for CEA-608 closed caption data.
+     */
+    public static final String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
+
     private Map<String, Object> mMap;
 
     /**
+     * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities}
+     * feature name describing a required or optional feature for a codec capabilities
+     * query.
+     * The associated value is an integer, where non-0 value means the feature is
+     * requested to be present, while 0 value means the feature is requested to be not
+     * present.
+     * @see MediaCodecList#findDecoderForFormat
+     * @see MediaCodecList#findEncoderForFormat
+     * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+     */
+    public static final String KEY_FEATURE_ = "feature-";
+
+    /**
      * A key describing the mime type of the MediaFormat.
      * The associated value is a string.
      */
@@ -222,6 +268,51 @@
     public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
 
     /**
+     * A key describing the encoding complexity.
+     * The associated value is an integer.  These values are device and codec specific,
+     * but lower values generally result in faster and/or less power-hungry encoding.
+     *
+     * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getComplexityRange
+     */
+    public static final String KEY_COMPLEXITY = "complexity";
+
+    /**
+     * A key describing the desired encoding quality.
+     * The associated value is an integer.  This key is only supported for encoders
+     * that are configured in constant-quality mode.  These values are device and
+     * codec specific, but lower values generally result in more efficient
+     * (smaller-sized) encoding.
+     *
+     * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getQualityRange
+     */
+    public static final String KEY_QUALITY = "quality";
+
+    /**
+     * A key describing the desired profile to be used by an encoder.
+     * Constants are declared in {@link MediaCodecInfo.CodecProfileLevel}.
+     * This key is only supported for codecs that specify a profile.
+     *
+     * @see MediaCodecInfo.CodecCapabilities#profileLevels
+     */
+    public static final String KEY_PROFILE = "profile";
+
+    /**
+     * A key describing the desired bitrate mode to be used by an encoder.
+     * Constants are declared in {@link MediaCodecInfo.CodecCapabilities}.
+     *
+     * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#isBitrateModeSupported
+     */
+    public static final String KEY_BITRATE_MODE = "bitrate-mode";
+
+    /**
+     * A key describing the reference clock ID for a tunneled codec.
+     * The associated value is an integer.
+     *
+     * @see MediaCodecInfo.CodecCapabilities#FEATURE_TunneledPlayback
+     */
+    public static final String KEY_REFERENCE_CLOCK_ID = "reference-clock-id";
+
+    /**
      * A key for boolean AUTOSELECT behavior for the track. Tracks with AUTOSELECT=true
      * are considered when automatically selecting a track without specific user
      * choice, based on the current locale.
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
new file mode 100644
index 0000000..12910ec
--- /dev/null
+++ b/media/java/android/media/Utils.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+package android.media;
+
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Vector;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+// package private
+class Utils {
+    private static final String TAG = "Utils";
+
+    /**
+     * Sorts distinct (non-intersecting) range array in ascending order.
+     * @throws java.lang.IllegalArgumentException if ranges are not distinct
+     */
+    public static <T extends Comparable<? super T>> void sortDistinctRanges(Range<T>[] ranges) {
+        Arrays.sort(ranges, new Comparator<Range<T>>() {
+            @Override
+            public int compare(Range<T> lhs, Range<T> rhs) {
+                if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
+                    return -1;
+                } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
+                    return 1;
+                }
+                throw new IllegalArgumentException(
+                        "sample rate ranges must be distinct (" + lhs + " and " + rhs + ")");
+            }
+        });
+    }
+
+    /**
+     * Returns the intersection of two sets of non-intersecting ranges
+     * @param one a sorted set of non-intersecting ranges in ascending order
+     * @param another another sorted set of non-intersecting ranges in ascending order
+     * @return the intersection of the two sets, sorted in ascending order
+     */
+    public static <T extends Comparable<? super T>>
+            Range<T>[] intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another) {
+        int ix = 0;
+        Vector<Range<T>> result = new Vector<Range<T>>();
+        for (Range<T> range: another) {
+            while (ix < one.length &&
+                    one[ix].getUpper().compareTo(range.getLower()) < 0) {
+                ++ix;
+            }
+            while (ix < one.length &&
+                    one[ix].getUpper().compareTo(range.getUpper()) < 0) {
+                result.add(range.intersect(one[ix]));
+                ++ix;
+            }
+            if (ix == one.length) {
+                break;
+            }
+            if (one[ix].getLower().compareTo(range.getUpper()) <= 0) {
+                result.add(range.intersect(one[ix]));
+            }
+        }
+        return result.toArray(new Range[result.size()]);
+    }
+
+    /**
+     * Returns the index of the range that contains a value in a sorted array of distinct ranges.
+     * @param ranges a sorted array of non-intersecting ranges in ascending order
+     * @param value the value to search for
+     * @return if the value is in one of the ranges, it returns the index of that range.  Otherwise,
+     * the return value is {@code (-1-index)} for the {@code index} of the range that is
+     * immediately following {@code value}.
+     */
+    public static <T extends Comparable<? super T>>
+            int binarySearchDistinctRanges(Range<T>[] ranges, T value) {
+        return Arrays.binarySearch(ranges, Range.create(value, value),
+                new Comparator<Range<T>>() {
+                    @Override
+                    public int compare(Range<T> lhs, Range<T> rhs) {
+                        if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
+                            return -1;
+                        } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
+                            return 1;
+                        }
+                        return 0;
+                    }
+                });
+    }
+
+    /**
+     * Returns greatest common divisor
+     */
+    static int gcd(int a, int b) {
+        if (a == 0 && b == 0) {
+            return 1;
+        }
+        if (b < 0) {
+            b = -b;
+        }
+        if (a < 0) {
+            a = -a;
+        }
+        while (a != 0) {
+            int c = b % a;
+            b = a;
+            a = c;
+        }
+        return b;
+    }
+
+    /** Returns the equivalent factored range {@code newrange}, where for every
+     * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
+     * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
+     */
+    static Range<Integer>factorRange(Range<Integer> range, int factor) {
+        if (factor == 1) {
+            return range;
+        }
+        return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
+    }
+
+    /** Returns the equivalent factored range {@code newrange}, where for every
+     * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
+     * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
+     */
+    static Range<Long>factorRange(Range<Long> range, long factor) {
+        if (factor == 1) {
+            return range;
+        }
+        return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
+    }
+
+    private static Rational scaleRatio(Rational ratio, int num, int den) {
+        int common = gcd(num, den);
+        num /= common;
+        den /= common;
+        return new Rational(
+                (int)(ratio.getNumerator() * (double)num),     // saturate to int
+                (int)(ratio.getDenominator() * (double)den));  // saturate to int
+    }
+
+    static Range<Rational> scaleRange(Range<Rational> range, int num, int den) {
+        if (num == den) {
+            return range;
+        }
+        return Range.create(
+                scaleRatio(range.getLower(), num, den),
+                scaleRatio(range.getUpper(), num, den));
+    }
+
+    static Range<Integer> alignRange(Range<Integer> range, int align) {
+        return range.intersect(
+                divUp(range.getLower(), align) * align,
+                (range.getUpper() / align) * align);
+    }
+
+    static int divUp(int num, int den) {
+        return (num + den - 1) / den;
+    }
+
+    private static long divUp(long num, long den) {
+        return (num + den - 1) / den;
+    }
+
+    /**
+     * Returns least common multiple
+     */
+    private static long lcm(int a, int b) {
+        if (a == 0 || b == 0) {
+            throw new IllegalArgumentException("lce is not defined for zero arguments");
+        }
+        return (long)a * b / gcd(a, b);
+    }
+
+    static Range<Integer> intRangeFor(double v) {
+        return Range.create((int)v, (int)Math.ceil(v));
+    }
+
+    static Range<Long> longRangeFor(double v) {
+        return Range.create((long)v, (long)Math.ceil(v));
+    }
+
+    static Size parseSize(Object o, Size fallback) {
+        try {
+            return Size.parseSize((String) o);
+        } catch (ClassCastException e) {
+        } catch (NumberFormatException e) {
+        } catch (NullPointerException e) {
+            return fallback;
+        }
+        Log.w(TAG, "could not parse size '" + o + "'");
+        return fallback;
+    }
+
+    static Range<Integer> parseIntRange(Object o, Range<Integer> fallback) {
+        try {
+            String s = (String)o;
+            int ix = s.indexOf('-');
+            if (ix >= 0) {
+                return Range.create(
+                        Integer.parseInt(s.substring(0, ix), 10),
+                        Integer.parseInt(s.substring(ix + 1), 10));
+            }
+            int value = Integer.parseInt(s);
+            return Range.create(value, value);
+        } catch (ClassCastException e) {
+        } catch (NumberFormatException e) {
+        } catch (NullPointerException e) {
+            return fallback;
+        } catch (IllegalArgumentException e) {
+        }
+        Log.w(TAG, "could not parse integer range '" + o + "'");
+        return fallback;
+    }
+
+    static Range<Long> parseLongRange(Object o, Range<Long> fallback) {
+        try {
+            String s = (String)o;
+            int ix = s.indexOf('-');
+            if (ix >= 0) {
+                return Range.create(
+                        Long.parseLong(s.substring(0, ix), 10),
+                        Long.parseLong(s.substring(ix + 1), 10));
+            }
+            long value = Long.parseLong(s);
+            return Range.create(value, value);
+        } catch (ClassCastException e) {
+        } catch (NumberFormatException e) {
+        } catch (NullPointerException e) {
+            return fallback;
+        } catch (IllegalArgumentException e) {
+        }
+        Log.w(TAG, "could not parse long range '" + o + "'");
+        return fallback;
+    }
+
+    static Range<Rational> parseRationalRange(Object o, Range<Rational> fallback) {
+        try {
+            String s = (String)o;
+            int ix = s.indexOf('-');
+            if (ix >= 0) {
+                return Range.create(
+                        Rational.parseRational(s.substring(0, ix)),
+                        Rational.parseRational(s.substring(ix + 1)));
+            }
+            Rational value = Rational.parseRational(s);
+            return Range.create(value, value);
+        } catch (ClassCastException e) {
+        } catch (NumberFormatException e) {
+        } catch (NullPointerException e) {
+            return fallback;
+        } catch (IllegalArgumentException e) {
+        }
+        Log.w(TAG, "could not parse rational range '" + o + "'");
+        return fallback;
+    }
+
+    static Pair<Size, Size> parseSizeRange(Object o) {
+        try {
+            String s = (String)o;
+            int ix = s.indexOf('-');
+            if (ix >= 0) {
+                return Pair.create(
+                        Size.parseSize(s.substring(0, ix)),
+                        Size.parseSize(s.substring(ix + 1)));
+            }
+            Size value = Size.parseSize(s);
+            return Pair.create(value, value);
+        } catch (ClassCastException e) {
+        } catch (NumberFormatException e) {
+        } catch (NullPointerException e) {
+            return null;
+        } catch (IllegalArgumentException e) {
+        }
+        Log.w(TAG, "could not parse size range '" + o + "'");
+        return null;
+    }
+}
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index caa594e..ed1eeb9 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -19,11 +19,13 @@
 #include <utils/Log.h>
 
 #include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaCodecList.h>
 
 #include "android_runtime/AndroidRuntime.h"
 #include "jni.h"
 #include "JNIHelp.h"
+#include "android_media_Utils.h"
 
 using namespace android;
 
@@ -111,10 +113,19 @@
     Vector<MediaCodecList::ProfileLevel> profileLevels;
     Vector<uint32_t> colorFormats;
     uint32_t flags;
+    sp<AMessage> capabilities;
+
+    sp<AMessage> defaultFormat = new AMessage();
+    defaultFormat->setString("mime", typeStr);
+
+    // TODO query default-format also from codec/codec list
 
     status_t err =
         MediaCodecList::getInstance()->getCodecCapabilities(
-                index, typeStr, &profileLevels, &colorFormats, &flags);
+                index, typeStr, &profileLevels, &colorFormats, &flags,
+                &capabilities);
+
+    bool isEncoder = MediaCodecList::getInstance()->isEncoder(index);
 
     env->ReleaseStringUTFChars(type, typeStr);
     typeStr = NULL;
@@ -124,15 +135,21 @@
         return NULL;
     }
 
+    jobject defaultFormatObj = NULL;
+    if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
+        return NULL;
+    }
+
+    jobject infoObj = NULL;
+    if (ConvertMessageToMap(env, capabilities, &infoObj)) {
+        env->DeleteLocalRef(defaultFormatObj);
+        return NULL;
+    }
+
     jclass capsClazz =
         env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
     CHECK(capsClazz != NULL);
 
-    jfieldID flagsField =
-        env->GetFieldID(capsClazz, "flags", "I");
-
-    jobject caps = env->AllocObject(capsClazz);
-
     jclass profileLevelClazz =
         env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
     CHECK(profileLevelClazz != NULL);
@@ -160,18 +177,6 @@
         profileLevelObj = NULL;
     }
 
-    jfieldID profileLevelsField = env->GetFieldID(
-            capsClazz,
-            "profileLevels",
-            "[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
-
-    env->SetObjectField(caps, profileLevelsField, profileLevelArray);
-
-    env->SetIntField(caps, flagsField, flags);
-
-    env->DeleteLocalRef(profileLevelArray);
-    profileLevelArray = NULL;
-
     jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
 
     for (size_t i = 0; i < colorFormats.size(); ++i) {
@@ -179,14 +184,46 @@
         env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
     }
 
+    jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+            "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZI"
+            "Ljava/util/Map;Ljava/util/Map;)V");
+
+    jobject caps = env->NewObject(capsClazz, capsConstructID,
+            profileLevelArray, colorFormatsArray, isEncoder, flags,
+            defaultFormatObj, infoObj);
+
+#if 0
+    jfieldID profileLevelsField = env->GetFieldID(
+            capsClazz,
+            "profileLevels",
+            "[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
+
+    env->SetObjectField(caps, profileLevelsField, profileLevelArray);
+
+    jfieldID flagsField =
+        env->GetFieldID(capsClazz, "mFlagsVerified", "I");
+
+    env->SetIntField(caps, flagsField, flags);
+
     jfieldID colorFormatsField = env->GetFieldID(
             capsClazz, "colorFormats", "[I");
 
     env->SetObjectField(caps, colorFormatsField, colorFormatsArray);
 
+#endif
+
+    env->DeleteLocalRef(profileLevelArray);
+    profileLevelArray = NULL;
+
     env->DeleteLocalRef(colorFormatsArray);
     colorFormatsArray = NULL;
 
+    env->DeleteLocalRef(defaultFormatObj);
+    defaultFormatObj = NULL;
+
+    env->DeleteLocalRef(infoObj);
+    infoObj = NULL;
+
     return caps;
 }
 
@@ -194,7 +231,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-    { "getCodecCount", "()I", (void *)android_media_MediaCodecList_getCodecCount },
+    { "native_getCodecCount", "()I", (void *)android_media_MediaCodecList_getCodecCount },
     { "getCodecName", "(I)Ljava/lang/String;",
       (void *)android_media_MediaCodecList_getCodecName },
     { "isEncoder", "(I)Z", (void *)android_media_MediaCodecList_isEncoder },