Extend MediaCodecInfo to describe usable codec limits and features
Bug: 11990470
Bug: 12065651
Bug: 16131974
Change-Id: I841b8507e823f1ddf14754e34029a9bed4f402d8
diff --git a/api/current.txt b/api/current.txt
index 6b5d051..d80d22d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14624,7 +14624,15 @@
public static final class MediaCodecInfo.CodecCapabilities {
ctor public MediaCodecInfo.CodecCapabilities();
+ method public static final android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int);
+ method public final android.media.MediaCodecInfo.CodecCapabilities.AudioCapabilities getAudioCapabilities();
+ method public final android.media.MediaFormat getDefaultFormat();
+ method public final android.media.MediaCodecInfo.CodecCapabilities.EncoderCapabilities getEncoderCapabilities();
+ method public final java.lang.String getMime();
+ method public final android.media.MediaCodecInfo.CodecCapabilities.VideoCapabilities getVideoCapabilities();
+ method public final boolean isFeatureRequired(java.lang.String);
method public final boolean isFeatureSupported(java.lang.String);
+ method public final boolean isFormatSupported(android.media.MediaFormat);
field public static final int COLOR_Format12bitRGB444 = 3; // 0x3
field public static final int COLOR_Format16bitARGB1555 = 5; // 0x5
field public static final int COLOR_Format16bitARGB4444 = 4; // 0x4
@@ -14660,6 +14668,7 @@
field public static final int COLOR_FormatYCrYCb = 26; // 0x1a
field public static final int COLOR_FormatYUV411PackedPlanar = 18; // 0x12
field public static final int COLOR_FormatYUV411Planar = 17; // 0x11
+ field public static final int COLOR_FormatYUV420Flexible = 2135033992; // 0x7f420888
field public static final int COLOR_FormatYUV420PackedPlanar = 20; // 0x14
field public static final int COLOR_FormatYUV420PackedSemiPlanar = 39; // 0x27
field public static final int COLOR_FormatYUV420Planar = 19; // 0x13
@@ -14672,10 +14681,45 @@
field public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00
field public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
field public static final java.lang.String FEATURE_AdaptivePlayback = "adaptive-playback";
+ field public static final java.lang.String FEATURE_SecurePlayback = "secure-playback";
+ field public static final java.lang.String FEATURE_TunneledPlayback = "tunneled-playback";
field public int[] colorFormats;
field public android.media.MediaCodecInfo.CodecProfileLevel[] profileLevels;
}
+ public static final class MediaCodecInfo.CodecCapabilities.AudioCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities {
+ method public final int getMaxInputChannelCount();
+ method public final android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
+ method public final int[] getSupportedSampleRates();
+ method public final boolean isSampleRateSupported(int);
+ }
+
+ public static class MediaCodecInfo.CodecCapabilities.BaseCapabilities {
+ method public final android.util.Range<java.lang.Integer> getBitrateRange();
+ }
+
+ public static final class MediaCodecInfo.CodecCapabilities.EncoderCapabilities {
+ method public final android.util.Range<java.lang.Integer> getComplexityRange();
+ method public final android.util.Range<java.lang.Integer> getQualityRange();
+ method public final boolean isBitrateModeSupported(int);
+ field public static final int BITRATE_MODE_CBR = 2; // 0x2
+ field public static final int BITRATE_MODE_CQ = 0; // 0x0
+ field public static final int BITRATE_MODE_VBR = 1; // 0x1
+ }
+
+ public static final class MediaCodecInfo.CodecCapabilities.VideoCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities {
+ method public final boolean areSizeAndRateSupported(int, int, double);
+ method public final int getHeightAlignment();
+ method public final android.util.Range<java.lang.Integer> getSupportedFrameRates();
+ method public final android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int);
+ method public final android.util.Range<java.lang.Integer> getSupportedHeights();
+ method public final android.util.Range<java.lang.Integer> getSupportedHeightsFor(int);
+ method public final android.util.Range<java.lang.Integer> getSupportedWidths();
+ method public final android.util.Range<java.lang.Integer> getSupportedWidthsFor(int);
+ method public final int getWidthAlignment();
+ method public final boolean isSizeSupported(int, int);
+ }
+
public static final class MediaCodecInfo.CodecProfileLevel {
ctor public MediaCodecInfo.CodecProfileLevel();
field public static final int AACObjectELD = 39; // 0x27
@@ -14704,6 +14748,7 @@
field public static final int AVCLevel42 = 8192; // 0x2000
field public static final int AVCLevel5 = 16384; // 0x4000
field public static final int AVCLevel51 = 32768; // 0x8000
+ field public static final int AVCLevel52 = 65536; // 0x10000
field public static final int AVCProfileBaseline = 1; // 0x1
field public static final int AVCProfileExtended = 4; // 0x4
field public static final int AVCProfileHigh = 8; // 0x8
@@ -14790,8 +14835,14 @@
}
public final class MediaCodecList {
+ ctor public MediaCodecList(int);
+ method public final java.lang.String findDecoderForFormat(android.media.MediaFormat);
+ method public final java.lang.String findEncoderForFormat(android.media.MediaFormat);
method public static final int getCodecCount();
method public static final android.media.MediaCodecInfo getCodecInfoAt(int);
+ method public final android.media.MediaCodecInfo[] getCodecInfos();
+ field public static final int ALL_CODECS = 1; // 0x1
+ field public static final int REGULAR_CODECS = 0; // 0x0
}
public final class MediaCrypto {
@@ -14917,11 +14968,14 @@
method public final void setLong(java.lang.String, long);
method public final void setString(java.lang.String, java.lang.String);
field public static final java.lang.String KEY_AAC_PROFILE = "aac-profile";
+ field public static final java.lang.String KEY_BITRATE_MODE = "bitrate-mode";
field public static final java.lang.String KEY_BIT_RATE = "bitrate";
field public static final java.lang.String KEY_CHANNEL_COUNT = "channel-count";
field public static final java.lang.String KEY_CHANNEL_MASK = "channel-mask";
field public static final java.lang.String KEY_COLOR_FORMAT = "color-format";
+ field public static final java.lang.String KEY_COMPLEXITY = "complexity";
field public static final java.lang.String KEY_DURATION = "durationUs";
+ field public static final java.lang.String KEY_FEATURE_ = "feature-";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
field public static final java.lang.String KEY_HEIGHT = "height";
@@ -14935,10 +14989,36 @@
field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
field public static final java.lang.String KEY_MIME = "mime";
+ field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
+ field public static final java.lang.String KEY_QUALITY = "quality";
+ field public static final java.lang.String KEY_REFERENCE_CLOCK_ID = "reference-clock-id";
field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate";
field public static final java.lang.String KEY_WIDTH = "width";
+ field public static final java.lang.String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
+ field public static final java.lang.String MIMETYPE_AUDIO_AC3 = "audio/ac3";
+ field public static final java.lang.String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
+ field public static final java.lang.String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
+ field public static final java.lang.String MIMETYPE_AUDIO_FLAC = "audio/flac";
+ field public static final java.lang.String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
+ field public static final java.lang.String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
+ field public static final java.lang.String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
+ field public static final java.lang.String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
+ field public static final java.lang.String MIMETYPE_AUDIO_OPUS = "audio/opus";
+ field public static final java.lang.String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
+ field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
+ field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+ field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
+ field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
+ field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
+ field public static final java.lang.String MIMETYPE_VIDEO_H263 = "video/3gpp";
+ field public static final java.lang.String MIMETYPE_VIDEO_HEVC = "video/hevc";
+ field public static final java.lang.String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
+ field public static final java.lang.String MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
+ field public static final java.lang.String MIMETYPE_VIDEO_RAW = "video/raw";
+ field public static final java.lang.String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
+ field public static final java.lang.String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
}
public final class MediaMetadata implements android.os.Parcelable {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index b5d0a57..2a68510 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -16,6 +16,24 @@
package android.media;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static android.media.Utils.intersectSortedDistinctRanges;
+import static android.media.Utils.sortDistinctRanges;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
/**
* Provides information about a given media codec available on the device. You can
* iterate through all codecs available by querying {@link MediaCodecList}. For example,
@@ -42,31 +60,42 @@
*
*/
public final class MediaCodecInfo {
- private int mIndex;
+ private boolean mIsEncoder;
+ private String mName;
+ private Map<String, CodecCapabilities> mCaps;
- /* package private */ MediaCodecInfo(int index) {
- mIndex = index;
+ /* package private */ MediaCodecInfo(
+ String name, boolean isEncoder, CodecCapabilities[] caps) {
+ mName = name;
+ mIsEncoder = isEncoder;
+ mCaps = new HashMap<String, CodecCapabilities>();
+ for (CodecCapabilities c: caps) {
+ mCaps.put(c.getMime(), c);
+ }
}
/**
* Retrieve the codec name.
*/
public final String getName() {
- return MediaCodecList.getCodecName(mIndex);
+ return mName;
}
/**
* Query if the codec is an encoder.
*/
public final boolean isEncoder() {
- return MediaCodecList.isEncoder(mIndex);
+ return mIsEncoder;
}
/**
* Query the media types supported by the codec.
*/
public final String[] getSupportedTypes() {
- return MediaCodecList.getSupportedTypes(mIndex);
+ Set<String> typeSet = mCaps.keySet();
+ String[] types = typeSet.toArray(new String[typeSet.size()]);
+ Arrays.sort(types);
+ return types;
}
/**
@@ -78,86 +107,1779 @@
* {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
*/
public static final class CodecCapabilities {
+ public CodecCapabilities() {
+ }
+
+ // CLASSIFICATION
+ private String mMime;
+
+ // LEGACY FIELDS
+
// Enumerates supported profile/level combinations as defined
// by the type of encoded data. These combinations impose restrictions
// on video resolution, bitrate... and limit the available encoder tools
// such as B-frame support, arithmetic coding...
- public CodecProfileLevel[] profileLevels;
+ public CodecProfileLevel[] profileLevels; // NOTE this array is modifiable by user
// from OMX_COLOR_FORMATTYPE
- public final static int COLOR_FormatMonochrome = 1;
- public final static int COLOR_Format8bitRGB332 = 2;
- public final static int COLOR_Format12bitRGB444 = 3;
- public final static int COLOR_Format16bitARGB4444 = 4;
- public final static int COLOR_Format16bitARGB1555 = 5;
- public final static int COLOR_Format16bitRGB565 = 6;
- public final static int COLOR_Format16bitBGR565 = 7;
- public final static int COLOR_Format18bitRGB666 = 8;
- public final static int COLOR_Format18bitARGB1665 = 9;
- public final static int COLOR_Format19bitARGB1666 = 10;
- public final static int COLOR_Format24bitRGB888 = 11;
- public final static int COLOR_Format24bitBGR888 = 12;
- public final static int COLOR_Format24bitARGB1887 = 13;
- public final static int COLOR_Format25bitARGB1888 = 14;
- public final static int COLOR_Format32bitBGRA8888 = 15;
- public final static int COLOR_Format32bitARGB8888 = 16;
- public final static int COLOR_FormatYUV411Planar = 17;
- public final static int COLOR_FormatYUV411PackedPlanar = 18;
- public final static int COLOR_FormatYUV420Planar = 19;
- public final static int COLOR_FormatYUV420PackedPlanar = 20;
- public final static int COLOR_FormatYUV420SemiPlanar = 21;
- public final static int COLOR_FormatYUV422Planar = 22;
- public final static int COLOR_FormatYUV422PackedPlanar = 23;
- public final static int COLOR_FormatYUV422SemiPlanar = 24;
- public final static int COLOR_FormatYCbYCr = 25;
- public final static int COLOR_FormatYCrYCb = 26;
- public final static int COLOR_FormatCbYCrY = 27;
- public final static int COLOR_FormatCrYCbY = 28;
- public final static int COLOR_FormatYUV444Interleaved = 29;
- public final static int COLOR_FormatRawBayer8bit = 30;
- public final static int COLOR_FormatRawBayer10bit = 31;
- public final static int COLOR_FormatRawBayer8bitcompressed = 32;
- public final static int COLOR_FormatL2 = 33;
- public final static int COLOR_FormatL4 = 34;
- public final static int COLOR_FormatL8 = 35;
- public final static int COLOR_FormatL16 = 36;
- public final static int COLOR_FormatL24 = 37;
- public final static int COLOR_FormatL32 = 38;
- public final static int COLOR_FormatYUV420PackedSemiPlanar = 39;
- public final static int COLOR_FormatYUV422PackedSemiPlanar = 40;
- public final static int COLOR_Format18BitBGR666 = 41;
- public final static int COLOR_Format24BitARGB6666 = 42;
- public final static int COLOR_Format24BitABGR6666 = 43;
+ public static final int COLOR_FormatMonochrome = 1;
+ public static final int COLOR_Format8bitRGB332 = 2;
+ public static final int COLOR_Format12bitRGB444 = 3;
+ public static final int COLOR_Format16bitARGB4444 = 4;
+ public static final int COLOR_Format16bitARGB1555 = 5;
+ public static final int COLOR_Format16bitRGB565 = 6;
+ public static final int COLOR_Format16bitBGR565 = 7;
+ public static final int COLOR_Format18bitRGB666 = 8;
+ public static final int COLOR_Format18bitARGB1665 = 9;
+ public static final int COLOR_Format19bitARGB1666 = 10;
+ public static final int COLOR_Format24bitRGB888 = 11;
+ public static final int COLOR_Format24bitBGR888 = 12;
+ public static final int COLOR_Format24bitARGB1887 = 13;
+ public static final int COLOR_Format25bitARGB1888 = 14;
+ public static final int COLOR_Format32bitBGRA8888 = 15;
+ public static final int COLOR_Format32bitARGB8888 = 16;
+ public static final int COLOR_FormatYUV411Planar = 17;
+ public static final int COLOR_FormatYUV411PackedPlanar = 18;
+ public static final int COLOR_FormatYUV420Planar = 19;
+ public static final int COLOR_FormatYUV420PackedPlanar = 20;
+ public static final int COLOR_FormatYUV420SemiPlanar = 21;
+ public static final int COLOR_FormatYUV422Planar = 22;
+ public static final int COLOR_FormatYUV422PackedPlanar = 23;
+ public static final int COLOR_FormatYUV422SemiPlanar = 24;
+ public static final int COLOR_FormatYCbYCr = 25;
+ public static final int COLOR_FormatYCrYCb = 26;
+ public static final int COLOR_FormatCbYCrY = 27;
+ public static final int COLOR_FormatCrYCbY = 28;
+ public static final int COLOR_FormatYUV444Interleaved = 29;
+ public static final int COLOR_FormatRawBayer8bit = 30;
+ public static final int COLOR_FormatRawBayer10bit = 31;
+ public static final int COLOR_FormatRawBayer8bitcompressed = 32;
+ public static final int COLOR_FormatL2 = 33;
+ public static final int COLOR_FormatL4 = 34;
+ public static final int COLOR_FormatL8 = 35;
+ public static final int COLOR_FormatL16 = 36;
+ public static final int COLOR_FormatL24 = 37;
+ public static final int COLOR_FormatL32 = 38;
+ public static final int COLOR_FormatYUV420PackedSemiPlanar = 39;
+ public static final int COLOR_FormatYUV422PackedSemiPlanar = 40;
+ public static final int COLOR_Format18BitBGR666 = 41;
+ public static final int COLOR_Format24BitARGB6666 = 42;
+ public static final int COLOR_Format24BitABGR6666 = 43;
- public final static int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100;
+ public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100;
// COLOR_FormatSurface indicates that the data will be a GraphicBuffer metadata reference.
// In OMX this is called OMX_COLOR_FormatAndroidOpaque.
- public final static int COLOR_FormatSurface = 0x7F000789;
- public final static int COLOR_QCOM_FormatYUV420SemiPlanar = 0x7fa30c00;
+ public static final int COLOR_FormatSurface = 0x7F000789;
+ // This corresponds to YUV_420_888 format
+ public static final int COLOR_FormatYUV420Flexible = 0x7F420888;
+ public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 0x7fa30c00;
/**
* Defined in the OpenMAX IL specs, color format values are drawn from
* OMX_COLOR_FORMATTYPE.
*/
- public int[] colorFormats;
+ public int[] colorFormats; // NOTE this array is modifiable by user
- private final static int FLAG_SupportsAdaptivePlayback = (1 << 0);
- private int flags;
+ // FEATURES
+
+ private int mFlagsSupported;
+ private int mFlagsRequired;
+ private int mFlagsVerified;
/**
* <b>video decoder only</b>: codec supports seamless resolution changes.
*/
- public final static String FEATURE_AdaptivePlayback = "adaptive-playback";
+ public static final String FEATURE_AdaptivePlayback = "adaptive-playback";
+
+ /**
+ * <b>video decoder only</b>: codec supports secure decryption.
+ */
+ public static final String FEATURE_SecurePlayback = "secure-playback";
+
+ /**
+ * <b>video or audio decoder only</b>: codec supports tunneled playback.
+ */
+ public static final String FEATURE_TunneledPlayback = "tunneled-playback";
/**
* Query codec feature capabilities.
+ * <p>
+ * These features are supported to be used by the codec. These
+ * include optional features that can be turned on, as well as
+ * features that are always on.
*/
public final boolean isFeatureSupported(String name) {
- if (name.equals(FEATURE_AdaptivePlayback)) {
- return (flags & FLAG_SupportsAdaptivePlayback) != 0;
+ return checkFeature(name, mFlagsSupported);
+ }
+
+ /**
+ * Query codec feature requirements.
+ * <p>
+ * These features are required to be used by the codec, and as such,
+ * they are always turned on.
+ */
+ public final boolean isFeatureRequired(String name) {
+ return checkFeature(name, mFlagsRequired);
+ }
+
+ private static class Feature {
+ public String mName;
+ public int mValue;
+ public boolean mDefault;
+ public Feature(String name, int value, boolean def) {
+ mName = name;
+ mValue = value;
+ mDefault = def;
+ }
+ }
+
+ private static final Feature[] decoderFeatures = {
+ new Feature(FEATURE_AdaptivePlayback, (1 << 0), true),
+ new Feature(FEATURE_SecurePlayback, (1 << 1), false),
+ new Feature(FEATURE_TunneledPlayback, (1 << 2), false),
+ };
+
+ /** @hide */
+ public String[] validFeatures() {
+ Feature[] features = getValidFeatures();
+ String[] res = new String[features.length];
+ for (int i = 0; i < res.length; i++) {
+ res[i] = features[i].mName;
+ }
+ return res;
+ }
+
+ private Feature[] getValidFeatures() {
+ if (!isEncoder()) {
+ return decoderFeatures;
+ }
+ return new Feature[] {};
+ }
+
+ private boolean checkFeature(String name, int flags) {
+ for (Feature feat: getValidFeatures()) {
+ if (feat.mName.equals(name)) {
+ return (flags & feat.mValue) != 0;
+ }
}
return false;
}
+
+ /** @hide */
+ public boolean isRegular() {
+ // regular codecs only require default features
+ for (Feature feat: getValidFeatures()) {
+ if (!feat.mDefault && isFeatureRequired(feat.mName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Query whether codec supports a given {@link MediaFormat}.
+ * @param format media format with optional feature directives.
+ * @throws IllegalArgumentException if format is not a valid media format.
+ * @return whether the codec capabilities support the given format
+ * and feature requests.
+ */
+ public final boolean isFormatSupported(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = (String)map.get(MediaFormat.KEY_MIME);
+
+ // mime must match if present
+ if (mime != null && !mMime.equalsIgnoreCase(mime)) {
+ return false;
+ }
+
+ // check feature support
+ for (Feature feat: getValidFeatures()) {
+ Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+ if ((yesNo == 1 && !isFeatureSupported(feat.mName)) ||
+ (yesNo == 0 && isFeatureRequired(feat.mName))) {
+ return false;
+ }
+ }
+ if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
+ return false;
+ }
+ if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
+ return false;
+ }
+ if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
+ return false;
+ }
+ return true;
+ }
+
+ // errors while reading profile levels
+ private int mError;
+ // found stuff that is not supported by framework (=> this should not happen)
+ private static final int ERROR_UNRECOGNIZED = (1 << 0);
+ // found profile/level for which we don't have capability estimates
+ private static final int ERROR_UNSUPPORTED = (1 << 1);
+ // have not found any profile/level for which we don't have capability estimate
+ private static final int ERROR_NONE_SUPPORTED = (1 << 2);
+
+
+ // UTILITY METHODS
+ private static final Range<Integer> POSITIVE_INTEGERS =
+ Range.create(1, Integer.MAX_VALUE);
+ private static final Range<Long> POSITIVE_LONGS =
+ Range.create(1l, Long.MAX_VALUE);
+ private static final Range<Rational> POSITIVE_RATIONALS =
+ Range.create(new Rational(1, Integer.MAX_VALUE),
+ new Rational(Integer.MAX_VALUE, 1));
+ private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
+ private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+
+ private static final String TAG = "CodecCapabilities";
+
+ // NEW-STYLE CAPABILITIES
+
+ /**
+ * Returns a MediaFormat object with default values for configurations that have
+ * defaults.
+ */
+ public final MediaFormat getDefaultFormat() {
+ return mDefaultFormat;
+ }
+ private MediaFormat mDefaultFormat;
+
+ /**
+ * Returns the mime type for which this codec-capability object was created.
+ */
+ public final String getMime() {
+ return mMime;
+ }
+
+ /**
+ * Returns the encoding capabilities or {@code null} if this is not an encoder.
+ */
+ public final EncoderCapabilities getEncoderCapabilities() {
+ return mEncoderCaps;
+ }
+ private EncoderCapabilities mEncoderCaps;
+
+ private boolean isEncoder() {
+ return mEncoderCaps != null;
+ }
+
+ /**
+ * A class that supports querying the encoding capabilities of a codec.
+ */
+ public static final class EncoderCapabilities {
+ /**
+ * Returns the supported range of quality values.
+ */
+ public final Range<Integer> getQualityRange() {
+ return mQualityRange;
+ }
+
+ /**
+ * Returns the supported range of encoder complexity values.
+ * <p>
+ * Some codecs may support multiple complexity levels, where higher
+ * complexity values use more encoder tools (e.g. perform more
+ * intensive calculations) to improve the quality or the compression
+ * ratio. Use a lower value to save power and/or time.
+ */
+ public final Range<Integer> getComplexityRange() {
+ return mComplexityRange;
+ }
+
+ /** Constant quality mode */
+ public static final int BITRATE_MODE_CQ = 0;
+ /** Variable bitrate mode */
+ public static final int BITRATE_MODE_VBR = 1;
+ /** Constant bitrate mode */
+ public static final int BITRATE_MODE_CBR = 2;
+
+ private static final Feature[] bitrates = new Feature[] {
+ new Feature("VBR", BITRATE_MODE_VBR, true),
+ new Feature("CBR", BITRATE_MODE_CBR, false),
+ new Feature("CQ", BITRATE_MODE_CQ, false)
+ };
+
+ private static int parseBitrateMode(String mode) {
+ for (Feature feat: bitrates) {
+ if (feat.mName.equalsIgnoreCase(mode)) {
+ return feat.mValue;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Query whether a bitrate mode is supported.
+ */
+ public final boolean isBitrateModeSupported(int mode) {
+ for (Feature feat: bitrates) {
+ if (mode == feat.mValue) {
+ return (mBitControl & (1 << mode)) != 0;
+ }
+ }
+ return false;
+ }
+
+ private Range<Integer> mQualityRange;
+ private Range<Integer> mComplexityRange;
+ private CodecCapabilities mParent;
+
+ /* no public constructor */
+ private EncoderCapabilities() { }
+
+ /** @hide */
+ public static EncoderCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ EncoderCapabilities caps = new EncoderCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ /** @hide */
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ // no support for complexity or quality yet
+ mParent = parent;
+ mComplexityRange = Range.create(0, 0);
+ mQualityRange = Range.create(0, 0);
+ mBitControl = (1 << BITRATE_MODE_VBR);
+
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ private void applyLevelLimits() {
+ if (mParent.getMime().equalsIgnoreCase(
+ MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ mComplexityRange = Range.create(0, 8);
+ mBitControl = (1 << BITRATE_MODE_CQ);
+ }
+ }
+
+ private int mBitControl;
+ private Integer mDefaultComplexity;
+ private Integer mDefaultQuality;
+ private String mQualityScale;
+
+ private void parseFromInfo(MediaFormat info) {
+ Map<String, Object> map = info.getMap();
+
+ if (info.containsKey("complexity-range")) {
+ mComplexityRange = Utils
+ .parseIntRange(info.getString("complexity-range"), mComplexityRange);
+ // TODO should we limit this to level limits?
+ }
+ if (info.containsKey("quality-range")) {
+ mQualityRange = Utils
+ .parseIntRange(info.getString("quality-range"), mQualityRange);
+ }
+ if (info.containsKey("feature-bitrate-control")) {
+ for (String mode: info.getString("feature-bitrate-control").split(",")) {
+ mBitControl |= parseBitrateMode(mode);
+ }
+ }
+
+ try {
+ mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+ } catch (NumberFormatException e) { }
+
+ try {
+ mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+ } catch (NumberFormatException e) { }
+
+ mQualityScale = (String)map.get("quality-scale");
+ }
+
+ private boolean supports(
+ Integer complexity, Integer quality, Integer profile) {
+ boolean ok = true;
+ if (ok && complexity != null) {
+ ok = mComplexityRange.contains(complexity);
+ }
+ if (ok && quality != null) {
+ ok = mQualityRange.contains(quality);
+ }
+ if (ok && profile != null) {
+ for (CodecProfileLevel pl: mParent.profileLevels) {
+ if (pl.profile == profile) {
+ profile = null;
+ break;
+ }
+ }
+ ok = profile == null;
+ }
+ return ok;
+ }
+
+ /** @hide */
+ public void setDefaultFormat(MediaFormat format) {
+ if (mQualityRange.getUpper() != mQualityRange.getLower()
+ && mDefaultQuality != null) {
+ format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
+ }
+ if (mComplexityRange.getUpper() != mComplexityRange.getLower()
+ && mDefaultComplexity != null) {
+ format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
+ }
+ // bitrates are listed in order of preference
+ for (Feature feat: bitrates) {
+ if ((mBitControl & (1 << feat.mValue)) != 0) {
+ format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
+ break;
+ }
+ }
+ }
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = mParent.getMime();
+
+ Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
+ if (mode != null && !isBitrateModeSupported(mode)) {
+ return false;
+ }
+
+ Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
+ if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
+ Integer flacComplexity =
+ (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
+ if (complexity == null) {
+ complexity = flacComplexity;
+ } else if (flacComplexity != null && complexity != flacComplexity) {
+ throw new IllegalArgumentException(
+ "conflicting values for complexity and " +
+ "flac-compression-level");
+ }
+ }
+
+ // other audio parameters
+ Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
+ if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
+ Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
+ if (profile == null) {
+ profile = aacProfile;
+ } else if (aacProfile != null && aacProfile != profile) {
+ throw new IllegalArgumentException(
+ "conflicting values for profile and aac-profile");
+ }
+ }
+
+ Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+ return supports(complexity, quality, profile);
+ }
+ };
+
+ /**
+ * A class that supports querying basic capabilities of a codec.
+ */
+ public static class BaseCapabilities {
+ /**
+ * Returns the range of supported bitrates in bits/second.
+ */
+ public final Range<Integer> getBitrateRange() {
+ return mBitrateRange;
+ }
+
+ /** @hide */
+ protected Range<Integer> mBitrateRange;
+
+ /** @hide */
+ protected CodecCapabilities mParent;
+
+ /** @hide */
+ protected BaseCapabilities() {
+ }
+
+ /** @hide */
+ protected void init(MediaFormat info, CodecCapabilities parent) {
+ mParent = parent;
+ mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+ }
+ }
+
+ /**
+ * A class that supports querying the video capabilities of a codec.
+ */
+ public static final class VideoCapabilities extends BaseCapabilities {
+ private static final String TAG = "VideoCapabilities";
+ private Range<Integer> mHeightRange;
+ private Range<Integer> mWidthRange;
+ private Range<Integer> mBlockCountRange;
+ private Range<Integer> mHorizontalBlockRange;
+ private Range<Integer> mVerticalBlockRange;
+ private Range<Rational> mAspectRatioRange;
+ private Range<Rational> mBlockAspectRatioRange;
+ private Range<Long> mBlocksPerSecondRange;
+ private Range<Integer> mFrameRateRange;
+
+ private int mBlockWidth;
+ private int mBlockHeight;
+ private int mWidthAlignment;
+ private int mHeightAlignment;
+ private int mSmallerDimensionUpperLimit;
+
+ /**
+ * Returns the range of supported video widths.
+ */
+ public final Range<Integer> getSupportedWidths() {
+ return mWidthRange;
+ }
+
+ /**
+ * Returns the range of supported video heights.
+ */
+ public final Range<Integer> getSupportedHeights() {
+ return mHeightRange;
+ }
+
+ /**
+ * Returns the alignment requirement for video width.
+ */
+ public final int getWidthAlignment() {
+ return mWidthAlignment;
+ }
+
+ /**
+ * Returns the alignment requirement for video height.
+ */
+ public final int getHeightAlignment() {
+ return mHeightAlignment;
+ }
+
+ /**
+ * Return the upper limit on the smaller dimension of width or height.
+ * <p></p>
+ * Some codecs have a limit on the smaller dimension, whether it be
+ * the width or the height. E.g. a codec may only be able to handle
+ * up to 1920x1080 both in landscape and portrait mode (1080x1920).
+ * In this case the maximum width and height are both 1920, but the
+ * smaller dimension limit will be 1080. For other codecs, this is
+ * {@code Math.min(getSupportedWidths().getUpper(),
+ * getSupportedHeights().getUpper())}.
+ *
+ * @hide
+ */
+ public int getSmallerDimensionUpperLimit() {
+ return mSmallerDimensionUpperLimit;
+ }
+
+ /**
+ * Returns the range of supported frame rates.
+ * <p>
+ * This is not a performance indicator. Rather, it expresses the
+ * limits specified in the coding standard, based on the complexities
+ * of encoding material for later playback at a certain frame rate,
+ * or the decoding of such material in non-realtime.
+ */
+ public final Range<Integer> getSupportedFrameRates() {
+ return mFrameRateRange;
+ }
+
+ /**
+ * Returns the range of supported video widths for a video height.
+ * @param height the height of the video
+ */
+ public final Range<Integer> getSupportedWidthsFor(int height) {
+ try {
+ Range<Integer> range = mWidthRange;
+ if (!mHeightRange.contains(height)
+ || (height % mHeightAlignment) != 0) {
+ throw new IllegalArgumentException("unsupported height");
+ }
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+
+ // constrain by block count and by block aspect ratio
+ final int minWidthInBlocks = Math.max(
+ Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
+ (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
+ * heightInBlocks));
+ final int maxWidthInBlocks = Math.min(
+ mBlockCountRange.getUpper() / heightInBlocks,
+ (int)(mBlockAspectRatioRange.getUpper().doubleValue()
+ * heightInBlocks));
+ range = range.intersect(
+ (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
+ maxWidthInBlocks * mBlockWidth);
+
+ // constrain by smaller dimension limit
+ if (height > mSmallerDimensionUpperLimit) {
+ range = range.intersect(1, mSmallerDimensionUpperLimit);
+ }
+
+ // constrain by aspect ratio
+ range = range.intersect(
+ (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
+ * height),
+ (int)(mAspectRatioRange.getUpper().doubleValue() * height));
+ return range;
+ } catch (IllegalArgumentException e) {
+ // should not be here
+ Log.w(TAG, "could not get supported widths for " + height , e);
+ throw new IllegalArgumentException("unsupported height");
+ }
+ }
+
+ /**
+ * Returns the range of supported video heights for a video width
+ * @param width the width of the video
+ */
+ public final Range<Integer> getSupportedHeightsFor(int width) {
+ try {
+ Range<Integer> range = mHeightRange;
+ if (!mWidthRange.contains(width)
+ || (width % mWidthAlignment) != 0) {
+ throw new IllegalArgumentException("unsupported width");
+ }
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+
+ // constrain by block count and by block aspect ratio
+ final int minHeightInBlocks = Math.max(
+ Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
+ (int)Math.ceil(widthInBlocks /
+ mBlockAspectRatioRange.getUpper().doubleValue()));
+ final int maxHeightInBlocks = Math.min(
+ mBlockCountRange.getUpper() / widthInBlocks,
+ (int)(widthInBlocks /
+ mBlockAspectRatioRange.getLower().doubleValue()));
+ range = range.intersect(
+ (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
+ maxHeightInBlocks * mBlockHeight);
+
+ // constrain by smaller dimension limit
+ if (width > mSmallerDimensionUpperLimit) {
+ range = range.intersect(1, mSmallerDimensionUpperLimit);
+ }
+
+ // constrain by aspect ratio
+ range = range.intersect(
+ (int)Math.ceil(width /
+ mAspectRatioRange.getUpper().doubleValue()),
+ (int)(width / mAspectRatioRange.getLower().doubleValue()));
+ return range;
+ } catch (IllegalArgumentException e) {
+ // should not be here
+ Log.w(TAG, "could not get supported heights for " + width , e);
+ throw new IllegalArgumentException("unsupported width");
+ }
+ }
+
+ /**
+ * Returns the range of supported video frame rates for a video size.
+ * <p>
+ * This is not a performance indicator. Rather, it expresses the limits specified in
+ * the coding standard, based on the complexities of encoding material of a given
+ * size for later playback at a certain frame rate, or the decoding of such material
+ * in non-realtime.
+
+ * @param width the width of the video
+ * @param height the height of the video
+ */
+ public final Range<Double> getSupportedFrameRatesFor(int width, int height) {
+ Range<Integer> range = mHeightRange;
+ if (!supports(width, height, null)) {
+ throw new IllegalArgumentException("unsupported size");
+ }
+ final int blockCount =
+ Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+
+ return Range.create(
+ Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
+ (double) mFrameRateRange.getLower()),
+ Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
+ (double) mFrameRateRange.getUpper()));
+ }
+
+ /**
+ * Returns whether a given video size ({@code width} and
+ * {@code height}) and {@code frameRate} combination is supported.
+ */
+ public final boolean areSizeAndRateSupported(
+ int width, int height, double frameRate) {
+ return supports(width, height, frameRate);
+ }
+
+ /**
+ * Returns whether a given video size ({@code width} and
+ * {@code height}) is supported.
+ */
+ public final boolean isSizeSupported(int width, int height) {
+ return supports(width, height, null);
+ }
+
+ private final boolean supports(
+ Integer width, Integer height, Double rate) {
+ boolean ok = true;
+
+ if (ok && width != null) {
+ ok = mWidthRange.contains(width)
+ && (width % mWidthAlignment == 0);
+ }
+ if (ok && height != null) {
+ ok = mHeightRange.contains(height)
+ && (height % mHeightAlignment == 0);
+ }
+ if (ok && rate != null) {
+ ok = mFrameRateRange.contains(Utils.intRangeFor(rate));
+ }
+ if (ok && height != null && width != null) {
+ ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+ final int blockCount = widthInBlocks * heightInBlocks;
+ ok = ok && mBlockCountRange.contains(blockCount)
+ && mBlockAspectRatioRange.contains(
+ new Rational(widthInBlocks, heightInBlocks))
+ && mAspectRatioRange.contains(new Rational(width, height));
+ if (ok && rate != null) {
+ double blocksPerSec = blockCount * rate;
+ ok = mBlocksPerSecondRange.contains(
+ Utils.longRangeFor(blocksPerSec));
+ }
+ }
+ return ok;
+ }
+
+ /**
+ * @hide
+ * @throws java.lang.ClassCastException */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+ Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+ Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE);
+
+ // we ignore color-format for now as it is not reliably reported by codec
+
+ return supports(width, height, rate);
+ }
+
+ /* no public constructor */
+ private VideoCapabilities() { }
+
+ /** @hide */
+ public static VideoCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ VideoCapabilities caps = new VideoCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ /** @hide */
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ super.init(info, parent);
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ updateLimits();
+ }
+
+ /** @hide */
+ public Size getBlockSize() {
+ return new Size(mBlockWidth, mBlockHeight);
+ }
+
+ /** @hide */
+ public Range<Integer> getBlockCountRange() {
+ return mBlockCountRange;
+ }
+
+ /** @hide */
+ public Range<Long> getBlocksPerSecondRange() {
+ return mBlocksPerSecondRange;
+ }
+
+ /** @hide */
+ public Range<Rational> getAspectRatioRange(boolean blocks) {
+ return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
+ }
+
+ private void initWithPlatformLimits() {
+ mWidthRange = SIZE_RANGE;
+ mHeightRange = SIZE_RANGE;
+ mFrameRateRange = FRAME_RATE_RANGE;
+
+ mHorizontalBlockRange = SIZE_RANGE;
+ mVerticalBlockRange = SIZE_RANGE;
+
+ // full positive ranges are supported as these get calculated
+ mBlockCountRange = POSITIVE_INTEGERS;
+ mBlocksPerSecondRange = POSITIVE_LONGS;
+
+ mBlockAspectRatioRange = POSITIVE_RATIONALS;
+ mAspectRatioRange = POSITIVE_RATIONALS;
+
+ // YUV 4:2:0 requires 2:2 alignment
+ mWidthAlignment = 2;
+ mHeightAlignment = 2;
+ mBlockWidth = 2;
+ mBlockHeight = 2;
+ mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+ Size blockSize = new Size(mBlockWidth, mBlockHeight);
+ Size alignment = new Size(mWidthAlignment, mHeightAlignment);
+ Range<Integer> counts = null, widths = null, heights = null;
+ Range<Integer> frameRates = null;
+ Range<Long> blockRates = null;
+ Range<Rational> ratios = null, blockRatios = null;
+
+ blockSize = Utils.parseSize(map.get("block-size"), blockSize);
+ alignment = Utils.parseSize(map.get("alignment"), alignment);
+ counts = Utils.parseIntRange(map.get("block-count-range"), null);
+ blockRates =
+ Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+ {
+ Object o = map.get("size-range");
+ Pair<Size, Size> sizeRange = Utils.parseSizeRange(o);
+ if (sizeRange != null) {
+ try {
+ widths = Range.create(
+ sizeRange.first.getWidth(),
+ sizeRange.second.getWidth());
+ heights = Range.create(
+ sizeRange.first.getHeight(),
+ sizeRange.second.getHeight());
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "could not parse size range '" + o + "'");
+ widths = null;
+ heights = null;
+ }
+ }
+ }
+ ratios = Utils.parseRationalRange(
+ map.get("block-aspect-ratio-range"), null);
+ blockRatios = Utils.parseRationalRange(
+ map.get("pixel-aspect-ratio-range"), null);
+ frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
+ if (frameRates != null) {
+ try {
+ frameRates = frameRates.intersect(FRAME_RATE_RANGE);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "frame rate range (" + frameRates
+ + ") is out of limits: " + FRAME_RATE_RANGE);
+ frameRates = null;
+ }
+ }
+
+ checkPowerOfTwo(
+ blockSize.getWidth(), "block-size width must be power of two");
+ checkPowerOfTwo(
+ blockSize.getHeight(), "block-size height must be power of two");
+
+ checkPowerOfTwo(
+ alignment.getWidth(), "alignment width must be power of two");
+ checkPowerOfTwo(
+ alignment.getHeight(), "alignment height must be power of two");
+
+ // update block-size and alignment
+ applyMacroBlockLimits(
+ Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
+ alignment.getWidth(), alignment.getHeight());
+
+ if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+ // codec supports profiles that we don't know.
+ // Extend ranges clipped to platform limits.
+ if (widths != null) {
+ mWidthRange = mWidthRange.extend(widths);
+ }
+ if (heights != null) {
+ mHeightRange = mHeightRange.extend(heights);
+ }
+ if (counts != null) {
+ mBlockCountRange = mBlockCountRange.extend(
+ Utils.factorRange(counts, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRates != null) {
+ mBlocksPerSecondRange = mBlocksPerSecondRange.extend(
+ Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRatios != null) {
+ mBlockAspectRatioRange = mBlockAspectRatioRange.extend(
+ Utils.scaleRange(blockRatios,
+ mBlockHeight / blockSize.getHeight(),
+ mBlockWidth / blockSize.getWidth()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = mAspectRatioRange.extend(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = mFrameRateRange.extend(frameRates);
+ }
+ } else {
+ // no unsupported profile/levels, so restrict values to known limits
+ if (widths != null) {
+ mWidthRange = mWidthRange.intersect(widths);
+ }
+ if (heights != null) {
+ mHeightRange = mHeightRange.intersect(heights);
+ }
+ if (counts != null) {
+ mBlockCountRange = mBlockCountRange.intersect(
+ Utils.factorRange(counts, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRates != null) {
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRatios != null) {
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ Utils.scaleRange(blockRatios,
+ mBlockHeight / blockSize.getHeight(),
+ mBlockWidth / blockSize.getWidth()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = mAspectRatioRange.intersect(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = mFrameRateRange.intersect(frameRates);
+ }
+ }
+ updateLimits();
+ }
+
+ private int checkPowerOfTwo(int value, String message) {
+ if ((value & (value - 1)) != 0) {
+ throw new IllegalArgumentException(message);
+ }
+ return value;
+ }
+
+ private void applyBlockLimits(
+ int blockWidth, int blockHeight,
+ Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
+ checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
+ checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
+
+ final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
+ final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
+
+ // factor will always be a power-of-2
+ int factor =
+ newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
+ if (factor != 1) {
+ mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
+ mBlocksPerSecondRange = Utils.factorRange(
+ mBlocksPerSecondRange, factor);
+ mBlockAspectRatioRange = Utils.scaleRange(
+ mBlockAspectRatioRange,
+ newBlockHeight / mBlockHeight,
+ newBlockWidth / mBlockWidth);
+ mHorizontalBlockRange = Utils.factorRange(
+ mHorizontalBlockRange, newBlockWidth / mBlockWidth);
+ mVerticalBlockRange = Utils.factorRange(
+ mVerticalBlockRange, newBlockHeight / mBlockHeight);
+ }
+ factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
+ if (factor != 1) {
+ counts = Utils.factorRange(counts, factor);
+ rates = Utils.factorRange(rates, factor);
+ ratios = Utils.scaleRange(
+ ratios, newBlockHeight / blockHeight,
+ newBlockWidth / blockWidth);
+ }
+ mBlockCountRange = mBlockCountRange.intersect(counts);
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
+ mBlockWidth = newBlockWidth;
+ mBlockHeight = newBlockHeight;
+ }
+
+ private void applyAlignment(int widthAlignment, int heightAlignment) {
+ checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
+ checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
+
+ if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
+ // maintain assumption that 0 < alignment <= block-size
+ applyBlockLimits(
+ Math.max(widthAlignment, mBlockWidth),
+ Math.max(heightAlignment, mBlockHeight),
+ POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+ }
+
+ mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+ mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+
+ mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+ mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+ }
+
+ private void updateLimits() {
+ // pixels -> blocks <- counts
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Utils.factorRange(mWidthRange, mBlockWidth)).intersect(mBlockCountRange);
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Utils.factorRange(mHeightRange, mBlockHeight)).intersect(mBlockCountRange);
+ mBlockCountRange = mBlockCountRange.intersect(
+ Range.create(
+ mHorizontalBlockRange.getLower()
+ * mVerticalBlockRange.getLower(),
+ mHorizontalBlockRange.getUpper()
+ * mVerticalBlockRange.getUpper()));
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ new Rational(
+ mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
+ new Rational(
+ mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
+
+ // blocks -> pixels
+ mWidthRange = mWidthRange.intersect(
+ (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
+ mHorizontalBlockRange.getUpper() * mBlockWidth);
+ mHeightRange = mHeightRange.intersect(
+ (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
+ mVerticalBlockRange.getUpper() * mBlockHeight);
+ mAspectRatioRange = mAspectRatioRange.intersect(
+ new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
+ new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
+
+ mSmallerDimensionUpperLimit = Math.min(
+ mSmallerDimensionUpperLimit,
+ Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
+
+ // blocks -> rate
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
+ mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
+ mFrameRateRange = mFrameRateRange.intersect(
+ (int)(mBlocksPerSecondRange.getLower()
+ / mBlockCountRange.getUpper()),
+ (int)(mBlocksPerSecondRange.getUpper()
+ / (double)mBlockCountRange.getLower()));
+ }
+
+ private void applyMacroBlockLimits(
+ int maxHorizontalBlocks, int maxVerticalBlocks,
+ int maxBlocks, long maxBlocksPerSecond,
+ int blockWidth, int blockHeight,
+ int widthAlignment, int heightAlignment) {
+ applyAlignment(widthAlignment, heightAlignment);
+ applyBlockLimits(
+ blockWidth, blockHeight, Range.create(1, maxBlocks),
+ Range.create(1L, maxBlocksPerSecond),
+ Range.create(
+ new Rational(1, maxVerticalBlocks),
+ new Rational(maxHorizontalBlocks, 1)));
+ mHorizontalBlockRange =
+ mHorizontalBlockRange.intersect(
+ 1, maxHorizontalBlocks / (mBlockWidth / blockWidth));
+ mVerticalBlockRange =
+ mVerticalBlockRange.intersect(
+ 1, maxVerticalBlocks / (mBlockHeight / blockHeight));
+ }
+
+ private void applyLevelLimits() {
+ int maxBlocksPerSecond = 0;
+ int maxBlocks = 0;
+ int maxBps = 0;
+ int maxDPBBlocks = 0;
+
+ int errors = ERROR_NONE_SUPPORTED;
+ CodecProfileLevel[] profileLevels = mParent.profileLevels;
+ String mime = mParent.getMime();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ maxDPBBlocks = 396;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, DPB = 0;
+ boolean supported = true;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.AVCLevel1:
+ MBPS = 1485; FS = 99; BR = 64; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel1b:
+ MBPS = 1485; FS = 99; BR = 128; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel11:
+ MBPS = 3000; FS = 396; BR = 192; DPB = 900; break;
+ case CodecProfileLevel.AVCLevel12:
+ MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel13:
+ MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel2:
+ MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel21:
+ MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break;
+ case CodecProfileLevel.AVCLevel22:
+ MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel3:
+ MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel31:
+ MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break;
+ case CodecProfileLevel.AVCLevel32:
+ MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break;
+ case CodecProfileLevel.AVCLevel4:
+ MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel41:
+ MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel42:
+ MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break;
+ case CodecProfileLevel.AVCLevel5:
+ MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break;
+ case CodecProfileLevel.AVCLevel51:
+ MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break;
+ case CodecProfileLevel.AVCLevel52:
+ MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ supported = false;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.AVCProfileHigh:
+ BR *= 1250; break;
+ case CodecProfileLevel.AVCProfileHigh10:
+ BR *= 3000; break;
+ case CodecProfileLevel.AVCProfileExtended:
+ case CodecProfileLevel.AVCProfileHigh422:
+ case CodecProfileLevel.AVCProfileHigh444:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ // fall through - treat as base profile
+ case CodecProfileLevel.AVCProfileBaseline:
+ case CodecProfileLevel.AVCProfileMain:
+ BR *= 1000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ BR *= 1000;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR, maxBps);
+ maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
+ }
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond, 16, 16, 1, 1);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG4ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level0b:
+ FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
+ // TODO while MPEG4 SP does not have level 4 or 5,
+ // some vendors report it
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
+ // case CodecProfileLevel.MPEG4Level3b:
+ // TODO: MPEG4 level 3b is not defined in OMX
+ // MBPS = 11880; FS = 396; BR = 1500; break;
+ case CodecProfileLevel.MPEG4Level4:
+ case CodecProfileLevel.MPEG4Level4a:
+ // TODO: MPEG4 level 4a is not defined in spec
+ FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
+ case CodecProfileLevel.MPEG4Level5:
+ FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileMain: // 2-4
+ case CodecProfileLevel.MPEG4ProfileNbit: // 2
+ case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCore: // 1-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
+ case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
+ case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
+ case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
+ case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
+ case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond, 16, 16, 1, 1);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.H263Level10:
+ FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level20:
+ // only supports CIF, 0..QCIF
+ FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level30:
+ // only supports CIF, 0..QCIF
+ FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level40:
+ // only supports CIF, 0..QCIF
+ FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level45:
+ // only implies level 10 support
+ FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level50:
+ // only supports 50fps for H > 15
+ FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level60:
+ // only supports 50fps for H > 15
+ FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level70:
+ // only supports 50fps for H > 30
+ FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
+ + "/" + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.H263ProfileBackwardCompatible:
+ case CodecProfileLevel.H263ProfileBaseline:
+ case CodecProfileLevel.H263ProfileH320Coding:
+ case CodecProfileLevel.H263ProfileHighCompression:
+ case CodecProfileLevel.H263ProfileHighLatency:
+ case CodecProfileLevel.H263ProfileInterlace:
+ case CodecProfileLevel.H263ProfileInternet:
+ case CodecProfileLevel.H263ProfileISWV2:
+ case CodecProfileLevel.H263ProfileISWV3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(W * H, maxBlocks);
+ maxBps = Math.max(BR * 64000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond, 16, 16, 1, 1);
+ mFrameRateRange = Range.create(1, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+ mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ maxBlocks = maxBlocksPerSecond = maxBps = Integer.MAX_VALUE;
+ // profile levels are not indicative for VPx, but verify
+ // them nonetheless
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ switch (profileLevel.level) {
+ case CodecProfileLevel.VP8Level_Version0:
+ case CodecProfileLevel.VP8Level_Version1:
+ case CodecProfileLevel.VP8Level_Version2:
+ case CodecProfileLevel.VP8Level_Version3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.VP8ProfileMain:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ maxBlocks = 36864;
+ maxBlocksPerSecond = maxBlocks * 15;
+ maxBps = 128000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ double FR = 0;
+ int FS = 0;
+ int BR = 0;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.HEVCMainTierLevel1:
+ case CodecProfileLevel.HEVCHighTierLevel1:
+ FR = 15; FS = 36864; BR = 128; break;
+ case CodecProfileLevel.HEVCMainTierLevel2:
+ case CodecProfileLevel.HEVCHighTierLevel2:
+ FR = 30; FS = 122880; BR = 1500; break;
+ case CodecProfileLevel.HEVCMainTierLevel21:
+ case CodecProfileLevel.HEVCHighTierLevel21:
+ FR = 30; FS = 245760; BR = 3000; break;
+ case CodecProfileLevel.HEVCMainTierLevel3:
+ case CodecProfileLevel.HEVCHighTierLevel3:
+ FR = 30; FS = 552960; BR = 6000; break;
+ case CodecProfileLevel.HEVCMainTierLevel31:
+ case CodecProfileLevel.HEVCHighTierLevel31:
+ FR = 33.75; FS = 983040; BR = 10000; break;
+ case CodecProfileLevel.HEVCMainTierLevel4:
+ FR = 30; FS = 2228224; BR = 12000; break;
+ case CodecProfileLevel.HEVCHighTierLevel4:
+ FR = 30; FS = 2228224; BR = 30000; break;
+ case CodecProfileLevel.HEVCHighTierLevel41:
+ FR = 60; FS = 2228224; BR = 20000; break;
+ case CodecProfileLevel.HEVCMainTierLevel41:
+ FR = 60; FS = 2228224; BR = 50000; break;
+ case CodecProfileLevel.HEVCHighTierLevel5:
+ FR = 30; FS = 8912896; BR = 25000; break;
+ case CodecProfileLevel.HEVCMainTierLevel5:
+ FR = 30; FS = 8912896; BR = 100000; break;
+ case CodecProfileLevel.HEVCHighTierLevel51:
+ FR = 60; FS = 8912896; BR = 40000; break;
+ case CodecProfileLevel.HEVCMainTierLevel51:
+ FR = 60; FS = 8912896; BR = 160000; break;
+ case CodecProfileLevel.HEVCMainTierLevel52:
+ FR = 120; FS = 8912896; BR = 60000; break;
+ case CodecProfileLevel.HEVCHighTierLevel52:
+ FR = 120; FS = 8912896; BR = 240000; break;
+ case CodecProfileLevel.HEVCMainTierLevel6:
+ FR = 30; FS = 35651584; BR = 60000; break;
+ case CodecProfileLevel.HEVCHighTierLevel6:
+ FR = 30; FS = 35651584; BR = 240000; break;
+ case CodecProfileLevel.HEVCHighTierLevel61:
+ FR = 60; FS = 35651584; BR = 120000; break;
+ case CodecProfileLevel.HEVCMainTierLevel61:
+ FR = 60; FS = 35651584; BR = 480000; break;
+ case CodecProfileLevel.HEVCHighTierLevel62:
+ FR = 120; FS = 35651584; BR = 240000; break;
+ case CodecProfileLevel.HEVCMainTierLevel62:
+ FR = 120; FS = 35651584; BR = 800000; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.HEVCProfileMain:
+ case CodecProfileLevel.HEVCProfileMain10:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+
+ /* DPB logic:
+ if (width * height <= FS / 4) DPB = 16;
+ else if (width * height <= FS / 2) DPB = 12;
+ else if (width * height <= FS * 0.75) DPB = 8;
+ else DPB = 6;
+ */
+
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ }
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ // CTBs are at least 8x8
+ maxBlocks = Utils.divUp(maxBlocks, 8 * 8);
+ maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8);
+ maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8);
+
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond, 8, 8, 1, 1);
+ } else {
+ Log.w(TAG, "Unsupported mime " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ }
+ mBitrateRange = Range.create(1, maxBps);
+ mParent.mError |= errors;
+ }
+ };
+
+ VideoCapabilities mVideoCaps;
+
+ private boolean isVideo() {
+ return mVideoCaps != null;
+ }
+
+ /**
+ * Returns the video capabilities or {@code null} if this is not a video codec.
+ */
+ public final VideoCapabilities getVideoCapabilities() {
+ return mVideoCaps;
+ }
+
+ /**
+ * Retrieve the codec capabilities for a certain {@code mime type}, {@code
+ * profile} and {@code level}. If the type, or profile-level combination
+ * is not understood by the framework, it returns null.
+ */
+ public static final CodecCapabilities CreateFromProfileLevel(
+ String mime, int profile, int level) {
+ CodecProfileLevel pl = new CodecProfileLevel();
+ pl.profile = profile;
+ pl.level = level;
+ MediaFormat defaultFormat = new MediaFormat();
+ defaultFormat.setString(MediaFormat.KEY_MIME, mime);
+
+ CodecCapabilities ret = new CodecCapabilities(
+ new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
+ 0 /* flags */, defaultFormat, new MediaFormat() /* info */);
+ if (ret.mError != 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /* package private */ CodecCapabilities(
+ CodecProfileLevel[] profLevs, int[] colFmts,
+ boolean encoder, int flags,
+ Map<String, Object>defaultFormatMap,
+ Map<String, Object>capabilitiesMap) {
+ this(profLevs, colFmts, encoder, flags,
+ new MediaFormat(defaultFormatMap),
+ new MediaFormat(capabilitiesMap));
+ }
+
+ private MediaFormat mCapabilitiesInfo;
+
+ /* package private */ CodecCapabilities(
+ CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags,
+ MediaFormat defaultFormat, MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+ profileLevels = profLevs;
+ colorFormats = colFmts;
+ mFlagsVerified = flags;
+ mDefaultFormat = defaultFormat;
+ mCapabilitiesInfo = info;
+ mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME);
+
+ if (mMime.toLowerCase().startsWith("audio/")) {
+ mAudioCaps = AudioCapabilities.create(info, this);
+ } else if (mMime.toLowerCase().startsWith("video/")) {
+ mVideoCaps = VideoCapabilities.create(info, this);
+ }
+ if (encoder) {
+ mEncoderCaps = EncoderCapabilities.create(info, this);
+ }
+
+ for (Feature feat: getValidFeatures()) {
+ Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName);
+ if (yesNo == null) {
+ continue;
+ } else if (yesNo > 0) {
+ mFlagsRequired |= feat.mValue;
+ } else {
+ mFlagsSupported |= feat.mValue;
+ }
+ // TODO restrict features by mFlagsVerified once all codecs reliably verify them
+ }
+ }
+
+ /**
+ * A class that supports querying the audio capabilities of a codec.
+ */
+ public static final class AudioCapabilities extends BaseCapabilities {
+ private int[] mSampleRates;
+ private Range<Integer>[] mSampleRateRanges;
+ private int mMaxInputChannelCount;
+
+ /**
+ * Returns the array of supported sample rates if the codec
+ * supports only discrete values. Otherwise, it returns
+ * {@code null}. The array is sorted in ascending order.
+ */
+ public final int[] getSupportedSampleRates() {
+ return Arrays.copyOf(mSampleRates, mSampleRates.length);
+ }
+
+ /**
+ * Returns the array of supported sample rate ranges. The
+ * array is sorted in ascending order, and the ranges are
+ * distinct.
+ */
+ public final Range<Integer>[] getSupportedSampleRateRanges() {
+ return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+ }
+
+ /**
+ * Returns the maximum number of input channels supported. The codec
+ * supports any number of channels between 1 and this maximum value.
+ */
+ public final int getMaxInputChannelCount() {
+ return mMaxInputChannelCount;
+ }
+
+ /* no public constructor */
+ private AudioCapabilities() { }
+
+ /** @hide */
+ public static AudioCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ AudioCapabilities caps = new AudioCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ /** @hide */
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ super.init(info, parent);
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ private void initWithPlatformLimits() {
+ mMaxInputChannelCount = 30;
+ // mBitrateRange = Range.create(1, 320000);
+ mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
+ mSampleRates = null;
+ }
+
+ private boolean supports(Integer sampleRate, Integer inputChannels) {
+ // channels and sample rates are checked orthogonally
+ if (inputChannels != null &&
+ (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
+ return false;
+ }
+ if (sampleRate != null) {
+ int ix = Utils.binarySearchDistinctRanges(
+ mSampleRateRanges, sampleRate);
+ if (ix < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Query whether the sample rate is supported by the codec.
+ */
+ public final boolean isSampleRateSupported(int sampleRate) {
+ return supports(sampleRate, null);
+ }
+
+ /** modifies rates */
+ private void limitSampleRates(int[] rates) {
+ Arrays.sort(rates);
+ ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
+ for (int rate: rates) {
+ if (supports(rate, null /* channels */)) {
+ ranges.add(Range.create(rate, rate));
+ }
+ }
+ mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
+ createDiscreteSampleRates();
+ }
+
+ private void createDiscreteSampleRates() {
+ mSampleRates = new int[mSampleRateRanges.length];
+ for (int i = 0; i < mSampleRateRanges.length; i++) {
+ mSampleRates[i] = mSampleRateRanges[i].getLower();
+ }
+ }
+
+ /** modifies rateRanges */
+ private void limitSampleRates(Range<Integer>[] rateRanges) {
+ sortDistinctRanges(rateRanges);
+ mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
+
+ // check if all values are discrete
+ for (Range<Integer> range: mSampleRateRanges) {
+ if (range.getLower() != range.getUpper()) {
+ mSampleRates = null;
+ return;
+ }
+ }
+ createDiscreteSampleRates();
+ }
+
+ private void applyLevelLimits() {
+ int[] sampleRates = null;
+ Range<Integer> sampleRateRange = null, bitRates = null;
+ int maxChannels = 0;
+ String mime = mParent.getMime();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
+ sampleRates = new int[] {
+ 8000, 11025, 12000,
+ 16000, 22050, 24000,
+ 32000, 44100, 48000 };
+ bitRates = Range.create(8192, 327680);
+ maxChannels = 2;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(4750, 12200);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+ sampleRates = new int[] { 16000 };
+ bitRates = Range.create(6600, 23850);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ sampleRates = new int[] {
+ 7350, 8000,
+ 11025, 12000, 16000,
+ 22050, 24000, 32000,
+ 44100, 48000, 64000,
+ 88200, 96000 };
+ bitRates = Range.create(8000, 510000);
+ maxChannels = 48;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
+ bitRates = Range.create(32000, 500000);
+ sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
+ bitRates = Range.create(6000, 510000);
+ sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
+ sampleRateRange = Range.create(1, 96000);
+ bitRates = Range.create(1, 10000000);
+ maxChannels = 6;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ sampleRateRange = Range.create(1, 655350);
+ // lossless codec, so bitrate is ignored
+ maxChannels = 255;
+ }
+
+ // restrict ranges
+ if (sampleRates != null) {
+ limitSampleRates(sampleRates);
+ } else if (sampleRateRange != null) {
+ limitSampleRates(new Range[] { sampleRateRange });
+ }
+ applyLimits(maxChannels, bitRates);
+ }
+
+ private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
+ mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
+ .clamp(maxInputChannels);
+ if (bitRates != null) {
+ mBitrateRange = mBitrateRange.intersect(bitRates);
+ }
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ int maxInputChannels = 1;
+ Range<Integer> bitRates = POSITIVE_INTEGERS;
+
+ if (info.containsKey("sample-rate-ranges")) {
+ String[] rateStrings = info.getString("sample-rate-ranges").split(",");
+ Range<Integer>[] rateRanges = new Range[rateStrings.length];
+ for (int i = 0; i < rateStrings.length; i++) {
+ rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
+ }
+ limitSampleRates(rateRanges);
+ }
+ if (info.containsKey("max-channel-count")) {
+ maxInputChannels = info.getInteger("max-channel-count");
+ }
+ if (info.containsKey("bitrate-range")) {
+ bitRates = bitRates.intersect(
+ Utils.parseIntRange(info.getString("bitrate"), bitRates));
+ }
+ applyLimits(maxInputChannels, bitRates);
+ }
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ Map<String, Object> map = format.getMap();
+ Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
+ Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
+ if (!supports(sampleRate, channels)) {
+ return false;
+ }
+
+ // nothing to do for:
+ // KEY_CHANNEL_MASK: codecs don't get this
+ // KEY_IS_ADTS: required feature for all AAC decoders
+ return true;
+ }
+ };
+
+ AudioCapabilities mAudioCaps;
+ private boolean isAudio() {
+ return mAudioCaps != null;
+ }
+
+ /**
+ * Returns the audio capabilities or {@code null} if this is not an audio codec.
+ */
+ public final AudioCapabilities getAudioCapabilities() {
+ return mAudioCaps;
+ }
+
+ /** @hide */
+ public CodecCapabilities dup() {
+ return new CodecCapabilities(
+ // clone writable arrays
+ Arrays.copyOf(profileLevels, profileLevels.length),
+ Arrays.copyOf(colorFormats, colorFormats.length),
+ isEncoder(),
+ mFlagsVerified,
+ mDefaultFormat,
+ mCapabilitiesInfo);
+ }
};
/**
@@ -193,6 +1915,7 @@
public static final int AVCLevel42 = 0x2000;
public static final int AVCLevel5 = 0x4000;
public static final int AVCLevel51 = 0x8000;
+ public static final int AVCLevel52 = 0x10000;
// from OMX_VIDEO_H263PROFILETYPE
public static final int H263ProfileBaseline = 0x01;
@@ -319,6 +2042,30 @@
*/
public final CodecCapabilities getCapabilitiesForType(
String type) {
- return MediaCodecList.getCodecCapabilities(mIndex, type);
+ CodecCapabilities caps = mCaps.get(type);
+ if (caps == null) {
+ throw new IllegalArgumentException("codec does not support type");
+ }
+ // clone writable object
+ return caps.dup();
+ }
+
+ /** @hide */
+ public MediaCodecInfo makeRegular() {
+ ArrayList<CodecCapabilities> caps = new ArrayList<CodecCapabilities>();
+ for (CodecCapabilities c: mCaps.values()) {
+ if (c.isRegular()) {
+ caps.add(c);
+ }
+ }
+ if (caps.size() == 0) {
+ return null;
+ } else if (caps.size() == mCaps.size()) {
+ return this;
+ }
+
+ return new MediaCodecInfo(
+ mName, mIsEncoder,
+ caps.toArray(new CodecCapabilities[caps.size()]));
}
}
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index 817ca31..f5d99e4 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -16,7 +16,13 @@
package android.media;
+import android.util.Log;
+
import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+
+import java.util.ArrayList;
+import java.util.Arrays;
/**
* Allows you to enumerate available codecs, each specified as a {@link MediaCodecInfo} object,
@@ -25,17 +31,74 @@
* <p>See {@link MediaCodecInfo} for sample usage.
*/
final public class MediaCodecList {
- /**
- * Count the number of available codecs.
- */
- public static native final int getCodecCount();
+ private static final String TAG = "MediaCodecList";
+ /**
+ * Count the number of available (regular) codecs.
+ *
+ * @see #REGULAR_CODECS
+ */
+ public static final int getCodecCount() {
+ initCodecList();
+ return sRegularCodecInfos.length;
+ }
+
+ private static native final int native_getCodecCount();
+
+ /**
+ * Return the {@link MediaCodecInfo} object for the codec at
+ * the given {@code index} in the regular list.
+ *
+ * @see #REGULAR_CODECS
+ */
public static final MediaCodecInfo getCodecInfoAt(int index) {
- if (index < 0 || index > getCodecCount()) {
+ initCodecList();
+ if (index < 0 || index > sRegularCodecInfos.length) {
throw new IllegalArgumentException();
}
+ return sRegularCodecInfos[index];
+ }
- return new MediaCodecInfo(index);
+ private static Object sInitLock = new Object();
+ private static MediaCodecInfo[] sAllCodecInfos;
+ private static MediaCodecInfo[] sRegularCodecInfos;
+
+ private static final void initCodecList() {
+ synchronized (sInitLock) {
+ if (sRegularCodecInfos == null) {
+ int count = native_getCodecCount();
+ ArrayList<MediaCodecInfo> regulars = new ArrayList<MediaCodecInfo>();
+ ArrayList<MediaCodecInfo> all = new ArrayList<MediaCodecInfo>();
+ for (int index = 0; index < count; index++) {
+ try {
+ MediaCodecInfo info = getNewCodecInfoAt(index);
+ all.add(info);
+ info = info.makeRegular();
+ if (info != null) {
+ regulars.add(info);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Could not get codec capabilities", e);
+ }
+ }
+ sRegularCodecInfos =
+ regulars.toArray(new MediaCodecInfo[regulars.size()]);
+ sAllCodecInfos =
+ all.toArray(new MediaCodecInfo[all.size()]);
+ }
+ }
+ }
+
+ private static MediaCodecInfo getNewCodecInfoAt(int index) {
+ String[] supportedTypes = getSupportedTypes(index);
+ MediaCodecInfo.CodecCapabilities[] caps =
+ new MediaCodecInfo.CodecCapabilities[supportedTypes.length];
+ int typeIx = 0;
+ for (String type: supportedTypes) {
+ caps[typeIx] = getCodecCapabilities(index, type);
+ }
+ return new MediaCodecInfo(
+ getCodecName(index), isEncoder(index), caps);
}
/* package private */ static native final String getCodecName(int index);
@@ -51,10 +114,95 @@
private static native final void native_init();
- private MediaCodecList() {}
+ /**
+ * Use in {@link #MediaCodecList} to enumerate only codecs that are suitable
+ * for normal playback and recording.
+ */
+ public static final int REGULAR_CODECS = 0;
+
+ /**
+ * Use in {@link #MediaCodecList} to enumerate all codecs, even ones that are
+ * not suitable for normal playback or recording.
+ */
+ public static final int ALL_CODECS = 1;
+
+ private MediaCodecList() {
+ this(REGULAR_CODECS);
+ }
+
+ private MediaCodecInfo[] mCodecInfos;
+
+ /**
+ * Create a list of media-codecs of a specific kind.
+ * @param kind Either {@code REGULAR_CODECS} or {@code ALL_CODECS}.
+ */
+ public MediaCodecList(int kind) {
+ initCodecList();
+ if (kind == REGULAR_CODECS) {
+ mCodecInfos = sRegularCodecInfos;
+ } else {
+ mCodecInfos = sAllCodecInfos;
+ }
+ }
+
+ /**
+ * Returns the list of {@link MediaCodecInfo} objects for the list
+ * of media-codecs.
+ */
+ public final MediaCodecInfo[] getCodecInfos() {
+ return Arrays.copyOf(mCodecInfos, mCodecInfos.length);
+ }
static {
System.loadLibrary("media_jni");
native_init();
+
+ // mediaserver is not yet alive here
+ }
+
+ /**
+ * Find a decoder supporting a given {@link MediaFormat} in the list
+ * of media-codecs.
+ *
+ * @param format A decoder media format with optional feature directives.
+ * @throws IllegalArgumentException if format is not a valid media format.
+ * @throws NullPointerException if format is null.
+ * @return the name of a decoder that supports the given format and feature
+ * requests, or {@code null} if no such codec has been found.
+ */
+ public final String findDecoderForFormat(MediaFormat format) {
+ return findCodecForFormat(false /* encoder */, format);
+ }
+
+ /**
+ * Find an encoder supporting a given {@link MediaFormat} in the list
+ * of media-codecs.
+ *
+ * @param format An encoder media format with optional feature directives.
+ * @throws IllegalArgumentException if format is not a valid media format.
+ * @throws NullPointerException if format is null.
+ * @return the name of an encoder that supports the given format and feature
+ * requests, or {@code null} if no such codec has been found.
+ */
+ public final String findEncoderForFormat(MediaFormat format) {
+ return findCodecForFormat(false /* encoder */, format);
+ }
+
+ private String findCodecForFormat(boolean encoder, MediaFormat format) {
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ for (MediaCodecInfo info: mCodecInfos) {
+ if (info.isEncoder() != encoder) {
+ continue;
+ }
+ try {
+ MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+ if (caps != null && caps.isFormatSupported(format)) {
+ return info.getName();
+ }
+ } catch (IllegalArgumentException e) {
+ // type is not supported
+ }
+ }
+ return null;
}
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 0f7906e..16a2235 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -76,9 +76,55 @@
* </table>
*/
public final class MediaFormat {
+ public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
+ public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+ public static final String MIMETYPE_VIDEO_AVC = "video/avc";
+ public static final String MIMETYPE_VIDEO_HEVC = "video/hevc";
+ public static final String MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
+ public static final String MIMETYPE_VIDEO_H263 = "video/3gpp";
+ public static final String MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
+ public static final String MIMETYPE_VIDEO_RAW = "video/raw";
+
+ public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
+ public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
+ public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
+ public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
+ public static final String MIMETYPE_AUDIO_QCELP = "audio/qcelp";
+ public static final String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+ public static final String MIMETYPE_AUDIO_OPUS = "audio/opus";
+ public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
+ public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
+ public static final String MIMETYPE_AUDIO_RAW = "audio/raw";
+ public static final String MIMETYPE_AUDIO_FLAC = "audio/flac";
+ public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
+ public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
+
+ /**
+ * MIME type for WebVTT subtitle data.
+ */
+ public static final String MIMETYPE_TEXT_VTT = "text/vtt";
+
+ /**
+ * MIME type for CEA-608 closed caption data.
+ */
+ public static final String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
+
private Map<String, Object> mMap;
/**
+ * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities}
+ * feature name describing a required or optional feature for a codec capabilities
+ * query.
+ * The associated value is an integer, where non-0 value means the feature is
+ * requested to be present, while 0 value means the feature is requested to be not
+ * present.
+ * @see MediaCodecList#findDecoderForFormat
+ * @see MediaCodecList#findEncoderForFormat
+ * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+ */
+ public static final String KEY_FEATURE_ = "feature-";
+
+ /**
* A key describing the mime type of the MediaFormat.
* The associated value is a string.
*/
@@ -222,6 +268,51 @@
public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
/**
+ * A key describing the encoding complexity.
+ * The associated value is an integer. These values are device and codec specific,
+ * but lower values generally result in faster and/or less power-hungry encoding.
+ *
+ * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getComplexityRange
+ */
+ public static final String KEY_COMPLEXITY = "complexity";
+
+ /**
+ * A key describing the desired encoding quality.
+ * The associated value is an integer. This key is only supported for encoders
+ * that are configured in constant-quality mode. These values are device and
+ * codec specific, but lower values generally result in more efficient
+ * (smaller-sized) encoding.
+ *
+ * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getQualityRange
+ */
+ public static final String KEY_QUALITY = "quality";
+
+ /**
+ * A key describing the desired profile to be used by an encoder.
+ * Constants are declared in {@link MediaCodecInfo.CodecProfileLevel}.
+ * This key is only supported for codecs that specify a profile.
+ *
+ * @see MediaCodecInfo.CodecCapabilities#profileLevels
+ */
+ public static final String KEY_PROFILE = "profile";
+
+ /**
+ * A key describing the desired bitrate mode to be used by an encoder.
+ * Constants are declared in {@link MediaCodecInfo.CodecCapabilities}.
+ *
+ * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#isBitrateModeSupported
+ */
+ public static final String KEY_BITRATE_MODE = "bitrate-mode";
+
+ /**
+ * A key describing the reference clock ID for a tunneled codec.
+ * The associated value is an integer.
+ *
+ * @see MediaCodecInfo.CodecCapabilities#FEATURE_TunneledPlayback
+ */
+ public static final String KEY_REFERENCE_CLOCK_ID = "reference-clock-id";
+
+ /**
* A key for boolean AUTOSELECT behavior for the track. Tracks with AUTOSELECT=true
* are considered when automatically selecting a track without specific user
* choice, based on the current locale.
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
new file mode 100644
index 0000000..12910ec
--- /dev/null
+++ b/media/java/android/media/Utils.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Vector;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+// package private
+class Utils {
+ private static final String TAG = "Utils";
+
+ /**
+ * Sorts distinct (non-intersecting) range array in ascending order.
+ * @throws java.lang.IllegalArgumentException if ranges are not distinct
+ */
+ public static <T extends Comparable<? super T>> void sortDistinctRanges(Range<T>[] ranges) {
+ Arrays.sort(ranges, new Comparator<Range<T>>() {
+ @Override
+ public int compare(Range<T> lhs, Range<T> rhs) {
+ if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
+ return -1;
+ } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
+ return 1;
+ }
+ throw new IllegalArgumentException(
+ "sample rate ranges must be distinct (" + lhs + " and " + rhs + ")");
+ }
+ });
+ }
+
+ /**
+ * Returns the intersection of two sets of non-intersecting ranges
+ * @param one a sorted set of non-intersecting ranges in ascending order
+ * @param another another sorted set of non-intersecting ranges in ascending order
+ * @return the intersection of the two sets, sorted in ascending order
+ */
+ public static <T extends Comparable<? super T>>
+ Range<T>[] intersectSortedDistinctRanges(Range<T>[] one, Range<T>[] another) {
+ int ix = 0;
+ Vector<Range<T>> result = new Vector<Range<T>>();
+ for (Range<T> range: another) {
+ while (ix < one.length &&
+ one[ix].getUpper().compareTo(range.getLower()) < 0) {
+ ++ix;
+ }
+ while (ix < one.length &&
+ one[ix].getUpper().compareTo(range.getUpper()) < 0) {
+ result.add(range.intersect(one[ix]));
+ ++ix;
+ }
+ if (ix == one.length) {
+ break;
+ }
+ if (one[ix].getLower().compareTo(range.getUpper()) <= 0) {
+ result.add(range.intersect(one[ix]));
+ }
+ }
+ return result.toArray(new Range[result.size()]);
+ }
+
+ /**
+ * Returns the index of the range that contains a value in a sorted array of distinct ranges.
+ * @param ranges a sorted array of non-intersecting ranges in ascending order
+ * @param value the value to search for
+ * @return if the value is in one of the ranges, it returns the index of that range. Otherwise,
+ * the return value is {@code (-1-index)} for the {@code index} of the range that is
+ * immediately following {@code value}.
+ */
+ public static <T extends Comparable<? super T>>
+ int binarySearchDistinctRanges(Range<T>[] ranges, T value) {
+ return Arrays.binarySearch(ranges, Range.create(value, value),
+ new Comparator<Range<T>>() {
+ @Override
+ public int compare(Range<T> lhs, Range<T> rhs) {
+ if (lhs.getUpper().compareTo(rhs.getLower()) < 0) {
+ return -1;
+ } else if (lhs.getLower().compareTo(rhs.getUpper()) > 0) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+ }
+
+ /**
+ * Returns greatest common divisor
+ */
+ static int gcd(int a, int b) {
+ if (a == 0 && b == 0) {
+ return 1;
+ }
+ if (b < 0) {
+ b = -b;
+ }
+ if (a < 0) {
+ a = -a;
+ }
+ while (a != 0) {
+ int c = b % a;
+ b = a;
+ a = c;
+ }
+ return b;
+ }
+
+ /** Returns the equivalent factored range {@code newrange}, where for every
+ * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
+ * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
+ */
+ static Range<Integer>factorRange(Range<Integer> range, int factor) {
+ if (factor == 1) {
+ return range;
+ }
+ return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
+ }
+
+ /** Returns the equivalent factored range {@code newrange}, where for every
+ * {@code e}: {@code newrange.contains(e)} implies that {@code range.contains(e * factor)},
+ * and {@code !newrange.contains(e)} implies that {@code !range.contains(e * factor)}.
+ */
+ static Range<Long>factorRange(Range<Long> range, long factor) {
+ if (factor == 1) {
+ return range;
+ }
+ return Range.create(divUp(range.getLower(), factor), range.getUpper() / factor);
+ }
+
+ private static Rational scaleRatio(Rational ratio, int num, int den) {
+ int common = gcd(num, den);
+ num /= common;
+ den /= common;
+ return new Rational(
+ (int)(ratio.getNumerator() * (double)num), // saturate to int
+ (int)(ratio.getDenominator() * (double)den)); // saturate to int
+ }
+
+ static Range<Rational> scaleRange(Range<Rational> range, int num, int den) {
+ if (num == den) {
+ return range;
+ }
+ return Range.create(
+ scaleRatio(range.getLower(), num, den),
+ scaleRatio(range.getUpper(), num, den));
+ }
+
+ static Range<Integer> alignRange(Range<Integer> range, int align) {
+ return range.intersect(
+ divUp(range.getLower(), align) * align,
+ (range.getUpper() / align) * align);
+ }
+
+ static int divUp(int num, int den) {
+ return (num + den - 1) / den;
+ }
+
+ private static long divUp(long num, long den) {
+ return (num + den - 1) / den;
+ }
+
+ /**
+ * Returns least common multiple
+ */
+ private static long lcm(int a, int b) {
+ if (a == 0 || b == 0) {
+ throw new IllegalArgumentException("lce is not defined for zero arguments");
+ }
+ return (long)a * b / gcd(a, b);
+ }
+
+ static Range<Integer> intRangeFor(double v) {
+ return Range.create((int)v, (int)Math.ceil(v));
+ }
+
+ static Range<Long> longRangeFor(double v) {
+ return Range.create((long)v, (long)Math.ceil(v));
+ }
+
+ static Size parseSize(Object o, Size fallback) {
+ try {
+ return Size.parseSize((String) o);
+ } catch (ClassCastException e) {
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
+ return fallback;
+ }
+ Log.w(TAG, "could not parse size '" + o + "'");
+ return fallback;
+ }
+
+ static Range<Integer> parseIntRange(Object o, Range<Integer> fallback) {
+ try {
+ String s = (String)o;
+ int ix = s.indexOf('-');
+ if (ix >= 0) {
+ return Range.create(
+ Integer.parseInt(s.substring(0, ix), 10),
+ Integer.parseInt(s.substring(ix + 1), 10));
+ }
+ int value = Integer.parseInt(s);
+ return Range.create(value, value);
+ } catch (ClassCastException e) {
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
+ return fallback;
+ } catch (IllegalArgumentException e) {
+ }
+ Log.w(TAG, "could not parse integer range '" + o + "'");
+ return fallback;
+ }
+
+ static Range<Long> parseLongRange(Object o, Range<Long> fallback) {
+ try {
+ String s = (String)o;
+ int ix = s.indexOf('-');
+ if (ix >= 0) {
+ return Range.create(
+ Long.parseLong(s.substring(0, ix), 10),
+ Long.parseLong(s.substring(ix + 1), 10));
+ }
+ long value = Long.parseLong(s);
+ return Range.create(value, value);
+ } catch (ClassCastException e) {
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
+ return fallback;
+ } catch (IllegalArgumentException e) {
+ }
+ Log.w(TAG, "could not parse long range '" + o + "'");
+ return fallback;
+ }
+
+ static Range<Rational> parseRationalRange(Object o, Range<Rational> fallback) {
+ try {
+ String s = (String)o;
+ int ix = s.indexOf('-');
+ if (ix >= 0) {
+ return Range.create(
+ Rational.parseRational(s.substring(0, ix)),
+ Rational.parseRational(s.substring(ix + 1)));
+ }
+ Rational value = Rational.parseRational(s);
+ return Range.create(value, value);
+ } catch (ClassCastException e) {
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
+ return fallback;
+ } catch (IllegalArgumentException e) {
+ }
+ Log.w(TAG, "could not parse rational range '" + o + "'");
+ return fallback;
+ }
+
+ static Pair<Size, Size> parseSizeRange(Object o) {
+ try {
+ String s = (String)o;
+ int ix = s.indexOf('-');
+ if (ix >= 0) {
+ return Pair.create(
+ Size.parseSize(s.substring(0, ix)),
+ Size.parseSize(s.substring(ix + 1)));
+ }
+ Size value = Size.parseSize(s);
+ return Pair.create(value, value);
+ } catch (ClassCastException e) {
+ } catch (NumberFormatException e) {
+ } catch (NullPointerException e) {
+ return null;
+ } catch (IllegalArgumentException e) {
+ }
+ Log.w(TAG, "could not parse size range '" + o + "'");
+ return null;
+ }
+}
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index caa594e..ed1eeb9 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -19,11 +19,13 @@
#include <utils/Log.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaCodecList.h>
#include "android_runtime/AndroidRuntime.h"
#include "jni.h"
#include "JNIHelp.h"
+#include "android_media_Utils.h"
using namespace android;
@@ -111,10 +113,19 @@
Vector<MediaCodecList::ProfileLevel> profileLevels;
Vector<uint32_t> colorFormats;
uint32_t flags;
+ sp<AMessage> capabilities;
+
+ sp<AMessage> defaultFormat = new AMessage();
+ defaultFormat->setString("mime", typeStr);
+
+ // TODO query default-format also from codec/codec list
status_t err =
MediaCodecList::getInstance()->getCodecCapabilities(
- index, typeStr, &profileLevels, &colorFormats, &flags);
+ index, typeStr, &profileLevels, &colorFormats, &flags,
+ &capabilities);
+
+ bool isEncoder = MediaCodecList::getInstance()->isEncoder(index);
env->ReleaseStringUTFChars(type, typeStr);
typeStr = NULL;
@@ -124,15 +135,21 @@
return NULL;
}
+ jobject defaultFormatObj = NULL;
+ if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
+ return NULL;
+ }
+
+ jobject infoObj = NULL;
+ if (ConvertMessageToMap(env, capabilities, &infoObj)) {
+ env->DeleteLocalRef(defaultFormatObj);
+ return NULL;
+ }
+
jclass capsClazz =
env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
CHECK(capsClazz != NULL);
- jfieldID flagsField =
- env->GetFieldID(capsClazz, "flags", "I");
-
- jobject caps = env->AllocObject(capsClazz);
-
jclass profileLevelClazz =
env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
CHECK(profileLevelClazz != NULL);
@@ -160,18 +177,6 @@
profileLevelObj = NULL;
}
- jfieldID profileLevelsField = env->GetFieldID(
- capsClazz,
- "profileLevels",
- "[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
-
- env->SetObjectField(caps, profileLevelsField, profileLevelArray);
-
- env->SetIntField(caps, flagsField, flags);
-
- env->DeleteLocalRef(profileLevelArray);
- profileLevelArray = NULL;
-
jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
for (size_t i = 0; i < colorFormats.size(); ++i) {
@@ -179,14 +184,46 @@
env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
}
+ jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+ "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZI"
+ "Ljava/util/Map;Ljava/util/Map;)V");
+
+ jobject caps = env->NewObject(capsClazz, capsConstructID,
+ profileLevelArray, colorFormatsArray, isEncoder, flags,
+ defaultFormatObj, infoObj);
+
+#if 0
+ jfieldID profileLevelsField = env->GetFieldID(
+ capsClazz,
+ "profileLevels",
+ "[Landroid/media/MediaCodecInfo$CodecProfileLevel;");
+
+ env->SetObjectField(caps, profileLevelsField, profileLevelArray);
+
+ jfieldID flagsField =
+ env->GetFieldID(capsClazz, "mFlagsVerified", "I");
+
+ env->SetIntField(caps, flagsField, flags);
+
jfieldID colorFormatsField = env->GetFieldID(
capsClazz, "colorFormats", "[I");
env->SetObjectField(caps, colorFormatsField, colorFormatsArray);
+#endif
+
+ env->DeleteLocalRef(profileLevelArray);
+ profileLevelArray = NULL;
+
env->DeleteLocalRef(colorFormatsArray);
colorFormatsArray = NULL;
+ env->DeleteLocalRef(defaultFormatObj);
+ defaultFormatObj = NULL;
+
+ env->DeleteLocalRef(infoObj);
+ infoObj = NULL;
+
return caps;
}
@@ -194,7 +231,7 @@
}
static JNINativeMethod gMethods[] = {
- { "getCodecCount", "()I", (void *)android_media_MediaCodecList_getCodecCount },
+ { "native_getCodecCount", "()I", (void *)android_media_MediaCodecList_getCodecCount },
{ "getCodecName", "(I)Ljava/lang/String;",
(void *)android_media_MediaCodecList_getCodecName },
{ "isEncoder", "(I)Z", (void *)android_media_MediaCodecList_isEncoder },