/*
 * Copyright (C) 2012 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.annotation.NonNull;
import android.annotation.Nullable;
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.HashMap;
import java.util.Map;
import java.util.Set;

import static android.media.Utils.intersectSortedDistinctRanges;
import static android.media.Utils.sortDistinctRanges;

/**
 * Provides information about a given media codec available on the device. You can
 * iterate through all codecs available by querying {@link MediaCodecList}. For example,
 * here's how to find an encoder that supports a given MIME type:
 * <pre>
 * private static MediaCodecInfo selectCodec(String mimeType) {
 *     int numCodecs = MediaCodecList.getCodecCount();
 *     for (int i = 0; i &lt; numCodecs; i++) {
 *         MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
 *
 *         if (!codecInfo.isEncoder()) {
 *             continue;
 *         }
 *
 *         String[] types = codecInfo.getSupportedTypes();
 *         for (int j = 0; j &lt; types.length; j++) {
 *             if (types[j].equalsIgnoreCase(mimeType)) {
 *                 return codecInfo;
 *             }
 *         }
 *     }
 *     return null;
 * }</pre>
 *
 */
public final class MediaCodecInfo {
    private boolean mIsEncoder;
    private String mName;
    private Map<String, CodecCapabilities> mCaps;

    /* 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.getMimeType(), c);
        }
    }

    /**
     * Retrieve the codec name.
     */
    public final String getName() {
        return mName;
    }

    /**
     * Query if the codec is an encoder.
     */
    public final boolean isEncoder() {
        return mIsEncoder;
    }

    /**
     * Query the media types supported by the codec.
     */
    public final String[] getSupportedTypes() {
        Set<String> typeSet = mCaps.keySet();
        String[] types = typeSet.toArray(new String[typeSet.size()]);
        Arrays.sort(types);
        return types;
    }

    private static int checkPowerOfTwo(int value, String message) {
        if ((value & (value - 1)) != 0) {
            throw new IllegalArgumentException(message);
        }
        return value;
    }

    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;
        }
    }

    // COMMON CONSTANTS
    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 Range<Integer> BITRATE_RANGE = Range.create(0, 500000000);
    private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32;
    private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256;

    // 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);


    /**
     * Encapsulates the capabilities of a given codec component.
     * For example, what profile/level combinations it supports and what colorspaces
     * it is capable of providing the decoded data in, as well as some
     * codec-type specific capability flags.
     * <p>You can get an instance for a given {@link MediaCodecInfo} object with
     * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
     */
    public static final class CodecCapabilities {
        public CodecCapabilities() {
        }

        // CLASSIFICATION
        private String mMime;
        private int mMaxSupportedInstances;

        // 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;  // NOTE this array is modifiable by user

        // from OMX_COLOR_FORMATTYPE
        /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */
        public static final int COLOR_FormatMonochrome              = 1;
        /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */
        public static final int COLOR_Format8bitRGB332              = 2;
        /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */
        public static final int COLOR_Format12bitRGB444             = 3;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format16bitARGB4444           = 4;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format16bitARGB1555           = 5;

        /**
         * 16 bits per pixel RGB color format, with 5-bit red & blue and 6-bit green component.
         * <p>
         * Using 16-bit little-endian representation, colors stored as Red 15:11, Green 10:5, Blue 4:0.
         * <pre>
         *            byte                   byte
         *  <--------- i --------> | <------ i + 1 ------>
         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
         * |     BLUE     |      GREEN      |     RED      |
         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
         *  0           4  5     7   0     2  3           7
         * bit
         * </pre>
         *
         * This format corresponds to {@link android.graphics.PixelFormat#RGB_565} and
         * {@link android.graphics.ImageFormat#RGB_565}.
         */
        public static final int COLOR_Format16bitRGB565             = 6;
        /** @deprecated Use {@link #COLOR_Format16bitRGB565}. */
        public static final int COLOR_Format16bitBGR565             = 7;
        /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */
        public static final int COLOR_Format18bitRGB666             = 8;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format18bitARGB1665           = 9;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format19bitARGB1666           = 10;

        /** @deprecated Use {@link #COLOR_Format24bitBGR888} or {@link #COLOR_FormatRGBFlexible}. */
        public static final int COLOR_Format24bitRGB888             = 11;

        /**
         * 24 bits per pixel RGB color format, with 8-bit red, green & blue components.
         * <p>
         * Using 24-bit little-endian representation, colors stored as Red 7:0, Green 15:8, Blue 23:16.
         * <pre>
         *         byte              byte             byte
         *  <------ i -----> | <---- i+1 ----> | <---- i+2 ----->
         * +-----------------+-----------------+-----------------+
         * |       RED       |      GREEN      |       BLUE      |
         * +-----------------+-----------------+-----------------+
         * </pre>
         *
         * This format corresponds to {@link android.graphics.PixelFormat#RGB_888}, and can also be
         * represented as a flexible format by {@link #COLOR_FormatRGBFlexible}.
         */
        public static final int COLOR_Format24bitBGR888             = 12;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format24bitARGB1887           = 13;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format25bitARGB1888           = 14;

        /**
         * @deprecated Use {@link #COLOR_Format32bitABGR8888} Or {@link #COLOR_FormatRGBAFlexible}.
         */
        public static final int COLOR_Format32bitBGRA8888           = 15;
        /**
         * @deprecated Use {@link #COLOR_Format32bitABGR8888} Or {@link #COLOR_FormatRGBAFlexible}.
         */
        public static final int COLOR_Format32bitARGB8888           = 16;
        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        public static final int COLOR_FormatYUV411Planar            = 17;
        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        public static final int COLOR_FormatYUV411PackedPlanar      = 18;
        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        public static final int COLOR_FormatYUV420Planar            = 19;
        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        public static final int COLOR_FormatYUV420PackedPlanar      = 20;
        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        public static final int COLOR_FormatYUV420SemiPlanar        = 21;

        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatYUV422Planar            = 22;
        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatYUV422PackedPlanar      = 23;
        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatYUV422SemiPlanar        = 24;

        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatYCbYCr                  = 25;
        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatYCrYCb                  = 26;
        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatCbYCrY                  = 27;
        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatCrYCbY                  = 28;

        /** @deprecated Use {@link #COLOR_FormatYUV444Flexible}. */
        public static final int COLOR_FormatYUV444Interleaved       = 29;

        /**
         * SMIA 8-bit Bayer format.
         * Each byte represents the top 8-bits of a 10-bit signal.
         */
        public static final int COLOR_FormatRawBayer8bit            = 30;
        /**
         * SMIA 10-bit Bayer format.
         */
        public static final int COLOR_FormatRawBayer10bit           = 31;

        /**
         * SMIA 8-bit compressed Bayer format.
         * Each byte represents a sample from the 10-bit signal that is compressed into 8-bits
         * using DPCM/PCM compression, as defined by the SMIA Functional Specification.
         */
        public static final int COLOR_FormatRawBayer8bitcompressed  = 32;

        /** @deprecated Use {@link #COLOR_FormatL8}. */
        public static final int COLOR_FormatL2                      = 33;
        /** @deprecated Use {@link #COLOR_FormatL8}. */
        public static final int COLOR_FormatL4                      = 34;

        /**
         * 8 bits per pixel Y color format.
         * <p>
         * Each byte contains a single pixel.
         * This format corresponds to {@link android.graphics.PixelFormat#L_8}.
         */
        public static final int COLOR_FormatL8                      = 35;

        /**
         * 16 bits per pixel, little-endian Y color format.
         * <p>
         * <pre>
         *            byte                   byte
         *  <--------- i --------> | <------ i + 1 ------>
         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
         * |                       Y                       |
         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
         *  0                    7   0                    7
         * bit
         * </pre>
         */
        public static final int COLOR_FormatL16                     = 36;
        /** @deprecated Use {@link #COLOR_FormatL16}. */
        public static final int COLOR_FormatL24                     = 37;

        /**
         * 32 bits per pixel, little-endian Y color format.
         * <p>
         * <pre>
         *         byte              byte             byte              byte
         *  <------ i -----> | <---- i+1 ----> | <---- i+2 ----> | <---- i+3 ----->
         * +-----------------+-----------------+-----------------+-----------------+
         * |                                   Y                                   |
         * +-----------------+-----------------+-----------------+-----------------+
         *  0               7 0               7 0               7 0               7
         * bit
         * </pre>
         *
         * @deprecated Use {@link #COLOR_FormatL16}.
         */
        public static final int COLOR_FormatL32                     = 38;

        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        public static final int COLOR_FormatYUV420PackedSemiPlanar  = 39;
        /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
        public static final int COLOR_FormatYUV422PackedSemiPlanar  = 40;

        /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */
        public static final int COLOR_Format18BitBGR666             = 41;

        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format24BitARGB6666           = 42;
        /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */
        public static final int COLOR_Format24BitABGR6666           = 43;

        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        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 static final int COLOR_FormatSurface                   = 0x7F000789;

        /**
         * 32 bits per pixel RGBA color format, with 8-bit red, green, blue, and alpha components.
         * <p>
         * Using 32-bit little-endian representation, colors stored as Red 7:0, Green 15:8,
         * Blue 23:16, and Alpha 31:24.
         * <pre>
         *         byte              byte             byte              byte
         *  <------ i -----> | <---- i+1 ----> | <---- i+2 ----> | <---- i+3 ----->
         * +-----------------+-----------------+-----------------+-----------------+
         * |       RED       |      GREEN      |       BLUE      |      ALPHA      |
         * +-----------------+-----------------+-----------------+-----------------+
         * </pre>
         *
         * This corresponds to {@link android.graphics.PixelFormat#RGBA_8888}.
         */
        public static final int COLOR_Format32bitABGR8888             = 0x7F00A000;

        /**
         * Flexible 12 bits per pixel, subsampled YUV color format with 8-bit chroma and luma
         * components.
         * <p>
         * Chroma planes are subsampled by 2 both horizontally and vertically.
         * Use this format with {@link Image}.
         * This format corresponds to {@link android.graphics.ImageFormat#YUV_420_888},
         * and can represent the {@link #COLOR_FormatYUV411Planar},
         * {@link #COLOR_FormatYUV411PackedPlanar}, {@link #COLOR_FormatYUV420Planar},
         * {@link #COLOR_FormatYUV420PackedPlanar}, {@link #COLOR_FormatYUV420SemiPlanar}
         * and {@link #COLOR_FormatYUV420PackedSemiPlanar} formats.
         *
         * @see Image#getFormat
         */
        public static final int COLOR_FormatYUV420Flexible            = 0x7F420888;

        /**
         * Flexible 16 bits per pixel, subsampled YUV color format with 8-bit chroma and luma
         * components.
         * <p>
         * Chroma planes are horizontally subsampled by 2. Use this format with {@link Image}.
         * This format corresponds to {@link android.graphics.ImageFormat#YUV_422_888},
         * and can represent the {@link #COLOR_FormatYCbYCr}, {@link #COLOR_FormatYCrYCb},
         * {@link #COLOR_FormatCbYCrY}, {@link #COLOR_FormatCrYCbY},
         * {@link #COLOR_FormatYUV422Planar}, {@link #COLOR_FormatYUV422PackedPlanar},
         * {@link #COLOR_FormatYUV422SemiPlanar} and {@link #COLOR_FormatYUV422PackedSemiPlanar}
         * formats.
         *
         * @see Image#getFormat
         */
        public static final int COLOR_FormatYUV422Flexible            = 0x7F422888;

        /**
         * Flexible 24 bits per pixel YUV color format with 8-bit chroma and luma
         * components.
         * <p>
         * Chroma planes are not subsampled. Use this format with {@link Image}.
         * This format corresponds to {@link android.graphics.ImageFormat#YUV_444_888},
         * and can represent the {@link #COLOR_FormatYUV444Interleaved} format.
         * @see Image#getFormat
         */
        public static final int COLOR_FormatYUV444Flexible            = 0x7F444888;

        /**
         * Flexible 24 bits per pixel RGB color format with 8-bit red, green and blue
         * components.
         * <p>
         * Use this format with {@link Image}. This format corresponds to
         * {@link android.graphics.ImageFormat#FLEX_RGB_888}, and can represent
         * {@link #COLOR_Format24bitBGR888} and {@link #COLOR_Format24bitRGB888} formats.
         * @see Image#getFormat.
         */
        public static final int COLOR_FormatRGBFlexible               = 0x7F36B888;

        /**
         * Flexible 32 bits per pixel RGBA color format with 8-bit red, green, blue, and alpha
         * components.
         * <p>
         * Use this format with {@link Image}. This format corresponds to
         * {@link android.graphics.ImageFormat#FLEX_RGBA_8888}, and can represent
         * {@link #COLOR_Format32bitBGRA8888}, {@link #COLOR_Format32bitABGR8888} and
         * {@link #COLOR_Format32bitARGB8888} formats.
         *
         * @see Image#getFormat
         */
        public static final int COLOR_FormatRGBAFlexible              = 0x7F36A888;

        /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
        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; // NOTE this array is modifiable by user

        // FEATURES

        private int mFlagsSupported;
        private int mFlagsRequired;
        private int mFlagsVerified;

        /**
         * <b>video decoder only</b>: codec supports seamless resolution changes.
         */
        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) {
            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 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}.
         *
         * <p class=note>
         * <strong>Note:</strong> On {@link android.os.Build.VERSION_CODES#LOLLIPOP},
         * {@code format} must not contain a {@linkplain MediaFormat#KEY_FRAME_RATE
         * frame rate}. Use
         * <code class=prettyprint>format.setString(MediaFormat.KEY_FRAME_RATE, null)</code>
         * to clear any existing frame rate setting in the format.
         *
         * @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 == null) {
                    continue;
                }
                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 - accessed from sister capabilities
        int mError;

        private static final String TAG = "CodecCapabilities";

        // NEW-STYLE CAPABILITIES
        private AudioCapabilities mAudioCaps;
        private VideoCapabilities mVideoCaps;
        private EncoderCapabilities mEncoderCaps;
        private MediaFormat mDefaultFormat;

        /**
         * Returns a MediaFormat object with default values for configurations that have
         * defaults.
         */
        public MediaFormat getDefaultFormat() {
            return mDefaultFormat;
        }

        /**
         * Returns the mime type for which this codec-capability object was created.
         */
        public String getMimeType() {
            return mMime;
        }

        /**
         * Returns the max number of the supported concurrent codec instances.
         * <p>
         * This is a hint for an upper bound. Applications should not expect to successfully
         * operate more instances than the returned value, but the actual number of
         * concurrently operable instances may be less as it depends on the available
         * resources at time of use.
         */
        public int getMaxSupportedInstances() {
            return mMaxSupportedInstances;
        }

        private boolean isAudio() {
            return mAudioCaps != null;
        }

        /**
         * Returns the audio capabilities or {@code null} if this is not an audio codec.
         */
        public AudioCapabilities getAudioCapabilities() {
            return mAudioCaps;
        }

        private boolean isEncoder() {
            return mEncoderCaps != null;
        }

        /**
         * Returns the encoding capabilities or {@code null} if this is not an encoder.
         */
        public EncoderCapabilities getEncoderCapabilities() {
            return mEncoderCaps;
        }

        private boolean isVideo() {
            return mVideoCaps != null;
        }

        /**
         * Returns the video capabilities or {@code null} if this is not a video codec.
         */
        public VideoCapabilities getVideoCapabilities() {
            return mVideoCaps;
        }

        /** @hide */
        public CodecCapabilities dup() {
            return new CodecCapabilities(
                // clone writable arrays
                Arrays.copyOf(profileLevels, profileLevels.length),
                Arrays.copyOf(colorFormats, colorFormats.length),
                isEncoder(),
                mFlagsVerified,
                mDefaultFormat,
                mCapabilitiesInfo);
        }

        /**
         * 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 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);
                mAudioCaps.setDefaultFormat(mDefaultFormat);
            } else if (mMime.toLowerCase().startsWith("video/")) {
                mVideoCaps = VideoCapabilities.create(info, this);
            }
            if (encoder) {
                mEncoderCaps = EncoderCapabilities.create(info, this);
                mEncoderCaps.setDefaultFormat(mDefaultFormat);
            }

            final Map<String, Object> global = MediaCodecList.getGlobalSettings();
            mMaxSupportedInstances = Utils.parseIntSafely(
                    global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);

            int maxInstances = Utils.parseIntSafely(
                    map.get("max-concurrent-instances"), mMaxSupportedInstances);
            mMaxSupportedInstances =
                    Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);

            for (Feature feat: getValidFeatures()) {
                String key = MediaFormat.KEY_FEATURE_ + feat.mName;
                Integer yesNo = (Integer)map.get(key);
                if (yesNo == null) {
                    continue;
                }
                if (yesNo > 0) {
                    mFlagsRequired |= feat.mValue;
                }
                mFlagsSupported |= feat.mValue;
                mDefaultFormat.setInteger(key, 1);
                // 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 {
        private static final String TAG = "AudioCapabilities";
        private CodecCapabilities mParent;
        private Range<Integer> mBitrateRange;

        private int[] mSampleRates;
        private Range<Integer>[] mSampleRateRanges;
        private int mMaxInputChannelCount;

        private static final int MAX_INPUT_CHANNEL_COUNT = 30;

        /**
         * Returns the range of supported bitrates in bits/second.
         */
        public Range<Integer> getBitrateRange() {
            return mBitrateRange;
        }

        /**
         * 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 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 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 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) {
            mParent = parent;
            initWithPlatformLimits();
            applyLevelLimits();
            parseFromInfo(info);
        }

        private void initWithPlatformLimits() {
            mBitrateRange = Range.create(0, Integer.MAX_VALUE);
            mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
            // 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 boolean isSampleRateSupported(int sampleRate) {
            return supports(sampleRate, null);
        }

        /** modifies rates */
        private void limitSampleRates(int[] rates) {
            Arrays.sort(rates);
            ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
            for (int rate: rates) {
                if (supports(rate, null /* channels */)) {
                    ranges.add(Range.create(rate, rate));
                }
            }
            mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
            createDiscreteSampleRates();
        }

        private void createDiscreteSampleRates() {
            mSampleRates = new int[mSampleRateRanges.length];
            for (int i = 0; i < mSampleRateRanges.length; i++) {
                mSampleRates[i] = mSampleRateRanges[i].getLower();
            }
        }

        /** modifies rateRanges */
        private void limitSampleRates(Range<Integer>[] rateRanges) {
            sortDistinctRanges(rateRanges);
            mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);

            // check if all values are discrete
            for (Range<Integer> range: mSampleRateRanges) {
                if (!range.getLower().equals(range.getUpper())) {
                    mSampleRates = null;
                    return;
                }
            }
            createDiscreteSampleRates();
        }

        private void applyLevelLimits() {
            int[] sampleRates = null;
            Range<Integer> sampleRateRange = null, bitRates = null;
            int maxChannels = 0;
            String mime = mParent.getMimeType();

            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
                sampleRates = new int[] {
                        8000, 11025, 12000,
                        16000, 22050, 24000,
                        32000, 44100, 48000 };
                bitRates = Range.create(8000, 320000);
                maxChannels = 2;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
                sampleRates = new int[] { 8000 };
                bitRates = Range.create(4750, 12200);
                maxChannels = 1;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
                sampleRates = new int[] { 16000 };
                bitRates = Range.create(6600, 23850);
                maxChannels = 1;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
                sampleRates = new int[] {
                        7350, 8000,
                        11025, 12000, 16000,
                        22050, 24000, 32000,
                        44100, 48000, 64000,
                        88200, 96000 };
                bitRates = Range.create(8000, 510000);
                maxChannels = 48;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
                bitRates = Range.create(32000, 500000);
                sampleRateRange = Range.create(8000, 192000);
                maxChannels = 255;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
                bitRates = Range.create(6000, 510000);
                sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
                maxChannels = 255;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
                sampleRateRange = Range.create(1, 96000);
                bitRates = Range.create(1, 10000000);
                maxChannels = 8;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
                sampleRateRange = Range.create(1, 655350);
                // lossless codec, so bitrate is ignored
                maxChannels = 255;
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
                sampleRates = new int[] { 8000 };
                bitRates = Range.create(64000, 64000);
                // platform allows multiple channels for this format
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
                sampleRates = new int[] { 8000 };
                bitRates = Range.create(13000, 13000);
                maxChannels = 1;
            } else {
                Log.w(TAG, "Unsupported mime " + mime);
                mParent.mError |= ERROR_UNSUPPORTED;
            }

            // restrict ranges
            if (sampleRates != null) {
                limitSampleRates(sampleRates);
            } else if (sampleRateRange != null) {
                limitSampleRates(new Range[] { sampleRateRange });
            }
            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 = MAX_INPUT_CHANNEL_COUNT;
            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 = Utils.parseIntSafely(
                        info.getString("max-channel-count"), maxInputChannels);
            }
            if (info.containsKey("bitrate-range")) {
                bitRates = bitRates.intersect(
                        Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
            }
            applyLimits(maxInputChannels, bitRates);
        }

        /** @hide */
        public void setDefaultFormat(MediaFormat format) {
            // report settings that have only a single choice
            if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
                format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
            }
            if (mMaxInputChannelCount == 1) {
                // mono-only format
                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
            }
            if (mSampleRates != null && mSampleRates.length == 1) {
                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
            }
        }

        /** @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;
        }
    }

    /**
     * A class that supports querying the video capabilities of a codec.
     */
    public static final class VideoCapabilities {
        private static final String TAG = "VideoCapabilities";
        private CodecCapabilities mParent;
        private Range<Integer> mBitrateRange;

        private Range<Integer> mHeightRange;
        private Range<Integer> mWidthRange;
        private Range<Integer> mBlockCountRange;
        private Range<Integer> mHorizontalBlockRange;
        private Range<Integer> mVerticalBlockRange;
        private Range<Rational> mAspectRatioRange;
        private Range<Rational> mBlockAspectRatioRange;
        private Range<Long> mBlocksPerSecondRange;
        private Map<Size, Range<Long>> mMeasuredFrameRates;
        private Range<Integer> mFrameRateRange;

        private int mBlockWidth;
        private int mBlockHeight;
        private int mWidthAlignment;
        private int mHeightAlignment;
        private int mSmallerDimensionUpperLimit;

        /**
         * Returns the range of supported bitrates in bits/second.
         */
        public Range<Integer> getBitrateRange() {
            return mBitrateRange;
        }

        /**
         * Returns the range of supported video widths.
         */
        public Range<Integer> getSupportedWidths() {
            return mWidthRange;
        }

        /**
         * Returns the range of supported video heights.
         */
        public Range<Integer> getSupportedHeights() {
            return mHeightRange;
        }

        /**
         * Returns the alignment requirement for video width (in pixels).
         *
         * This is a power-of-2 value that video width must be a
         * multiple of.
         */
        public int getWidthAlignment() {
            return mWidthAlignment;
        }

        /**
         * Returns the alignment requirement for video height (in pixels).
         *
         * This is a power-of-2 value that video height must be a
         * multiple of.
         */
        public int getHeightAlignment() {
            return mHeightAlignment;
        }

        /**
         * Return the upper limit on the smaller dimension of width or height.
         * <p></p>
         * Some codecs have a limit on the smaller dimension, whether it be
         * the width or the height.  E.g. a codec may only be able to handle
         * up to 1920x1080 both in landscape and portrait mode (1080x1920).
         * In this case the maximum width and height are both 1920, but the
         * smaller dimension limit will be 1080. For other codecs, this is
         * {@code Math.min(getSupportedWidths().getUpper(),
         * getSupportedHeights().getUpper())}.
         *
         * @hide
         */
        public int getSmallerDimensionUpperLimit() {
            return mSmallerDimensionUpperLimit;
        }

        /**
         * Returns the range of supported frame rates.
         * <p>
         * This is not a performance indicator.  Rather, it expresses the
         * limits specified in the coding standard, based on the complexities
         * of encoding material for later playback at a certain frame rate,
         * or the decoding of such material in non-realtime.
         */
        public Range<Integer> getSupportedFrameRates() {
            return mFrameRateRange;
        }

        /**
         * Returns the range of supported video widths for a video height.
         * @param height the height of the video
         */
        public Range<Integer> getSupportedWidthsFor(int height) {
            try {
                Range<Integer> range = mWidthRange;
                if (!mHeightRange.contains(height)
                        || (height % mHeightAlignment) != 0) {
                    throw new IllegalArgumentException("unsupported height");
                }
                final int heightInBlocks = Utils.divUp(height, mBlockHeight);

                // constrain by block count and by block aspect ratio
                final int minWidthInBlocks = Math.max(
                        Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
                        (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
                                * heightInBlocks));
                final int maxWidthInBlocks = Math.min(
                        mBlockCountRange.getUpper() / heightInBlocks,
                        (int)(mBlockAspectRatioRange.getUpper().doubleValue()
                                * heightInBlocks));
                range = range.intersect(
                        (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
                        maxWidthInBlocks * mBlockWidth);

                // constrain by smaller dimension limit
                if (height > mSmallerDimensionUpperLimit) {
                    range = range.intersect(1, mSmallerDimensionUpperLimit);
                }

                // constrain by aspect ratio
                range = range.intersect(
                        (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
                                * height),
                        (int)(mAspectRatioRange.getUpper().doubleValue() * height));
                return range;
            } catch (IllegalArgumentException e) {
                // height is not supported because there are no suitable widths
                Log.v(TAG, "could not get supported widths for " + height);
                throw new IllegalArgumentException("unsupported height");
            }
        }

        /**
         * Returns the range of supported video heights for a video width
         * @param width the width of the video
         */
        public Range<Integer> getSupportedHeightsFor(int width) {
            try {
                Range<Integer> range = mHeightRange;
                if (!mWidthRange.contains(width)
                        || (width % mWidthAlignment) != 0) {
                    throw new IllegalArgumentException("unsupported width");
                }
                final int widthInBlocks = Utils.divUp(width, mBlockWidth);

                // constrain by block count and by block aspect ratio
                final int minHeightInBlocks = Math.max(
                        Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
                        (int)Math.ceil(widthInBlocks /
                                mBlockAspectRatioRange.getUpper().doubleValue()));
                final int maxHeightInBlocks = Math.min(
                        mBlockCountRange.getUpper() / widthInBlocks,
                        (int)(widthInBlocks /
                                mBlockAspectRatioRange.getLower().doubleValue()));
                range = range.intersect(
                        (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
                        maxHeightInBlocks * mBlockHeight);

                // constrain by smaller dimension limit
                if (width > mSmallerDimensionUpperLimit) {
                    range = range.intersect(1, mSmallerDimensionUpperLimit);
                }

                // constrain by aspect ratio
                range = range.intersect(
                        (int)Math.ceil(width /
                                mAspectRatioRange.getUpper().doubleValue()),
                        (int)(width / mAspectRatioRange.getLower().doubleValue()));
                return range;
            } catch (IllegalArgumentException e) {
                // width is not supported because there are no suitable heights
                Log.v(TAG, "could not get supported heights for " + width);
                throw new IllegalArgumentException("unsupported width");
            }
        }

        /**
         * Returns the range of supported video frame rates for a video size.
         * <p>
         * This is not a performance indicator.  Rather, it expresses the limits specified in
         * the coding standard, based on the complexities of encoding material of a given
         * size for later playback at a certain frame rate, or the decoding of such material
         * in non-realtime.

         * @param width the width of the video
         * @param height the height of the video
         */
        public Range<Double> getSupportedFrameRatesFor(int width, int height) {
            Range<Integer> range = mHeightRange;
            if (!supports(width, height, null)) {
                throw new IllegalArgumentException("unsupported size");
            }
            final int blockCount =
                Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);

            return Range.create(
                    Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
                            (double) mFrameRateRange.getLower()),
                    Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
                            (double) mFrameRateRange.getUpper()));
        }

        private int getBlockCount(int width, int height) {
            return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
        }

        @NonNull
        private Size findClosestSize(int width, int height) {
            int targetBlockCount = getBlockCount(width, height);
            Size closestSize = null;
            int minDiff = Integer.MAX_VALUE;
            for (Size size : mMeasuredFrameRates.keySet()) {
                int diff = Math.abs(targetBlockCount -
                        getBlockCount(size.getWidth(), size.getHeight()));
                if (diff < minDiff) {
                    minDiff = diff;
                    closestSize = size;
                }
            }
            return closestSize;
        }

        private Range<Double> estimateFrameRatesFor(int width, int height) {
            Size size = findClosestSize(width, height);
            Range<Long> range = mMeasuredFrameRates.get(size);
            Double ratio = (double)(size.getWidth() * size.getHeight()) / (width * height);
            return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
        }

        /**
         * Returns the range of achievable video frame rates for a video size.
         * May return {@code null}, if the codec did not publish any measurement
         * data.
         * <p>
         * This is a performance estimate provided by the device manufacturer
         * based on full-speed decoding and encoding measurements in various configurations
         * of common video sizes supported by the codec. As such it should only be used to
         * compare individual codecs on the device. The value is not suitable for comparing
         * different devices or even different android releases for the same device.
         * <p>
         * The returned range corresponds to the fastest frame rates achieved in the tested
         * configurations. It is interpolated from the nearest frame size(s) tested. Codec
         * performance is severely impacted by other activity on the device, and can vary
         * significantly.
         * <p class=note>
         * Use this method in cases where only codec performance matters, e.g. to evaluate if
         * a codec has any chance of meeting a performance target. Codecs are listed
         * in {@link MediaCodecList} in the preferred order as defined by the device
         * manufacturer. As such, applications should use the first suitable codec in the
         * list to achieve the best balance between power use and performance.
         *
         * @param width the width of the video
         * @param height the height of the video
         *
         * @throws IllegalArgumentException if the video size is not supported.
         */
        @Nullable
        public Range<Double> getAchievableFrameRatesFor(int width, int height) {
            if (!supports(width, height, null)) {
                throw new IllegalArgumentException("unsupported size");
            }

            if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
                Log.w(TAG, "Codec did not publish any measurement data.");
                return null;
            }

            return estimateFrameRatesFor(width, height);
        }

        /**
         * Returns whether a given video size ({@code width} and
         * {@code height}) and {@code frameRate} combination is supported.
         */
        public boolean areSizeAndRateSupported(
                int width, int height, double frameRate) {
            return supports(width, height, frameRate);
        }

        /**
         * Returns whether a given video size ({@code width} and
         * {@code height}) is supported.
         */
        public boolean isSizeSupported(int width, int height) {
            return supports(width, height, null);
        }

        private boolean supports(
                Integer width, Integer height, Number rate) {
            boolean ok = true;

            if (ok && width != null) {
                ok = mWidthRange.contains(width)
                        && (width % mWidthAlignment == 0);
            }
            if (ok && height != null) {
                ok = mHeightRange.contains(height)
                        && (height % mHeightAlignment == 0);
            }
            if (ok && rate != null) {
                ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
            }
            if (ok && height != null && width != null) {
                ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;

                final int widthInBlocks = Utils.divUp(width, mBlockWidth);
                final int heightInBlocks = Utils.divUp(height, mBlockHeight);
                final int blockCount = widthInBlocks * heightInBlocks;
                ok = ok && mBlockCountRange.contains(blockCount)
                        && mBlockAspectRatioRange.contains(
                                new Rational(widthInBlocks, heightInBlocks))
                        && mAspectRatioRange.contains(new Rational(width, height));
                if (ok && rate != null) {
                    double blocksPerSec = blockCount * rate.doubleValue();
                    ok = mBlocksPerSecondRange.contains(
                            Utils.longRangeFor(blocksPerSec));
                }
            }
            return ok;
        }

        /**
         * @hide
         * @throws java.lang.ClassCastException */
        public boolean supportsFormat(MediaFormat format) {
            final Map<String, Object> map = format.getMap();
            Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
            Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
            Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);

            // 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) {
            mParent = parent;
            initWithPlatformLimits();
            applyLevelLimits();
            parseFromInfo(info);
            updateLimits();
        }

        /** @hide */
        public Size getBlockSize() {
            return new Size(mBlockWidth, mBlockHeight);
        }

        /** @hide */
        public Range<Integer> getBlockCountRange() {
            return mBlockCountRange;
        }

        /** @hide */
        public Range<Long> getBlocksPerSecondRange() {
            return mBlocksPerSecondRange;
        }

        /** @hide */
        public Range<Rational> getAspectRatioRange(boolean blocks) {
            return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
        }

        private void initWithPlatformLimits() {
            mBitrateRange = BITRATE_RANGE;

            mWidthRange  = 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 Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
            Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
            final String prefix = "measured-frame-rate-";
            Set<String> keys = map.keySet();
            for (String key : keys) {
                // looking for: measured-frame-rate-WIDTHxHEIGHT-range
                if (!key.startsWith(prefix)) {
                    continue;
                }
                String subKey = key.substring(prefix.length());
                String[] temp = key.split("-");
                if (temp.length != 5) {
                    continue;
                }
                String sizeStr = temp[3];
                Size size = Utils.parseSize(sizeStr, null);
                if (size == null || size.getWidth() * size.getHeight() <= 0) {
                    continue;
                }
                Range<Long> range = Utils.parseLongRange(map.get(key), null);
                if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
                    continue;
                }
                ret.put(size, range);
            }
            return ret;
        }

        private void parseFromInfo(MediaFormat info) {
            final Map<String, Object> map = info.getMap();
            Size blockSize = new Size(mBlockWidth, mBlockHeight);
            Size alignment = new Size(mWidthAlignment, mHeightAlignment);
            Range<Integer> counts = null, widths = null, heights = null;
            Range<Integer> frameRates = null, bitRates = null;
            Range<Long> blockRates = null;
            Range<Rational> ratios = null, blockRatios = null;

            blockSize = Utils.parseSize(map.get("block-size"), blockSize);
            alignment = Utils.parseSize(map.get("alignment"), alignment);
            counts = Utils.parseIntRange(map.get("block-count-range"), null);
            blockRates =
                Utils.parseLongRange(map.get("blocks-per-second-range"), null);
            mMeasuredFrameRates = getMeasuredFrameRates(map);
            {
                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;
                    }
                }
            }
            // for now this just means using the smaller max size as 2nd
            // upper limit.
            // for now we are keeping the profile specific "width/height
            // in macroblocks" limits.
            if (map.containsKey("feature-can-swap-width-height")) {
                if (widths != null) {
                    mSmallerDimensionUpperLimit =
                        Math.min(widths.getUpper(), heights.getUpper());
                    widths = heights = widths.extend(heights);
                } else {
                    Log.w(TAG, "feature can-swap-width-height is best used with size-range");
                    mSmallerDimensionUpperLimit =
                        Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
                    mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
                }
            }

            ratios = Utils.parseRationalRange(
                    map.get("block-aspect-ratio-range"), null);
            blockRatios = Utils.parseRationalRange(
                    map.get("pixel-aspect-ratio-range"), null);
            frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
            if (frameRates != null) {
                try {
                    frameRates = frameRates.intersect(FRAME_RATE_RANGE);
                } catch (IllegalArgumentException e) {
                    Log.w(TAG, "frame rate range (" + frameRates
                            + ") is out of limits: " + FRAME_RATE_RANGE);
                    frameRates = null;
                }
            }
            bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
            if (bitRates != null) {
                try {
                    bitRates = bitRates.intersect(BITRATE_RANGE);
                } catch (IllegalArgumentException e) {
                    Log.w(TAG,  "bitrate range (" + bitRates
                            + ") is out of limits: " + BITRATE_RANGE);
                    bitRates = null;
                }
            }

            checkPowerOfTwo(
                    blockSize.getWidth(), "block-size width must be power of two");
            checkPowerOfTwo(
                    blockSize.getHeight(), "block-size height must be power of two");

            checkPowerOfTwo(
                    alignment.getWidth(), "alignment width must be power of two");
            checkPowerOfTwo(
                    alignment.getHeight(), "alignment height must be power of two");

            // update block-size and alignment
            applyMacroBlockLimits(
                    Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
                    Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
                    alignment.getWidth(), alignment.getHeight());

            if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
                // codec supports profiles that we don't know.
                // Use supplied values clipped to platform limits
                if (widths != null) {
                    mWidthRange = SIZE_RANGE.intersect(widths);
                }
                if (heights != null) {
                    mHeightRange = SIZE_RANGE.intersect(heights);
                }
                if (counts != null) {
                    mBlockCountRange = POSITIVE_INTEGERS.intersect(
                            Utils.factorRange(counts, mBlockWidth * mBlockHeight
                                    / blockSize.getWidth() / blockSize.getHeight()));
                }
                if (blockRates != null) {
                    mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
                            Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
                                    / blockSize.getWidth() / blockSize.getHeight()));
                }
                if (blockRatios != null) {
                    mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
                            Utils.scaleRange(blockRatios,
                                    mBlockHeight / blockSize.getHeight(),
                                    mBlockWidth / blockSize.getWidth()));
                }
                if (ratios != null) {
                    mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
                }
                if (frameRates != null) {
                    mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
                }
                if (bitRates != null) {
                    mBitrateRange = BITRATE_RANGE.intersect(bitRates);
                }
            } else {
                // no unsupported profile/levels, so restrict values to known limits
                if (widths != null) {
                    mWidthRange = mWidthRange.intersect(widths);
                }
                if (heights != null) {
                    mHeightRange = mHeightRange.intersect(heights);
                }
                if (counts != null) {
                    mBlockCountRange = mBlockCountRange.intersect(
                            Utils.factorRange(counts, mBlockWidth * mBlockHeight
                                    / blockSize.getWidth() / blockSize.getHeight()));
                }
                if (blockRates != null) {
                    mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
                            Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
                                    / blockSize.getWidth() / blockSize.getHeight()));
                }
                if (blockRatios != null) {
                    mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
                            Utils.scaleRange(blockRatios,
                                    mBlockHeight / blockSize.getHeight(),
                                    mBlockWidth / blockSize.getWidth()));
                }
                if (ratios != null) {
                    mAspectRatioRange = mAspectRatioRange.intersect(ratios);
                }
                if (frameRates != null) {
                    mFrameRateRange = mFrameRateRange.intersect(frameRates);
                }
                if (bitRates != null) {
                    mBitrateRange = mBitrateRange.intersect(bitRates);
                }
            }
            updateLimits();
        }

        private void applyBlockLimits(
                int blockWidth, int blockHeight,
                Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
            checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
            checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");

            final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
            final int newBlockHeight = Math.max(blockHeight, mBlockHeight);

            // factor will always be a power-of-2
            int factor =
                newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
            if (factor != 1) {
                mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
                mBlocksPerSecondRange = Utils.factorRange(
                        mBlocksPerSecondRange, factor);
                mBlockAspectRatioRange = Utils.scaleRange(
                        mBlockAspectRatioRange,
                        newBlockHeight / mBlockHeight,
                        newBlockWidth / mBlockWidth);
                mHorizontalBlockRange = Utils.factorRange(
                        mHorizontalBlockRange, newBlockWidth / mBlockWidth);
                mVerticalBlockRange = Utils.factorRange(
                        mVerticalBlockRange, newBlockHeight / mBlockHeight);
            }
            factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
            if (factor != 1) {
                counts = Utils.factorRange(counts, factor);
                rates = Utils.factorRange(rates, factor);
                ratios = Utils.scaleRange(
                        ratios, newBlockHeight / blockHeight,
                        newBlockWidth / blockWidth);
            }
            mBlockCountRange = mBlockCountRange.intersect(counts);
            mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
            mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
            mBlockWidth = newBlockWidth;
            mBlockHeight = newBlockHeight;
        }

        private void applyAlignment(int widthAlignment, int heightAlignment) {
            checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
            checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");

            if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
                // maintain assumption that 0 < alignment <= block-size
                applyBlockLimits(
                        Math.max(widthAlignment, mBlockWidth),
                        Math.max(heightAlignment, mBlockHeight),
                        POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
            }

            mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
            mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);

            mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
            mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
        }

        private void updateLimits() {
            // pixels -> blocks <- counts
            mHorizontalBlockRange = mHorizontalBlockRange.intersect(
                    Utils.factorRange(mWidthRange, mBlockWidth));
            mHorizontalBlockRange = mHorizontalBlockRange.intersect(
                    Range.create(
                            mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
                            mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
            mVerticalBlockRange = mVerticalBlockRange.intersect(
                    Utils.factorRange(mHeightRange, mBlockHeight));
            mVerticalBlockRange = mVerticalBlockRange.intersect(
                    Range.create(
                            mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
                            mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
            mBlockCountRange = mBlockCountRange.intersect(
                    Range.create(
                            mHorizontalBlockRange.getLower()
                                    * mVerticalBlockRange.getLower(),
                            mHorizontalBlockRange.getUpper()
                                    * mVerticalBlockRange.getUpper()));
            mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
                    new Rational(
                            mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
                    new Rational(
                            mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));

            // blocks -> pixels
            mWidthRange = mWidthRange.intersect(
                    (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
                    mHorizontalBlockRange.getUpper() * mBlockWidth);
            mHeightRange = mHeightRange.intersect(
                    (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
                    mVerticalBlockRange.getUpper() * mBlockHeight);
            mAspectRatioRange = mAspectRatioRange.intersect(
                    new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
                    new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));

            mSmallerDimensionUpperLimit = Math.min(
                    mSmallerDimensionUpperLimit,
                    Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));

            // blocks -> rate
            mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
                    mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
                    mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
            mFrameRateRange = mFrameRateRange.intersect(
                    (int)(mBlocksPerSecondRange.getLower()
                            / mBlockCountRange.getUpper()),
                    (int)(mBlocksPerSecondRange.getUpper()
                            / (double)mBlockCountRange.getLower()));
        }

        private void applyMacroBlockLimits(
                int maxHorizontalBlocks, int maxVerticalBlocks,
                int maxBlocks, long maxBlocksPerSecond,
                int blockWidth, int blockHeight,
                int widthAlignment, int heightAlignment) {
            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.getMimeType();

            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
                maxBlocks = 99;
                maxBlocksPerSecond = 1485;
                maxBps = 64000;
                maxDPBBlocks = 396;
                for (CodecProfileLevel profileLevel: profileLevels) {
                    int MBPS = 0, FS = 0, BR = 0, DPB = 0;
                    boolean supported = true;
                    switch (profileLevel.level) {
                        case CodecProfileLevel.AVCLevel1:
                            MBPS =    1485; FS =    99; BR =     64; DPB =    396; break;
                        case CodecProfileLevel.AVCLevel1b:
                            MBPS =    1485; FS =    99; BR =    128; DPB =    396; break;
                        case CodecProfileLevel.AVCLevel11:
                            MBPS =    3000; FS =   396; BR =    192; DPB =    900; break;
                        case CodecProfileLevel.AVCLevel12:
                            MBPS =    6000; FS =   396; BR =    384; DPB =   2376; break;
                        case CodecProfileLevel.AVCLevel13:
                            MBPS =   11880; FS =   396; BR =    768; DPB =   2376; break;
                        case CodecProfileLevel.AVCLevel2:
                            MBPS =   11880; FS =   396; BR =   2000; DPB =   2376; break;
                        case CodecProfileLevel.AVCLevel21:
                            MBPS =   19800; FS =   792; BR =   4000; DPB =   4752; break;
                        case CodecProfileLevel.AVCLevel22:
                            MBPS =   20250; FS =  1620; BR =   4000; DPB =   8100; break;
                        case CodecProfileLevel.AVCLevel3:
                            MBPS =   40500; FS =  1620; BR =  10000; DPB =   8100; break;
                        case CodecProfileLevel.AVCLevel31:
                            MBPS =  108000; FS =  3600; BR =  14000; DPB =  18000; break;
                        case CodecProfileLevel.AVCLevel32:
                            MBPS =  216000; FS =  5120; BR =  20000; DPB =  20480; break;
                        case CodecProfileLevel.AVCLevel4:
                            MBPS =  245760; FS =  8192; BR =  20000; DPB =  32768; break;
                        case CodecProfileLevel.AVCLevel41:
                            MBPS =  245760; FS =  8192; BR =  50000; DPB =  32768; break;
                        case CodecProfileLevel.AVCLevel42:
                            MBPS =  522240; FS =  8704; BR =  50000; DPB =  34816; break;
                        case CodecProfileLevel.AVCLevel5:
                            MBPS =  589824; FS = 22080; BR = 135000; DPB = 110400; break;
                        case CodecProfileLevel.AVCLevel51:
                            MBPS =  983040; FS = 36864; BR = 240000; DPB = 184320; break;
                        case CodecProfileLevel.AVCLevel52:
                            MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
                        default:
                            Log.w(TAG, "Unrecognized level "
                                    + profileLevel.level + " for " + mime);
                            errors |= ERROR_UNRECOGNIZED;
                    }
                    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, "Unsupported 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 /* blockWidth */, 16 /* blockHeight */,
                        1 /* widthAlignment */, 1 /* heightAlignment */);
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
                int maxWidth = 11, maxHeight = 9, maxRate = 15;
                maxBlocks = 99;
                maxBlocksPerSecond = 1485;
                maxBps = 64000;
                for (CodecProfileLevel profileLevel: profileLevels) {
                    int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
                    boolean supported = true;
                    switch (profileLevel.profile) {
                        case CodecProfileLevel.MPEG2ProfileSimple:
                            switch (profileLevel.level) {
                                case CodecProfileLevel.MPEG2LevelML:
                                    FR = 30; W = 45; H =  36; MBPS =  48600; FS =  1620; BR =  15000; break;
                                default:
                                    Log.w(TAG, "Unrecognized profile/level "
                                            + profileLevel.profile + "/"
                                            + profileLevel.level + " for " + mime);
                                    errors |= ERROR_UNRECOGNIZED;
                            }
                            break;
                        case CodecProfileLevel.MPEG2ProfileMain:
                            switch (profileLevel.level) {
                                case CodecProfileLevel.MPEG2LevelLL:
                                    FR = 30; W = 22; H =  18; MBPS =  11880; FS =  396; BR =  4000; break;
                                case CodecProfileLevel.MPEG2LevelML:
                                    FR = 30; W = 45; H =  36; MBPS =  48600; FS =  1620; BR =  15000; break;
                                case CodecProfileLevel.MPEG2LevelH14:
                                    FR = 60; W = 90; H =  68; MBPS =  367200; FS =  6120; BR = 60000; break;
                                case CodecProfileLevel.MPEG2LevelHL:
                                    FR = 60; W = 120; H = 68; MBPS =  489600; FS =  8160; BR = 80000; break;
                                default:
                                    Log.w(TAG, "Unrecognized profile/level "
                                            + profileLevel.profile + "/"
                                            + profileLevel.level + " for " + mime);
                                    errors |= ERROR_UNRECOGNIZED;
                            }
                            break;
                        case CodecProfileLevel.MPEG2Profile422:
                        case CodecProfileLevel.MPEG2ProfileSNR:
                        case CodecProfileLevel.MPEG2ProfileSpatial:
                        case CodecProfileLevel.MPEG2ProfileHigh:
                            Log.i(TAG, "Unsupported profile "
                                    + profileLevel.profile + " for " + mime);
                            errors |= ERROR_UNSUPPORTED;
                            supported = false;
                            break;
                        default:
                            Log.w(TAG, "Unrecognized profile "
                                    + profileLevel.profile + " for " + mime);
                            errors |= ERROR_UNRECOGNIZED;
                    }
                    if (supported) {
                        errors &= ~ERROR_NONE_SUPPORTED;
                    }
                    maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
                    maxBlocks = Math.max(FS, maxBlocks);
                    maxBps = Math.max(BR * 1000, maxBps);
                    maxWidth = Math.max(W, maxWidth);
                    maxHeight = Math.max(H, maxHeight);
                    maxRate = Math.max(FR, maxRate);
                }
                applyMacroBlockLimits(maxWidth, maxHeight,
                        maxBlocks, maxBlocksPerSecond,
                        16 /* blockWidth */, 16 /* blockHeight */,
                        1 /* widthAlignment */, 1 /* heightAlignment */);
                mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
                int maxWidth = 11, maxHeight = 9, maxRate = 15;
                maxBlocks = 99;
                maxBlocksPerSecond = 1485;
                maxBps = 64000;
                for (CodecProfileLevel profileLevel: profileLevels) {
                    int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
                    boolean 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;
                                case CodecProfileLevel.MPEG4Level4:
                                case CodecProfileLevel.MPEG4Level4a:
                                case CodecProfileLevel.MPEG4Level5:
                                    // While MPEG4 SP does not have level 4 or 5, some vendors
                                    // report it. Use the same limits as level 3, but mark as
                                    // unsupported.
                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384;
                                    supported = false;
                                    break;
                                default:
                                    Log.w(TAG, "Unrecognized profile/level "
                                            + profileLevel.profile + "/"
                                            + profileLevel.level + " for " + mime);
                                    errors |= ERROR_UNRECOGNIZED;
                            }
                            break;
                        case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
                            switch (profileLevel.level) {
                                case CodecProfileLevel.MPEG4Level0:
                                case CodecProfileLevel.MPEG4Level1:
                                    FR = 30; W = 11; H =  9; MBPS =  2970; FS =   99; BR =  128; break;
                                case CodecProfileLevel.MPEG4Level2:
                                    FR = 30; W = 22; H = 18; MBPS =  5940; FS =  396; BR =  384; break;
                                case CodecProfileLevel.MPEG4Level3:
                                    FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR =  768; break;
                                // case CodecProfileLevel.MPEG4Level3b:
                                // 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 /* blockWidth */, 16 /* blockHeight */,
                        1 /* widthAlignment */, 1 /* heightAlignment */);
                mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
                int maxWidth = 11, maxHeight = 9, maxRate = 15;
                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 /* blockWidth */, 16 /* blockHeight */,
                        1 /* widthAlignment */, 1 /* heightAlignment */);
                mFrameRateRange = Range.create(1, maxRate);
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
                    mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
                maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE;

                // TODO: set to 100Mbps for now, need a number for VPX
                maxBps = 100000000;

                // profile levels are not indicative for VPx, but verify
                // them nonetheless
                for (CodecProfileLevel profileLevel: profileLevels) {
                    switch (profileLevel.level) {
                        case CodecProfileLevel.VP8Level_Version0:
                        case CodecProfileLevel.VP8Level_Version1:
                        case CodecProfileLevel.VP8Level_Version2:
                        case CodecProfileLevel.VP8Level_Version3:
                            break;
                        default:
                            Log.w(TAG, "Unrecognized level "
                                    + profileLevel.level + " for " + mime);
                            errors |= ERROR_UNRECOGNIZED;
                    }
                    switch (profileLevel.profile) {
                        case CodecProfileLevel.VP8ProfileMain:
                            break;
                        default:
                            Log.w(TAG, "Unrecognized profile "
                                    + profileLevel.profile + " for " + mime);
                            errors |= ERROR_UNRECOGNIZED;
                    }
                    errors &= ~ERROR_NONE_SUPPORTED;
                }

                final int blockSize =
                    mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8;
                applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
                        maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
                        1 /* widthAlignment */, 1 /* heightAlignment */);
            } 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.HEVCMainTierLevel41:
                            FR =    60; FS =  2228224; BR =  20000; break;
                        case CodecProfileLevel.HEVCHighTierLevel41:
                            FR =    60; FS =  2228224; BR =  50000; break;
                        case CodecProfileLevel.HEVCMainTierLevel5:
                            FR =    30; FS =  8912896; BR =  25000; break;
                        case CodecProfileLevel.HEVCHighTierLevel5:
                            FR =    30; FS =  8912896; BR = 100000; break;
                        case CodecProfileLevel.HEVCMainTierLevel51:
                            FR =    60; FS =  8912896; BR =  40000; break;
                        case CodecProfileLevel.HEVCHighTierLevel51:
                            FR =    60; FS =  8912896; BR = 160000; break;
                        case CodecProfileLevel.HEVCMainTierLevel52:
                            FR =   120; FS =  8912896; BR =  60000; break;
                        case CodecProfileLevel.HEVCHighTierLevel52:
                            FR =   120; FS =  8912896; BR = 240000; break;
                        case CodecProfileLevel.HEVCMainTierLevel6:
                            FR =    30; FS = 35651584; BR =  60000; break;
                        case CodecProfileLevel.HEVCHighTierLevel6:
                            FR =    30; FS = 35651584; BR = 240000; break;
                        case CodecProfileLevel.HEVCMainTierLevel61:
                            FR =    60; FS = 35651584; BR = 120000; break;
                        case CodecProfileLevel.HEVCHighTierLevel61:
                            FR =    60; FS = 35651584; BR = 480000; break;
                        case CodecProfileLevel.HEVCMainTierLevel62:
                            FR =   120; FS = 35651584; BR = 240000; break;
                        case CodecProfileLevel.HEVCHighTierLevel62:
                            FR =   120; FS = 35651584; BR = 800000; break;
                        default:
                            Log.w(TAG, "Unrecognized level "
                                    + profileLevel.level + " for " + mime);
                            errors |= ERROR_UNRECOGNIZED;
                    }
                    switch (profileLevel.profile) {
                        case CodecProfileLevel.HEVCProfileMain:
                        case CodecProfileLevel.HEVCProfileMain10:
                            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 /* blockWidth */, 8 /* blockHeight */,
                        1 /* widthAlignment */, 1 /* heightAlignment */);
            } else {
                Log.w(TAG, "Unsupported mime " + mime);
                // using minimal bitrate here.  should be overriden by
                // info from media_codecs.xml
                maxBps = 64000;
                errors |= ERROR_UNSUPPORTED;
            }
            mBitrateRange = Range.create(1, maxBps);
            mParent.mError |= errors;
        }
    }

    /**
     * A class that supports querying the encoding capabilities of a codec.
     */
    public static final class EncoderCapabilities {
        /**
         * Returns the supported range of quality values.
         *
         * @hide
         */
        public 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 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 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() {
            String mime = mParent.getMimeType();
            if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
                mComplexityRange = Range.create(0, 8);
                mBitControl = (1 << BITRATE_MODE_CQ);
            } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
                    || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
                mBitControl = (1 << BITRATE_MODE_CBR);
            }
        }

        private int mBitControl;
        private Integer mDefaultComplexity;
        private Integer mDefaultQuality;
        private String mQualityScale;

        private void parseFromInfo(MediaFormat info) {
            Map<String, Object> map = info.getMap();

            if (info.containsKey("complexity-range")) {
                mComplexityRange = Utils
                        .parseIntRange(info.getString("complexity-range"), mComplexityRange);
                // TODO should we limit this to level limits?
            }
            if (info.containsKey("quality-range")) {
                mQualityRange = Utils
                        .parseIntRange(info.getString("quality-range"), mQualityRange);
            }
            if (info.containsKey("feature-bitrate-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) {
            // don't list trivial quality/complexity as default for now
            if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
                    && mDefaultQuality != null) {
                format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
            }
            if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
                    && mDefaultComplexity != null) {
                format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
            }
            // bitrates are listed in order of preference
            for (Feature feat: bitrates) {
                if ((mBitControl & (1 << feat.mValue)) != 0) {
                    format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
                    break;
                }
            }
        }

        /** @hide */
        public boolean supportsFormat(MediaFormat format) {
            final Map<String, Object> map = format.getMap();
            final String mime = mParent.getMimeType();

            Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
            if (mode != null && !isBitrateModeSupported(mode)) {
                return false;
            }

            Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
            if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
                Integer flacComplexity =
                    (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
                if (complexity == null) {
                    complexity = flacComplexity;
                } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
                    throw new IllegalArgumentException(
                            "conflicting values for complexity and " +
                            "flac-compression-level");
                }
            }

            // other audio parameters
            Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
            if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
                Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
                if (profile == null) {
                    profile = aacProfile;
                } else if (aacProfile != null && !aacProfile.equals(profile)) {
                    throw new IllegalArgumentException(
                            "conflicting values for profile and aac-profile");
                }
            }

            Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);

            return supports(complexity, quality, profile);
        }
    };

    /**
     * Encapsulates the profiles available for a codec component.
     * <p>You can get a set of {@link MediaCodecInfo.CodecProfileLevel} objects for a given
     * {@link MediaCodecInfo} object from the
     * {@link MediaCodecInfo.CodecCapabilities#profileLevels} field.
     */
    public static final class CodecProfileLevel {
        // from OMX_VIDEO_AVCPROFILETYPE
        public static final int AVCProfileBaseline = 0x01;
        public static final int AVCProfileMain     = 0x02;
        public static final int AVCProfileExtended = 0x04;
        public static final int AVCProfileHigh     = 0x08;
        public static final int AVCProfileHigh10   = 0x10;
        public static final int AVCProfileHigh422  = 0x20;
        public static final int AVCProfileHigh444  = 0x40;

        // from OMX_VIDEO_AVCLEVELTYPE
        public static final int AVCLevel1       = 0x01;
        public static final int AVCLevel1b      = 0x02;
        public static final int AVCLevel11      = 0x04;
        public static final int AVCLevel12      = 0x08;
        public static final int AVCLevel13      = 0x10;
        public static final int AVCLevel2       = 0x20;
        public static final int AVCLevel21      = 0x40;
        public static final int AVCLevel22      = 0x80;
        public static final int AVCLevel3       = 0x100;
        public static final int AVCLevel31      = 0x200;
        public static final int AVCLevel32      = 0x400;
        public static final int AVCLevel4       = 0x800;
        public static final int AVCLevel41      = 0x1000;
        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;
        public static final int H263ProfileH320Coding           = 0x02;
        public static final int H263ProfileBackwardCompatible   = 0x04;
        public static final int H263ProfileISWV2                = 0x08;
        public static final int H263ProfileISWV3                = 0x10;
        public static final int H263ProfileHighCompression      = 0x20;
        public static final int H263ProfileInternet             = 0x40;
        public static final int H263ProfileInterlace            = 0x80;
        public static final int H263ProfileHighLatency          = 0x100;

        // from OMX_VIDEO_H263LEVELTYPE
        public static final int H263Level10      = 0x01;
        public static final int H263Level20      = 0x02;
        public static final int H263Level30      = 0x04;
        public static final int H263Level40      = 0x08;
        public static final int H263Level45      = 0x10;
        public static final int H263Level50      = 0x20;
        public static final int H263Level60      = 0x40;
        public static final int H263Level70      = 0x80;

        // from OMX_VIDEO_MPEG4PROFILETYPE
        public static final int MPEG4ProfileSimple              = 0x01;
        public static final int MPEG4ProfileSimpleScalable      = 0x02;
        public static final int MPEG4ProfileCore                = 0x04;
        public static final int MPEG4ProfileMain                = 0x08;
        public static final int MPEG4ProfileNbit                = 0x10;
        public static final int MPEG4ProfileScalableTexture     = 0x20;
        public static final int MPEG4ProfileSimpleFace          = 0x40;
        public static final int MPEG4ProfileSimpleFBA           = 0x80;
        public static final int MPEG4ProfileBasicAnimated       = 0x100;
        public static final int MPEG4ProfileHybrid              = 0x200;
        public static final int MPEG4ProfileAdvancedRealTime    = 0x400;
        public static final int MPEG4ProfileCoreScalable        = 0x800;
        public static final int MPEG4ProfileAdvancedCoding      = 0x1000;
        public static final int MPEG4ProfileAdvancedCore        = 0x2000;
        public static final int MPEG4ProfileAdvancedScalable    = 0x4000;
        public static final int MPEG4ProfileAdvancedSimple      = 0x8000;

        // from OMX_VIDEO_MPEG4LEVELTYPE
        public static final int MPEG4Level0      = 0x01;
        public static final int MPEG4Level0b     = 0x02;
        public static final int MPEG4Level1      = 0x04;
        public static final int MPEG4Level2      = 0x08;
        public static final int MPEG4Level3      = 0x10;
        public static final int MPEG4Level4      = 0x20;
        public static final int MPEG4Level4a     = 0x40;
        public static final int MPEG4Level5      = 0x80;

        // from OMX_VIDEO_MPEG2PROFILETYPE
        public static final int MPEG2ProfileSimple              = 0x00;
        public static final int MPEG2ProfileMain                = 0x01;
        public static final int MPEG2Profile422                 = 0x02;
        public static final int MPEG2ProfileSNR                 = 0x03;
        public static final int MPEG2ProfileSpatial             = 0x04;
        public static final int MPEG2ProfileHigh                = 0x05;

        // from OMX_VIDEO_MPEG2LEVELTYPE
        public static final int MPEG2LevelLL     = 0x00;
        public static final int MPEG2LevelML     = 0x01;
        public static final int MPEG2LevelH14    = 0x02;
        public static final int MPEG2LevelHL     = 0x03;

        // from OMX_AUDIO_AACPROFILETYPE
        public static final int AACObjectMain       = 1;
        public static final int AACObjectLC         = 2;
        public static final int AACObjectSSR        = 3;
        public static final int AACObjectLTP        = 4;
        public static final int AACObjectHE         = 5;
        public static final int AACObjectScalable   = 6;
        public static final int AACObjectERLC       = 17;
        public static final int AACObjectLD         = 23;
        public static final int AACObjectHE_PS      = 29;
        public static final int AACObjectELD        = 39;

        // from OMX_VIDEO_VP8LEVELTYPE
        public static final int VP8Level_Version0 = 0x01;
        public static final int VP8Level_Version1 = 0x02;
        public static final int VP8Level_Version2 = 0x04;
        public static final int VP8Level_Version3 = 0x08;

        // from OMX_VIDEO_VP8PROFILETYPE
        public static final int VP8ProfileMain = 0x01;

        // from OMX_VIDEO_HEVCPROFILETYPE
        public static final int HEVCProfileMain   = 0x01;
        public static final int HEVCProfileMain10 = 0x02;

        // from OMX_VIDEO_HEVCLEVELTYPE
        public static final int HEVCMainTierLevel1  = 0x1;
        public static final int HEVCHighTierLevel1  = 0x2;
        public static final int HEVCMainTierLevel2  = 0x4;
        public static final int HEVCHighTierLevel2  = 0x8;
        public static final int HEVCMainTierLevel21 = 0x10;
        public static final int HEVCHighTierLevel21 = 0x20;
        public static final int HEVCMainTierLevel3  = 0x40;
        public static final int HEVCHighTierLevel3  = 0x80;
        public static final int HEVCMainTierLevel31 = 0x100;
        public static final int HEVCHighTierLevel31 = 0x200;
        public static final int HEVCMainTierLevel4  = 0x400;
        public static final int HEVCHighTierLevel4  = 0x800;
        public static final int HEVCMainTierLevel41 = 0x1000;
        public static final int HEVCHighTierLevel41 = 0x2000;
        public static final int HEVCMainTierLevel5  = 0x4000;
        public static final int HEVCHighTierLevel5  = 0x8000;
        public static final int HEVCMainTierLevel51 = 0x10000;
        public static final int HEVCHighTierLevel51 = 0x20000;
        public static final int HEVCMainTierLevel52 = 0x40000;
        public static final int HEVCHighTierLevel52 = 0x80000;
        public static final int HEVCMainTierLevel6  = 0x100000;
        public static final int HEVCHighTierLevel6  = 0x200000;
        public static final int HEVCMainTierLevel61 = 0x400000;
        public static final int HEVCHighTierLevel61 = 0x800000;
        public static final int HEVCMainTierLevel62 = 0x1000000;
        public static final int HEVCHighTierLevel62 = 0x2000000;

        /**
         * Defined in the OpenMAX IL specs, depending on the type of media
         * this can be OMX_VIDEO_AVCPROFILETYPE, OMX_VIDEO_H263PROFILETYPE,
         * OMX_VIDEO_MPEG4PROFILETYPE or OMX_VIDEO_VP8PROFILETYPE.
         */
        public int profile;

        /**
         * Defined in the OpenMAX IL specs, depending on the type of media
         * this can be OMX_VIDEO_AVCLEVELTYPE, OMX_VIDEO_H263LEVELTYPE
         * OMX_VIDEO_MPEG4LEVELTYPE or OMX_VIDEO_VP8LEVELTYPE.
         */
        public int level;
    };

    /**
     * Enumerates the capabilities of the codec component. Since a single
     * component can support data of a variety of types, the type has to be
     * specified to yield a meaningful result.
     * @param type The MIME type to query
     */
    public final CodecCapabilities getCapabilitiesForType(
            String 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()]));
    }
}
