diff options
author | Lajos Molnar <lajos@google.com> | 2014-07-18 12:10:33 -0700 |
---|---|---|
committer | Lajos Molnar <lajos@google.com> | 2014-07-19 02:14:00 -0700 |
commit | b58dc3132272a5ec1ad4792c3c9d48b4198bd57f (patch) | |
tree | ec3e6a0ec682799444c58652aee69d6a18abd601 | |
parent | 713964504960b27a15d4b780501d938961338913 (diff) | |
download | frameworks_base-b58dc3132272a5ec1ad4792c3c9d48b4198bd57f.zip frameworks_base-b58dc3132272a5ec1ad4792c3c9d48b4198bd57f.tar.gz frameworks_base-b58dc3132272a5ec1ad4792c3c9d48b4198bd57f.tar.bz2 |
Extend MediaCodecInfo to describe usable codec limits and features
Bug: 11990470
Bug: 12065651
Bug: 16131974
Change-Id: I841b8507e823f1ddf14754e34029a9bed4f402d8
-rw-r--r-- | api/current.txt | 80 | ||||
-rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 1869 | ||||
-rw-r--r-- | media/java/android/media/MediaCodecList.java | 158 | ||||
-rw-r--r-- | media/java/android/media/MediaFormat.java | 91 | ||||
-rw-r--r-- | media/java/android/media/Utils.java | 296 | ||||
-rw-r--r-- | media/jni/android_media_MediaCodecList.cpp | 71 |
6 files changed, 2482 insertions, 83 deletions
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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media { } 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media; * */ 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 @@ public final class MediaCodecInfo { * {@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 final static int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100; + 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 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 final class MediaCodecInfo { 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 class MediaCodecInfo { */ 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 @@ import android.media.MediaCodecInfo; * <p>See {@link MediaCodecInfo} for sample usage. */ final public class MediaCodecList { + private static final String TAG = "MediaCodecList"; + /** - * Count the number of available codecs. + * Count the number of available (regular) codecs. + * + * @see #REGULAR_CODECS */ - public static native final int getCodecCount(); + 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]; + } + + private static Object sInitLock = new Object(); + private static MediaCodecInfo[] sAllCodecInfos; + private static MediaCodecInfo[] sRegularCodecInfos; - return new MediaCodecInfo(index); + 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 @@ final public class MediaCodecList { 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 @@ import java.util.Map; * </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 final class MediaFormat { 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 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( 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 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( 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,6 +177,22 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( profileLevelObj = NULL; } + jintArray colorFormatsArray = env->NewIntArray(colorFormats.size()); + + for (size_t i = 0; i < colorFormats.size(); ++i) { + jint val = colorFormats.itemAt(i); + 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", @@ -167,26 +200,30 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( env->SetObjectField(caps, profileLevelsField, profileLevelArray); - env->SetIntField(caps, flagsField, flags); - - env->DeleteLocalRef(profileLevelArray); - profileLevelArray = NULL; - - jintArray colorFormatsArray = env->NewIntArray(colorFormats.size()); + jfieldID flagsField = + env->GetFieldID(capsClazz, "mFlagsVerified", "I"); - for (size_t i = 0; i < colorFormats.size(); ++i) { - jint val = colorFormats.itemAt(i); - env->SetIntArrayRegion(colorFormatsArray, i, 1, &val); - } + 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 void android_media_MediaCodecList_native_init(JNIEnv *env) { } 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 }, |