Java tutorial
/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Build; import android.util.Log; import android.util.Pair; import android.util.Range; import android.util.Rational; import android.util.Size; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import static android.media.Utils.intersectSortedDistinctRanges; import static android.media.Utils.sortDistinctRanges; /** * Provides information about a given media codec available on the device. You can * iterate through all codecs available by querying {@link MediaCodecList}. For example, * here's how to find an encoder that supports a given MIME type: * <pre> * private static MediaCodecInfo selectCodec(String mimeType) { * int numCodecs = MediaCodecList.getCodecCount(); * for (int i = 0; i < numCodecs; i++) { * MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); * * if (!codecInfo.isEncoder()) { * continue; * } * * String[] types = codecInfo.getSupportedTypes(); * for (int j = 0; j < types.length; j++) { * if (types[j].equalsIgnoreCase(mimeType)) { * return codecInfo; * } * } * } * return null; * }</pre> * */ public final class MediaCodecInfo { private boolean mIsEncoder; private String mName; private Map<String, CodecCapabilities> mCaps; /* package private */ MediaCodecInfo(String name, boolean isEncoder, CodecCapabilities[] caps) { mName = name; mIsEncoder = isEncoder; mCaps = new HashMap<String, CodecCapabilities>(); for (CodecCapabilities c : caps) { mCaps.put(c.getMimeType(), c); } } /** * Retrieve the codec name. */ public final String getName() { return mName; } /** * Query if the codec is an encoder. */ public final boolean isEncoder() { return mIsEncoder; } /** * Query the media types supported by the codec. */ public final String[] getSupportedTypes() { Set<String> typeSet = mCaps.keySet(); String[] types = typeSet.toArray(new String[typeSet.size()]); Arrays.sort(types); return types; } private static int checkPowerOfTwo(int value, String message) { if ((value & (value - 1)) != 0) { throw new IllegalArgumentException(message); } return value; } private static class Feature { public String mName; public int mValue; public boolean mDefault; public Feature(String name, int value, boolean def) { mName = name; mValue = value; mDefault = def; } } // COMMON CONSTANTS private static final Range<Integer> POSITIVE_INTEGERS = Range.create(1, Integer.MAX_VALUE); private static final Range<Long> POSITIVE_LONGS = Range.create(1l, Long.MAX_VALUE); private static final Range<Rational> POSITIVE_RATIONALS = Range.create(new Rational(1, Integer.MAX_VALUE), new Rational(Integer.MAX_VALUE, 1)); private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768); private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960); private static final Range<Integer> BITRATE_RANGE = Range.create(0, 500000000); private static final int DEFAULT_MAX_SUPPORTED_INSTANCES = 32; private static final int MAX_SUPPORTED_INSTANCES_LIMIT = 256; // found stuff that is not supported by framework (=> this should not happen) private static final int ERROR_UNRECOGNIZED = (1 << 0); // found profile/level for which we don't have capability estimates private static final int ERROR_UNSUPPORTED = (1 << 1); // have not found any profile/level for which we don't have capability estimate private static final int ERROR_NONE_SUPPORTED = (1 << 2); /** * Encapsulates the capabilities of a given codec component. * For example, what profile/level combinations it supports and what colorspaces * it is capable of providing the decoded data in, as well as some * codec-type specific capability flags. * <p>You can get an instance for a given {@link MediaCodecInfo} object with * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type. */ public static final class CodecCapabilities { public CodecCapabilities() { } // CLASSIFICATION private String mMime; private int mMaxSupportedInstances; // LEGACY FIELDS // Enumerates supported profile/level combinations as defined // by the type of encoded data. These combinations impose restrictions // on video resolution, bitrate... and limit the available encoder tools // such as B-frame support, arithmetic coding... public CodecProfileLevel[] profileLevels; // NOTE this array is modifiable by user // from OMX_COLOR_FORMATTYPE /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */ public static final int COLOR_FormatMonochrome = 1; /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */ public static final int COLOR_Format8bitRGB332 = 2; /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */ public static final int COLOR_Format12bitRGB444 = 3; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format16bitARGB4444 = 4; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format16bitARGB1555 = 5; /** * 16 bits per pixel RGB color format, with 5-bit red & blue and 6-bit green component. * <p> * Using 16-bit little-endian representation, colors stored as Red 15:11, Green 10:5, Blue 4:0. * <pre> * byte byte * <--------- i --------> | <------ i + 1 ------> * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | BLUE | GREEN | RED | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * 0 4 5 7 0 2 3 7 * bit * </pre> * * This format corresponds to {@link android.graphics.PixelFormat#RGB_565} and * {@link android.graphics.ImageFormat#RGB_565}. */ public static final int COLOR_Format16bitRGB565 = 6; /** @deprecated Use {@link #COLOR_Format16bitRGB565}. */ public static final int COLOR_Format16bitBGR565 = 7; /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */ public static final int COLOR_Format18bitRGB666 = 8; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format18bitARGB1665 = 9; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format19bitARGB1666 = 10; /** @deprecated Use {@link #COLOR_Format24bitBGR888} or {@link #COLOR_FormatRGBFlexible}. */ public static final int COLOR_Format24bitRGB888 = 11; /** * 24 bits per pixel RGB color format, with 8-bit red, green & blue components. * <p> * Using 24-bit little-endian representation, colors stored as Red 7:0, Green 15:8, Blue 23:16. * <pre> * byte byte byte * <------ i -----> | <---- i+1 ----> | <---- i+2 -----> * +-----------------+-----------------+-----------------+ * | RED | GREEN | BLUE | * +-----------------+-----------------+-----------------+ * </pre> * * This format corresponds to {@link android.graphics.PixelFormat#RGB_888}, and can also be * represented as a flexible format by {@link #COLOR_FormatRGBFlexible}. */ public static final int COLOR_Format24bitBGR888 = 12; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format24bitARGB1887 = 13; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format25bitARGB1888 = 14; /** * @deprecated Use {@link #COLOR_Format32bitABGR8888} Or {@link #COLOR_FormatRGBAFlexible}. */ public static final int COLOR_Format32bitBGRA8888 = 15; /** * @deprecated Use {@link #COLOR_Format32bitABGR8888} Or {@link #COLOR_FormatRGBAFlexible}. */ public static final int COLOR_Format32bitARGB8888 = 16; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_FormatYUV411Planar = 17; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_FormatYUV411PackedPlanar = 18; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_FormatYUV420Planar = 19; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_FormatYUV420PackedPlanar = 20; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_FormatYUV420SemiPlanar = 21; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatYUV422Planar = 22; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatYUV422PackedPlanar = 23; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatYUV422SemiPlanar = 24; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatYCbYCr = 25; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatYCrYCb = 26; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatCbYCrY = 27; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatCrYCbY = 28; /** @deprecated Use {@link #COLOR_FormatYUV444Flexible}. */ public static final int COLOR_FormatYUV444Interleaved = 29; /** * SMIA 8-bit Bayer format. * Each byte represents the top 8-bits of a 10-bit signal. */ public static final int COLOR_FormatRawBayer8bit = 30; /** * SMIA 10-bit Bayer format. */ public static final int COLOR_FormatRawBayer10bit = 31; /** * SMIA 8-bit compressed Bayer format. * Each byte represents a sample from the 10-bit signal that is compressed into 8-bits * using DPCM/PCM compression, as defined by the SMIA Functional Specification. */ public static final int COLOR_FormatRawBayer8bitcompressed = 32; /** @deprecated Use {@link #COLOR_FormatL8}. */ public static final int COLOR_FormatL2 = 33; /** @deprecated Use {@link #COLOR_FormatL8}. */ public static final int COLOR_FormatL4 = 34; /** * 8 bits per pixel Y color format. * <p> * Each byte contains a single pixel. * This format corresponds to {@link android.graphics.PixelFormat#L_8}. */ public static final int COLOR_FormatL8 = 35; /** * 16 bits per pixel, little-endian Y color format. * <p> * <pre> * byte byte * <--------- i --------> | <------ i + 1 ------> * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | Y | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * 0 7 0 7 * bit * </pre> */ public static final int COLOR_FormatL16 = 36; /** @deprecated Use {@link #COLOR_FormatL16}. */ public static final int COLOR_FormatL24 = 37; /** * 32 bits per pixel, little-endian Y color format. * <p> * <pre> * byte byte byte byte * <------ i -----> | <---- i+1 ----> | <---- i+2 ----> | <---- i+3 -----> * +-----------------+-----------------+-----------------+-----------------+ * | Y | * +-----------------+-----------------+-----------------+-----------------+ * 0 7 0 7 0 7 0 7 * bit * </pre> * * @deprecated Use {@link #COLOR_FormatL16}. */ public static final int COLOR_FormatL32 = 38; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_FormatYUV420PackedSemiPlanar = 39; /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */ public static final int COLOR_FormatYUV422PackedSemiPlanar = 40; /** @deprecated Use {@link #COLOR_Format24bitBGR888}. */ public static final int COLOR_Format18BitBGR666 = 41; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format24BitARGB6666 = 42; /** @deprecated Use {@link #COLOR_Format32bitABGR8888}. */ public static final int COLOR_Format24BitABGR6666 = 43; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100; // COLOR_FormatSurface indicates that the data will be a GraphicBuffer metadata reference. // In OMX this is called OMX_COLOR_FormatAndroidOpaque. public static final int COLOR_FormatSurface = 0x7F000789; /** * 32 bits per pixel RGBA color format, with 8-bit red, green, blue, and alpha components. * <p> * Using 32-bit little-endian representation, colors stored as Red 7:0, Green 15:8, * Blue 23:16, and Alpha 31:24. * <pre> * byte byte byte byte * <------ i -----> | <---- i+1 ----> | <---- i+2 ----> | <---- i+3 -----> * +-----------------+-----------------+-----------------+-----------------+ * | RED | GREEN | BLUE | ALPHA | * +-----------------+-----------------+-----------------+-----------------+ * </pre> * * This corresponds to {@link android.graphics.PixelFormat#RGBA_8888}. */ public static final int COLOR_Format32bitABGR8888 = 0x7F00A000; /** * Flexible 12 bits per pixel, subsampled YUV color format with 8-bit chroma and luma * components. * <p> * Chroma planes are subsampled by 2 both horizontally and vertically. * Use this format with {@link Image}. * This format corresponds to {@link android.graphics.ImageFormat#YUV_420_888}, * and can represent the {@link #COLOR_FormatYUV411Planar}, * {@link #COLOR_FormatYUV411PackedPlanar}, {@link #COLOR_FormatYUV420Planar}, * {@link #COLOR_FormatYUV420PackedPlanar}, {@link #COLOR_FormatYUV420SemiPlanar} * and {@link #COLOR_FormatYUV420PackedSemiPlanar} formats. * * @see Image#getFormat */ public static final int COLOR_FormatYUV420Flexible = 0x7F420888; /** * Flexible 16 bits per pixel, subsampled YUV color format with 8-bit chroma and luma * components. * <p> * Chroma planes are horizontally subsampled by 2. Use this format with {@link Image}. * This format corresponds to {@link android.graphics.ImageFormat#YUV_422_888}, * and can represent the {@link #COLOR_FormatYCbYCr}, {@link #COLOR_FormatYCrYCb}, * {@link #COLOR_FormatCbYCrY}, {@link #COLOR_FormatCrYCbY}, * {@link #COLOR_FormatYUV422Planar}, {@link #COLOR_FormatYUV422PackedPlanar}, * {@link #COLOR_FormatYUV422SemiPlanar} and {@link #COLOR_FormatYUV422PackedSemiPlanar} * formats. * * @see Image#getFormat */ public static final int COLOR_FormatYUV422Flexible = 0x7F422888; /** * Flexible 24 bits per pixel YUV color format with 8-bit chroma and luma * components. * <p> * Chroma planes are not subsampled. Use this format with {@link Image}. * This format corresponds to {@link android.graphics.ImageFormat#YUV_444_888}, * and can represent the {@link #COLOR_FormatYUV444Interleaved} format. * @see Image#getFormat */ public static final int COLOR_FormatYUV444Flexible = 0x7F444888; /** * Flexible 24 bits per pixel RGB color format with 8-bit red, green and blue * components. * <p> * Use this format with {@link Image}. This format corresponds to * {@link android.graphics.ImageFormat#FLEX_RGB_888}, and can represent * {@link #COLOR_Format24bitBGR888} and {@link #COLOR_Format24bitRGB888} formats. * @see Image#getFormat. */ public static final int COLOR_FormatRGBFlexible = 0x7F36B888; /** * Flexible 32 bits per pixel RGBA color format with 8-bit red, green, blue, and alpha * components. * <p> * Use this format with {@link Image}. This format corresponds to * {@link android.graphics.ImageFormat#FLEX_RGBA_8888}, and can represent * {@link #COLOR_Format32bitBGRA8888}, {@link #COLOR_Format32bitABGR8888} and * {@link #COLOR_Format32bitARGB8888} formats. * * @see Image#getFormat */ public static final int COLOR_FormatRGBAFlexible = 0x7F36A888; /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */ public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 0x7fa30c00; /** * Defined in the OpenMAX IL specs, color format values are drawn from * OMX_COLOR_FORMATTYPE. */ public int[] colorFormats; // NOTE this array is modifiable by user // FEATURES private int mFlagsSupported; private int mFlagsRequired; private int mFlagsVerified; /** * <b>video decoder only</b>: codec supports seamless resolution changes. */ public static final String FEATURE_AdaptivePlayback = "adaptive-playback"; /** * <b>video decoder only</b>: codec supports secure decryption. */ public static final String FEATURE_SecurePlayback = "secure-playback"; /** * <b>video or audio decoder only</b>: codec supports tunneled playback. */ public static final String FEATURE_TunneledPlayback = "tunneled-playback"; /** * <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), }; private static final Feature[] encoderFeatures = { new Feature(FEATURE_IntraRefresh, (1 << 0), 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 */, 0 /* flags */, defaultFormat, new MediaFormat() /* info */); if (ret.mError != 0) { return null; } return ret; } /* package private */ CodecCapabilities(CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags, Map<String, Object> defaultFormatMap, Map<String, Object> capabilitiesMap) { this(profLevs, colFmts, encoder, flags, new MediaFormat(defaultFormatMap), new MediaFormat(capabilitiesMap)); } private MediaFormat mCapabilitiesInfo; /* package private */ CodecCapabilities(CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags, MediaFormat defaultFormat, MediaFormat info) { final Map<String, Object> map = info.getMap(); colorFormats = colFmts; mFlagsVerified = flags; 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 Arrays.copyOf(mSampleRates, mSampleRates.length); } /** * Returns the array of supported sample rate ranges. The * array is sorted in ascending order, and the ranges are * distinct. */ public Range<Integer>[] getSupportedSampleRateRanges() { return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); } /** * Returns the maximum number of input channels supported. The codec * supports any number of channels between 1 and this maximum value. */ public int getMaxInputChannelCount() { return mMaxInputChannelCount; } /* no public constructor */ private AudioCapabilities() { } /** @hide */ public static AudioCapabilities create(MediaFormat info, CodecCapabilities parent) { AudioCapabilities caps = new AudioCapabilities(); caps.init(info, parent); return caps; } 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); mSampleRateRanges = new Range[] { Range.create(8000, 96000) }; mSampleRates = null; } private boolean supports(Integer sampleRate, Integer inputChannels) { // channels and sample rates are checked orthogonally if (inputChannels != null && (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) { return false; } if (sampleRate != null) { int ix = Utils.binarySearchDistinctRanges(mSampleRateRanges, sampleRate); if (ix < 0) { return false; } } return true; } /** * Query whether the sample rate is supported by the codec. */ public boolean isSampleRateSupported(int sampleRate) { return supports(sampleRate, null); } /** modifies rates */ private void limitSampleRates(int[] rates) { Arrays.sort(rates); ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); for (int rate : rates) { if (supports(rate, null /* channels */)) { ranges.add(Range.create(rate, rate)); } } mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); createDiscreteSampleRates(); } private void createDiscreteSampleRates() { mSampleRates = new int[mSampleRateRanges.length]; for (int i = 0; i < mSampleRateRanges.length; i++) { mSampleRates[i] = mSampleRateRanges[i].getLower(); } } /** modifies rateRanges */ private void limitSampleRates(Range<Integer>[] rateRanges) { sortDistinctRanges(rateRanges); mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); // check if all values are discrete for (Range<Integer> range : mSampleRateRanges) { if (!range.getLower().equals(range.getUpper())) { mSampleRates = null; return; } } createDiscreteSampleRates(); } private void applyLevelLimits() { int[] sampleRates = null; Range<Integer> sampleRateRange = null, bitRates = null; int maxChannels = 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 = AudioTrack.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 { 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 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); } /** * 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 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); 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; 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: 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.HEVCProfileMain10HDR10: 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 { 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 { // from OMX_VIDEO_AVCPROFILETYPE public static final int AVCProfileBaseline = 0x01; public static final int AVCProfileMain = 0x02; public static final int AVCProfileExtended = 0x04; public static final int AVCProfileHigh = 0x08; public static final int AVCProfileHigh10 = 0x10; public static final int AVCProfileHigh422 = 0x20; public static final int AVCProfileHigh444 = 0x40; public static final int AVCProfileConstrainedBaseline = 0x10000; public static final int AVCProfileConstrainedHigh = 0x80000; // from OMX_VIDEO_AVCLEVELTYPE public static final int AVCLevel1 = 0x01; public static final int AVCLevel1b = 0x02; public static final int AVCLevel11 = 0x04; public static final int AVCLevel12 = 0x08; public static final int AVCLevel13 = 0x10; public static final int AVCLevel2 = 0x20; public static final int AVCLevel21 = 0x40; public static final int AVCLevel22 = 0x80; public static final int AVCLevel3 = 0x100; public static final int AVCLevel31 = 0x200; public static final int AVCLevel32 = 0x400; public static final int AVCLevel4 = 0x800; public static final int AVCLevel41 = 0x1000; public static final int AVCLevel42 = 0x2000; public static final int AVCLevel5 = 0x4000; public static final int AVCLevel51 = 0x8000; public static final int AVCLevel52 = 0x10000; // from OMX_VIDEO_H263PROFILETYPE public static final int H263ProfileBaseline = 0x01; public static final int H263ProfileH320Coding = 0x02; public static final int H263ProfileBackwardCompatible = 0x04; public static final int H263ProfileISWV2 = 0x08; public static final int H263ProfileISWV3 = 0x10; public static final int H263ProfileHighCompression = 0x20; public static final int H263ProfileInternet = 0x40; public static final int H263ProfileInterlace = 0x80; public static final int H263ProfileHighLatency = 0x100; // from OMX_VIDEO_H263LEVELTYPE public static final int H263Level10 = 0x01; public static final int H263Level20 = 0x02; public static final int H263Level30 = 0x04; public static final int H263Level40 = 0x08; public static final int H263Level45 = 0x10; public static final int H263Level50 = 0x20; public static final int H263Level60 = 0x40; public static final int H263Level70 = 0x80; // from OMX_VIDEO_MPEG4PROFILETYPE public static final int MPEG4ProfileSimple = 0x01; public static final int MPEG4ProfileSimpleScalable = 0x02; public static final int MPEG4ProfileCore = 0x04; public static final int MPEG4ProfileMain = 0x08; public static final int MPEG4ProfileNbit = 0x10; public static final int MPEG4ProfileScalableTexture = 0x20; public static final int MPEG4ProfileSimpleFace = 0x40; public static final int MPEG4ProfileSimpleFBA = 0x80; public static final int MPEG4ProfileBasicAnimated = 0x100; public static final int MPEG4ProfileHybrid = 0x200; public static final int MPEG4ProfileAdvancedRealTime = 0x400; public static final int MPEG4ProfileCoreScalable = 0x800; public static final int MPEG4ProfileAdvancedCoding = 0x1000; public static final int MPEG4ProfileAdvancedCore = 0x2000; public static final int MPEG4ProfileAdvancedScalable = 0x4000; public static final int MPEG4ProfileAdvancedSimple = 0x8000; // from OMX_VIDEO_MPEG4LEVELTYPE public static final int MPEG4Level0 = 0x01; public static final int MPEG4Level0b = 0x02; public static final int MPEG4Level1 = 0x04; public static final int MPEG4Level2 = 0x08; public static final int MPEG4Level3 = 0x10; public static final int 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; // from OMX_VIDEO_MPEG2PROFILETYPE public static final int MPEG2ProfileSimple = 0x00; public static final int MPEG2ProfileMain = 0x01; public static final int MPEG2Profile422 = 0x02; public static final int MPEG2ProfileSNR = 0x03; public static final int MPEG2ProfileSpatial = 0x04; public static final int MPEG2ProfileHigh = 0x05; // from OMX_VIDEO_MPEG2LEVELTYPE public static final int MPEG2LevelLL = 0x00; public static final int MPEG2LevelML = 0x01; public static final int MPEG2LevelH14 = 0x02; public static final int MPEG2LevelHL = 0x03; public static final int MPEG2LevelHP = 0x04; // from OMX_AUDIO_AACPROFILETYPE public static final int AACObjectMain = 1; public static final int AACObjectLC = 2; public static final int AACObjectSSR = 3; public static final int AACObjectLTP = 4; public static final int AACObjectHE = 5; public static final int AACObjectScalable = 6; public static final int AACObjectERLC = 17; public static final int 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; // from OMX_VIDEO_VP8LEVELTYPE public static final int VP8Level_Version0 = 0x01; public static final int VP8Level_Version1 = 0x02; public static final int VP8Level_Version2 = 0x04; public static final int VP8Level_Version3 = 0x08; // from OMX_VIDEO_VP8PROFILETYPE public static final int VP8ProfileMain = 0x01; // from OMX_VIDEO_VP9PROFILETYPE public static final int VP9Profile0 = 0x01; public static final int VP9Profile1 = 0x02; public static final int VP9Profile2 = 0x04; public static final int VP9Profile3 = 0x08; // HDR profiles also support passing HDR metadata public static final int VP9Profile2HDR = 0x1000; public static final int VP9Profile3HDR = 0x2000; // from OMX_VIDEO_VP9LEVELTYPE 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; // from OMX_VIDEO_HEVCPROFILETYPE public static final int HEVCProfileMain = 0x01; public static final int HEVCProfileMain10 = 0x02; public static final int HEVCProfileMainStill = 0x04; public static final int HEVCProfileMain10HDR10 = 0x1000; // from OMX_VIDEO_HEVCLEVELTYPE public static final int HEVCMainTierLevel1 = 0x1; public static final int HEVCHighTierLevel1 = 0x2; public static final int HEVCMainTierLevel2 = 0x4; public static final int HEVCHighTierLevel2 = 0x8; public static final int HEVCMainTierLevel21 = 0x10; public static final int HEVCHighTierLevel21 = 0x20; public static final int HEVCMainTierLevel3 = 0x40; public static final int HEVCHighTierLevel3 = 0x80; public static final int HEVCMainTierLevel31 = 0x100; public static final int HEVCHighTierLevel31 = 0x200; public static final int HEVCMainTierLevel4 = 0x400; public static final int HEVCHighTierLevel4 = 0x800; public static final int HEVCMainTierLevel41 = 0x1000; public static final int HEVCHighTierLevel41 = 0x2000; public static final int HEVCMainTierLevel5 = 0x4000; public static final int HEVCHighTierLevel5 = 0x8000; public static final int HEVCMainTierLevel51 = 0x10000; public static final int HEVCHighTierLevel51 = 0x20000; public static final int HEVCMainTierLevel52 = 0x40000; public static final int HEVCHighTierLevel52 = 0x80000; public static final int HEVCMainTierLevel6 = 0x100000; public static final int HEVCHighTierLevel6 = 0x200000; public static final int HEVCMainTierLevel61 = 0x400000; public static final int HEVCHighTierLevel61 = 0x800000; public static final int HEVCMainTierLevel62 = 0x1000000; public static final int HEVCHighTierLevel62 = 0x2000000; private static final int HEVCHighTierLevels = HEVCHighTierLevel1 | HEVCHighTierLevel2 | HEVCHighTierLevel21 | HEVCHighTierLevel3 | HEVCHighTierLevel31 | HEVCHighTierLevel4 | HEVCHighTierLevel41 | HEVCHighTierLevel5 | HEVCHighTierLevel51 | HEVCHighTierLevel52 | HEVCHighTierLevel6 | HEVCHighTierLevel61 | HEVCHighTierLevel62; // from OMX_VIDEO_DOLBYVISIONPROFILETYPE 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; // from OMX_VIDEO_DOLBYVISIONLEVELTYPE 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; /** * Defined in the OpenMAX IL specs, depending on the type of media * this can be OMX_VIDEO_AVCPROFILETYPE, OMX_VIDEO_H263PROFILETYPE, * OMX_VIDEO_MPEG4PROFILETYPE, OMX_VIDEO_VP8PROFILETYPE or OMX_VIDEO_VP9PROFILETYPE. */ public int profile; /** * Defined in the OpenMAX IL specs, depending on the type of media * this can be OMX_VIDEO_AVCLEVELTYPE, OMX_VIDEO_H263LEVELTYPE * OMX_VIDEO_MPEG4LEVELTYPE, OMX_VIDEO_VP8LEVELTYPE or OMX_VIDEO_VP9LEVELTYPE. * * 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, mIsEncoder, caps.toArray(new CodecCapabilities[caps.size()])); } }