content: /*
   * 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 static android.media.Utils.intersectSortedDistinctRanges;
  import static android.media.Utils.sortDistinctRanges;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.TestApi;
  import android.annotation.UnsupportedAppUsage;
  import android.os.Build;
  import android.os.SystemProperties;
  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.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.Vector;
  
  /**
   * 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 static final String TAG = "MediaCodecInfo";
  
      private static final int FLAG_IS_ENCODER = (1 << 0);
      private static final int FLAG_IS_VENDOR = (1 << 1);
      private static final int FLAG_IS_SOFTWARE_ONLY = (1 << 2);
      private static final int FLAG_IS_HARDWARE_ACCELERATED = (1 << 3);
  
      private int mFlags;
      private String mName;
      private String mCanonicalName;
      private Map<String, CodecCapabilities> mCaps;
  
      /* package private */ MediaCodecInfo(
              String name, String canonicalName, int flags, CodecCapabilities[] caps) {
          mName = name;
          mCanonicalName = canonicalName;
          mFlags = flags;
          mCaps = new HashMap<String, CodecCapabilities>();
  
          for (CodecCapabilities c: caps) {
              mCaps.put(c.getMimeType(), c);
          }
      }
  
      /**
       * Retrieve the codec name.
       *
       * <strong>Note:</strong> Implementations may provide multiple aliases (codec
       * names) for the same underlying codec, any of which can be used to instantiate the same
       * underlying codec in {@link MediaCodec#createByCodecName}.
       *
       * Applications targeting SDK < {@link android.os.Build.VERSION_CODES#Q}, cannot determine if
       * the multiple codec names listed in MediaCodecList are in-fact for the same codec.
       */
      @NonNull
      public final String getName() {
          return mName;
      }
  
      /**
       * Retrieve the underlying codec name.
       *
       * Device implementations may provide multiple aliases (codec names) for the same underlying
       * codec to maintain backward app compatibility. This method returns the name of the underlying
       * codec name, which must not be another alias. For non-aliases this is always the name of the
       * codec.
       */
      @NonNull
      public final String getCanonicalName() {
          return mCanonicalName;
      }
  
      /**
       * Query if the codec is an alias for another underlying codec.
       */
      public final boolean isAlias() {
          return !mName.equals(mCanonicalName);
      }
  
      /**
       * Query if the codec is an encoder.
       */
      public final boolean isEncoder() {
          return (mFlags & FLAG_IS_ENCODER) != 0;
      }
  
      /**
       * Query if the codec is provided by the Android platform (false) or the device manufacturer
       * (true).
       */
      public final boolean isVendor() {
          return (mFlags & FLAG_IS_VENDOR) != 0;
      }
  
      /**
       * Query if the codec is software only. Software-only codecs are more secure as they run in
       * a tighter security sandbox. On the other hand, software-only codecs do not provide any
       * performance guarantees.
       */
      public final boolean isSoftwareOnly() {
          return (mFlags & FLAG_IS_SOFTWARE_ONLY) != 0;
      }
  
      /**
       * Query if the codec is hardware accelerated. This attribute is provided by the device
       * manufacturer. Note that it cannot be tested for correctness.
       */
      public final boolean isHardwareAccelerated() {
          return (mFlags & FLAG_IS_HARDWARE_ACCELERATED) != 0;
      }
  
      /**
       * 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 MediaCodecConstants
          /** @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.
          // Note: 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;
  
          /**
           * The color format for the media. This is one of the color constants defined in this class.
           */
          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";
  
          /**
           * If true, the timestamp of each output buffer is derived from the timestamp of the input
           * buffer that produced the output. If false, the timestamp of each output buffer is
           * derived from the timestamp of the first input buffer.
           */
          public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp";
  
          /**
           * <b>decoder only</b>If true, the codec supports partial (including multiple) access units
           * per input buffer.
           */
          public static final String FEATURE_FrameParsing = "frame-parsing";
  
          /**
           * If true, the codec supports multiple access units (for decoding, or to output for
           * encoders). If false, the codec only supports single access units. Producing multiple
           * access units for output is an optional feature.
           */
          public static final String FEATURE_MultipleFrames = "multiple-frames";
  
          /**
           * <b>video decoder only</b>: codec supports queuing partial frames.
           */
          public static final String FEATURE_PartialFrame = "partial-frame";
  
          /**
           * <b>video encoder only</b>: codec supports intra refresh.
           */
          public static final String FEATURE_IntraRefresh = "intra-refresh";
  
          /**
           * 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),
              new Feature(FEATURE_PartialFrame,     (1 << 3), false),
              new Feature(FEATURE_FrameParsing,     (1 << 4), false),
              new Feature(FEATURE_MultipleFrames,   (1 << 5), false),
              new Feature(FEATURE_DynamicTimestamp, (1 << 6), false),
          };
  
          private static final Feature[] encoderFeatures = {
              new Feature(FEATURE_IntraRefresh, (1 << 0), false),
              new Feature(FEATURE_MultipleFrames, (1 << 1), false),
              new Feature(FEATURE_DynamicTimestamp, (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 encoderFeatures;
          }
  
          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.
           * <p>
           *
           * The following table summarizes the format keys considered by this method.
           *
           * <table style="width: 0%">
           *  <thead>
           *   <tr>
           *    <th rowspan=3>OS Version(s)</th>
           *    <td colspan=3>{@code MediaFormat} keys considered for</th>
           *   </tr><tr>
           *    <th>Audio Codecs</th>
           *    <th>Video Codecs</th>
           *    <th>Encoders</th>
           *   </tr>
           *  </thead>
           *  <tbody>
           *   <tr>
           *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</th>
           *    <td rowspan=3>{@link MediaFormat#KEY_MIME}<sup>*</sup>,<br>
           *        {@link MediaFormat#KEY_SAMPLE_RATE},<br>
           *        {@link MediaFormat#KEY_CHANNEL_COUNT},</td>
           *    <td>{@link MediaFormat#KEY_MIME}<sup>*</sup>,<br>
           *        {@link CodecCapabilities#FEATURE_AdaptivePlayback}<sup>D</sup>,<br>
           *        {@link CodecCapabilities#FEATURE_SecurePlayback}<sup>D</sup>,<br>
           *        {@link CodecCapabilities#FEATURE_TunneledPlayback}<sup>D</sup>,<br>
           *        {@link MediaFormat#KEY_WIDTH},<br>
           *        {@link MediaFormat#KEY_HEIGHT},<br>
           *        <strong>no</strong> {@code KEY_FRAME_RATE}</td>
           *    <td rowspan=4>{@link MediaFormat#KEY_BITRATE_MODE},<br>
           *        {@link MediaFormat#KEY_PROFILE}
           *        (and/or {@link MediaFormat#KEY_AAC_PROFILE}<sup>~</sup>),<br>
           *        <!-- {link MediaFormat#KEY_QUALITY},<br> -->
           *        {@link MediaFormat#KEY_COMPLEXITY}
           *        (and/or {@link MediaFormat#KEY_FLAC_COMPRESSION_LEVEL}<sup>~</sup>)</td>
           *   </tr><tr>
           *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</th>
           *    <td rowspan=2>as above, plus<br>
           *        {@link MediaFormat#KEY_FRAME_RATE}</td>
           *   </tr><tr>
           *    <td>{@link android.os.Build.VERSION_CODES#M}</th>
           *   </tr><tr>
           *    <td>{@link android.os.Build.VERSION_CODES#N}</th>
           *    <td>as above, plus<br>
           *        {@link MediaFormat#KEY_PROFILE},<br>
           *        <!-- {link MediaFormat#KEY_MAX_BIT_RATE},<br> -->
           *        {@link MediaFormat#KEY_BIT_RATE}</td>
           *    <td>as above, plus<br>
           *        {@link MediaFormat#KEY_PROFILE},<br>
           *        {@link MediaFormat#KEY_LEVEL}<sup>+</sup>,<br>
           *        <!-- {link MediaFormat#KEY_MAX_BIT_RATE},<br> -->
           *        {@link MediaFormat#KEY_BIT_RATE},<br>
           *        {@link CodecCapabilities#FEATURE_IntraRefresh}<sup>E</sup></td>
           *   </tr>
           *   <tr>
           *    <td colspan=4>
           *     <p class=note><strong>Notes:</strong><br>
           *      *: must be specified; otherwise, method returns {@code false}.<br>
           *      +: method does not verify that the format parameters are supported
           *      by the specified level.<br>
           *      D: decoders only<br>
           *      E: encoders only<br>
           *      ~: if both keys are provided values must match
           *    </td>
           *   </tr>
           *  </tbody>
           * </table>
           *
           * @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;
                  }
              }
  
              Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
              Integer level = (Integer)map.get(MediaFormat.KEY_LEVEL);
  
              if (profile != null) {
                  if (!supportsProfileLevel(profile, level)) {
                      return false;
                  }
  
                  // If we recognize this profile, check that this format is supported by the
                  // highest level supported by the codec for that profile. (Ignore specified
                  // level beyond the above profile/level check as level is only used as a
                  // guidance. E.g. AVC Level 1 CIF format is supported if codec supports level 1.1
                  // even though max size for Level 1 is QCIF. However, MPEG2 Simple Profile
                  // 1080p format is not supported even if codec supports Main Profile Level High,
                  // as Simple Profile does not support 1080p.
                  CodecCapabilities levelCaps = null;
                  int maxLevel = 0;
                  for (CodecProfileLevel pl : profileLevels) {
                      if (pl.profile == profile && pl.level > maxLevel) {
                          maxLevel = pl.level;
                      }
                  }
                  levelCaps = createFromProfileLevel(mMime, profile, maxLevel);
                  // remove profile from this format otherwise levelCaps.isFormatSupported will
                  // get into this same conditon and loop forever.
                  Map<String, Object> mapWithoutProfile = new HashMap<>(map);
                  mapWithoutProfile.remove(MediaFormat.KEY_PROFILE);
                  MediaFormat formatWithoutProfile = new MediaFormat(mapWithoutProfile);
                  if (levelCaps != null && !levelCaps.isFormatSupported(formatWithoutProfile)) {
                      return false;
                  }
              }
              if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
                  return false;
              }
              if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
                  return false;
              }
              if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
                  return false;
              }
              return true;
          }
  
          private static boolean supportsBitrate(
                  Range<Integer> bitrateRange, MediaFormat format) {
              Map<String, Object> map = format.getMap();
  
              // consider max bitrate over average bitrate for support
              Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
              Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
              if (bitrate == null) {
                  bitrate = maxBitrate;
              } else if (maxBitrate != null) {
                  bitrate = Math.max(bitrate, maxBitrate);
              }
  
              if (bitrate != null && bitrate > 0) {
                  return bitrateRange.contains(bitrate);
              }
  
              return true;
          }
  
          private boolean supportsProfileLevel(int profile, Integer level) {
              for (CodecProfileLevel pl: profileLevels) {
                  if (pl.profile != profile) {
                      continue;
                  }
  
                  // AAC does not use levels
                  if (level == null || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
                      return true;
                  }
  
                  // H.263 levels are not completely ordered:
                  // Level45 support only implies Level10 support
                  if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
                      if (pl.level != level && pl.level == CodecProfileLevel.H263Level45
                              && level > CodecProfileLevel.H263Level10) {
                          continue;
                      }
                  }
  
                  // MPEG4 levels are not completely ordered:
                  // Level1 support only implies Level0 (and not Level0b) support
                  if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
                      if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
                              && level > CodecProfileLevel.MPEG4Level0) {
                          continue;
                      }
                  }
  
                  // HEVC levels incorporate both tiers and levels. Verify tier support.
                  if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
                      boolean supportsHighTier =
                          (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
                      boolean checkingHighTier = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
                      // high tier levels are only supported by other high tier levels
                      if (checkingHighTier && !supportsHighTier) {
                          continue;
                      }
                  }
  
                  if (pl.level >= level) {
                      // if we recognize the listed profile/level, we must also recognize the
                      // profile/level arguments.
                      if (createFromProfileLevel(mMime, profile, pl.level) != null) {
                          return createFromProfileLevel(mMime, profile, level) != null;
                      }
                      return true;
                  }
              }
              return false;
          }
  
          // errors while reading profile levels - accessed from sister capabilities
          int mError;
  
          private static final String TAG = "CodecCapabilities";
  
          // NEW-STYLE CAPABILITIES
          private AudioCapabilities mAudioCaps;
          private VideoCapabilities mVideoCaps;
          private EncoderCapabilities mEncoderCaps;
          private MediaFormat mDefaultFormat;
  
          /**
           * Returns a MediaFormat object with default values for configurations that have
           * defaults.
           */
          public MediaFormat getDefaultFormat() {
              return mDefaultFormat;
          }
  
          /**
           * 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() {
              CodecCapabilities caps = new CodecCapabilities();
  
              // profileLevels and colorFormats may be modified by client.
              caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length);
              caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length);
  
              caps.mMime = mMime;
              caps.mMaxSupportedInstances = mMaxSupportedInstances;
              caps.mFlagsRequired = mFlagsRequired;
              caps.mFlagsSupported = mFlagsSupported;
              caps.mFlagsVerified = mFlagsVerified;
              caps.mAudioCaps = mAudioCaps;
              caps.mVideoCaps = mVideoCaps;
              caps.mEncoderCaps = mEncoderCaps;
              caps.mDefaultFormat = mDefaultFormat;
              caps.mCapabilitiesInfo = mCapabilitiesInfo;
  
              return caps;
          }
  
          /**
           * Retrieve the codec capabilities for a certain {@code mime type}, {@code
           * profile} and {@code level}.  If the type, or profile-level combination
           * is not understood by the framework, it returns null.
           * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
           * method without calling any method of the {@link MediaCodecList} class beforehand
           * results in a {@link NullPointerException}.</p>
           */
          public static CodecCapabilities createFromProfileLevel(
                  String mime, int profile, int level) {
              CodecProfileLevel pl = new CodecProfileLevel();
              pl.profile = profile;
              pl.level = level;
              MediaFormat defaultFormat = new MediaFormat();
              defaultFormat.setString(MediaFormat.KEY_MIME, mime);
  
              CodecCapabilities ret = new CodecCapabilities(
                  new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
                  defaultFormat, new MediaFormat() /* info */);
              if (ret.mError != 0) {
                  return null;
              }
              return ret;
          }
  
          /* package private */ CodecCapabilities(
                  CodecProfileLevel[] profLevs, int[] colFmts,
                  boolean encoder,
                  Map<String, Object>defaultFormatMap,
                  Map<String, Object>capabilitiesMap) {
              this(profLevs, colFmts, encoder,
                      new MediaFormat(defaultFormatMap),
                      new MediaFormat(capabilitiesMap));
          }
  
          private MediaFormat mCapabilitiesInfo;
  
          /* package private */ CodecCapabilities(
                  CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder,
                  MediaFormat defaultFormat, MediaFormat info) {
              final Map<String, Object> map = info.getMap();
              colorFormats = colFmts;
              mFlagsVerified = 0; // TODO: remove as it is unused
              mDefaultFormat = defaultFormat;
              mCapabilitiesInfo = info;
              mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
  
              /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any
                 supported profiles. Determine the level for them using the info they provide. */
              if (profLevs.length == 0 && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
                  CodecProfileLevel profLev = new CodecProfileLevel();
                  profLev.profile = CodecProfileLevel.VP9Profile0;
                  profLev.level = VideoCapabilities.equivalentVP9Level(info);
                  profLevs = new CodecProfileLevel[] { profLev };
              }
              profileLevels = profLevs;
  
              if (mMime.toLowerCase().startsWith("audio/")) {
                  mAudioCaps = AudioCapabilities.create(info, this);
                  mAudioCaps.getDefaultFormat(mDefaultFormat);
              } else if (mMime.toLowerCase().startsWith("video/")
                      || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
                  mVideoCaps = VideoCapabilities.create(info, this);
              }
              if (encoder) {
                  mEncoderCaps = EncoderCapabilities.create(info, this);
                  mEncoderCaps.getDefaultFormat(mDefaultFormat);
              }
  
              final Map<String, Object> global = MediaCodecList.getGlobalSettings();
              mMaxSupportedInstances = Utils.parseIntSafely(
                      global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES);
  
              int maxInstances = Utils.parseIntSafely(
                      map.get("max-concurrent-instances"), mMaxSupportedInstances);
              mMaxSupportedInstances =
                      Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances);
  
              for (Feature feat: getValidFeatures()) {
                  String key = MediaFormat.KEY_FEATURE_ + feat.mName;
                  Integer yesNo = (Integer)map.get(key);
                  if (yesNo == null) {
                      continue;
                  }
                  if (yesNo > 0) {
                      mFlagsRequired |= feat.mValue;
                  }
                  mFlagsSupported |= feat.mValue;
                  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 mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) : null;
          }
  
          /**
           * 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;
          }
  
          private 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);
              final int minSampleRate = SystemProperties.
                  getInt("ro.mediacodec.min_sample_rate", 7350);
              final int maxSampleRate = SystemProperties.
                  getInt("ro.mediacodec.max_sample_rate", 192000);
              mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) };
              mSampleRates = null;
          }
  
          private boolean supports(Integer sampleRate, Integer inputChannels) {
              // channels and sample rates are checked orthogonally
              if (inputChannels != null &&
                      (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 = MAX_INPUT_CHANNEL_COUNT;
              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 = AudioSystem.OUT_CHANNEL_COUNT_MAX;
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
                  sampleRateRange = Range.create(1, 655350);
                  // lossless codec, so bitrate is ignored
                  maxChannels = 255;
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
                      || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
                  sampleRates = new int[] { 8000 };
                  bitRates = Range.create(64000, 64000);
                  // platform allows multiple channels for this format
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
                  sampleRates = new int[] { 8000 };
                  bitRates = Range.create(13000, 13000);
                  maxChannels = 1;
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) {
                  maxChannels = 6;
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) {
                  maxChannels = 16;
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) {
                  sampleRates = new int[] { 48000 };
                  bitRates = Range.create(32000, 6144000);
                  maxChannels = 16;
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) {
                  sampleRates = new int[] { 44100, 48000, 96000, 192000 };
                  bitRates = Range.create(16000, 2688000);
                  maxChannels = 24;
              } else {
                  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);
              } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
                  maxInputChannels = 0;
              }
              if (info.containsKey("bitrate-range")) {
                  bitRates = bitRates.intersect(
                          Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
              }
              applyLimits(maxInputChannels, bitRates);
          }
  
          /** @hide */
          public void getDefaultFormat(MediaFormat format) {
              // report settings that have only a single choice
              if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
                  format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
              }
              if (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;
              }
  
              if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
                  return false;
              }
  
              // nothing to do for:
              // KEY_CHANNEL_MASK: codecs don't get this
              // KEY_IS_ADTS:      required feature for all AAC decoders
              return true;
          }
      }
  
      /**
       * 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 List<PerformancePoint> mPerformancePoints;
          private Range<Integer> mFrameRateRange;
  
          private int mBlockWidth;
          private int mBlockHeight;
          private int mWidthAlignment;
          private int mHeightAlignment;
          private int mSmallerDimensionUpperLimit;
  
          private boolean mAllowMbOverride; // allow XML to override calculated limits
  
          /**
           * Returns the range of supported bitrates in bits/second.
           */
          public Range<Integer> getBitrateRange() {
              return mBitrateRange;
          }
  
          /**
           * Returns the range of supported video widths.
           */
          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 = getBlockCount(size.getWidth(), size.getHeight())
                      / (double)Math.max(getBlockCount(width, height), 1);
              return Range.create(range.getLower() * ratio, range.getUpper() * ratio);
          }
  
          /**
           * Returns the range of achievable video frame rates for a video size.
           * May return {@code null}, if the codec did not publish any measurement
           * data.
           * <p>
           * This is a performance estimate provided by the device manufacturer based on statistical
           * sampling of full-speed decoding and encoding measurements in various configurations
           * of common video sizes supported by the codec. As such it should only be used to
           * compare individual codecs on the device. The value is not suitable for comparing
           * different devices or even different android releases for the same device.
           * <p>
           * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range
           * corresponds to the fastest frame rates achieved in the tested configurations. As
           * such, it should not be used to gauge guaranteed or even average codec performance
           * on the device.
           * <p>
           * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range
           * corresponds closer to sustained performance <em>in tested configurations</em>.
           * One can expect to achieve sustained performance higher than the lower limit more than
           * 50% of the time, and higher than half of the lower limit at least 90% of the time
           * <em>in tested configurations</em>.
           * Conversely, one can expect performance lower than twice the upper limit at least
           * 90% of the time.
           * <p class=note>
           * Tested configurations use a single active codec. For use cases where multiple
           * codecs are active, applications can expect lower and in most cases significantly lower
           * performance.
           * <p class=note>
           * The returned range value is interpolated from the nearest frame size(s) tested.
           * Codec performance is severely impacted by other activity on the device as well
           * as environmental factors (such as battery level, temperature or power source), and can
           * vary significantly even in a steady environment.
           * <p class=note>
           * Use this method in cases where only codec performance matters, e.g. to evaluate if
           * a codec has any chance of meeting a performance target. Codecs are listed
           * in {@link MediaCodecList} in the preferred order as defined by the device
           * manufacturer. As such, applications should use the first suitable codec in the
           * list to achieve the best balance between power use and performance.
           *
           * @param width the width of the video
           * @param height the height of the video
           *
           * @throws IllegalArgumentException if the video size is not supported.
           */
          @Nullable
          public Range<Double> getAchievableFrameRatesFor(int width, int height) {
              if (!supports(width, height, null)) {
                  throw new IllegalArgumentException("unsupported size");
              }
  
              if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) {
                  Log.w(TAG, "Codec did not publish any measurement data.");
                  return null;
              }
  
              return estimateFrameRatesFor(width, height);
          }
  
          /**
           * Video performance points are a set of standard performance points defined by number of
           * pixels, pixel rate and frame rate. Performance point represents an upper bound. This
           * means that it covers all performance points with fewer pixels, pixel rate and frame
           * rate.
           */
          public static final class PerformancePoint {
              private Size mBlockSize; // codec block size in macroblocks
              private int mWidth; // width in macroblocks
              private int mHeight; // height in macroblocks
              private int mMaxFrameRate; // max frames per second
              private long mMaxMacroBlockRate; // max macro block rate
  
              /**
               * Maximum number of macroblocks in the frame.
               *
               * Video frames are conceptually divided into 16-by-16 pixel blocks called macroblocks.
               * Most coding standards operate on these 16-by-16 pixel blocks; thus, codec performance
               * is characterized using such blocks.
               *
               * @hide
               */
              @TestApi
              public int getMaxMacroBlocks() {
                  return saturateLongToInt(mWidth * (long)mHeight);
              }
  
              /**
               * Maximum frame rate in frames per second.
               *
               * @hide
               */
              @TestApi
              public int getMaxFrameRate() {
                  return mMaxFrameRate;
              }
  
              /**
               * Maximum number of macroblocks processed per second.
               *
               * @hide
               */
              @TestApi
              public long getMaxMacroBlockRate() {
                  return mMaxMacroBlockRate;
              }
  
              /** Convert to a debug string */
              public String toString() {
                  int blockWidth = 16 * mBlockSize.getWidth();
                  int blockHeight = 16 * mBlockSize.getHeight();
                  int origRate = (int)Utils.divUp(mMaxMacroBlockRate, getMaxMacroBlocks());
                  String info = (mWidth * 16) + "x" + (mHeight * 16) + "@" + origRate;
                  if (origRate < mMaxFrameRate) {
                      info += ", max " + mMaxFrameRate + "fps";
                  }
                  if (blockWidth > 16 || blockHeight > 16) {
                      info += ", " + blockWidth + "x" + blockHeight + " blocks";
                  }
                  return "PerformancePoint(" + info + ")";
              }
  
              @Override
              public int hashCode() {
                  // only max frame rate must equal between performance points that equal to one
                  // another
                  return mMaxFrameRate;
              }
  
              /**
               * Create a detailed performance point with custom max frame rate and macroblock size.
               *
               * @param width  frame width in pixels
               * @param height frame height in pixels
               * @param frameRate frames per second for frame width and height
               * @param maxFrameRate maximum frames per second for any frame size
               * @param blockSize block size for codec implementation. Must be powers of two in both
               *        width and height.
               *
               * @throws IllegalArgumentException if the blockSize dimensions are not powers of two.
               *
               * @hide
               */
              @TestApi
              public PerformancePoint(
                      int width, int height, int frameRate, int maxFrameRate,
                      @NonNull Size blockSize) {
                  checkPowerOfTwo(blockSize.getWidth(), "block width");
                  checkPowerOfTwo(blockSize.getHeight(), "block height");
  
                  mBlockSize = new Size(Utils.divUp(blockSize.getWidth(), 16),
                                        Utils.divUp(blockSize.getHeight(), 16));
                  // these are guaranteed not to overflow as we decimate by 16
                  mWidth = (int)(Utils.divUp(Math.max(1L, width),
                                             Math.max(blockSize.getWidth(), 16))
                                 * mBlockSize.getWidth());
                  mHeight = (int)(Utils.divUp(Math.max(1L, height),
                                              Math.max(blockSize.getHeight(), 16))
                                  * mBlockSize.getHeight());
                  mMaxFrameRate = Math.max(1, Math.max(frameRate, maxFrameRate));
                  mMaxMacroBlockRate = Math.max(1, frameRate) * getMaxMacroBlocks();
              }
  
              /**
               * Convert a performance point to a larger blocksize.
               *
               * @param pp performance point
               * @param blockSize block size for codec implementation
               *
               * @hide
               */
              @TestApi
              public PerformancePoint(@NonNull PerformancePoint pp, @NonNull Size newBlockSize) {
                  this(
                          pp.mWidth * 16, pp.mHeight * 16,
                          // guaranteed not to overflow as these were multiplied at construction
                          (int)Utils.divUp(pp.mMaxMacroBlockRate, pp.getMaxMacroBlocks()),
                          pp.mMaxFrameRate,
                          new Size(Math.max(newBlockSize.getWidth(), pp.mBlockSize.getWidth() * 16),
                                   Math.max(newBlockSize.getHeight(), pp.mBlockSize.getHeight() * 16))
                  );
              }
  
              /**
               * Create a performance point for a given frame size and frame rate.
               *
               * @param width width of the frame in pixels
               * @param height height of the frame in pixels
               * @param frameRate frame rate in frames per second
               */
              public PerformancePoint(int width, int height, int frameRate) {
                  this(width, height, frameRate, frameRate /* maxFrameRate */, new Size(16, 16));
              }
  
              /** Saturates a long value to int */
              private int saturateLongToInt(long value) {
                  if (value < Integer.MIN_VALUE) {
                      return Integer.MIN_VALUE;
                  } else if (value > Integer.MAX_VALUE) {
                      return Integer.MAX_VALUE;
                  } else {
                      return (int)value;
                  }
              }
  
              /* This method may overflow */
              private int align(int value, int alignment) {
                  return Utils.divUp(value, alignment) * alignment;
              }
  
              /** Checks that value is a power of two. */
              private void checkPowerOfTwo2(int value, @NonNull String description) {
                  if (value == 0 || (value & (value - 1)) != 0) {
                      throw new IllegalArgumentException(
                              description + " (" + value + ") must be a power of 2");
                  }
              }
  
              /**
               * Checks whether the performance point covers a media format.
               *
               * @param format Stream format considered
               *
               * @return {@code true} if the performance point covers the format.
               */
              public boolean covers(@NonNull MediaFormat format) {
                  PerformancePoint other = new PerformancePoint(
                          format.getInteger(MediaFormat.KEY_WIDTH, 0),
                          format.getInteger(MediaFormat.KEY_HEIGHT, 0),
                          // safely convert ceil(double) to int through float cast and Math.round
                          Math.round((float)(
                                  Math.ceil(format.getNumber(MediaFormat.KEY_FRAME_RATE, 0)
                                          .doubleValue()))));
                  return covers(other);
              }
  
              /**
               * Checks whether the performance point covers another performance point. Use this
               * method to determine if a performance point advertised by a codec covers the
               * performance point required. This method can also be used for loose ordering as this
               * method is transitive.
               *
               * @param other other performance point considered
               *
               * @return {@code true} if the performance point covers the other.
               */
              public boolean covers(@NonNull PerformancePoint other) {
                  // convert performance points to common block size
                  Size commonSize = getCommonBlockSize(other);
                  PerformancePoint aligned = new PerformancePoint(this, commonSize);
                  PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
  
                  return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks()
                          && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate
                          && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
              }
  
              private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) {
                  return new Size(
                          Math.max(mBlockSize.getWidth(), other.mBlockSize.getWidth()) * 16,
                          Math.max(mBlockSize.getHeight(), other.mBlockSize.getHeight()) * 16);
              }
  
              @Override
              public boolean equals(Object o) {
                  if (o instanceof PerformancePoint) {
                      // convert performance points to common block size
                      PerformancePoint other = (PerformancePoint)o;
                      Size commonSize = getCommonBlockSize(other);
                      PerformancePoint aligned = new PerformancePoint(this, commonSize);
                      PerformancePoint otherAligned = new PerformancePoint(other, commonSize);
  
                      return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks()
                              && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate
                              && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate);
                  }
                  return false;
              }
  
              /** 480p 24fps */
              @NonNull
              public static final PerformancePoint SD_24 = new PerformancePoint(720, 480, 24);
              /** 576p 25fps */
              @NonNull
              public static final PerformancePoint SD_25 = new PerformancePoint(720, 576, 25);
              /** 480p 30fps */
              @NonNull
              public static final PerformancePoint SD_30 = new PerformancePoint(720, 480, 30);
              /** 480p 48fps */
              @NonNull
              public static final PerformancePoint SD_48 = new PerformancePoint(720, 480, 48);
              /** 576p 50fps */
              @NonNull
              public static final PerformancePoint SD_50 = new PerformancePoint(720, 576, 50);
              /** 480p 60fps */
              @NonNull
              public static final PerformancePoint SD_60 = new PerformancePoint(720, 480, 60);
  
              /** 720p 24fps */
              @NonNull
              public static final PerformancePoint HD_24 = new PerformancePoint(1280, 720, 24);
              /** 720p 25fps */
              @NonNull
              public static final PerformancePoint HD_25 = new PerformancePoint(1280, 720, 25);
              /** 720p 30fps */
              @NonNull
              public static final PerformancePoint HD_30 = new PerformancePoint(1280, 720, 30);
              /** 720p 50fps */
              @NonNull
              public static final PerformancePoint HD_50 = new PerformancePoint(1280, 720, 50);
              /** 720p 60fps */
              @NonNull
              public static final PerformancePoint HD_60 = new PerformancePoint(1280, 720, 60);
              /** 720p 100fps */
              @NonNull
              public static final PerformancePoint HD_100 = new PerformancePoint(1280, 720, 100);
              /** 720p 120fps */
              @NonNull
              public static final PerformancePoint HD_120 = new PerformancePoint(1280, 720, 120);
              /** 720p 200fps */
              @NonNull
              public static final PerformancePoint HD_200 = new PerformancePoint(1280, 720, 200);
              /** 720p 240fps */
              @NonNull
              public static final PerformancePoint HD_240 = new PerformancePoint(1280, 720, 240);
  
              /** 1080p 24fps */
              @NonNull
              public static final PerformancePoint FHD_24 = new PerformancePoint(1920, 1080, 24);
              /** 1080p 25fps */
              @NonNull
              public static final PerformancePoint FHD_25 = new PerformancePoint(1920, 1080, 25);
              /** 1080p 30fps */
              @NonNull
              public static final PerformancePoint FHD_30 = new PerformancePoint(1920, 1080, 30);
              /** 1080p 50fps */
              @NonNull
              public static final PerformancePoint FHD_50 = new PerformancePoint(1920, 1080, 50);
              /** 1080p 60fps */
              @NonNull
              public static final PerformancePoint FHD_60 = new PerformancePoint(1920, 1080, 60);
              /** 1080p 100fps */
              @NonNull
              public static final PerformancePoint FHD_100 = new PerformancePoint(1920, 1080, 100);
              /** 1080p 120fps */
              @NonNull
              public static final PerformancePoint FHD_120 = new PerformancePoint(1920, 1080, 120);
              /** 1080p 200fps */
              @NonNull
              public static final PerformancePoint FHD_200 = new PerformancePoint(1920, 1080, 200);
              /** 1080p 240fps */
              @NonNull
              public static final PerformancePoint FHD_240 = new PerformancePoint(1920, 1080, 240);
  
              /** 2160p 24fps */
              @NonNull
              public static final PerformancePoint UHD_24 = new PerformancePoint(3840, 2160, 24);
              /** 2160p 25fps */
              @NonNull
              public static final PerformancePoint UHD_25 = new PerformancePoint(3840, 2160, 25);
              /** 2160p 30fps */
              @NonNull
              public static final PerformancePoint UHD_30 = new PerformancePoint(3840, 2160, 30);
              /** 2160p 50fps */
              @NonNull
              public static final PerformancePoint UHD_50 = new PerformancePoint(3840, 2160, 50);
              /** 2160p 60fps */
              @NonNull
              public static final PerformancePoint UHD_60 = new PerformancePoint(3840, 2160, 60);
              /** 2160p 100fps */
              @NonNull
              public static final PerformancePoint UHD_100 = new PerformancePoint(3840, 2160, 100);
              /** 2160p 120fps */
              @NonNull
              public static final PerformancePoint UHD_120 = new PerformancePoint(3840, 2160, 120);
              /** 2160p 200fps */
              @NonNull
              public static final PerformancePoint UHD_200 = new PerformancePoint(3840, 2160, 200);
              /** 2160p 240fps */
              @NonNull
              public static final PerformancePoint UHD_240 = new PerformancePoint(3840, 2160, 240);
          }
  
          /**
           * Returns the supported performance points. May return {@code null} if the codec did not
           * publish any performance point information (e.g. the vendor codecs have not been updated
           * to the latest android release). May return an empty list if the codec published that
           * if does not guarantee any performance points.
           * <p>
           * This is a performance guarantee provided by the device manufacturer for hardware codecs
           * based on hardware capabilities of the device.
           * <p>
           * The returned list is sorted first by decreasing number of pixels, then by decreasing
           * width, and finally by decreasing frame rate.
           * Performance points assume a single active codec. For use cases where multiple
           * codecs are active, should use that highest pixel count, and add the frame rates of
           * each individual codec.
           */
          @Nullable
          public List<PerformancePoint> getSupportedPerformancePoints() {
              return mPerformancePoints;
          }
  
          /**
           * 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);
  
              if (!supports(width, height, rate)) {
                  return false;
              }
  
              if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
                  return false;
              }
  
              // we ignore color-format for now as it is not reliably reported by codec
              return true;
          }
  
          /* no public constructor */
          private VideoCapabilities() { }
  
          /** @hide */
          @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
          public static VideoCapabilities create(
                  MediaFormat info, CodecCapabilities parent) {
              VideoCapabilities caps = new VideoCapabilities();
              caps.init(info, parent);
              return caps;
          }
  
          private void init(MediaFormat info, CodecCapabilities parent) {
              mParent = parent;
              initWithPlatformLimits();
              applyLevelLimits();
              parseFromInfo(info);
              updateLimits();
          }
  
          /** @hide */
          public Size getBlockSize() {
              return new Size(mBlockWidth, mBlockHeight);
          }
  
          /** @hide */
          public Range<Integer> getBlockCountRange() {
              return mBlockCountRange;
          }
  
          /** @hide */
          public Range<Long> getBlocksPerSecondRange() {
              return mBlocksPerSecondRange;
          }
  
          /** @hide */
          public Range<Rational> getAspectRatioRange(boolean blocks) {
              return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
          }
  
          private void initWithPlatformLimits() {
              mBitrateRange = BITRATE_RANGE;
  
              mWidthRange  = 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 @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
              Vector<PerformancePoint> ret = new Vector<>();
              final String prefix = "performance-point-";
              Set<String> keys = map.keySet();
              for (String key : keys) {
                  // looking for: performance-point-WIDTHxHEIGHT-range
                  if (!key.startsWith(prefix)) {
                      continue;
                  }
                  String subKey = key.substring(prefix.length());
                  if (subKey.equals("none") && ret.size() == 0) {
                      // This means that component knowingly did not publish performance points.
                      // This is different from when the component forgot to publish performance
                      // points.
                      return Collections.unmodifiableList(ret);
                  }
                  String[] temp = key.split("-");
                  if (temp.length != 4) {
                      continue;
                  }
                  String sizeStr = temp[2];
                  Size size = Utils.parseSize(sizeStr, null);
                  if (size == null || size.getWidth() * size.getHeight() <= 0) {
                      continue;
                  }
                  Range<Long> range = Utils.parseLongRange(map.get(key), null);
                  if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
                      continue;
                  }
                  PerformancePoint given = new PerformancePoint(
                          size.getWidth(), size.getHeight(), range.getLower().intValue(),
                          range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
                  PerformancePoint rotated = new PerformancePoint(
                          size.getHeight(), size.getWidth(), range.getLower().intValue(),
                          range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
                  ret.add(given);
                  if (!given.covers(rotated)) {
                      ret.add(rotated);
                  }
              }
  
              // check if the component specified no performance point indication
              if (ret.size() == 0) {
                  return null;
              }
  
              // sort reversed by area first, then by frame rate
              ret.sort((a, b) ->
                       -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ?
                                 (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) :
                         (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ?
                                 (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) :
                         (a.getMaxFrameRate() != b.getMaxFrameRate()) ?
                                 (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
              return Collections.unmodifiableList(ret);
          }
  
          private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) {
              Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>();
              final String prefix = "measured-frame-rate-";
              Set<String> keys = map.keySet();
              for (String key : keys) {
                  // looking for: measured-frame-rate-WIDTHxHEIGHT-range
                  if (!key.startsWith(prefix)) {
                      continue;
                  }
                  String subKey = key.substring(prefix.length());
                  String[] temp = key.split("-");
                  if (temp.length != 5) {
                      continue;
                  }
                  String sizeStr = temp[3];
                  Size size = Utils.parseSize(sizeStr, null);
                  if (size == null || size.getWidth() * size.getHeight() <= 0) {
                      continue;
                  }
                  Range<Long> range = Utils.parseLongRange(map.get(key), null);
                  if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
                      continue;
                  }
                  ret.put(size, range);
              }
              return ret;
          }
  
          private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
              Pair<Size, Size> range = Utils.parseSizeRange(o);
              if (range != null) {
                  try {
                      return Pair.create(
                              Range.create(range.first.getWidth(), range.second.getWidth()),
                              Range.create(range.first.getHeight(), range.second.getHeight()));
                  } catch (IllegalArgumentException e) {
                      Log.w(TAG, "could not parse size range '" + o + "'");
                  }
              }
              return null;
          }
  
          /** @hide */
          public static int equivalentVP9Level(MediaFormat info) {
              final Map<String, Object> map = info.getMap();
  
              Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
              int BS = blockSize.getWidth() * blockSize.getHeight();
  
              Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
              int FS = counts == null ? 0 : BS * counts.getUpper();
  
              Range<Long> blockRates =
                  Utils.parseLongRange(map.get("blocks-per-second-range"), null);
              long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
  
              Pair<Range<Integer>, Range<Integer>> dimensionRanges =
                  parseWidthHeightRanges(map.get("size-range"));
              int D = dimensionRanges == null ? 0 : Math.max(
                      dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
  
              Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
              int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
  
              if (SR <=      829440 && FS <=    36864 && BR <=    200 && D <=   512)
                  return CodecProfileLevel.VP9Level1;
              if (SR <=     2764800 && FS <=    73728 && BR <=    800 && D <=   768)
                  return CodecProfileLevel.VP9Level11;
              if (SR <=     4608000 && FS <=   122880 && BR <=   1800 && D <=   960)
                  return CodecProfileLevel.VP9Level2;
              if (SR <=     9216000 && FS <=   245760 && BR <=   3600 && D <=  1344)
                  return CodecProfileLevel.VP9Level21;
              if (SR <=    20736000 && FS <=   552960 && BR <=   7200 && D <=  2048)
                  return CodecProfileLevel.VP9Level3;
              if (SR <=    36864000 && FS <=   983040 && BR <=  12000 && D <=  2752)
                  return CodecProfileLevel.VP9Level31;
              if (SR <=    83558400 && FS <=  2228224 && BR <=  18000 && D <=  4160)
                  return CodecProfileLevel.VP9Level4;
              if (SR <=   160432128 && FS <=  2228224 && BR <=  30000 && D <=  4160)
                  return CodecProfileLevel.VP9Level41;
              if (SR <=   311951360 && FS <=  8912896 && BR <=  60000 && D <=  8384)
                  return CodecProfileLevel.VP9Level5;
              if (SR <=   588251136 && FS <=  8912896 && BR <= 120000 && D <=  8384)
                  return CodecProfileLevel.VP9Level51;
              if (SR <=  1176502272 && FS <=  8912896 && BR <= 180000 && D <=  8384)
                  return CodecProfileLevel.VP9Level52;
              if (SR <=  1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
                  return CodecProfileLevel.VP9Level6;
              if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
                  return CodecProfileLevel.VP9Level61;
              if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
                  return CodecProfileLevel.VP9Level62;
              // returning largest level
              return CodecProfileLevel.VP9Level62;
          }
  
          private void parseFromInfo(MediaFormat info) {
              final Map<String, Object> map = info.getMap();
              Size blockSize = new Size(mBlockWidth, mBlockHeight);
              Size alignment = new Size(mWidthAlignment, mHeightAlignment);
              Range<Integer> counts = null, widths = null, heights = null;
              Range<Integer> frameRates = null, bitRates = null;
              Range<Long> blockRates = null;
              Range<Rational> ratios = null, blockRatios = null;
  
              blockSize = Utils.parseSize(map.get("block-size"), blockSize);
              alignment = Utils.parseSize(map.get("alignment"), alignment);
              counts = Utils.parseIntRange(map.get("block-count-range"), null);
              blockRates =
                  Utils.parseLongRange(map.get("blocks-per-second-range"), null);
              mMeasuredFrameRates = getMeasuredFrameRates(map);
              mPerformancePoints = getPerformancePoints(map);
              Pair<Range<Integer>, Range<Integer>> sizeRanges =
                  parseWidthHeightRanges(map.get("size-range"));
              if (sizeRanges != null) {
                  widths = sizeRanges.first;
                  heights = sizeRanges.second;
              }
              // for now this just means using the smaller max size as 2nd
              // upper limit.
              // for now we are keeping the profile specific "width/height
              // in macroblocks" limits.
              if (map.containsKey("feature-can-swap-width-height")) {
                  if (widths != null) {
                      mSmallerDimensionUpperLimit =
                          Math.min(widths.getUpper(), heights.getUpper());
                      widths = heights = widths.extend(heights);
                  } else {
                      Log.w(TAG, "feature can-swap-width-height is best used with size-range");
                      mSmallerDimensionUpperLimit =
                          Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
                      mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
                  }
              }
  
              ratios = Utils.parseRationalRange(
                      map.get("block-aspect-ratio-range"), null);
              blockRatios = Utils.parseRationalRange(
                      map.get("pixel-aspect-ratio-range"), null);
              frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
              if (frameRates != null) {
                  try {
                      frameRates = frameRates.intersect(FRAME_RATE_RANGE);
                  } catch (IllegalArgumentException e) {
                      Log.w(TAG, "frame rate range (" + frameRates
                              + ") is out of limits: " + FRAME_RATE_RANGE);
                      frameRates = null;
                  }
              }
              bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
              if (bitRates != null) {
                  try {
                      bitRates = bitRates.intersect(BITRATE_RANGE);
                  } catch (IllegalArgumentException e) {
                      Log.w(TAG,  "bitrate range (" + bitRates
                              + ") is out of limits: " + BITRATE_RANGE);
                      bitRates = null;
                  }
              }
  
              checkPowerOfTwo(
                      blockSize.getWidth(), "block-size width must be power of two");
              checkPowerOfTwo(
                      blockSize.getHeight(), "block-size height must be power of two");
  
              checkPowerOfTwo(
                      alignment.getWidth(), "alignment width must be power of two");
              checkPowerOfTwo(
                      alignment.getHeight(), "alignment height must be power of two");
  
              // update block-size and alignment
              applyMacroBlockLimits(
                      Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
                      Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
                      alignment.getWidth(), alignment.getHeight());
  
              if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
                  // codec supports profiles that we don't know.
                  // Use supplied values clipped to platform limits
                  if (widths != null) {
                      mWidthRange = 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) {
                      // only allow bitrate override if unsupported profiles were encountered
                      if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
                          mBitrateRange = BITRATE_RANGE.intersect(bitRates);
                      } else {
                          mBitrateRange = mBitrateRange.intersect(bitRates);
                      }
                  }
              } else {
                  // no unsupported profile/levels, so restrict values to known limits
                  if (widths != null) {
                      mWidthRange = mWidthRange.intersect(widths);
                  }
                  if (heights != null) {
                      mHeightRange = mHeightRange.intersect(heights);
                  }
                  if (counts != null) {
                      mBlockCountRange = mBlockCountRange.intersect(
                              Utils.factorRange(counts, mBlockWidth * mBlockHeight
                                      / blockSize.getWidth() / blockSize.getHeight()));
                  }
                  if (blockRates != null) {
                      mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
                              Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
                                      / blockSize.getWidth() / blockSize.getHeight()));
                  }
                  if (blockRatios != null) {
                      mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
                              Utils.scaleRange(blockRatios,
                                      mBlockHeight / blockSize.getHeight(),
                                      mBlockWidth / blockSize.getWidth()));
                  }
                  if (ratios != null) {
                      mAspectRatioRange = mAspectRatioRange.intersect(ratios);
                  }
                  if (frameRates != null) {
                      mFrameRateRange = mFrameRateRange.intersect(frameRates);
                  }
                  if (bitRates != null) {
                      mBitrateRange = mBitrateRange.intersect(bitRates);
                  }
              }
              updateLimits();
          }
  
          private void applyBlockLimits(
                  int blockWidth, int blockHeight,
                  Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
              checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
              checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
  
              final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
              final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
  
              // factor will always be a power-of-2
              int factor =
                  newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
              if (factor != 1) {
                  mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
                  mBlocksPerSecondRange = Utils.factorRange(
                          mBlocksPerSecondRange, factor);
                  mBlockAspectRatioRange = Utils.scaleRange(
                          mBlockAspectRatioRange,
                          newBlockHeight / mBlockHeight,
                          newBlockWidth / mBlockWidth);
                  mHorizontalBlockRange = Utils.factorRange(
                          mHorizontalBlockRange, newBlockWidth / mBlockWidth);
                  mVerticalBlockRange = Utils.factorRange(
                          mVerticalBlockRange, newBlockHeight / mBlockHeight);
              }
              factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
              if (factor != 1) {
                  counts = Utils.factorRange(counts, factor);
                  rates = Utils.factorRange(rates, factor);
                  ratios = Utils.scaleRange(
                          ratios, newBlockHeight / blockHeight,
                          newBlockWidth / blockWidth);
              }
              mBlockCountRange = mBlockCountRange.intersect(counts);
              mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
              mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
              mBlockWidth = newBlockWidth;
              mBlockHeight = newBlockHeight;
          }
  
          private void applyAlignment(int widthAlignment, int heightAlignment) {
              checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
              checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
  
              if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
                  // maintain assumption that 0 < alignment <= block-size
                  applyBlockLimits(
                          Math.max(widthAlignment, mBlockWidth),
                          Math.max(heightAlignment, mBlockHeight),
                          POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
              }
  
              mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
              mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
  
              mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
              mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
          }
  
          private void updateLimits() {
              // pixels -> blocks <- counts
              mHorizontalBlockRange = mHorizontalBlockRange.intersect(
                      Utils.factorRange(mWidthRange, mBlockWidth));
              mHorizontalBlockRange = mHorizontalBlockRange.intersect(
                      Range.create(
                              mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
                              mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
              mVerticalBlockRange = mVerticalBlockRange.intersect(
                      Utils.factorRange(mHeightRange, mBlockHeight));
              mVerticalBlockRange = mVerticalBlockRange.intersect(
                      Range.create(
                              mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
                              mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
              mBlockCountRange = mBlockCountRange.intersect(
                      Range.create(
                              mHorizontalBlockRange.getLower()
                                      * mVerticalBlockRange.getLower(),
                              mHorizontalBlockRange.getUpper()
                                      * mVerticalBlockRange.getUpper()));
              mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
                      new Rational(
                              mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
                      new Rational(
                              mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
  
              // blocks -> pixels
              mWidthRange = mWidthRange.intersect(
                      (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
                      mHorizontalBlockRange.getUpper() * mBlockWidth);
              mHeightRange = mHeightRange.intersect(
                      (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
                      mVerticalBlockRange.getUpper() * mBlockHeight);
              mAspectRatioRange = mAspectRatioRange.intersect(
                      new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
                      new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
  
              mSmallerDimensionUpperLimit = Math.min(
                      mSmallerDimensionUpperLimit,
                      Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
  
              // blocks -> rate
              mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
                      mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
                      mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
              mFrameRateRange = mFrameRateRange.intersect(
                      (int)(mBlocksPerSecondRange.getLower()
                              / mBlockCountRange.getUpper()),
                      (int)(mBlocksPerSecondRange.getUpper()
                              / (double)mBlockCountRange.getLower()));
          }
  
          private void applyMacroBlockLimits(
                  int maxHorizontalBlocks, int maxVerticalBlocks,
                  int maxBlocks, long maxBlocksPerSecond,
                  int blockWidth, int blockHeight,
                  int widthAlignment, int heightAlignment) {
              applyMacroBlockLimits(
                      1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
                      maxHorizontalBlocks, maxVerticalBlocks,
                      maxBlocks, maxBlocksPerSecond,
                      blockWidth, blockHeight, widthAlignment, heightAlignment);
          }
  
          private void applyMacroBlockLimits(
                  int minHorizontalBlocks, int minVerticalBlocks,
                  int maxHorizontalBlocks, int maxVerticalBlocks,
                  int maxBlocks, long maxBlocksPerSecond,
                  int blockWidth, int blockHeight,
                  int widthAlignment, int heightAlignment) {
              applyAlignment(widthAlignment, heightAlignment);
              applyBlockLimits(
                      blockWidth, blockHeight, Range.create(1, maxBlocks),
                      Range.create(1L, maxBlocksPerSecond),
                      Range.create(
                              new Rational(1, maxVerticalBlocks),
                              new Rational(maxHorizontalBlocks, 1)));
              mHorizontalBlockRange =
                      mHorizontalBlockRange.intersect(
                              Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
                              maxHorizontalBlocks / (mBlockWidth / blockWidth));
              mVerticalBlockRange =
                      mVerticalBlockRange.intersect(
                              Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
                              maxVerticalBlocks / (mBlockHeight / blockHeight));
          }
  
          private void applyLevelLimits() {
              long maxBlocksPerSecond = 0;
              int maxBlocks = 0;
              int maxBps = 0;
              int maxDPBBlocks = 0;
  
              int errors = ERROR_NONE_SUPPORTED;
              CodecProfileLevel[] profileLevels = mParent.profileLevels;
              String mime = mParent.getMimeType();
  
              if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
                  maxBlocks = 99;
                  maxBlocksPerSecond = 1485;
                  maxBps = 64000;
                  maxDPBBlocks = 396;
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      int MBPS = 0, FS = 0, BR = 0, DPB = 0;
                      boolean supported = true;
                      switch (profileLevel.level) {
                          case CodecProfileLevel.AVCLevel1:
                              MBPS =     1485; FS =     99; BR =     64; DPB =    396; break;
                          case CodecProfileLevel.AVCLevel1b:
                              MBPS =     1485; FS =     99; BR =    128; DPB =    396; break;
                          case CodecProfileLevel.AVCLevel11:
                              MBPS =     3000; FS =    396; BR =    192; DPB =    900; break;
                          case CodecProfileLevel.AVCLevel12:
                              MBPS =     6000; FS =    396; BR =    384; DPB =   2376; break;
                          case CodecProfileLevel.AVCLevel13:
                              MBPS =    11880; FS =    396; BR =    768; DPB =   2376; break;
                          case CodecProfileLevel.AVCLevel2:
                              MBPS =    11880; FS =    396; BR =   2000; DPB =   2376; break;
                          case CodecProfileLevel.AVCLevel21:
                              MBPS =    19800; FS =    792; BR =   4000; DPB =   4752; break;
                          case CodecProfileLevel.AVCLevel22:
                              MBPS =    20250; FS =   1620; BR =   4000; DPB =   8100; break;
                          case CodecProfileLevel.AVCLevel3:
                              MBPS =    40500; FS =   1620; BR =  10000; DPB =   8100; break;
                          case CodecProfileLevel.AVCLevel31:
                              MBPS =   108000; FS =   3600; BR =  14000; DPB =  18000; break;
                          case CodecProfileLevel.AVCLevel32:
                              MBPS =   216000; FS =   5120; BR =  20000; DPB =  20480; break;
                          case CodecProfileLevel.AVCLevel4:
                              MBPS =   245760; FS =   8192; BR =  20000; DPB =  32768; break;
                          case CodecProfileLevel.AVCLevel41:
                              MBPS =   245760; FS =   8192; BR =  50000; DPB =  32768; break;
                          case CodecProfileLevel.AVCLevel42:
                              MBPS =   522240; FS =   8704; BR =  50000; DPB =  34816; break;
                          case CodecProfileLevel.AVCLevel5:
                              MBPS =   589824; FS =  22080; BR = 135000; DPB = 110400; break;
                          case CodecProfileLevel.AVCLevel51:
                              MBPS =   983040; FS =  36864; BR = 240000; DPB = 184320; break;
                          case CodecProfileLevel.AVCLevel52:
                              MBPS =  2073600; FS =  36864; BR = 240000; DPB = 184320; break;
                          case CodecProfileLevel.AVCLevel6:
                              MBPS =  4177920; FS = 139264; BR = 240000; DPB = 696320; break;
                          case CodecProfileLevel.AVCLevel61:
                              MBPS =  8355840; FS = 139264; BR = 480000; DPB = 696320; break;
                          case CodecProfileLevel.AVCLevel62:
                              MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break;
                          default:
                              Log.w(TAG, "Unrecognized level "
                                      + profileLevel.level + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.AVCProfileConstrainedHigh:
                          case CodecProfileLevel.AVCProfileHigh:
                              BR *= 1250; break;
                          case CodecProfileLevel.AVCProfileHigh10:
                              BR *= 3000; break;
                          case CodecProfileLevel.AVCProfileExtended:
                          case CodecProfileLevel.AVCProfileHigh422:
                          case CodecProfileLevel.AVCProfileHigh444:
                              Log.w(TAG, "Unsupported profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNSUPPORTED;
                              supported = false;
                              // fall through - treat as base profile
                          case CodecProfileLevel.AVCProfileConstrainedBaseline:
                          case CodecProfileLevel.AVCProfileBaseline:
                          case CodecProfileLevel.AVCProfileMain:
                              BR *= 1000; break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                              BR *= 1000;
                      }
                      if (supported) {
                          errors &= ~ERROR_NONE_SUPPORTED;
                      }
                      maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
                      maxBlocks = Math.max(FS, maxBlocks);
                      maxBps = Math.max(BR, maxBps);
                      maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
                  }
  
                  int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
                  applyMacroBlockLimits(
                          maxLengthInBlocks, maxLengthInBlocks,
                          maxBlocks, maxBlocksPerSecond,
                          16 /* blockWidth */, 16 /* blockHeight */,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
                  int maxWidth = 11, maxHeight = 9, maxRate = 15;
                  maxBlocks = 99;
                  maxBlocksPerSecond = 1485;
                  maxBps = 64000;
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
                      boolean supported = true;
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.MPEG2ProfileSimple:
                              switch (profileLevel.level) {
                                  case CodecProfileLevel.MPEG2LevelML:
                                      FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR =  15000; break;
                                  default:
                                      Log.w(TAG, "Unrecognized profile/level "
                                              + profileLevel.profile + "/"
                                              + profileLevel.level + " for " + mime);
                                      errors |= ERROR_UNRECOGNIZED;
                              }
                              break;
                          case CodecProfileLevel.MPEG2ProfileMain:
                              switch (profileLevel.level) {
                                  case CodecProfileLevel.MPEG2LevelLL:
                                      FR = 30; W = 22; H =  18; MBPS =  11880; FS =   396; BR =  4000; break;
                                  case CodecProfileLevel.MPEG2LevelML:
                                      FR = 30; W = 45; H =  36; MBPS =  40500; FS =  1620; BR = 15000; break;
                                  case CodecProfileLevel.MPEG2LevelH14:
                                      FR = 60; W = 90; H =  68; MBPS = 183600; FS =  6120; BR = 60000; break;
                                  case CodecProfileLevel.MPEG2LevelHL:
                                      FR = 60; W = 120; H = 68; MBPS = 244800; FS =  8160; BR = 80000; break;
                                  case CodecProfileLevel.MPEG2LevelHP:
                                      FR = 60; W = 120; H = 68; MBPS = 489600; FS =  8160; BR = 80000; break;
                                  default:
                                      Log.w(TAG, "Unrecognized profile/level "
                                              + profileLevel.profile + "/"
                                              + profileLevel.level + " for " + mime);
                                      errors |= ERROR_UNRECOGNIZED;
                              }
                              break;
                          case CodecProfileLevel.MPEG2Profile422:
                          case CodecProfileLevel.MPEG2ProfileSNR:
                          case CodecProfileLevel.MPEG2ProfileSpatial:
                          case CodecProfileLevel.MPEG2ProfileHigh:
                              Log.i(TAG, "Unsupported profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNSUPPORTED;
                              supported = false;
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      if (supported) {
                          errors &= ~ERROR_NONE_SUPPORTED;
                      }
                      maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
                      maxBlocks = Math.max(FS, maxBlocks);
                      maxBps = Math.max(BR * 1000, maxBps);
                      maxWidth = Math.max(W, maxWidth);
                      maxHeight = Math.max(H, maxHeight);
                      maxRate = Math.max(FR, maxRate);
                  }
                  applyMacroBlockLimits(maxWidth, maxHeight,
                          maxBlocks, maxBlocksPerSecond,
                          16 /* blockWidth */, 16 /* blockHeight */,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
                  mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
                  int maxWidth = 11, maxHeight = 9, maxRate = 15;
                  maxBlocks = 99;
                  maxBlocksPerSecond = 1485;
                  maxBps = 64000;
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
                      boolean strict = false; // true: W, H and FR are individual max limits
                      boolean supported = true;
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.MPEG4ProfileSimple:
                              switch (profileLevel.level) {
                                  case CodecProfileLevel.MPEG4Level0:
                                      strict = true;
                                      FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
                                  case CodecProfileLevel.MPEG4Level1:
                                      FR = 30; W = 11; H =  9; MBPS =  1485; FS =  99; BR =  64; break;
                                  case CodecProfileLevel.MPEG4Level0b:
                                      strict = true;
                                      FR = 15; W = 11; H =  9; MBPS =  1485; FS =  99; BR = 128; break;
                                  case CodecProfileLevel.MPEG4Level2:
                                      FR = 30; W = 22; H = 18; MBPS =  5940; FS = 396; BR = 128; break;
                                  case CodecProfileLevel.MPEG4Level3:
                                      FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
                                  case CodecProfileLevel.MPEG4Level4a:
                                      FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
                                  case CodecProfileLevel.MPEG4Level5:
                                      FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
                                  case CodecProfileLevel.MPEG4Level6:
                                      FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
                                  default:
                                      Log.w(TAG, "Unrecognized profile/level "
                                              + profileLevel.profile + "/"
                                              + profileLevel.level + " for " + mime);
                                      errors |= ERROR_UNRECOGNIZED;
                              }
                              break;
                          case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
                              switch (profileLevel.level) {
                                  case CodecProfileLevel.MPEG4Level0:
                                  case CodecProfileLevel.MPEG4Level1:
                                      FR = 30; W = 11; H =  9; MBPS =  2970; FS =   99; BR =  128; break;
                                  case CodecProfileLevel.MPEG4Level2:
                                      FR = 30; W = 22; H = 18; MBPS =  5940; FS =  396; BR =  384; break;
                                  case CodecProfileLevel.MPEG4Level3:
                                      FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR =  768; break;
                                  case CodecProfileLevel.MPEG4Level3b:
                                      FR = 30; W = 22; H = 18; MBPS = 11880; FS =  396; BR = 1500; break;
                                  case CodecProfileLevel.MPEG4Level4:
                                      FR = 30; W = 44; H = 36; MBPS = 23760; FS =  792; BR = 3000; break;
                                  case CodecProfileLevel.MPEG4Level5:
                                      FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
                                  default:
                                      Log.w(TAG, "Unrecognized profile/level "
                                              + profileLevel.profile + "/"
                                              + profileLevel.level + " for " + mime);
                                      errors |= ERROR_UNRECOGNIZED;
                              }
                              break;
                          case CodecProfileLevel.MPEG4ProfileMain:             // 2-4
                          case CodecProfileLevel.MPEG4ProfileNbit:             // 2
                          case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
                          case CodecProfileLevel.MPEG4ProfileCoreScalable:     // 1-3
                          case CodecProfileLevel.MPEG4ProfileAdvancedCoding:   // 1-4
                          case CodecProfileLevel.MPEG4ProfileCore:             // 1-2
                          case CodecProfileLevel.MPEG4ProfileAdvancedCore:     // 1-4
                          case CodecProfileLevel.MPEG4ProfileSimpleScalable:   // 0-2
                          case CodecProfileLevel.MPEG4ProfileHybrid:           // 1-2
  
                          // Studio profiles are not supported by our codecs.
  
                          // Only profiles that can decode simple object types are considered.
                          // The following profiles are not able to.
                          case CodecProfileLevel.MPEG4ProfileBasicAnimated:    // 1-2
                          case CodecProfileLevel.MPEG4ProfileScalableTexture:  // 1
                          case CodecProfileLevel.MPEG4ProfileSimpleFace:       // 1-2
                          case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
                          case CodecProfileLevel.MPEG4ProfileSimpleFBA:        // 1-2
                              Log.i(TAG, "Unsupported profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNSUPPORTED;
                              supported = false;
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      if (supported) {
                          errors &= ~ERROR_NONE_SUPPORTED;
                      }
                      maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
                      maxBlocks = Math.max(FS, maxBlocks);
                      maxBps = Math.max(BR * 1000, maxBps);
                      if (strict) {
                          maxWidth = Math.max(W, maxWidth);
                          maxHeight = Math.max(H, maxHeight);
                          maxRate = Math.max(FR, maxRate);
                      } else {
                          // assuming max 60 fps frame rate and 1:2 aspect ratio
                          int maxDim = (int)Math.sqrt(FS * 2);
                          maxWidth = Math.max(maxDim, maxWidth);
                          maxHeight = Math.max(maxDim, maxHeight);
                          maxRate = Math.max(Math.max(FR, 60), maxRate);
                      }
                  }
                  applyMacroBlockLimits(maxWidth, maxHeight,
                          maxBlocks, maxBlocksPerSecond,
                          16 /* blockWidth */, 16 /* blockHeight */,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
                  mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
                  int maxWidth = 11, maxHeight = 9, maxRate = 15;
                  int minWidth = maxWidth, minHeight = maxHeight;
                  int minAlignment = 16;
                  maxBlocks = 99;
                  maxBlocksPerSecond = 1485;
                  maxBps = 64000;
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
                      boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
                      switch (profileLevel.level) {
                          case CodecProfileLevel.H263Level10:
                              strict = true; // only supports sQCIF & QCIF
                              FR = 15; W = 11; H =  9; BR =   1; MBPS =  W * H * FR; break;
                          case CodecProfileLevel.H263Level20:
                              strict = true; // only supports sQCIF, QCIF & CIF
                              FR = 30; W = 22; H = 18; BR =   2; MBPS =  W * H * 15; break;
                          case CodecProfileLevel.H263Level30:
                              strict = true; // only supports sQCIF, QCIF & CIF
                              FR = 30; W = 22; H = 18; BR =   6; MBPS =  W * H * FR; break;
                          case CodecProfileLevel.H263Level40:
                              strict = true; // only supports sQCIF, QCIF & CIF
                              FR = 30; W = 22; H = 18; BR =  32; MBPS =  W * H * FR; break;
                          case CodecProfileLevel.H263Level45:
                              // only implies level 10 support
                              strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
                                      || profileLevel.profile ==
                                              CodecProfileLevel.H263ProfileBackwardCompatible;
                              if (!strict) {
                                  minW = 1; minH = 1; minAlignment = 4;
                              }
                              FR = 15; W = 11; H =  9; BR =   2; MBPS =  W * H * FR; break;
                          case CodecProfileLevel.H263Level50:
                              // only supports 50fps for H > 15
                              minW = 1; minH = 1; minAlignment = 4;
                              FR = 60; W = 22; H = 18; BR =  64; MBPS =  W * H * 50; break;
                          case CodecProfileLevel.H263Level60:
                              // only supports 50fps for H > 15
                              minW = 1; minH = 1; minAlignment = 4;
                              FR = 60; W = 45; H = 18; BR = 128; MBPS =  W * H * 50; break;
                          case CodecProfileLevel.H263Level70:
                              // only supports 50fps for H > 30
                              minW = 1; minH = 1; minAlignment = 4;
                              FR = 60; W = 45; H = 36; BR = 256; MBPS =  W * H * 50; break;
                          default:
                              Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
                                      + "/" + profileLevel.level + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.H263ProfileBackwardCompatible:
                          case CodecProfileLevel.H263ProfileBaseline:
                          case CodecProfileLevel.H263ProfileH320Coding:
                          case CodecProfileLevel.H263ProfileHighCompression:
                          case CodecProfileLevel.H263ProfileHighLatency:
                          case CodecProfileLevel.H263ProfileInterlace:
                          case CodecProfileLevel.H263ProfileInternet:
                          case CodecProfileLevel.H263ProfileISWV2:
                          case CodecProfileLevel.H263ProfileISWV3:
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      if (strict) {
                          // Strict levels define sub-QCIF min size and enumerated sizes. We cannot
                          // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities
                          // but we can express "only QCIF (& CIF)", so set minimume size at QCIF.
                          // minW = 8; minH = 6;
                          minW = 11; minH = 9;
                      } else {
                          // any support for non-strict levels (including unrecognized profiles or
                          // levels) allow custom frame size support beyond supported limits
                          // (other than bitrate)
                          mAllowMbOverride = true;
                      }
                      errors &= ~ERROR_NONE_SUPPORTED;
                      maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
                      maxBlocks = Math.max(W * H, maxBlocks);
                      maxBps = Math.max(BR * 64000, maxBps);
                      maxWidth = Math.max(W, maxWidth);
                      maxHeight = Math.max(H, maxHeight);
                      maxRate = Math.max(FR, maxRate);
                      minWidth = Math.min(minW, minWidth);
                      minHeight = Math.min(minH, minHeight);
                  }
                  // unless we encountered custom frame size support, limit size to QCIF and CIF
                  // using aspect ratio.
                  if (!mAllowMbOverride) {
                      mBlockAspectRatioRange =
                          Range.create(new Rational(11, 9), new Rational(11, 9));
                  }
                  applyMacroBlockLimits(
                          minWidth, minHeight,
                          maxWidth, maxHeight,
                          maxBlocks, maxBlocksPerSecond,
                          16 /* blockWidth */, 16 /* blockHeight */,
                          minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
                  mFrameRateRange = Range.create(1, maxRate);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
                  maxBlocks = Integer.MAX_VALUE;
                  maxBlocksPerSecond = Integer.MAX_VALUE;
  
                  // TODO: set to 100Mbps for now, need a number for VP8
                  maxBps = 100000000;
  
                  // profile levels are not indicative for VPx, but verify
                  // them nonetheless
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      switch (profileLevel.level) {
                          case CodecProfileLevel.VP8Level_Version0:
                          case CodecProfileLevel.VP8Level_Version1:
                          case CodecProfileLevel.VP8Level_Version2:
                          case CodecProfileLevel.VP8Level_Version3:
                              break;
                          default:
                              Log.w(TAG, "Unrecognized level "
                                      + profileLevel.level + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.VP8ProfileMain:
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      errors &= ~ERROR_NONE_SUPPORTED;
                  }
  
                  final int blockSize = 16;
                  applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
                          maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
                  maxBlocksPerSecond = 829440;
                  maxBlocks = 36864;
                  maxBps = 200000;
                  int maxDim = 512;
  
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      long SR = 0; // luma sample rate
                      int FS = 0;  // luma picture size
                      int BR = 0;  // bit rate kbps
                      int D = 0;   // luma dimension
                      switch (profileLevel.level) {
                          case CodecProfileLevel.VP9Level1:
                              SR =      829440; FS =    36864; BR =    200; D =   512; break;
                          case CodecProfileLevel.VP9Level11:
                              SR =     2764800; FS =    73728; BR =    800; D =   768; break;
                          case CodecProfileLevel.VP9Level2:
                              SR =     4608000; FS =   122880; BR =   1800; D =   960; break;
                          case CodecProfileLevel.VP9Level21:
                              SR =     9216000; FS =   245760; BR =   3600; D =  1344; break;
                          case CodecProfileLevel.VP9Level3:
                              SR =    20736000; FS =   552960; BR =   7200; D =  2048; break;
                          case CodecProfileLevel.VP9Level31:
                              SR =    36864000; FS =   983040; BR =  12000; D =  2752; break;
                          case CodecProfileLevel.VP9Level4:
                              SR =    83558400; FS =  2228224; BR =  18000; D =  4160; break;
                          case CodecProfileLevel.VP9Level41:
                              SR =   160432128; FS =  2228224; BR =  30000; D =  4160; break;
                          case CodecProfileLevel.VP9Level5:
                              SR =   311951360; FS =  8912896; BR =  60000; D =  8384; break;
                          case CodecProfileLevel.VP9Level51:
                              SR =   588251136; FS =  8912896; BR = 120000; D =  8384; break;
                          case CodecProfileLevel.VP9Level52:
                              SR =  1176502272; FS =  8912896; BR = 180000; D =  8384; break;
                          case CodecProfileLevel.VP9Level6:
                              SR =  1176502272; FS = 35651584; BR = 180000; D = 16832; break;
                          case CodecProfileLevel.VP9Level61:
                              SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
                          case CodecProfileLevel.VP9Level62:
                              SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
                          default:
                              Log.w(TAG, "Unrecognized level "
                                      + profileLevel.level + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.VP9Profile0:
                          case CodecProfileLevel.VP9Profile1:
                          case CodecProfileLevel.VP9Profile2:
                          case CodecProfileLevel.VP9Profile3:
                          case CodecProfileLevel.VP9Profile2HDR:
                          case CodecProfileLevel.VP9Profile3HDR:
                          case CodecProfileLevel.VP9Profile2HDR10Plus:
                          case CodecProfileLevel.VP9Profile3HDR10Plus:
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      errors &= ~ERROR_NONE_SUPPORTED;
                      maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
                      maxBlocks = Math.max(FS, maxBlocks);
                      maxBps = Math.max(BR * 1000, maxBps);
                      maxDim = Math.max(D, maxDim);
                  }
  
                  final int blockSize = 8;
                  int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
                  maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
                  maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
  
                  applyMacroBlockLimits(
                          maxLengthInBlocks, maxLengthInBlocks,
                          maxBlocks, maxBlocksPerSecond,
                          blockSize, blockSize,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
                  // CTBs are at least 8x8 so use 8x8 block size
                  maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks
                  maxBlocksPerSecond = maxBlocks * 15;
                  maxBps = 128000;
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      double FR = 0;
                      int FS = 0;
                      int BR = 0;
                      switch (profileLevel.level) {
                          /* The HEVC spec talks only in a very convoluted manner about the
                             existence of levels 1-3.1 for High tier, which could also be
                             understood as 'decoders and encoders should treat these levels
                             as if they were Main tier', so we do that. */
                          case CodecProfileLevel.HEVCMainTierLevel1:
                          case CodecProfileLevel.HEVCHighTierLevel1:
                              FR =    15; FS =    36864; BR =    128; break;
                          case CodecProfileLevel.HEVCMainTierLevel2:
                          case CodecProfileLevel.HEVCHighTierLevel2:
                              FR =    30; FS =   122880; BR =   1500; break;
                          case CodecProfileLevel.HEVCMainTierLevel21:
                          case CodecProfileLevel.HEVCHighTierLevel21:
                              FR =    30; FS =   245760; BR =   3000; break;
                          case CodecProfileLevel.HEVCMainTierLevel3:
                          case CodecProfileLevel.HEVCHighTierLevel3:
                              FR =    30; FS =   552960; BR =   6000; break;
                          case CodecProfileLevel.HEVCMainTierLevel31:
                          case CodecProfileLevel.HEVCHighTierLevel31:
                              FR = 33.75; FS =   983040; BR =  10000; break;
                          case CodecProfileLevel.HEVCMainTierLevel4:
                              FR =    30; FS =  2228224; BR =  12000; break;
                          case CodecProfileLevel.HEVCHighTierLevel4:
                              FR =    30; FS =  2228224; BR =  30000; break;
                          case CodecProfileLevel.HEVCMainTierLevel41:
                              FR =    60; FS =  2228224; BR =  20000; break;
                          case CodecProfileLevel.HEVCHighTierLevel41:
                              FR =    60; FS =  2228224; BR =  50000; break;
                          case CodecProfileLevel.HEVCMainTierLevel5:
                              FR =    30; FS =  8912896; BR =  25000; break;
                          case CodecProfileLevel.HEVCHighTierLevel5:
                              FR =    30; FS =  8912896; BR = 100000; break;
                          case CodecProfileLevel.HEVCMainTierLevel51:
                              FR =    60; FS =  8912896; BR =  40000; break;
                          case CodecProfileLevel.HEVCHighTierLevel51:
                              FR =    60; FS =  8912896; BR = 160000; break;
                          case CodecProfileLevel.HEVCMainTierLevel52:
                              FR =   120; FS =  8912896; BR =  60000; break;
                          case CodecProfileLevel.HEVCHighTierLevel52:
                              FR =   120; FS =  8912896; BR = 240000; break;
                          case CodecProfileLevel.HEVCMainTierLevel6:
                              FR =    30; FS = 35651584; BR =  60000; break;
                          case CodecProfileLevel.HEVCHighTierLevel6:
                              FR =    30; FS = 35651584; BR = 240000; break;
                          case CodecProfileLevel.HEVCMainTierLevel61:
                              FR =    60; FS = 35651584; BR = 120000; break;
                          case CodecProfileLevel.HEVCHighTierLevel61:
                              FR =    60; FS = 35651584; BR = 480000; break;
                          case CodecProfileLevel.HEVCMainTierLevel62:
                              FR =   120; FS = 35651584; BR = 240000; break;
                          case CodecProfileLevel.HEVCHighTierLevel62:
                              FR =   120; FS = 35651584; BR = 800000; break;
                          default:
                              Log.w(TAG, "Unrecognized level "
                                      + profileLevel.level + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.HEVCProfileMain:
                          case CodecProfileLevel.HEVCProfileMain10:
                          case CodecProfileLevel.HEVCProfileMainStill:
                          case CodecProfileLevel.HEVCProfileMain10HDR10:
                          case CodecProfileLevel.HEVCProfileMain10HDR10Plus:
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
  
                      /* DPB logic:
                      if      (width * height <= FS / 4)    DPB = 16;
                      else if (width * height <= FS / 2)    DPB = 12;
                      else if (width * height <= FS * 0.75) DPB = 8;
                      else                                  DPB = 6;
                      */
  
                      FS >>= 6; // convert pixels to blocks
                      errors &= ~ERROR_NONE_SUPPORTED;
                      maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
                      maxBlocks = Math.max(FS, maxBlocks);
                      maxBps = Math.max(BR * 1000, maxBps);
                  }
  
                  int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
                  applyMacroBlockLimits(
                          maxLengthInBlocks, maxLengthInBlocks,
                          maxBlocks, maxBlocksPerSecond,
                          8 /* blockWidth */, 8 /* blockHeight */,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) {
                  maxBlocksPerSecond = 829440;
                  maxBlocks = 36864;
                  maxBps = 200000;
                  int maxDim = 512;
  
                  // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec,
                  // corresponding to the definitions in
                  // "AV1 Bitstream & Decoding Process Specification", Annex A
                  // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
                  for (CodecProfileLevel profileLevel: profileLevels) {
                      long SR = 0; // luma sample rate
                      int FS = 0;  // luma picture size
                      int BR = 0;  // bit rate kbps
                      int D = 0;   // luma D
                      switch (profileLevel.level) {
                          case CodecProfileLevel.AV1Level2:
                              SR =     5529600; FS =   147456; BR =   1500; D =  2048; break;
                          case CodecProfileLevel.AV1Level21:
                          case CodecProfileLevel.AV1Level22:
                          case CodecProfileLevel.AV1Level23:
                              SR =    10454400; FS =   278784; BR =   3000; D =  2816; break;
  
                          case CodecProfileLevel.AV1Level3:
                              SR =    24969600; FS =   665856; BR =   6000; D =  4352; break;
                          case CodecProfileLevel.AV1Level31:
                          case CodecProfileLevel.AV1Level32:
                          case CodecProfileLevel.AV1Level33:
                              SR =    39938400; FS =  1065024; BR =  10000; D =  5504; break;
  
                          case CodecProfileLevel.AV1Level4:
                              SR =    77856768; FS =  2359296; BR =  12000; D =  6144; break;
                          case CodecProfileLevel.AV1Level41:
                          case CodecProfileLevel.AV1Level42:
                          case CodecProfileLevel.AV1Level43:
                              SR =   155713536; FS =  2359296; BR =  20000; D =  6144; break;
  
                          case CodecProfileLevel.AV1Level5:
                              SR =   273715200; FS =  8912896; BR =  30000; D =  8192; break;
                          case CodecProfileLevel.AV1Level51:
                              SR =   547430400; FS =  8912896; BR =  40000; D =  8192; break;
                          case CodecProfileLevel.AV1Level52:
                              SR =  1094860800; FS =  8912896; BR =  60000; D =  8192; break;
                          case CodecProfileLevel.AV1Level53:
                              SR =  1176502272; FS =  8912896; BR =  60000; D =  8192; break;
  
                          case CodecProfileLevel.AV1Level6:
                              SR =  1176502272; FS = 35651584; BR =  60000; D = 16384; break;
                          case CodecProfileLevel.AV1Level61:
                              SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break;
                          case CodecProfileLevel.AV1Level62:
                              SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break;
                          case CodecProfileLevel.AV1Level63:
                              SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break;
  
                          default:
                              Log.w(TAG, "Unrecognized level "
                                      + profileLevel.level + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      switch (profileLevel.profile) {
                          case CodecProfileLevel.AV1ProfileMain8:
                          case CodecProfileLevel.AV1ProfileMain10:
                          case CodecProfileLevel.AV1ProfileMain10HDR10:
                          case CodecProfileLevel.AV1ProfileMain10HDR10Plus:
                              break;
                          default:
                              Log.w(TAG, "Unrecognized profile "
                                      + profileLevel.profile + " for " + mime);
                              errors |= ERROR_UNRECOGNIZED;
                      }
                      errors &= ~ERROR_NONE_SUPPORTED;
                      maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond);
                      maxBlocks = Math.max(FS, maxBlocks);
                      maxBps = Math.max(BR * 1000, maxBps);
                      maxDim = Math.max(D, maxDim);
                  }
  
                  final int blockSize = 8;
                  int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
                  maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
                  maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
                  applyMacroBlockLimits(
                          maxLengthInBlocks, maxLengthInBlocks,
                          maxBlocks, maxBlocksPerSecond,
                          blockSize, blockSize,
                          1 /* widthAlignment */, 1 /* heightAlignment */);
              } else {
                  Log.w(TAG, "Unsupported mime " + mime);
                  // using minimal bitrate here.  should be overriden by
                  // info from media_codecs.xml
                  maxBps = 64000;
                  errors |= ERROR_UNSUPPORTED;
              }
              mBitrateRange = Range.create(1, maxBps);
              mParent.mError |= errors;
          }
      }
  
      /**
       * A class that supports querying the encoding capabilities of a codec.
       */
      public static final class EncoderCapabilities {
          /**
           * Returns the supported range of quality values.
           *
           * Quality is implementation-specific. As a general rule, a higher quality
           * setting results in a better image quality and a lower compression ratio.
           */
          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;
          }
  
          private void init(MediaFormat info, CodecCapabilities parent) {
              // no support for complexity or quality yet
              mParent = parent;
              mComplexityRange = Range.create(0, 0);
              mQualityRange = Range.create(0, 0);
              mBitControl = (1 << BITRATE_MODE_VBR);
  
              applyLevelLimits();
              parseFromInfo(info);
          }
  
          private void applyLevelLimits() {
              String mime = mParent.getMimeType();
              if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
                  mComplexityRange = Range.create(0, 8);
                  mBitControl = (1 << BITRATE_MODE_CQ);
              } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
                      || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
                      || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
                      || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
                      || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
                  mBitControl = (1 << BITRATE_MODE_CBR);
              }
          }
  
          private int mBitControl;
          private Integer mDefaultComplexity;
          private Integer mDefaultQuality;
          private String mQualityScale;
  
          private void parseFromInfo(MediaFormat info) {
              Map<String, Object> map = info.getMap();
  
              if (info.containsKey("complexity-range")) {
                  mComplexityRange = Utils
                          .parseIntRange(info.getString("complexity-range"), mComplexityRange);
                  // TODO should we limit this to level limits?
              }
              if (info.containsKey("quality-range")) {
                  mQualityRange = Utils
                          .parseIntRange(info.getString("quality-range"), mQualityRange);
              }
              if (info.containsKey("feature-bitrate-modes")) {
                  for (String mode: info.getString("feature-bitrate-modes").split(",")) {
                      mBitControl |= (1 << parseBitrateMode(mode));
                  }
              }
  
              try {
                  mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
              } catch (NumberFormatException e) { }
  
              try {
                  mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
              } catch (NumberFormatException e) { }
  
              mQualityScale = (String)map.get("quality-scale");
          }
  
          private boolean supports(
                  Integer complexity, Integer quality, Integer profile) {
              boolean ok = true;
              if (ok && complexity != null) {
                  ok = mComplexityRange.contains(complexity);
              }
              if (ok && quality != null) {
                  ok = mQualityRange.contains(quality);
              }
              if (ok && profile != null) {
                  for (CodecProfileLevel pl: mParent.profileLevels) {
                      if (pl.profile == profile) {
                          profile = null;
                          break;
                      }
                  }
                  ok = profile == null;
              }
              return ok;
          }
  
          /** @hide */
          public void getDefaultFormat(MediaFormat format) {
              // don't list trivial quality/complexity as default for now
              if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
                      && mDefaultQuality != null) {
                  format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
              }
              if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
                      && mDefaultComplexity != null) {
                  format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
              }
              // bitrates are listed in order of preference
              for (Feature feat: bitrates) {
                  if ((mBitControl & (1 << feat.mValue)) != 0) {
                      format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
                      break;
                  }
              }
          }
  
          /** @hide */
          public boolean supportsFormat(MediaFormat format) {
              final Map<String, Object> map = format.getMap();
              final String mime = mParent.getMimeType();
  
              Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
              if (mode != null && !isBitrateModeSupported(mode)) {
                  return false;
              }
  
              Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
              if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
                  Integer flacComplexity =
                      (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
                  if (complexity == null) {
                      complexity = flacComplexity;
                  } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
                      throw new IllegalArgumentException(
                              "conflicting values for complexity and " +
                              "flac-compression-level");
                  }
              }
  
              // other audio parameters
              Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
              if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
                  Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
                  if (profile == null) {
                      profile = aacProfile;
                  } else if (aacProfile != null && !aacProfile.equals(profile)) {
                      throw new IllegalArgumentException(
                              "conflicting values for profile and aac-profile");
                  }
              }
  
              Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
  
              return supports(complexity, quality, profile);
          }
      };
  
      /**
       * 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 {
          // These constants were originally in-line with OMX values, but this
          // correspondence is no longer maintained.
  
          // Profiles and levels for AVC Codec, corresponding to the definitions in
          // "SERIES H: AUDIOVISUAL AND MULTIMEDIA SYSTEMS,
          // Infrastructure of audiovisual services – Coding of moving video
          // Advanced video coding for generic audiovisual services"
          // found at
          // https://www.itu.int/rec/T-REC-H.264-201704-I
  
          /**
           * AVC Baseline profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileBaseline = 0x01;
  
          /**
           * AVC Main profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileMain     = 0x02;
  
          /**
           * AVC Extended profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileExtended = 0x04;
  
          /**
           * AVC High profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileHigh     = 0x08;
  
          /**
           * AVC High 10 profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileHigh10   = 0x10;
  
          /**
           * AVC High 4:2:2 profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileHigh422  = 0x20;
  
          /**
           * AVC High 4:4:4 profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileHigh444  = 0x40;
  
          /**
           * AVC Constrained Baseline profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileConstrainedBaseline = 0x10000;
  
          /**
           * AVC Constrained High profile.
           * See definition in
           * <a href="https://www.itu.int/rec/T-REC-H.264-201704-I">H.264 recommendation</a>,
           * Annex A.
           */
          public static final int AVCProfileConstrainedHigh     = 0x80000;
  
          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;
          public static final int AVCLevel6       = 0x20000;
          public static final int AVCLevel61      = 0x40000;
          public static final int AVCLevel62      = 0x80000;
  
          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;
  
          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;
  
          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;
  
          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 MPEG4Level3b     = 0x18;
          public static final int MPEG4Level4      = 0x20;
          public static final int MPEG4Level4a     = 0x40;
          public static final int MPEG4Level5      = 0x80;
          public static final int MPEG4Level6      = 0x100;
  
          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;
  
          public static final int MPEG2LevelLL     = 0x00;
          public static final int MPEG2LevelML     = 0x01;
          public static final int MPEG2LevelH14    = 0x02;
          public static final int MPEG2LevelHL     = 0x03;
          public static final int MPEG2LevelHP     = 0x04;
  
          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 AACObjectERScalable = 20;
          public static final int AACObjectLD         = 23;
          public static final int AACObjectHE_PS      = 29;
          public static final int AACObjectELD        = 39;
          /** xHE-AAC (includes USAC) */
          public static final int AACObjectXHE        = 42;
  
          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;
  
          public static final int VP8ProfileMain = 0x01;
  
          /** VP9 Profile 0 4:2:0 8-bit */
          public static final int VP9Profile0 = 0x01;
  
          /** VP9 Profile 1 4:2:2 8-bit */
          public static final int VP9Profile1 = 0x02;
  
          /** VP9 Profile 2 4:2:0 10-bit */
          public static final int VP9Profile2 = 0x04;
  
          /** VP9 Profile 3 4:2:2 10-bit */
          public static final int VP9Profile3 = 0x08;
  
          // HDR profiles also support passing HDR metadata
          /** VP9 Profile 2 4:2:0 10-bit HDR */
          public static final int VP9Profile2HDR = 0x1000;
  
          /** VP9 Profile 3 4:2:2 10-bit HDR */
          public static final int VP9Profile3HDR = 0x2000;
  
          /** VP9 Profile 2 4:2:0 10-bit HDR10Plus */
          public static final int VP9Profile2HDR10Plus = 0x4000;
  
          /** VP9 Profile 3 4:2:2 10-bit HDR10Plus */
          public static final int VP9Profile3HDR10Plus = 0x8000;
  
          public static final int VP9Level1  = 0x1;
          public static final int VP9Level11 = 0x2;
          public static final int VP9Level2  = 0x4;
          public static final int VP9Level21 = 0x8;
          public static final int VP9Level3  = 0x10;
          public static final int VP9Level31 = 0x20;
          public static final int VP9Level4  = 0x40;
          public static final int VP9Level41 = 0x80;
          public static final int VP9Level5  = 0x100;
          public static final int VP9Level51 = 0x200;
          public static final int VP9Level52 = 0x400;
          public static final int VP9Level6  = 0x800;
          public static final int VP9Level61 = 0x1000;
          public static final int VP9Level62 = 0x2000;
  
          public static final int HEVCProfileMain        = 0x01;
          public static final int HEVCProfileMain10      = 0x02;
          public static final int HEVCProfileMainStill   = 0x04;
          public static final int HEVCProfileMain10HDR10 = 0x1000;
          public static final int HEVCProfileMain10HDR10Plus = 0x2000;
  
          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;
  
          private static final int HEVCHighTierLevels =
              HEVCHighTierLevel1 | HEVCHighTierLevel2 | HEVCHighTierLevel21 | HEVCHighTierLevel3 |
              HEVCHighTierLevel31 | HEVCHighTierLevel4 | HEVCHighTierLevel41 | HEVCHighTierLevel5 |
              HEVCHighTierLevel51 | HEVCHighTierLevel52 | HEVCHighTierLevel6 | HEVCHighTierLevel61 |
              HEVCHighTierLevel62;
  
          public static final int DolbyVisionProfileDvavPer = 0x1;
          public static final int DolbyVisionProfileDvavPen = 0x2;
          public static final int DolbyVisionProfileDvheDer = 0x4;
          public static final int DolbyVisionProfileDvheDen = 0x8;
          public static final int DolbyVisionProfileDvheDtr = 0x10;
          public static final int DolbyVisionProfileDvheStn = 0x20;
          public static final int DolbyVisionProfileDvheDth = 0x40;
          public static final int DolbyVisionProfileDvheDtb = 0x80;
          public static final int DolbyVisionProfileDvheSt = 0x100;
          public static final int DolbyVisionProfileDvavSe = 0x200;
  
          public static final int DolbyVisionLevelHd24    = 0x1;
          public static final int DolbyVisionLevelHd30    = 0x2;
          public static final int DolbyVisionLevelFhd24   = 0x4;
          public static final int DolbyVisionLevelFhd30   = 0x8;
          public static final int DolbyVisionLevelFhd60   = 0x10;
          public static final int DolbyVisionLevelUhd24   = 0x20;
          public static final int DolbyVisionLevelUhd30   = 0x40;
          public static final int DolbyVisionLevelUhd48   = 0x80;
          public static final int DolbyVisionLevelUhd60   = 0x100;
  
          // Profiles and levels for AV1 Codec, corresponding to the definitions in
          // "AV1 Bitstream & Decoding Process Specification", Annex A
          // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/
  
          /**
           * AV1 Main profile 4:2:0 8-bit
           *
           * See definition in
           * <a href="https://aomedia.org/av1-bitstream-and-decoding-process-specification/">AV1 Specification</a>
           * Annex A.
           */
          public static final int AV1ProfileMain8   = 0x1;
  
          /**
           * AV1 Main profile 4:2:0 10-bit
           *
           * See definition in
           * <a href="https://aomedia.org/av1-bitstream-and-decoding-process-specification/">AV1 Specification</a>
           * Annex A.
           */
          public static final int AV1ProfileMain10  = 0x2;
  
  
          /** AV1 Main profile 4:2:0 10-bit with HDR10. */
          public static final int AV1ProfileMain10HDR10 = 0x1000;
  
          /** AV1 Main profile 4:2:0 10-bit with HDR10Plus. */
          public static final int AV1ProfileMain10HDR10Plus = 0x2000;
  
          public static final int AV1Level2       = 0x1;
          public static final int AV1Level21      = 0x2;
          public static final int AV1Level22      = 0x4;
          public static final int AV1Level23      = 0x8;
          public static final int AV1Level3       = 0x10;
          public static final int AV1Level31      = 0x20;
          public static final int AV1Level32      = 0x40;
          public static final int AV1Level33      = 0x80;
          public static final int AV1Level4       = 0x100;
          public static final int AV1Level41      = 0x200;
          public static final int AV1Level42      = 0x400;
          public static final int AV1Level43      = 0x800;
          public static final int AV1Level5       = 0x1000;
          public static final int AV1Level51      = 0x2000;
          public static final int AV1Level52      = 0x4000;
          public static final int AV1Level53      = 0x8000;
          public static final int AV1Level6       = 0x10000;
          public static final int AV1Level61      = 0x20000;
          public static final int AV1Level62      = 0x40000;
          public static final int AV1Level63      = 0x80000;
          public static final int AV1Level7       = 0x100000;
          public static final int AV1Level71      = 0x200000;
          public static final int AV1Level72      = 0x400000;
          public static final int AV1Level73      = 0x800000;
  
          /**
           * The profile of the media content. Depending on the type of media this can be
           * one of the profile values defined in this class.
           */
          public int profile;
  
          /**
           * The level of the media content. Depending on the type of media this can be
           * one of the level values defined in this class.
           *
           * Note that VP9 decoder on platforms before {@link android.os.Build.VERSION_CODES#N} may
           * not advertise a profile level support. For those VP9 decoders, please use
           * {@link VideoCapabilities} to determine the codec capabilities.
           */
          public int level;
  
          @Override
          public boolean equals(Object obj) {
              if (obj == null) {
                  return false;
              }
              if (obj instanceof CodecProfileLevel) {
                  CodecProfileLevel other = (CodecProfileLevel)obj;
                  return other.profile == profile && other.level == level;
              }
              return false;
          }
  
          @Override
          public int hashCode() {
              return Long.hashCode(((long)profile << Integer.SIZE) | 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, mCanonicalName, mFlags,
                  caps.toArray(new CodecCapabilities[caps.size()]));
      }
  }
  