diff options
author | 2025-01-07 16:09:31 -0800 | |
---|---|---|
committer | 2025-01-07 16:09:31 -0800 | |
commit | f7ada53a3dda485f6772d3b83b564fe578e06fdc (patch) | |
tree | 79881cc3938cb9912949d7260c037f80dc223ba6 | |
parent | 8da39900e937657337b6ddec99e18f4407ec75b9 (diff) | |
parent | 2f916cb9d0867404ed9e3def8cd663adfc027d79 (diff) |
Merge changes from topic "revert-3434737-DMCCDOROHG" into main am: dbcf0c8d0c am: 2f916cb9d0
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/3439590
Change-Id: I43cb586d2d3079d683b709310d76d82f1a633012
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 5728 | ||||
-rw-r--r-- | media/jni/Android.bp | 2 | ||||
-rw-r--r-- | media/jni/android_media_CodecCapabilities.cpp | 1042 | ||||
-rw-r--r-- | media/jni/android_media_CodecCapabilities.h | 48 | ||||
-rw-r--r-- | media/jni/android_media_MediaCodec.cpp | 39 | ||||
-rw-r--r-- | media/jni/android_media_MediaCodecList.cpp | 154 | ||||
-rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 6 |
7 files changed, 2517 insertions, 4502 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 1449d0d8dd89..302969f58ba8 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -245,7 +245,12 @@ public final class MediaCodecInfo { * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type. */ public static final class CodecCapabilities { - private static final String TAG = "CodecCapabilities"; + public CodecCapabilities() { + } + + // CLASSIFICATION + private String mMime; + private int mMaxSupportedInstances; // LEGACY FIELDS @@ -623,6 +628,12 @@ public final class MediaCodecInfo { */ public int[] colorFormats; // NOTE this array is modifiable by user + // FEATURES + + private int mFlagsSupported; + private int mFlagsRequired; + private int mFlagsVerified; + /** * <b>video decoder only</b>: codec supports seamless resolution changes. */ @@ -812,692 +823,122 @@ public final class MediaCodecInfo { @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) public static final String FEATURE_DetachedSurface = "detached-surface"; - /** package private */ interface CodecCapsIntf { - CodecCapsIntf dup(); - - boolean isFeatureSupported(String name); - - boolean isFeatureRequired(String name); - - boolean isFormatSupported(MediaFormat format); - - MediaFormat getDefaultFormat(); - - String getMimeType(); - - int getMaxSupportedInstances(); - - AudioCapabilities getAudioCapabilities(); - - VideoCapabilities getVideoCapabilities(); - - EncoderCapabilities getEncoderCapabilities(); - - boolean isRegular(); - - String[] validFeatures(); - - CodecProfileLevel[] getProfileLevels(); - - int[] getColorFormats(); + /** + * Query codec feature capabilities. + * <p> + * These features are supported to be used by the codec. These + * include optional features that can be turned on, as well as + * features that are always on. + */ + public final boolean isFeatureSupported(String name) { + return checkFeature(name, mFlagsSupported); } - /* package private */ static final class CodecCapsLegacyImpl implements CodecCapsIntf { - // errors while reading profile levels - accessed from sister capabilities - int mError; - - private CodecProfileLevel[] mProfileLevels; - private int[] mColorFormats; - - // CLASSIFICATION - private String mMime; - private int mMaxSupportedInstances; - - // FEATURES - private int mFlagsSupported; - private int mFlagsRequired; - private int mFlagsVerified; - - // NEW-STYLE CAPABILITIES - private AudioCapabilities mAudioCaps; - private VideoCapabilities mVideoCaps; - private EncoderCapabilities mEncoderCaps; - private MediaFormat mDefaultFormat; - - private MediaFormat mCapabilitiesInfo; - - public CodecProfileLevel[] getProfileLevels() { - return mProfileLevels; - } - - public int[] getColorFormats() { - return mColorFormats; - } - - public CodecCapsLegacyImpl() {} - - public CodecCapsLegacyImpl dup() { - CodecCapsLegacyImpl caps = new CodecCapsLegacyImpl(); - - caps.mProfileLevels = Arrays.copyOf(mProfileLevels, mProfileLevels.length); - caps.mColorFormats = Arrays.copyOf(mColorFormats, mColorFormats.length); - - caps.mMime = mMime; - caps.mMaxSupportedInstances = mMaxSupportedInstances; - caps.mFlagsRequired = mFlagsRequired; - caps.mFlagsSupported = mFlagsSupported; - caps.mFlagsVerified = mFlagsVerified; - caps.mAudioCaps = mAudioCaps; - caps.mVideoCaps = mVideoCaps; - caps.mEncoderCaps = mEncoderCaps; - caps.mDefaultFormat = mDefaultFormat; - caps.mCapabilitiesInfo = mCapabilitiesInfo; - - return caps; - } - - public boolean isFeatureSupported(String name) { - return checkFeature(name, mFlagsSupported); - } - - public boolean isFeatureRequired(String name) { - return checkFeature(name, mFlagsRequired); - } - - // Flags are used for feature list creation so separate this into a private - // static class to delay reading the flags only when constructing the list. - private static class FeatureList { - private static Feature[] getDecoderFeatures() { - ArrayList<Feature> features = new ArrayList(); - features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true)); - features.add(new Feature(FEATURE_SecurePlayback, (1 << 1), false)); - features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false)); - features.add(new Feature(FEATURE_PartialFrame, (1 << 3), false)); - features.add(new Feature(FEATURE_FrameParsing, (1 << 4), false)); - features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false)); - features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false)); - features.add(new Feature(FEATURE_LowLatency, (1 << 7), true)); - if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) { - features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true)); - } - if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) { - features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true)); - } - - // feature to exclude codec from REGULAR codec list - features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); - - return features.toArray(new Feature[0]); - }; - - private static Feature[] decoderFeatures = getDecoderFeatures(); - - private static Feature[] getEncoderFeatures() { - ArrayList<Feature> features = new ArrayList(); - - features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false)); - features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false)); - features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false)); - features.add(new Feature(FEATURE_QpBounds, (1 << 3), false)); - features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false)); - features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false)); - if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) { - features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true)); - } - if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) { - features.add(new Feature(FEATURE_Roi, (1 << 7), true)); - } - - // feature to exclude codec from REGULAR codec list - features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); - - return features.toArray(new Feature[0]); - }; - - private static Feature[] encoderFeatures = getEncoderFeatures(); - - public static Feature[] getFeatures(boolean isEncoder) { - if (isEncoder) { - return encoderFeatures; - } else { - return decoderFeatures; - } - } - } - - /** @hide */ - public String[] validFeatures() { - Feature[] features = getValidFeatures(); - String[] res = new String[features.length]; - for (int i = 0; i < res.length; i++) { - if (!features[i].mInternal) { - res[i] = features[i].mName; - } - } - return res; - } - - private Feature[] getValidFeatures() { - return FeatureList.getFeatures(isEncoder()); - } - - private boolean checkFeature(String name, int flags) { - for (Feature feat: getValidFeatures()) { - if (feat.mName.equals(name)) { - return (flags & feat.mValue) != 0; - } - } - return false; - } - - public boolean isRegular() { - // regular codecs only require default features - for (Feature feat: getValidFeatures()) { - if (!feat.mDefault && isFeatureRequired(feat.mName)) { - return false; - } - } - return true; - } - - public 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()) { - if (feat.mInternal) { - continue; - } - - Integer yesNo = (Integer) map.get(MediaFormat.KEY_FEATURE_ + feat.mName); - if (yesNo == null) { - continue; - } - if ((yesNo == 1 && !isFeatureSupported(feat.mName)) - || (yesNo == 0 && isFeatureRequired(feat.mName))) { - return false; - } - } - - Integer profile = (Integer) map.get(MediaFormat.KEY_PROFILE); - Integer level = (Integer) map.get(MediaFormat.KEY_LEVEL); - - if (profile != null) { - if (!supportsProfileLevel(profile, level)) { - return false; - } + /** + * 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); + } - // If we recognize this profile, check that this format is supported by the - // highest level supported by the codec for that profile. (Ignore specified - // level beyond the above profile/level check as level is only used as a - // guidance. E.g. AVC Level 1 CIF format is supported if codec supports - // level 1.1 even though max size for Level 1 is QCIF. However, MPEG2 Simple - // Profile 1080p format is not supported even if codec supports Main Profile - // Level High, as Simple Profile does not support 1080p. - CodecCapsLegacyImpl levelCaps = null; - int maxLevel = 0; - for (CodecProfileLevel pl : mProfileLevels) { - if (pl.profile == profile && pl.level > maxLevel) { - // H.263 levels are not completely ordered: - // Level45 support only implies Level10 support - if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263) - || pl.level != CodecProfileLevel.H263Level45 - || maxLevel == CodecProfileLevel.H263Level10) { - maxLevel = pl.level; - } - } - } - levelCaps = createFromProfileLevel(mMime, profile, maxLevel); - // We must remove the profile from this format otherwise - // levelCaps.isFormatSupported will get into this same condition and loop - // forever. Furthermore, since levelCaps does not contain features and bitrate - // specific keys, keep only keys relevant for a level check. - Map<String, Object> levelCriticalFormatMap = new HashMap<>(map); - final Set<String> criticalKeys = isVideo() - ? VideoCapabilities.VideoCapsLegacyImpl.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS - : isAudio() - ? AudioCapabilities.AudioCapsLegacyImpl.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS - : null; - - // critical keys will always contain KEY_MIME, but should also contain others - // to be meaningful - if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) { - levelCriticalFormatMap.keySet().retainAll(criticalKeys); - - MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap); - if (!levelCaps.isFormatSupported(levelCriticalFormat)) { - 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; + // Flags are used for feature list creation so separate this into a private + // static class to delay reading the flags only when constructing the list. + private static class FeatureList { + private static Feature[] getDecoderFeatures() { + ArrayList<Feature> features = new ArrayList(); + features.add(new Feature(FEATURE_AdaptivePlayback, (1 << 0), true)); + features.add(new Feature(FEATURE_SecurePlayback, (1 << 1), false)); + features.add(new Feature(FEATURE_TunneledPlayback, (1 << 2), false)); + features.add(new Feature(FEATURE_PartialFrame, (1 << 3), false)); + features.add(new Feature(FEATURE_FrameParsing, (1 << 4), false)); + features.add(new Feature(FEATURE_MultipleFrames, (1 << 5), false)); + features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 6), false)); + features.add(new Feature(FEATURE_LowLatency, (1 << 7), true)); + if (GetFlag(() -> android.media.codec.Flags.dynamicColorAspects())) { + features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true)); } - return true; - } - - private static boolean supportsBitrate( - Range<Integer> bitrateRange, MediaFormat format) { - Map<String, Object> map = format.getMap(); - - // consider max bitrate over average bitrate for support - Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE); - Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE); - if (bitrate == null) { - bitrate = maxBitrate; - } else if (maxBitrate != null) { - bitrate = Math.max(bitrate, maxBitrate); + if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) { + features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true)); } - if (bitrate != null && bitrate > 0) { - return bitrateRange.contains(bitrate); - } + // feature to exclude codec from REGULAR codec list + features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); - return true; - } - - private boolean supportsProfileLevel(int profile, Integer level) { - for (CodecProfileLevel pl: mProfileLevels) { - if (pl.profile != profile) { - continue; - } - - // No specific level requested - if (level == null) { - return true; - } - - // AAC doesn't use levels - if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { - return true; - } - - // DTS doesn't use levels - if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS) - || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD) - || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) { - return true; - } - - // H.263 levels are not completely ordered: - // Level45 support only implies Level10 support - if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { - if (pl.level != level && pl.level == CodecProfileLevel.H263Level45 - && level > CodecProfileLevel.H263Level10) { - continue; - } - } + return features.toArray(new Feature[0]); + }; - // MPEG4 levels are not completely ordered: - // Level1 support only implies Level0 (and not Level0b) support - if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { - if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1 - && level > CodecProfileLevel.MPEG4Level0) { - continue; - } - } + private static Feature[] decoderFeatures = getDecoderFeatures(); - // HEVC levels incorporate both tiers and levels. Verify tier support. - if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { - boolean supportsHighTier = - (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0; - boolean checkingHighTier = - (level & CodecProfileLevel.HEVCHighTierLevels) != 0; - // high tier levels are only supported by other high tier levels - if (checkingHighTier && !supportsHighTier) { - continue; - } - } + private static Feature[] getEncoderFeatures() { + ArrayList<Feature> features = new ArrayList(); - if (pl.level >= level) { - // if we recognize the listed profile/level, we must also recognize the - // profile/level arguments. - if (createFromProfileLevel(mMime, profile, pl.level) != null) { - return createFromProfileLevel(mMime, profile, level) != null; - } - return true; - } + features.add(new Feature(FEATURE_IntraRefresh, (1 << 0), false)); + features.add(new Feature(FEATURE_MultipleFrames, (1 << 1), false)); + features.add(new Feature(FEATURE_DynamicTimestamp, (1 << 2), false)); + features.add(new Feature(FEATURE_QpBounds, (1 << 3), false)); + features.add(new Feature(FEATURE_EncodingStatistics, (1 << 4), false)); + features.add(new Feature(FEATURE_HdrEditing, (1 << 5), false)); + if (GetFlag(() -> android.media.codec.Flags.hlgEditing())) { + features.add(new Feature(FEATURE_HlgEditing, (1 << 6), true)); } - return false; - } - - public MediaFormat getDefaultFormat() { - return mDefaultFormat; - } - - public String getMimeType() { - return mMime; - } - - public int getMaxSupportedInstances() { - return mMaxSupportedInstances; - } - - private boolean isAudio() { - return mAudioCaps != null; - } - - public AudioCapabilities getAudioCapabilities() { - return mAudioCaps; - } - - private boolean isEncoder() { - return mEncoderCaps != null; - } - - public EncoderCapabilities getEncoderCapabilities() { - return mEncoderCaps; - } - - private boolean isVideo() { - return mVideoCaps != null; - } - - public VideoCapabilities getVideoCapabilities() { - return mVideoCaps; - } - - public static CodecCapsLegacyImpl 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); - - CodecCapsLegacyImpl ret = new CodecCapsLegacyImpl( - new CodecProfileLevel[] { pl }, new int[0], true /* encoder */, - defaultFormat, new MediaFormat() /* info */); - if (ret.mError != 0) { - return null; + if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) { + features.add(new Feature(FEATURE_Roi, (1 << 7), true)); } - return ret; - } - - /* package private */ CodecCapsLegacyImpl( - CodecProfileLevel[] profLevs, int[] colFmts, - boolean encoder, - Map<String, Object> defaultFormatMap, - Map<String, Object> capabilitiesMap) { - this(profLevs, colFmts, encoder, - new MediaFormat(defaultFormatMap), - new MediaFormat(capabilitiesMap)); - } - /* package private */ CodecCapsLegacyImpl( - CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, - MediaFormat defaultFormat, MediaFormat info) { - final Map<String, Object> map = info.getMap(); - mColorFormats = colFmts; - mFlagsVerified = 0; // TODO: remove as it is unused - mDefaultFormat = defaultFormat; - mCapabilitiesInfo = info; - mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME); - - /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any - supported profiles. Determine the level for them using the info they provide. */ - if (profLevs.length == 0 - && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { - CodecProfileLevel profLev = new CodecProfileLevel(); - profLev.profile = CodecProfileLevel.VP9Profile0; - profLev.level = VideoCapabilities.VideoCapsLegacyImpl.equivalentVP9Level(info); - profLevs = new CodecProfileLevel[] { profLev }; - } - mProfileLevels = profLevs; - - if (mMime.toLowerCase().startsWith("audio/")) { - mAudioCaps = AudioCapabilities.create(info, this); - mAudioCaps.getDefaultFormat(mDefaultFormat); - } else if (mMime.toLowerCase().startsWith("video/") - || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) { - mVideoCaps = VideoCapabilities.create(info, this); - } - if (encoder) { - mEncoderCaps = EncoderCapabilities.create(info, this); - mEncoderCaps.getDefaultFormat(mDefaultFormat); - } + // feature to exclude codec from REGULAR codec list + features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); - final Map<String, Object> global = MediaCodecList.getGlobalSettings(); - mMaxSupportedInstances = Utils.parseIntSafely( - global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES); + return features.toArray(new Feature[0]); + }; - int maxInstances = Utils.parseIntSafely( - map.get("max-concurrent-instances"), mMaxSupportedInstances); - mMaxSupportedInstances = - Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances); + private static Feature[] encoderFeatures = getEncoderFeatures(); - for (Feature feat: getValidFeatures()) { - String key = MediaFormat.KEY_FEATURE_ + feat.mName; - Integer yesNo = (Integer)map.get(key); - if (yesNo == null) { - continue; - } - if (yesNo > 0) { - mFlagsRequired |= feat.mValue; - } - mFlagsSupported |= feat.mValue; - if (!feat.mInternal) { - mDefaultFormat.setInteger(key, 1); - } - // TODO restrict features by mFlagsVerified once all codecs reliably verify them + public static Feature[] getFeatures(boolean isEncoder) { + if (isEncoder) { + return encoderFeatures; + } else { + return decoderFeatures; } } } - /* package private */ static final class CodecCapsNativeImpl implements CodecCapsIntf { - private long mNativeContext; // accessed by native methods - - private CodecProfileLevel[] mProfileLevels; - private int[] mColorFormats; - - private MediaFormat mDefaultFormat; - private AudioCapabilities mAudioCaps; - private VideoCapabilities mVideoCaps; - private EncoderCapabilities mEncoderCaps; - - public static CodecCapsNativeImpl createFromProfileLevel( - String mime, int profile, int level) { - return native_createFromProfileLevel(mime, profile, level); - } - - /** - * Constructor used by JNI. - * - * The Java CodecCapabilities object keeps these subobjects to avoid recontructing. - */ - /* package private */ CodecCapsNativeImpl(CodecProfileLevel[] profLevs, int[] colFmts, - MediaFormat defaultFormat, AudioCapabilities audioCaps, - VideoCapabilities videoCaps, EncoderCapabilities encoderCaps) { - mProfileLevels = profLevs; - mColorFormats = colFmts; - mDefaultFormat = defaultFormat; - mAudioCaps = audioCaps; - mVideoCaps = videoCaps; - mEncoderCaps = encoderCaps; - } - - public CodecCapsNativeImpl dup() { - CodecCapsNativeImpl impl = native_dup(); - return impl; - } - - @Override - protected void finalize() { - native_finalize(); - } - - public CodecProfileLevel[] getProfileLevels() { - return mProfileLevels; - } - - public int[] getColorFormats() { - return mColorFormats; - } - - public boolean isFeatureSupported(String name) { - return native_isFeatureSupported(name); - } - - public boolean isFeatureRequired(String name) { - return native_isFeatureRequired(name); - } - - public String[] validFeatures() { - return native_validFeatures(); - } - - public boolean isRegular() { - return native_isRegular(); - } - - public boolean isFormatSupported(MediaFormat format) { - if (format == null) { - throw new NullPointerException(); - } - - Map<String, Object> formatMap = format.getMap(); - String[] keys = new String[formatMap.size()]; - Object[] values = new Object[formatMap.size()]; - - int i = 0; - for (Map.Entry<String, Object> entry: formatMap.entrySet()) { - keys[i] = entry.getKey(); - values[i] = entry.getValue(); - ++i; + /** @hide */ + public String[] validFeatures() { + Feature[] features = getValidFeatures(); + String[] res = new String[features.length]; + for (int i = 0; i < res.length; i++) { + if (!features[i].mInternal) { + res[i] = features[i].mName; } - - return native_isFormatSupported(keys, values); - } - - public MediaFormat getDefaultFormat() { - return mDefaultFormat; - } - - public String getMimeType() { - return native_getMimeType(); - } - - public int getMaxSupportedInstances() { - return native_getMaxSupportedInstances(); - } - - public AudioCapabilities getAudioCapabilities() { - return mAudioCaps; } - - public EncoderCapabilities getEncoderCapabilities() { - return mEncoderCaps; - } - - public VideoCapabilities getVideoCapabilities() { - return mVideoCaps; - } - - private static native void native_init(); - private static native CodecCapsNativeImpl native_createFromProfileLevel( - String mime, int profile, int level); - private native CodecCapsNativeImpl native_dup(); - private native void native_finalize(); - private native int native_getMaxSupportedInstances(); - private native String native_getMimeType(); - private native boolean native_isFeatureRequired(String name); - private native boolean native_isFeatureSupported(String name); - private native boolean native_isFormatSupported(@Nullable String[] keys, - @Nullable Object[] values); - private native boolean native_isRegular(); - private native String[] native_validFeatures(); - - static { - System.loadLibrary("media_jni"); - native_init(); - } - } - - private CodecCapsIntf mImpl; - - /** - * Retrieve the codec capabilities for a certain {@code mime type}, {@code - * profile} and {@code level}. If the type, or profile-level combination - * is not understood by the framework, it returns null. - * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this - * method without calling any method of the {@link MediaCodecList} class beforehand - * results in a {@link NullPointerException}.</p> - */ - public static CodecCapabilities createFromProfileLevel( - String mime, int profile, int level) { - CodecCapsIntf impl; - if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { - impl = CodecCapsNativeImpl.createFromProfileLevel(mime, profile, level); - } else { - impl = CodecCapsLegacyImpl.createFromProfileLevel(mime, profile, level); - } - return new CodecCapabilities(impl); - } - - public CodecCapabilities() { - mImpl = new CodecCapsLegacyImpl(); + return res; } - /** package private */ CodecCapabilities(CodecCapsIntf impl) { - mImpl = impl; - profileLevels = mImpl.getProfileLevels(); - colorFormats = mImpl.getColorFormats(); + private Feature[] getValidFeatures() { + return FeatureList.getFeatures(isEncoder()); } - /** @hide */ - public CodecCapabilities dup() { - CodecCapabilities caps = new CodecCapabilities(); - - // profileLevels and colorFormats may be modified by client. - caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length); - caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length); - - caps.mImpl = mImpl.dup(); - - return caps; - } - - /** - * Query codec feature capabilities. - * <p> - * These features are supported to be used by the codec. These - * include optional features that can be turned on, as well as - * features that are always on. - */ - public final boolean isFeatureSupported(String name) { - return mImpl.isFeatureSupported(name); - } - - /** - * 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 mImpl.isFeatureRequired(name); - } - - /** @hide */ - public String[] validFeatures() { - return mImpl.validFeatures(); + 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() { - return mImpl.isRegular(); + // regular codecs only require default features + for (Feature feat: getValidFeatures()) { + if (!feat.mDefault && isFeatureRequired(feat.mName)) { + return false; + } + } + return true; } /** @@ -1606,573 +1047,384 @@ public final class MediaCodecInfo { * and feature requests. */ public final boolean isFormatSupported(MediaFormat format) { - return mImpl.isFormatSupported(format); - } - - /** - * Returns a MediaFormat object with default values for configurations that have - * defaults. - */ - public MediaFormat getDefaultFormat() { - return mImpl.getDefaultFormat(); - } + final Map<String, Object> map = format.getMap(); + final String mime = (String)map.get(MediaFormat.KEY_MIME); - /** - * Returns the mime type for which this codec-capability object was created. - */ - public String getMimeType() { - return mImpl.getMimeType(); - } - - /** - * Returns the max number of the supported concurrent codec instances. - * <p> - * This is a hint for an upper bound. Applications should not expect to successfully - * operate more instances than the returned value, but the actual number of - * concurrently operable instances may be less as it depends on the available - * resources at time of use. - */ - public int getMaxSupportedInstances() { - return mImpl.getMaxSupportedInstances(); - } - - /** - * Returns the audio capabilities or {@code null} if this is not an audio codec. - */ - public AudioCapabilities getAudioCapabilities() { - return mImpl.getAudioCapabilities(); - } - - /** - * Returns the encoding capabilities or {@code null} if this is not an encoder. - */ - public EncoderCapabilities getEncoderCapabilities() { - return mImpl.getEncoderCapabilities(); - } - - /** - * Returns the video capabilities or {@code null} if this is not a video codec. - */ - public VideoCapabilities getVideoCapabilities() { - return mImpl.getVideoCapabilities(); - } - } - - /** - * A class that supports querying the audio capabilities of a codec. - */ - public static final class AudioCapabilities { - private static final String TAG = "AudioCapabilities"; - - /* package private */ interface AudioCapsIntf { - public Range<Integer> getBitrateRange(); - - public int[] getSupportedSampleRates(); - - public Range<Integer>[] getSupportedSampleRateRanges(); - - public int getMaxInputChannelCount(); - - public int getMinInputChannelCount(); - - public Range<Integer>[] getInputChannelCountRanges(); - - public boolean isSampleRateSupported(int sampleRate); - - public void getDefaultFormat(MediaFormat format); - - public boolean supportsFormat(MediaFormat format); - } - - /* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf { - private CodecCapabilities.CodecCapsLegacyImpl mParent; - private Range<Integer> mBitrateRange; - - private int[] mSampleRates; - private Range<Integer>[] mSampleRateRanges; - private Range<Integer>[] mInputChannelRanges; - - private static final int MAX_INPUT_CHANNEL_COUNT = 30; - - public Range<Integer> getBitrateRange() { - return mBitrateRange; - } - - public int[] getSupportedSampleRates() { - return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) - : null; - } - - public Range<Integer>[] getSupportedSampleRateRanges() { - return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); + // mime must match if present + if (mime != null && !mMime.equalsIgnoreCase(mime)) { + return false; } - public int getMaxInputChannelCount() { - int overall_max = 0; - for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { - int lmax = mInputChannelRanges[i].getUpper(); - if (lmax > overall_max) { - overall_max = lmax; - } + // check feature support + for (Feature feat: getValidFeatures()) { + if (feat.mInternal) { + continue; } - return overall_max; - } - public int getMinInputChannelCount() { - int overall_min = MAX_INPUT_CHANNEL_COUNT; - for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { - int lmin = mInputChannelRanges[i].getLower(); - if (lmin < overall_min) { - overall_min = lmin; - } + Integer yesNo = (Integer)map.get(MediaFormat.KEY_FEATURE_ + feat.mName); + if (yesNo == null) { + continue; } - return overall_min; - } - - public Range<Integer>[] getInputChannelCountRanges() { - return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length); - } - - /* no public constructor */ - private AudioCapsLegacyImpl() { } - - public static AudioCapsLegacyImpl create( - MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { - Log.d(TAG, "Legacy implementation is called while native flag is on."); + if ((yesNo == 1 && !isFeatureSupported(feat.mName)) || + (yesNo == 0 && isFeatureRequired(feat.mName))) { + return false; } - - AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl(); - caps.init(info, parent); - return caps; } - private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - mParent = parent; - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); - } + Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); + Integer level = (Integer)map.get(MediaFormat.KEY_LEVEL); - private void initWithPlatformLimits() { - mBitrateRange = Range.create(0, Integer.MAX_VALUE); - mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)}; - // mBitrateRange = Range.create(1, 320000); - final int minSampleRate = SystemProperties - .getInt("ro.mediacodec.min_sample_rate", 7350); - final int maxSampleRate = SystemProperties - .getInt("ro.mediacodec.max_sample_rate", 192000); - mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) }; - mSampleRates = null; - } + if (profile != null) { + if (!supportsProfileLevel(profile, level)) { + return false; + } - private boolean supports(Integer sampleRate, Integer inputChannels) { - // channels and sample rates are checked orthogonally - if (inputChannels != null) { - int ix = Utils.binarySearchDistinctRanges( - mInputChannelRanges, inputChannels); - if (ix < 0) { - return false; + // If we recognize this profile, check that this format is supported by the + // highest level supported by the codec for that profile. (Ignore specified + // level beyond the above profile/level check as level is only used as a + // guidance. E.g. AVC Level 1 CIF format is supported if codec supports level 1.1 + // even though max size for Level 1 is QCIF. However, MPEG2 Simple Profile + // 1080p format is not supported even if codec supports Main Profile Level High, + // as Simple Profile does not support 1080p. + CodecCapabilities levelCaps = null; + int maxLevel = 0; + for (CodecProfileLevel pl : profileLevels) { + if (pl.profile == profile && pl.level > maxLevel) { + // H.263 levels are not completely ordered: + // Level45 support only implies Level10 support + if (!mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263) + || pl.level != CodecProfileLevel.H263Level45 + || maxLevel == CodecProfileLevel.H263Level10) { + maxLevel = pl.level; + } } } - if (sampleRate != null) { - int ix = Utils.binarySearchDistinctRanges( - mSampleRateRanges, sampleRate); - if (ix < 0) { + levelCaps = createFromProfileLevel(mMime, profile, maxLevel); + // We must remove the profile from this format otherwise levelCaps.isFormatSupported + // will get into this same condition and loop forever. Furthermore, since levelCaps + // does not contain features and bitrate specific keys, keep only keys relevant for + // a level check. + Map<String, Object> levelCriticalFormatMap = new HashMap<>(map); + final Set<String> criticalKeys = + isVideo() ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS : + isAudio() ? AudioCapabilities.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS : + null; + + // critical keys will always contain KEY_MIME, but should also contain others to be + // meaningful + if (criticalKeys != null && criticalKeys.size() > 1 && levelCaps != null) { + levelCriticalFormatMap.keySet().retainAll(criticalKeys); + + MediaFormat levelCriticalFormat = new MediaFormat(levelCriticalFormatMap); + if (!levelCaps.isFormatSupported(levelCriticalFormat)) { return false; } } - return true; } - - public boolean isSampleRateSupported(int sampleRate) { - return supports(sampleRate, null); + if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) { + return false; } - - /** 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(); + if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) { + return false; } - - private void createDiscreteSampleRates() { - mSampleRates = new int[mSampleRateRanges.length]; - for (int i = 0; i < mSampleRateRanges.length; i++) { - mSampleRates[i] = mSampleRateRanges[i].getLower(); - } + if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) { + return false; } + return true; + } - /** modifies rateRanges */ - private void limitSampleRates(Range<Integer>[] rateRanges) { - sortDistinctRanges(rateRanges); - mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); + private static boolean supportsBitrate( + Range<Integer> bitrateRange, MediaFormat format) { + Map<String, Object> map = format.getMap(); - // check if all values are discrete - for (Range<Integer> range: mSampleRateRanges) { - if (!range.getLower().equals(range.getUpper())) { - mSampleRates = null; - return; - } - } - createDiscreteSampleRates(); + // consider max bitrate over average bitrate for support + Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE); + Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE); + if (bitrate == null) { + bitrate = maxBitrate; + } else if (maxBitrate != null) { + bitrate = Math.max(bitrate, maxBitrate); } - private void applyLevelLimits() { - int[] sampleRates = null; - Range<Integer> sampleRateRange = null, bitRates = null; - int maxChannels = MAX_INPUT_CHANNEL_COUNT; - CodecProfileLevel[] profileLevels = mParent.getProfileLevels(); - String mime = mParent.getMimeType(); - - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { - sampleRates = new int[] { - 8000, 11025, 12000, - 16000, 22050, 24000, - 32000, 44100, 48000 }; - bitRates = Range.create(8000, 320000); - maxChannels = 2; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(4750, 12200); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { - sampleRates = new int[] { 16000 }; - bitRates = Range.create(6600, 23850); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { - sampleRates = new int[] { - 7350, 8000, - 11025, 12000, 16000, - 22050, 24000, 32000, - 44100, 48000, 64000, - 88200, 96000 }; - bitRates = Range.create(8000, 510000); - maxChannels = 48; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { - bitRates = Range.create(32000, 500000); - sampleRateRange = Range.create(8000, 192000); - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { - bitRates = Range.create(6000, 510000); - sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { - sampleRateRange = Range.create(1, 192000); - bitRates = Range.create(1, 10000000); - maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - sampleRateRange = Range.create(1, 655350); - // lossless codec, so bitrate is ignored - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(64000, 64000); - // platform allows multiple channels for this format - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(13000, 13000); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) { - maxChannels = 6; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) { - maxChannels = 16; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) { - sampleRates = new int[] { 48000 }; - bitRates = Range.create(32000, 6144000); - maxChannels = 16; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) { - sampleRates = new int[] { 44100, 48000, 96000, 192000 }; - bitRates = Range.create(16000, 2688000); - maxChannels = 24; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) { - sampleRates = new int[] { 44100, 48000 }; - bitRates = Range.create(96000, 1524000); - maxChannels = 6; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) { - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.profile) { - case CodecProfileLevel.DTS_HDProfileLBR: - sampleRates = new int[]{ 22050, 24000, 44100, 48000 }; - bitRates = Range.create(32000, 768000); - break; - case CodecProfileLevel.DTS_HDProfileHRA: - case CodecProfileLevel.DTS_HDProfileMA: - sampleRates = - new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - mParent.mError |= ERROR_UNRECOGNIZED; - sampleRates = - new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - } - } - maxChannels = 8; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) { - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.profile) { - case CodecProfileLevel.DTS_UHDProfileP2: - sampleRates = new int[]{ 48000 }; - bitRates = Range.create(96000, 768000); - maxChannels = 10; - break; - case CodecProfileLevel.DTS_UHDProfileP1: - sampleRates = - new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - maxChannels = 32; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - mParent.mError |= ERROR_UNRECOGNIZED; - sampleRates = - new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; - bitRates = Range.create(96000, 24500000); - maxChannels = 32; - } - } - } else { - Log.w(TAG, "Unsupported mime " + mime); - mParent.mError |= ERROR_UNSUPPORTED; - } - - // restrict ranges - if (sampleRates != null) { - limitSampleRates(sampleRates); - } else if (sampleRateRange != null) { - limitSampleRates(new Range[] { sampleRateRange }); - } - - Range<Integer> channelRange = Range.create(1, maxChannels); - - applyLimits(new Range[] { channelRange }, bitRates); + if (bitrate != null && bitrate > 0) { + return bitrateRange.contains(bitrate); } - private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) { + return true; + } - // clamp & make a local copy - Range<Integer>[] myInputChannels = new Range[inputChannels.length]; - for (int i = 0; i < inputChannels.length; i++) { - int lower = inputChannels[i].clamp(1); - int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT); - myInputChannels[i] = Range.create(lower, upper); + private boolean supportsProfileLevel(int profile, Integer level) { + for (CodecProfileLevel pl: profileLevels) { + if (pl.profile != profile) { + continue; } - // sort, intersect with existing, & save channel list - sortDistinctRanges(myInputChannels); - Range<Integer>[] joinedChannelList = - intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges); - mInputChannelRanges = joinedChannelList; + // No specific level requested + if (level == null) { + return true; + } - if (bitRates != null) { - mBitrateRange = mBitrateRange.intersect(bitRates); + // AAC doesn't use levels + if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { + return true; } - } - private void parseFromInfo(MediaFormat info) { - int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; - Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)}; - Range<Integer> bitRates = POSITIVE_INTEGERS; + // DTS doesn't use levels + if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS) + || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD) + || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) { + return true; + } - 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); + // H.263 levels are not completely ordered: + // Level45 support only implies Level10 support + if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { + if (pl.level != level && pl.level == CodecProfileLevel.H263Level45 + && level > CodecProfileLevel.H263Level10) { + continue; } - limitSampleRates(rateRanges); } - // we will prefer channel-ranges over max-channel-count - if (info.containsKey("channel-ranges")) { - String[] channelStrings = info.getString("channel-ranges").split(","); - Range<Integer>[] channelRanges = new Range[channelStrings.length]; - for (int i = 0; i < channelStrings.length; i++) { - channelRanges[i] = Utils.parseIntRange(channelStrings[i], null); - } - channels = channelRanges; - } else if (info.containsKey("channel-range")) { - Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"), - null); - channels = new Range[] { oneRange }; - } else if (info.containsKey("max-channel-count")) { - maxInputChannels = Utils.parseIntSafely( - info.getString("max-channel-count"), maxInputChannels); - if (maxInputChannels == 0) { - channels = new Range[] {Range.create(0, 0)}; - } else { - channels = new Range[] {Range.create(1, maxInputChannels)}; + // MPEG4 levels are not completely ordered: + // Level1 support only implies Level0 (and not Level0b) support + if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { + if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1 + && level > CodecProfileLevel.MPEG4Level0) { + continue; } - } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { - maxInputChannels = 0; - channels = new Range[] {Range.create(0, 0)}; } - if (info.containsKey("bitrate-range")) { - bitRates = bitRates.intersect( - Utils.parseIntRange(info.getString("bitrate-range"), bitRates)); + // HEVC levels incorporate both tiers and levels. Verify tier support. + if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { + boolean supportsHighTier = + (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0; + boolean checkingHighTier = (level & CodecProfileLevel.HEVCHighTierLevels) != 0; + // high tier levels are only supported by other high tier levels + if (checkingHighTier && !supportsHighTier) { + continue; + } } - applyLimits(channels, bitRates); - } - - /** @hide */ - public void getDefaultFormat(MediaFormat format) { - // report settings that have only a single choice - if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { - format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); - } - if (getMaxInputChannelCount() == 1) { - // mono-only format - format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); - } - if (mSampleRates != null && mSampleRates.length == 1) { - format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + if (pl.level >= level) { + // if we recognize the listed profile/level, we must also recognize the + // profile/level arguments. + if (createFromProfileLevel(mMime, profile, pl.level) != null) { + return createFromProfileLevel(mMime, profile, level) != null; + } + return true; } } + return false; + } - /* package private */ - // must not contain KEY_PROFILE - static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( - // We don't set level-specific limits for audio codecs today. Key candidates - // would be sample rate, bit rate or channel count. - // MediaFormat.KEY_SAMPLE_RATE, - // MediaFormat.KEY_CHANNEL_COUNT, - // MediaFormat.KEY_BIT_RATE, - MediaFormat.KEY_MIME); - - /** @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; - } + // errors while reading profile levels - accessed from sister capabilities + int mError; - if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) { - return false; - } + private static final String TAG = "CodecCapabilities"; - // nothing to do for: - // KEY_CHANNEL_MASK: codecs don't get this - // KEY_IS_ADTS: required feature for all AAC decoders - return true; - } + // NEW-STYLE CAPABILITIES + private AudioCapabilities mAudioCaps; + private VideoCapabilities mVideoCaps; + private EncoderCapabilities mEncoderCaps; + private MediaFormat mDefaultFormat; + + /** + * Returns a MediaFormat object with default values for configurations that have + * defaults. + */ + public MediaFormat getDefaultFormat() { + return mDefaultFormat; } - /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf { - private long mNativeContext; // accessed by native methods + /** + * Returns the mime type for which this codec-capability object was created. + */ + public String getMimeType() { + return mMime; + } - private Range<Integer> mBitrateRange; - private int[] mSampleRates; - private Range<Integer>[] mSampleRateRanges; - private Range<Integer>[] mInputChannelRanges; + /** + * Returns the max number of the supported concurrent codec instances. + * <p> + * This is a hint for an upper bound. Applications should not expect to successfully + * operate more instances than the returned value, but the actual number of + * concurrently operable instances may be less as it depends on the available + * resources at time of use. + */ + public int getMaxSupportedInstances() { + return mMaxSupportedInstances; + } - /** - * Constructor used by JNI. - * - * The Java AudioCapabilities object keeps these subobjects to avoid recontruction. - */ - /* package private */ AudioCapsNativeImpl(Range<Integer> bitrateRange, - int[] sampleRates, Range<Integer>[] sampleRateRanges, - Range<Integer>[] inputChannelRanges) { - mBitrateRange = bitrateRange; - mSampleRates = sampleRates; - mSampleRateRanges = sampleRateRanges; - mInputChannelRanges = inputChannelRanges; - } + private boolean isAudio() { + return mAudioCaps != null; + } - /* no public constructor */ - private AudioCapsNativeImpl() { } + /** + * Returns the audio capabilities or {@code null} if this is not an audio codec. + */ + public AudioCapabilities getAudioCapabilities() { + return mAudioCaps; + } - public Range<Integer> getBitrateRange() { - return mBitrateRange; - } + private boolean isEncoder() { + return mEncoderCaps != null; + } - public int[] getSupportedSampleRates() { - return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) - : null; - } + /** + * Returns the encoding capabilities or {@code null} if this is not an encoder. + */ + public EncoderCapabilities getEncoderCapabilities() { + return mEncoderCaps; + } - public Range<Integer>[] getSupportedSampleRateRanges() { - return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); - } + private boolean isVideo() { + return mVideoCaps != null; + } - public Range<Integer>[] getInputChannelCountRanges() { - return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length); - } + /** + * Returns the video capabilities or {@code null} if this is not a video codec. + */ + public VideoCapabilities getVideoCapabilities() { + return mVideoCaps; + } - public int getMaxInputChannelCount() { - return native_getMaxInputChannelCount(); - } + /** @hide */ + public CodecCapabilities dup() { + CodecCapabilities caps = new CodecCapabilities(); - public int getMinInputChannelCount() { - return native_getMinInputChannelCount(); - } + // profileLevels and colorFormats may be modified by client. + caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length); + caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length); - public boolean isSampleRateSupported(int sampleRate) { - return native_isSampleRateSupported(sampleRate); - } + caps.mMime = mMime; + caps.mMaxSupportedInstances = mMaxSupportedInstances; + caps.mFlagsRequired = mFlagsRequired; + caps.mFlagsSupported = mFlagsSupported; + caps.mFlagsVerified = mFlagsVerified; + caps.mAudioCaps = mAudioCaps; + caps.mVideoCaps = mVideoCaps; + caps.mEncoderCaps = mEncoderCaps; + caps.mDefaultFormat = mDefaultFormat; + caps.mCapabilitiesInfo = mCapabilitiesInfo; - // This API is for internal Java implementation only. Should not be called. - public void getDefaultFormat(MediaFormat format) { - throw new UnsupportedOperationException( - "Java Implementation should not call native implemenatation"); - } + return caps; + } - // This API is for internal Java implementation only. Should not be called. - public boolean supportsFormat(MediaFormat format) { - throw new UnsupportedOperationException( - "Java Implementation should not call native implemenatation"); + /** + * Retrieve the codec capabilities for a certain {@code mime type}, {@code + * profile} and {@code level}. If the type, or profile-level combination + * is not understood by the framework, it returns null. + * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this + * method without calling any method of the {@link MediaCodecList} class beforehand + * results in a {@link NullPointerException}.</p> + */ + public static CodecCapabilities createFromProfileLevel( + String mime, int profile, int level) { + CodecProfileLevel pl = new CodecProfileLevel(); + pl.profile = profile; + pl.level = level; + MediaFormat defaultFormat = new MediaFormat(); + defaultFormat.setString(MediaFormat.KEY_MIME, mime); + + CodecCapabilities ret = new CodecCapabilities( + new CodecProfileLevel[] { pl }, new int[0], true /* encoder */, + defaultFormat, new MediaFormat() /* info */); + if (ret.mError != 0) { + return null; } + return ret; + } - private native int native_getMaxInputChannelCount(); - private native int native_getMinInputChannelCount(); - private native boolean native_isSampleRateSupported(int sampleRate); - private static native void native_init(); + /* package private */ CodecCapabilities( + CodecProfileLevel[] profLevs, int[] colFmts, + boolean encoder, + Map<String, Object>defaultFormatMap, + Map<String, Object>capabilitiesMap) { + this(profLevs, colFmts, encoder, + new MediaFormat(defaultFormatMap), + new MediaFormat(capabilitiesMap)); + } - static { - System.loadLibrary("media_jni"); - native_init(); + private MediaFormat mCapabilitiesInfo; + + /* package private */ CodecCapabilities( + CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, + MediaFormat defaultFormat, MediaFormat info) { + final Map<String, Object> map = info.getMap(); + colorFormats = colFmts; + mFlagsVerified = 0; // TODO: remove as it is unused + mDefaultFormat = defaultFormat; + mCapabilitiesInfo = info; + mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME); + + /* VP9 introduced profiles around 2016, so some VP9 codecs may not advertise any + supported profiles. Determine the level for them using the info they provide. */ + if (profLevs.length == 0 && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { + CodecProfileLevel profLev = new CodecProfileLevel(); + profLev.profile = CodecProfileLevel.VP9Profile0; + profLev.level = VideoCapabilities.equivalentVP9Level(info); + profLevs = new CodecProfileLevel[] { profLev }; + } + profileLevels = profLevs; + + if (mMime.toLowerCase().startsWith("audio/")) { + mAudioCaps = AudioCapabilities.create(info, this); + mAudioCaps.getDefaultFormat(mDefaultFormat); + } else if (mMime.toLowerCase().startsWith("video/") + || mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) { + mVideoCaps = VideoCapabilities.create(info, this); + } + if (encoder) { + mEncoderCaps = EncoderCapabilities.create(info, this); + mEncoderCaps.getDefaultFormat(mDefaultFormat); + } + + final Map<String, Object> global = MediaCodecList.getGlobalSettings(); + mMaxSupportedInstances = Utils.parseIntSafely( + global.get("max-concurrent-instances"), DEFAULT_MAX_SUPPORTED_INSTANCES); + + int maxInstances = Utils.parseIntSafely( + map.get("max-concurrent-instances"), mMaxSupportedInstances); + mMaxSupportedInstances = + Range.create(1, MAX_SUPPORTED_INSTANCES_LIMIT).clamp(maxInstances); + + for (Feature feat: getValidFeatures()) { + String key = MediaFormat.KEY_FEATURE_ + feat.mName; + Integer yesNo = (Integer)map.get(key); + if (yesNo == null) { + continue; + } + if (yesNo > 0) { + mFlagsRequired |= feat.mValue; + } + mFlagsSupported |= feat.mValue; + if (!feat.mInternal) { + mDefaultFormat.setInteger(key, 1); + } + // TODO restrict features by mFlagsVerified once all codecs reliably verify them } } + } - private AudioCapsIntf mImpl; - - /** @hide */ - public static AudioCapabilities create( - MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent); - AudioCapabilities caps = new AudioCapabilities(impl); - return caps; - } + /** + * A class that supports querying the audio capabilities of a codec. + */ + public static final class AudioCapabilities { + private static final String TAG = "AudioCapabilities"; + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; - /* package private */ AudioCapabilities(AudioCapsIntf impl) { - mImpl = impl; - } + private int[] mSampleRates; + private Range<Integer>[] mSampleRateRanges; + private Range<Integer>[] mInputChannelRanges; - /* no public constructor */ - private AudioCapabilities() { } + private static final int MAX_INPUT_CHANNEL_COUNT = 30; /** * Returns the range of supported bitrates in bits/second. */ public Range<Integer> getBitrateRange() { - return mImpl.getBitrateRange(); + return mBitrateRange; } /** @@ -2181,7 +1433,7 @@ public final class MediaCodecInfo { * {@code null}. The array is sorted in ascending order. */ public int[] getSupportedSampleRates() { - return mImpl.getSupportedSampleRates(); + return mSampleRates != null ? Arrays.copyOf(mSampleRates, mSampleRates.length) : null; } /** @@ -2190,21 +1442,7 @@ public final class MediaCodecInfo { * distinct. */ public Range<Integer>[] getSupportedSampleRateRanges() { - return mImpl.getSupportedSampleRateRanges(); - } - - /* - * Returns an array of ranges representing the number of input channels supported. - * The codec supports any number of input channels within this range. - * - * This supersedes the {@link #getMaxInputChannelCount} method. - * - * For many codecs, this will be a single range [1..N], for some N. - */ - @SuppressLint("ArrayReturn") - @NonNull - public Range<Integer>[] getInputChannelCountRanges() { - return mImpl.getInputChannelCountRanges(); + return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); } /** @@ -2224,7 +1462,14 @@ public final class MediaCodecInfo { */ @IntRange(from = 1, to = 255) public int getMaxInputChannelCount() { - return mImpl.getMaxInputChannelCount(); + int overall_max = 0; + for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { + int lmax = mInputChannelRanges[i].getUpper(); + if (lmax > overall_max) { + overall_max = lmax; + } + } + return overall_max; } /** @@ -2236,24 +1481,364 @@ public final class MediaCodecInfo { */ @IntRange(from = 1, to = 255) public int getMinInputChannelCount() { - return mImpl.getMinInputChannelCount(); + int overall_min = MAX_INPUT_CHANNEL_COUNT; + for (int i = mInputChannelRanges.length - 1; i >= 0; i--) { + int lmin = mInputChannelRanges[i].getLower(); + if (lmin < overall_min) { + overall_min = lmin; + } + } + return overall_min; + } + + /* + * Returns an array of ranges representing the number of input channels supported. + * The codec supports any number of input channels within this range. + * + * This supersedes the {@link #getMaxInputChannelCount} method. + * + * For many codecs, this will be a single range [1..N], for some N. + */ + @SuppressLint("ArrayReturn") + @NonNull + public Range<Integer>[] getInputChannelCountRanges() { + return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length); + } + + /* no public constructor */ + private AudioCapabilities() { } + + /** @hide */ + public static AudioCapabilities create( + MediaFormat info, CodecCapabilities parent) { + AudioCapabilities caps = new AudioCapabilities(); + caps.init(info, parent); + return caps; + } + + private void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + } + + private void initWithPlatformLimits() { + mBitrateRange = Range.create(0, Integer.MAX_VALUE); + mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)}; + // mBitrateRange = Range.create(1, 320000); + final int minSampleRate = SystemProperties. + getInt("ro.mediacodec.min_sample_rate", 7350); + final int maxSampleRate = SystemProperties. + getInt("ro.mediacodec.max_sample_rate", 192000); + mSampleRateRanges = new Range[] { Range.create(minSampleRate, maxSampleRate) }; + mSampleRates = null; + } + + private boolean supports(Integer sampleRate, Integer inputChannels) { + // channels and sample rates are checked orthogonally + if (inputChannels != null) { + int ix = Utils.binarySearchDistinctRanges( + mInputChannelRanges, inputChannels); + if (ix < 0) { + return false; + } + } + if (sampleRate != null) { + int ix = Utils.binarySearchDistinctRanges( + mSampleRateRanges, sampleRate); + if (ix < 0) { + return false; + } + } + return true; } /** * Query whether the sample rate is supported by the codec. */ public boolean isSampleRateSupported(int sampleRate) { - return mImpl.isSampleRateSupported(sampleRate); + return supports(sampleRate, null); + } + + /** modifies rates */ + private void limitSampleRates(int[] rates) { + Arrays.sort(rates); + ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); + for (int rate: rates) { + if (supports(rate, null /* channels */)) { + ranges.add(Range.create(rate, rate)); + } + } + mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); + createDiscreteSampleRates(); + } + + private void createDiscreteSampleRates() { + mSampleRates = new int[mSampleRateRanges.length]; + for (int i = 0; i < mSampleRateRanges.length; i++) { + mSampleRates[i] = mSampleRateRanges[i].getLower(); + } + } + + /** modifies rateRanges */ + private void limitSampleRates(Range<Integer>[] rateRanges) { + sortDistinctRanges(rateRanges); + mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); + + // check if all values are discrete + for (Range<Integer> range: mSampleRateRanges) { + if (!range.getLower().equals(range.getUpper())) { + mSampleRates = null; + return; + } + } + createDiscreteSampleRates(); + } + + private void applyLevelLimits() { + int[] sampleRates = null; + Range<Integer> sampleRateRange = null, bitRates = null; + int maxChannels = MAX_INPUT_CHANNEL_COUNT; + CodecProfileLevel[] profileLevels = mParent.profileLevels; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { + sampleRates = new int[] { + 8000, 11025, 12000, + 16000, 22050, 24000, + 32000, 44100, 48000 }; + bitRates = Range.create(8000, 320000); + maxChannels = 2; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(4750, 12200); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { + sampleRates = new int[] { 16000 }; + bitRates = Range.create(6600, 23850); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { + sampleRates = new int[] { + 7350, 8000, + 11025, 12000, 16000, + 22050, 24000, 32000, + 44100, 48000, 64000, + 88200, 96000 }; + bitRates = Range.create(8000, 510000); + maxChannels = 48; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { + bitRates = Range.create(32000, 500000); + sampleRateRange = Range.create(8000, 192000); + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { + bitRates = Range.create(6000, 510000); + sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { + sampleRateRange = Range.create(1, 192000); + bitRates = Range.create(1, 10000000); + maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + sampleRateRange = Range.create(1, 655350); + // lossless codec, so bitrate is ignored + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(64000, 64000); + // platform allows multiple channels for this format + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(13000, 13000); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3)) { + maxChannels = 6; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3)) { + maxChannels = 16; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC)) { + sampleRates = new int[] { 48000 }; + bitRates = Range.create(32000, 6144000); + maxChannels = 16; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC4)) { + sampleRates = new int[] { 44100, 48000, 96000, 192000 }; + bitRates = Range.create(16000, 2688000); + maxChannels = 24; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS)) { + sampleRates = new int[] { 44100, 48000 }; + bitRates = Range.create(96000, 1524000); + maxChannels = 6; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_HD)) { + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.profile) { + case CodecProfileLevel.DTS_HDProfileLBR: + sampleRates = new int[]{ 22050, 24000, 44100, 48000 }; + bitRates = Range.create(32000, 768000); + break; + case CodecProfileLevel.DTS_HDProfileHRA: + case CodecProfileLevel.DTS_HDProfileMA: + sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + mParent.mError |= ERROR_UNRECOGNIZED; + sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + } + } + maxChannels = 8; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_DTS_UHD)) { + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.profile) { + case CodecProfileLevel.DTS_UHDProfileP2: + sampleRates = new int[]{ 48000 }; + bitRates = Range.create(96000, 768000); + maxChannels = 10; + break; + case CodecProfileLevel.DTS_UHDProfileP1: + sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + maxChannels = 32; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + mParent.mError |= ERROR_UNRECOGNIZED; + sampleRates = new int[]{ 44100, 48000, 88200, 96000, 176400, 192000 }; + bitRates = Range.create(96000, 24500000); + maxChannels = 32; + } + } + } else { + Log.w(TAG, "Unsupported mime " + mime); + mParent.mError |= ERROR_UNSUPPORTED; + } + + // restrict ranges + if (sampleRates != null) { + limitSampleRates(sampleRates); + } else if (sampleRateRange != null) { + limitSampleRates(new Range[] { sampleRateRange }); + } + + Range<Integer> channelRange = Range.create(1, maxChannels); + + applyLimits(new Range[] { channelRange }, bitRates); + } + + private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) { + + // clamp & make a local copy + Range<Integer>[] myInputChannels = new Range[inputChannels.length]; + for (int i = 0; i < inputChannels.length; i++) { + int lower = inputChannels[i].clamp(1); + int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT); + myInputChannels[i] = Range.create(lower, upper); + } + + // sort, intersect with existing, & save channel list + sortDistinctRanges(myInputChannels); + Range<Integer>[] joinedChannelList = + intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges); + mInputChannelRanges = joinedChannelList; + + if (bitRates != null) { + mBitrateRange = mBitrateRange.intersect(bitRates); + } + } + + private void parseFromInfo(MediaFormat info) { + int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; + Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)}; + 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); + } + + // we will prefer channel-ranges over max-channel-count + if (info.containsKey("channel-ranges")) { + String[] channelStrings = info.getString("channel-ranges").split(","); + Range<Integer>[] channelRanges = new Range[channelStrings.length]; + for (int i = 0; i < channelStrings.length; i++) { + channelRanges[i] = Utils.parseIntRange(channelStrings[i], null); + } + channels = channelRanges; + } else if (info.containsKey("channel-range")) { + Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"), + null); + channels = new Range[] { oneRange }; + } else if (info.containsKey("max-channel-count")) { + maxInputChannels = Utils.parseIntSafely( + info.getString("max-channel-count"), maxInputChannels); + if (maxInputChannels == 0) { + channels = new Range[] {Range.create(0, 0)}; + } else { + channels = new Range[] {Range.create(1, maxInputChannels)}; + } + } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { + maxInputChannels = 0; + channels = new Range[] {Range.create(0, 0)}; + } + + if (info.containsKey("bitrate-range")) { + bitRates = bitRates.intersect( + Utils.parseIntRange(info.getString("bitrate-range"), bitRates)); + } + + applyLimits(channels, bitRates); } /** @hide */ public void getDefaultFormat(MediaFormat format) { - mImpl.getDefaultFormat(format); + // report settings that have only a single choice + if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { + format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); + } + if (getMaxInputChannelCount() == 1) { + // mono-only format + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + } + if (mSampleRates != null && mSampleRates.length == 1) { + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + } } + /* package private */ + // must not contain KEY_PROFILE + static final Set<String> AUDIO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( + // We don't set level-specific limits for audio codecs today. Key candidates would + // be sample rate, bit rate or channel count. + // MediaFormat.KEY_SAMPLE_RATE, + // MediaFormat.KEY_CHANNEL_COUNT, + // MediaFormat.KEY_BIT_RATE, + MediaFormat.KEY_MIME); + /** @hide */ public boolean supportsFormat(MediaFormat format) { - return mImpl.supportsFormat(format); + Map<String, Object> map = format.getMap(); + Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); + Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); + + if (!supports(sampleRate, channels)) { + return false; + } + + if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) { + return false; + } + + // nothing to do for: + // KEY_CHANNEL_MASK: codecs don't get this + // KEY_IS_ADTS: required feature for all AAC decoders + return true; } } @@ -2313,6 +1898,304 @@ public final class MediaCodecInfo { */ public static final class VideoCapabilities { private static final String TAG = "VideoCapabilities"; + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; + + private Range<Integer> mHeightRange; + private Range<Integer> mWidthRange; + private Range<Integer> mBlockCountRange; + private Range<Integer> mHorizontalBlockRange; + private Range<Integer> mVerticalBlockRange; + private Range<Rational> mAspectRatioRange; + private Range<Rational> mBlockAspectRatioRange; + private Range<Long> mBlocksPerSecondRange; + private Map<Size, Range<Long>> mMeasuredFrameRates; + private List<PerformancePoint> mPerformancePoints; + private Range<Integer> mFrameRateRange; + + private int mBlockWidth; + private int mBlockHeight; + private int mWidthAlignment; + private int mHeightAlignment; + private int mSmallerDimensionUpperLimit; + + private boolean mAllowMbOverride; // allow XML to override calculated limits + + /** + * Returns the range of supported bitrates in bits/second. + */ + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } + + /** + * Returns the range of supported video widths. + * <p class=note> + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space. + */ + public Range<Integer> getSupportedWidths() { + return mWidthRange; + } + + /** + * Returns the range of supported video heights. + * <p class=note> + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space. + */ + public Range<Integer> getSupportedHeights() { + return mHeightRange; + } + + /** + * Returns the alignment requirement for video width (in pixels). + * + * This is a power-of-2 value that video width must be a + * multiple of. + */ + public int getWidthAlignment() { + return mWidthAlignment; + } + + /** + * Returns the alignment requirement for video height (in pixels). + * + * This is a power-of-2 value that video height must be a + * multiple of. + */ + public int getHeightAlignment() { + return mHeightAlignment; + } + + /** + * Return the upper limit on the smaller dimension of width or height. + * <p></p> + * Some codecs have a limit on the smaller dimension, whether it be + * the width or the height. E.g. a codec may only be able to handle + * up to 1920x1080 both in landscape and portrait mode (1080x1920). + * In this case the maximum width and height are both 1920, but the + * smaller dimension limit will be 1080. For other codecs, this is + * {@code Math.min(getSupportedWidths().getUpper(), + * getSupportedHeights().getUpper())}. + * + * @hide + */ + public int getSmallerDimensionUpperLimit() { + return mSmallerDimensionUpperLimit; + } + + /** + * Returns the range of supported frame rates. + * <p> + * This is not a performance indicator. Rather, it expresses the + * limits specified in the coding standard, based on the complexities + * of encoding material for later playback at a certain frame rate, + * or the decoding of such material in non-realtime. + */ + public Range<Integer> getSupportedFrameRates() { + return mFrameRateRange; + } + + /** + * Returns the range of supported video widths for a video height. + * @param height the height of the video + */ + public Range<Integer> getSupportedWidthsFor(int height) { + try { + Range<Integer> range = mWidthRange; + if (!mHeightRange.contains(height) + || (height % mHeightAlignment) != 0) { + throw new IllegalArgumentException("unsupported height"); + } + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + + // constrain by block count and by block aspect ratio + final int minWidthInBlocks = Math.max( + Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), + (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() + * heightInBlocks)); + final int maxWidthInBlocks = Math.min( + mBlockCountRange.getUpper() / heightInBlocks, + (int)(mBlockAspectRatioRange.getUpper().doubleValue() + * heightInBlocks)); + range = range.intersect( + (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, + maxWidthInBlocks * mBlockWidth); + + // constrain by smaller dimension limit + if (height > mSmallerDimensionUpperLimit) { + range = range.intersect(1, mSmallerDimensionUpperLimit); + } + + // constrain by aspect ratio + range = range.intersect( + (int)Math.ceil(mAspectRatioRange.getLower().doubleValue() + * height), + (int)(mAspectRatioRange.getUpper().doubleValue() * height)); + return range; + } catch (IllegalArgumentException e) { + // height is not supported because there are no suitable widths + Log.v(TAG, "could not get supported widths for " + height); + throw new IllegalArgumentException("unsupported height"); + } + } + + /** + * Returns the range of supported video heights for a video width + * @param width the width of the video + */ + public Range<Integer> getSupportedHeightsFor(int width) { + try { + Range<Integer> range = mHeightRange; + if (!mWidthRange.contains(width) + || (width % mWidthAlignment) != 0) { + throw new IllegalArgumentException("unsupported width"); + } + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + + // constrain by block count and by block aspect ratio + final int minHeightInBlocks = Math.max( + Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), + (int)Math.ceil(widthInBlocks / + mBlockAspectRatioRange.getUpper().doubleValue())); + final int maxHeightInBlocks = Math.min( + mBlockCountRange.getUpper() / widthInBlocks, + (int)(widthInBlocks / + mBlockAspectRatioRange.getLower().doubleValue())); + range = range.intersect( + (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, + maxHeightInBlocks * mBlockHeight); + + // constrain by smaller dimension limit + if (width > mSmallerDimensionUpperLimit) { + range = range.intersect(1, mSmallerDimensionUpperLimit); + } + + // constrain by aspect ratio + range = range.intersect( + (int)Math.ceil(width / + mAspectRatioRange.getUpper().doubleValue()), + (int)(width / mAspectRatioRange.getLower().doubleValue())); + return range; + } catch (IllegalArgumentException e) { + // width is not supported because there are no suitable heights + Log.v(TAG, "could not get supported heights for " + width); + throw new IllegalArgumentException("unsupported width"); + } + } + + /** + * Returns the range of supported video frame rates for a video size. + * <p> + * This is not a performance indicator. Rather, it expresses the limits specified in + * the coding standard, based on the complexities of encoding material of a given + * size for later playback at a certain frame rate, or the decoding of such material + * in non-realtime. + + * @param width the width of the video + * @param height the height of the video + */ + public Range<Double> getSupportedFrameRatesFor(int width, int height) { + Range<Integer> range = mHeightRange; + if (!supports(width, height, null)) { + throw new IllegalArgumentException("unsupported size"); + } + final int blockCount = + Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); + + return Range.create( + Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, + (double) mFrameRateRange.getLower()), + Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, + (double) mFrameRateRange.getUpper())); + } + + private int getBlockCount(int width, int height) { + return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); + } + + @NonNull + private Size findClosestSize(int width, int height) { + int targetBlockCount = getBlockCount(width, height); + Size closestSize = null; + int minDiff = Integer.MAX_VALUE; + for (Size size : mMeasuredFrameRates.keySet()) { + int diff = Math.abs(targetBlockCount - + getBlockCount(size.getWidth(), size.getHeight())); + if (diff < minDiff) { + minDiff = diff; + closestSize = size; + } + } + return closestSize; + } + + private Range<Double> estimateFrameRatesFor(int width, int height) { + Size size = findClosestSize(width, height); + Range<Long> range = mMeasuredFrameRates.get(size); + Double ratio = getBlockCount(size.getWidth(), size.getHeight()) + / (double)Math.max(getBlockCount(width, height), 1); + return Range.create(range.getLower() * ratio, range.getUpper() * ratio); + } + + /** + * Returns the range of achievable video frame rates for a video size. + * May return {@code null}, if the codec did not publish any measurement + * data. + * <p> + * This is a performance estimate provided by the device manufacturer based on statistical + * sampling of full-speed decoding and encoding measurements in various configurations + * of common video sizes supported by the codec. As such it should only be used to + * compare individual codecs on the device. The value is not suitable for comparing + * different devices or even different android releases for the same device. + * <p> + * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range + * corresponds to the fastest frame rates achieved in the tested configurations. As + * such, it should not be used to gauge guaranteed or even average codec performance + * on the device. + * <p> + * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range + * corresponds closer to sustained performance <em>in tested configurations</em>. + * One can expect to achieve sustained performance higher than the lower limit more than + * 50% of the time, and higher than half of the lower limit at least 90% of the time + * <em>in tested configurations</em>. + * Conversely, one can expect performance lower than twice the upper limit at least + * 90% of the time. + * <p class=note> + * Tested configurations use a single active codec. For use cases where multiple + * codecs are active, applications can expect lower and in most cases significantly lower + * performance. + * <p class=note> + * The returned range value is interpolated from the nearest frame size(s) tested. + * Codec performance is severely impacted by other activity on the device as well + * as environmental factors (such as battery level, temperature or power source), and can + * vary significantly even in a steady environment. + * <p class=note> + * Use this method in cases where only codec performance matters, e.g. to evaluate if + * a codec has any chance of meeting a performance target. Codecs are listed + * in {@link MediaCodecList} in the preferred order as defined by the device + * manufacturer. As such, applications should use the first suitable codec in the + * list to achieve the best balance between power use and performance. + * + * @param width the width of the video + * @param height the height of the video + * + * @throws IllegalArgumentException if the video size is not supported. + */ + @Nullable + public Range<Double> getAchievableFrameRatesFor(int width, int height) { + if (!supports(width, height, null)) { + throw new IllegalArgumentException("unsupported size"); + } + + if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) { + Log.w(TAG, "Codec did not publish any measurement data."); + return null; + } + + return estimateFrameRatesFor(width, height); + } /** * Video performance points are a set of standard performance points defined by number of @@ -2342,24 +2225,6 @@ public final class MediaCodecInfo { } /** - * Width in macroblocks. - * - * @hide - */ - /** package private */ int getWidth() { - return mWidth; - } - - /** - * Height in macroblocks. - * - * @hide - */ - /** package private */ int getHeight() { - return mHeight; - } - - /** * Maximum frame rate in frames per second. * * @hide @@ -2379,24 +2244,6 @@ public final class MediaCodecInfo { return mMaxMacroBlockRate; } - /** - * Codec block width in macroblocks. - * - * @hide - */ - /** package private */ int getBlockWidth() { - return mBlockSize.getWidth(); - } - - /** - * Codec block height in macroblocks. - * - * @hide - */ - /** package private */ int getBlockHeight() { - return mBlockSize.getHeight(); - } - /** Convert to a debug string */ public String toString() { int blockWidth = 16 * mBlockSize.getWidth(); @@ -2484,20 +2331,6 @@ public final class MediaCodecInfo { this(width, height, frameRate, frameRate /* maxFrameRate */, new Size(16, 16)); } - /* package private */ PerformancePoint(int width, int height, int maxFrameRate, - long maxMacroBlockRate, int blockSizeWidth, int blockSizeHeight) { - mWidth = width; - mHeight = height; - mMaxFrameRate = maxFrameRate; - mMaxMacroBlockRate = maxMacroBlockRate; - mBlockSize = new Size(blockSizeWidth, blockSizeHeight); - } - - private PerformancePoint(PerformancePoint pp) { - this(pp.mWidth, pp.mHeight, pp.mMaxFrameRate, pp.mMaxMacroBlockRate, - pp.mBlockSize.getWidth(), pp.mBlockSize.getHeight()); - } - /** Saturates a long value to int */ private int saturateLongToInt(long value) { if (value < Integer.MIN_VALUE) { @@ -2551,18 +2384,14 @@ public final class MediaCodecInfo { * @return {@code true} if the performance point covers the other. */ public boolean covers(@NonNull PerformancePoint other) { - if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { - return native_covers(other); - } else { - // convert performance points to common block size - Size commonSize = getCommonBlockSize(other); - PerformancePoint aligned = new PerformancePoint(this, commonSize); - PerformancePoint otherAligned = new PerformancePoint(other, commonSize); + // convert performance points to common block size + Size commonSize = getCommonBlockSize(other); + PerformancePoint aligned = new PerformancePoint(this, commonSize); + PerformancePoint otherAligned = new PerformancePoint(other, commonSize); - return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks() - && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate - && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate); - } + return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks() + && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate + && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate); } private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) { @@ -2576,28 +2405,17 @@ public final class MediaCodecInfo { if (o instanceof PerformancePoint) { // convert performance points to common block size PerformancePoint other = (PerformancePoint)o; - if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { - return native_equals(other); - } else { - Size commonSize = getCommonBlockSize(other); - PerformancePoint aligned = new PerformancePoint(this, commonSize); - PerformancePoint otherAligned = new PerformancePoint(other, commonSize); + Size commonSize = getCommonBlockSize(other); + PerformancePoint aligned = new PerformancePoint(this, commonSize); + PerformancePoint otherAligned = new PerformancePoint(other, commonSize); - return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks() - && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate - && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate); - } + return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks() + && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate + && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate); } return false; } - private native boolean native_covers(PerformancePoint other); - private native boolean native_equals(PerformancePoint other); - - static { - System.loadLibrary("media_jni"); - } - /** 480p 24fps */ @NonNull public static final PerformancePoint SD_24 = new PerformancePoint(720, 480, 24); @@ -2702,1869 +2520,1350 @@ public final class MediaCodecInfo { public static final PerformancePoint UHD_240 = new PerformancePoint(3840, 2160, 240); } - /* package private */ interface VideoCapsIntf { - public Range<Integer> getBitrateRange(); - - public Range<Integer> getSupportedWidths(); - - public Range<Integer> getSupportedHeights(); + /** + * Returns the supported performance points. May return {@code null} if the codec did not + * publish any performance point information (e.g. the vendor codecs have not been updated + * to the latest android release). May return an empty list if the codec published that + * if does not guarantee any performance points. + * <p> + * This is a performance guarantee provided by the device manufacturer for hardware codecs + * based on hardware capabilities of the device. + * <p> + * The returned list is sorted first by decreasing number of pixels, then by decreasing + * width, and finally by decreasing frame rate. + * Performance points assume a single active codec. For use cases where multiple + * codecs are active, should use that highest pixel count, and add the frame rates of + * each individual codec. + * <p class=note> + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space, but performance points will be presented as is. + * In other words, even though a component publishes a performance point for + * a resolution higher than 4096x4096, it does not mean that the resolution is supported + * for 32-bit processes. + */ + @Nullable + public List<PerformancePoint> getSupportedPerformancePoints() { + return mPerformancePoints; + } - public int getWidthAlignment(); + /** + * Returns whether a given video size ({@code width} and + * {@code height}) and {@code frameRate} combination is supported. + */ + public boolean areSizeAndRateSupported( + int width, int height, double frameRate) { + return supports(width, height, frameRate); + } - public int getHeightAlignment(); + /** + * Returns whether a given video size ({@code width} and + * {@code height}) is supported. + */ + public boolean isSizeSupported(int width, int height) { + return supports(width, height, null); + } - public int getSmallerDimensionUpperLimit(); + private boolean supports(Integer width, Integer height, Number rate) { + boolean ok = true; - public Range<Integer> getSupportedFrameRates(); + if (ok && width != null) { + ok = mWidthRange.contains(width) + && (width % mWidthAlignment == 0); + } + if (ok && height != null) { + ok = mHeightRange.contains(height) + && (height % mHeightAlignment == 0); + } + if (ok && rate != null) { + ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue())); + } + if (ok && height != null && width != null) { + ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; - public Range<Integer> getSupportedWidthsFor(int height); + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + final int blockCount = widthInBlocks * heightInBlocks; + ok = ok && mBlockCountRange.contains(blockCount) + && mBlockAspectRatioRange.contains( + new Rational(widthInBlocks, heightInBlocks)) + && mAspectRatioRange.contains(new Rational(width, height)); + if (ok && rate != null) { + double blocksPerSec = blockCount * rate.doubleValue(); + ok = mBlocksPerSecondRange.contains( + Utils.longRangeFor(blocksPerSec)); + } + } + return ok; + } - public Range<Integer> getSupportedHeightsFor(int width); + /* package private */ + // must not contain KEY_PROFILE + static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( + MediaFormat.KEY_WIDTH, + MediaFormat.KEY_HEIGHT, + MediaFormat.KEY_FRAME_RATE, + MediaFormat.KEY_BIT_RATE, + MediaFormat.KEY_MIME); - public Range<Double> getSupportedFrameRatesFor(int width, int height); + /** + * @hide + * @throws java.lang.ClassCastException */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); + Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); + Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE); - public Range<Double> getAchievableFrameRatesFor(int width, int height); + if (!supports(width, height, rate)) { + return false; + } - public boolean areSizeAndRateSupported(int width, int height, double frameRate); + if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) { + return false; + } - public boolean isSizeSupported(int width, int height); + // we ignore color-format for now as it is not reliably reported by codec + return true; + } - public boolean supportsFormat(MediaFormat format); + /* no public constructor */ + private VideoCapabilities() { } - public List<PerformancePoint> getSupportedPerformancePoints(); + /** @hide */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public static VideoCapabilities create( + MediaFormat info, CodecCapabilities parent) { + VideoCapabilities caps = new VideoCapabilities(); + caps.init(info, parent); + return caps; } - /* package private */ static final class VideoCapsLegacyImpl implements VideoCapsIntf { - /* package private */ - // must not contain KEY_PROFILE - static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( - MediaFormat.KEY_WIDTH, - MediaFormat.KEY_HEIGHT, - MediaFormat.KEY_FRAME_RATE, - MediaFormat.KEY_BIT_RATE, - MediaFormat.KEY_MIME); - - private CodecCapabilities.CodecCapsLegacyImpl mParent; - private Range<Integer> mBitrateRange; - - private Range<Integer> mHeightRange; - private Range<Integer> mWidthRange; - private Range<Integer> mBlockCountRange; - private Range<Integer> mHorizontalBlockRange; - private Range<Integer> mVerticalBlockRange; - private Range<Rational> mAspectRatioRange; - private Range<Rational> mBlockAspectRatioRange; - private Range<Long> mBlocksPerSecondRange; - private Map<Size, Range<Long>> mMeasuredFrameRates; - private List<PerformancePoint> mPerformancePoints; - private Range<Integer> mFrameRateRange; - - private int mBlockWidth; - private int mBlockHeight; - private int mWidthAlignment; - private int mHeightAlignment; - private int mSmallerDimensionUpperLimit; - - private boolean mAllowMbOverride; // allow XML to override calculated limits - - /* no public constructor */ - private VideoCapsLegacyImpl() { } - - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static VideoCapsLegacyImpl create( - MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { - Log.d(TAG, "Legacy implementation is called while native flag is on."); - } + private void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + updateLimits(); + } - VideoCapsLegacyImpl caps = new VideoCapsLegacyImpl(); - caps.init(info, parent); - return caps; - } + /** @hide */ + public Size getBlockSize() { + return new Size(mBlockWidth, mBlockHeight); + } - private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - mParent = parent; - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); - updateLimits(); - } + /** @hide */ + public Range<Integer> getBlockCountRange() { + return mBlockCountRange; + } - public Range<Integer> getBitrateRange() { - return mBitrateRange; - } + /** @hide */ + public Range<Long> getBlocksPerSecondRange() { + return mBlocksPerSecondRange; + } - public Range<Integer> getSupportedWidths() { - return mWidthRange; - } + /** @hide */ + public Range<Rational> getAspectRatioRange(boolean blocks) { + return blocks ? mBlockAspectRatioRange : mAspectRatioRange; + } - public Range<Integer> getSupportedHeights() { - return mHeightRange; - } + private void initWithPlatformLimits() { + mBitrateRange = BITRATE_RANGE; - public int getWidthAlignment() { - return mWidthAlignment; - } + mWidthRange = getSizeRange(); + mHeightRange = getSizeRange(); + mFrameRateRange = FRAME_RATE_RANGE; - public int getHeightAlignment() { - return mHeightAlignment; - } + mHorizontalBlockRange = getSizeRange(); + mVerticalBlockRange = getSizeRange(); - /** @hide */ - public int getSmallerDimensionUpperLimit() { - return mSmallerDimensionUpperLimit; - } + // full positive ranges are supported as these get calculated + mBlockCountRange = POSITIVE_INTEGERS; + mBlocksPerSecondRange = POSITIVE_LONGS; - public Range<Integer> getSupportedFrameRates() { - return mFrameRateRange; - } + mBlockAspectRatioRange = POSITIVE_RATIONALS; + mAspectRatioRange = POSITIVE_RATIONALS; - public Range<Integer> getSupportedWidthsFor(int height) { - try { - Range<Integer> range = mWidthRange; - if (!mHeightRange.contains(height) - || (height % mHeightAlignment) != 0) { - throw new IllegalArgumentException("unsupported height"); - } - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - - // constrain by block count and by block aspect ratio - final int minWidthInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), - (int) Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() - * heightInBlocks)); - final int maxWidthInBlocks = Math.min( - mBlockCountRange.getUpper() / heightInBlocks, - (int) (mBlockAspectRatioRange.getUpper().doubleValue() - * heightInBlocks)); - range = range.intersect( - (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, - maxWidthInBlocks * mBlockWidth); - - // constrain by smaller dimension limit - if (height > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } + mWidthAlignment = 1; + mHeightAlignment = 1; + mBlockWidth = 1; + mBlockHeight = 1; + mSmallerDimensionUpperLimit = getSizeRange().getUpper(); + } - // constrain by aspect ratio - range = range.intersect( - (int) Math.ceil(mAspectRatioRange.getLower().doubleValue() - * height), - (int) (mAspectRatioRange.getUpper().doubleValue() * height)); - return range; - } catch (IllegalArgumentException e) { - // height is not supported because there are no suitable widths - Log.v(TAG, "could not get supported widths for " + height); - throw new IllegalArgumentException("unsupported height"); + private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) { + Vector<PerformancePoint> ret = new Vector<>(); + final String prefix = "performance-point-"; + Set<String> keys = map.keySet(); + for (String key : keys) { + // looking for: performance-point-WIDTHxHEIGHT-range + if (!key.startsWith(prefix)) { + continue; } - } - - public Range<Integer> getSupportedHeightsFor(int width) { - try { - Range<Integer> range = mHeightRange; - if (!mWidthRange.contains(width) - || (width % mWidthAlignment) != 0) { - throw new IllegalArgumentException("unsupported width"); - } - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - - // constrain by block count and by block aspect ratio - final int minHeightInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), - (int) Math.ceil(widthInBlocks - / mBlockAspectRatioRange.getUpper().doubleValue())); - final int maxHeightInBlocks = Math.min( - mBlockCountRange.getUpper() / widthInBlocks, - (int) (widthInBlocks - / mBlockAspectRatioRange.getLower().doubleValue())); - range = range.intersect( - (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, - maxHeightInBlocks * mBlockHeight); - - // constrain by smaller dimension limit - if (width > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } - - // constrain by aspect ratio - range = range.intersect( - (int) Math.ceil(width - / mAspectRatioRange.getUpper().doubleValue()), - (int) (width / mAspectRatioRange.getLower().doubleValue())); - return range; - } catch (IllegalArgumentException e) { - // width is not supported because there are no suitable heights - Log.v(TAG, "could not get supported heights for " + width); - throw new IllegalArgumentException("unsupported width"); + String subKey = key.substring(prefix.length()); + if (subKey.equals("none") && ret.size() == 0) { + // This means that component knowingly did not publish performance points. + // This is different from when the component forgot to publish performance + // points. + return Collections.unmodifiableList(ret); } - } - - public Range<Double> getSupportedFrameRatesFor(int width, int height) { - Range<Integer> range = mHeightRange; - if (!supports(width, height, null)) { - throw new IllegalArgumentException("unsupported size"); + String[] temp = key.split("-"); + if (temp.length != 4) { + continue; } - final int blockCount = - Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); - - return Range.create( - Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, - (double) mFrameRateRange.getLower()), - Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, - (double) mFrameRateRange.getUpper())); - } - - private int getBlockCount(int width, int height) { - return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); - } - - @NonNull - private Size findClosestSize(int width, int height) { - int targetBlockCount = getBlockCount(width, height); - Size closestSize = null; - int minDiff = Integer.MAX_VALUE; - for (Size size : mMeasuredFrameRates.keySet()) { - int diff = Math.abs(targetBlockCount - - getBlockCount(size.getWidth(), size.getHeight())); - if (diff < minDiff) { - minDiff = diff; - closestSize = size; - } + String sizeStr = temp[2]; + Size size = Utils.parseSize(sizeStr, null); + if (size == null || size.getWidth() * size.getHeight() <= 0) { + continue; } - return closestSize; - } - - private Range<Double> estimateFrameRatesFor(int width, int height) { - Size size = findClosestSize(width, height); - Range<Long> range = mMeasuredFrameRates.get(size); - Double ratio = getBlockCount(size.getWidth(), size.getHeight()) - / (double)Math.max(getBlockCount(width, height), 1); - return Range.create(range.getLower() * ratio, range.getUpper() * ratio); - } - - /** @throws IllegalArgumentException if the video size is not supported. */ - @Nullable - public Range<Double> getAchievableFrameRatesFor(int width, int height) { - if (!supports(width, height, null)) { - throw new IllegalArgumentException("unsupported size"); + Range<Long> range = Utils.parseLongRange(map.get(key), null); + if (range == null || range.getLower() < 0 || range.getUpper() < 0) { + continue; } - - if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) { - Log.w(TAG, "Codec did not publish any measurement data."); - return null; + PerformancePoint given = new PerformancePoint( + size.getWidth(), size.getHeight(), range.getLower().intValue(), + range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); + PerformancePoint rotated = new PerformancePoint( + size.getHeight(), size.getWidth(), range.getLower().intValue(), + range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); + ret.add(given); + if (!given.covers(rotated)) { + ret.add(rotated); } - - return estimateFrameRatesFor(width, height); } - @Nullable - public List<PerformancePoint> getSupportedPerformancePoints() { - return mPerformancePoints; - } - - public boolean areSizeAndRateSupported( - int width, int height, double frameRate) { - return supports(width, height, frameRate); + // check if the component specified no performance point indication + if (ret.size() == 0) { + return null; } - public boolean isSizeSupported(int width, int height) { - return supports(width, height, null); - } + // sort reversed by area first, then by frame rate + ret.sort((a, b) -> + -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ? + (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) : + (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ? + (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) : + (a.getMaxFrameRate() != b.getMaxFrameRate()) ? + (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0)); - private boolean supports(Integer width, Integer height, Number rate) { - boolean ok = true; + return Collections.unmodifiableList(ret); + } - if (ok && width != null) { - ok = mWidthRange.contains(width) - && (width % mWidthAlignment == 0); + private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) { + Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>(); + final String prefix = "measured-frame-rate-"; + Set<String> keys = map.keySet(); + for (String key : keys) { + // looking for: measured-frame-rate-WIDTHxHEIGHT-range + if (!key.startsWith(prefix)) { + continue; } - if (ok && height != null) { - ok = mHeightRange.contains(height) - && (height % mHeightAlignment == 0); + String subKey = key.substring(prefix.length()); + String[] temp = key.split("-"); + if (temp.length != 5) { + continue; } - if (ok && rate != null) { - ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue())); + String sizeStr = temp[3]; + Size size = Utils.parseSize(sizeStr, null); + if (size == null || size.getWidth() * size.getHeight() <= 0) { + continue; } - if (ok && height != null && width != null) { - ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; - - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - final int blockCount = widthInBlocks * heightInBlocks; - ok = ok && mBlockCountRange.contains(blockCount) - && mBlockAspectRatioRange.contains( - new Rational(widthInBlocks, heightInBlocks)) - && mAspectRatioRange.contains(new Rational(width, height)); - if (ok && rate != null) { - double blocksPerSec = blockCount * rate.doubleValue(); - ok = mBlocksPerSecondRange.contains( - Utils.longRangeFor(blocksPerSec)); - } + Range<Long> range = Utils.parseLongRange(map.get(key), null); + if (range == null || range.getLower() < 0 || range.getUpper() < 0) { + continue; } - return ok; + ret.put(size, range); } + return ret; + } - /** - * @hide - * @throws java.lang.ClassCastException */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); - Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); - Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE); - - if (!supports(width, height, rate)) { - return false; - } - - if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) { - return false; + private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) { + Pair<Size, Size> range = Utils.parseSizeRange(o); + if (range != null) { + try { + return Pair.create( + Range.create(range.first.getWidth(), range.second.getWidth()), + Range.create(range.first.getHeight(), range.second.getHeight())); + } catch (IllegalArgumentException e) { + Log.w(TAG, "could not parse size range '" + o + "'"); } - - // we ignore color-format for now as it is not reliably reported by codec - return true; } + return null; + } - /** @hide */ - public Size getBlockSize() { - return new Size(mBlockWidth, mBlockHeight); - } + /** @hide */ + public static int equivalentVP9Level(MediaFormat info) { + final Map<String, Object> map = info.getMap(); + + Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8)); + int BS = blockSize.getWidth() * blockSize.getHeight(); + + Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null); + int FS = counts == null ? 0 : BS * counts.getUpper(); + + Range<Long> blockRates = + Utils.parseLongRange(map.get("blocks-per-second-range"), null); + long SR = blockRates == null ? 0 : BS * blockRates.getUpper(); + + Pair<Range<Integer>, Range<Integer>> dimensionRanges = + parseWidthHeightRanges(map.get("size-range")); + int D = dimensionRanges == null ? 0 : Math.max( + dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper()); + + Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); + int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000); + + if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512) + return CodecProfileLevel.VP9Level1; + if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768) + return CodecProfileLevel.VP9Level11; + if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960) + return CodecProfileLevel.VP9Level2; + if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344) + return CodecProfileLevel.VP9Level21; + if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048) + return CodecProfileLevel.VP9Level3; + if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752) + return CodecProfileLevel.VP9Level31; + if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160) + return CodecProfileLevel.VP9Level4; + if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160) + return CodecProfileLevel.VP9Level41; + if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384) + return CodecProfileLevel.VP9Level5; + if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384) + return CodecProfileLevel.VP9Level51; + if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384) + return CodecProfileLevel.VP9Level52; + if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832) + return CodecProfileLevel.VP9Level6; + if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832) + return CodecProfileLevel.VP9Level61; + if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832) + return CodecProfileLevel.VP9Level62; + // returning largest level + return CodecProfileLevel.VP9Level62; + } - /** @hide */ - public Range<Integer> getBlockCountRange() { - return mBlockCountRange; + private void parseFromInfo(MediaFormat info) { + final Map<String, Object> map = info.getMap(); + Size blockSize = new Size(mBlockWidth, mBlockHeight); + Size alignment = new Size(mWidthAlignment, mHeightAlignment); + Range<Integer> counts = null, widths = null, heights = null; + Range<Integer> frameRates = null, bitRates = null; + Range<Long> blockRates = null; + Range<Rational> ratios = null, blockRatios = null; + + blockSize = Utils.parseSize(map.get("block-size"), blockSize); + alignment = Utils.parseSize(map.get("alignment"), alignment); + counts = Utils.parseIntRange(map.get("block-count-range"), null); + blockRates = + Utils.parseLongRange(map.get("blocks-per-second-range"), null); + mMeasuredFrameRates = getMeasuredFrameRates(map); + mPerformancePoints = getPerformancePoints(map); + Pair<Range<Integer>, Range<Integer>> sizeRanges = + parseWidthHeightRanges(map.get("size-range")); + if (sizeRanges != null) { + widths = sizeRanges.first; + heights = sizeRanges.second; + } + // for now this just means using the smaller max size as 2nd + // upper limit. + // for now we are keeping the profile specific "width/height + // in macroblocks" limits. + if (map.containsKey("feature-can-swap-width-height")) { + if (widths != null) { + mSmallerDimensionUpperLimit = + Math.min(widths.getUpper(), heights.getUpper()); + widths = heights = widths.extend(heights); + } else { + Log.w(TAG, "feature can-swap-width-height is best used with size-range"); + mSmallerDimensionUpperLimit = + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); + mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); + } } - /** @hide */ - public Range<Long> getBlocksPerSecondRange() { - return mBlocksPerSecondRange; + 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; + } } - - /** @hide */ - public Range<Rational> getAspectRatioRange(boolean blocks) { - return blocks ? mBlockAspectRatioRange : mAspectRatioRange; + bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); + if (bitRates != null) { + try { + bitRates = bitRates.intersect(BITRATE_RANGE); + } catch (IllegalArgumentException e) { + Log.w(TAG, "bitrate range (" + bitRates + + ") is out of limits: " + BITRATE_RANGE); + bitRates = null; + } } - private void initWithPlatformLimits() { - mBitrateRange = BITRATE_RANGE; + checkPowerOfTwo( + blockSize.getWidth(), "block-size width must be power of two"); + checkPowerOfTwo( + blockSize.getHeight(), "block-size height must be power of two"); - mWidthRange = getSizeRange(); - mHeightRange = getSizeRange(); - mFrameRateRange = FRAME_RATE_RANGE; + checkPowerOfTwo( + alignment.getWidth(), "alignment width must be power of two"); + checkPowerOfTwo( + alignment.getHeight(), "alignment height must be power of two"); - mHorizontalBlockRange = getSizeRange(); - mVerticalBlockRange = getSizeRange(); + // 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()); - // full positive ranges are supported as these get calculated - mBlockCountRange = POSITIVE_INTEGERS; - mBlocksPerSecondRange = POSITIVE_LONGS; - - mBlockAspectRatioRange = POSITIVE_RATIONALS; - mAspectRatioRange = POSITIVE_RATIONALS; - - mWidthAlignment = 1; - mHeightAlignment = 1; - mBlockWidth = 1; - mBlockHeight = 1; - mSmallerDimensionUpperLimit = getSizeRange().getUpper(); - } - - private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) { - Vector<PerformancePoint> ret = new Vector<>(); - final String prefix = "performance-point-"; - Set<String> keys = map.keySet(); - for (String key : keys) { - // looking for: performance-point-WIDTHxHEIGHT-range - if (!key.startsWith(prefix)) { - continue; - } - String subKey = key.substring(prefix.length()); - if (subKey.equals("none") && ret.size() == 0) { - // This means that component knowingly did not publish performance points. - // This is different from when the component forgot to publish performance - // points. - return Collections.unmodifiableList(ret); - } - String[] temp = key.split("-"); - if (temp.length != 4) { - continue; - } - String sizeStr = temp[2]; - Size size = Utils.parseSize(sizeStr, null); - if (size == null || size.getWidth() * size.getHeight() <= 0) { - continue; - } - Range<Long> range = Utils.parseLongRange(map.get(key), null); - if (range == null || range.getLower() < 0 || range.getUpper() < 0) { - continue; - } - PerformancePoint given = new PerformancePoint( - size.getWidth(), size.getHeight(), range.getLower().intValue(), - range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); - PerformancePoint rotated = new PerformancePoint( - size.getHeight(), size.getWidth(), range.getLower().intValue(), - range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); - ret.add(given); - if (!given.covers(rotated)) { - ret.add(rotated); - } + if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) { + // codec supports profiles that we don't know. + // Use supplied values clipped to platform limits + if (widths != null) { + mWidthRange = getSizeRange().intersect(widths); } - - // check if the component specified no performance point indication - if (ret.size() == 0) { - return null; + if (heights != null) { + mHeightRange = getSizeRange().intersect(heights); } - - // sort reversed by area first, then by frame rate - ret.sort((a, b) -> - -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) - ? (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) : - (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) - ? (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) : - (a.getMaxFrameRate() != b.getMaxFrameRate()) - ? (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0)); - - return Collections.unmodifiableList(ret); - } - - private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) { - Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>(); - final String prefix = "measured-frame-rate-"; - Set<String> keys = map.keySet(); - for (String key : keys) { - // looking for: measured-frame-rate-WIDTHxHEIGHT-range - if (!key.startsWith(prefix)) { - continue; - } - String subKey = key.substring(prefix.length()); - String[] temp = key.split("-"); - if (temp.length != 5) { - continue; - } - String sizeStr = temp[3]; - Size size = Utils.parseSize(sizeStr, null); - if (size == null || size.getWidth() * size.getHeight() <= 0) { - continue; - } - Range<Long> range = Utils.parseLongRange(map.get(key), null); - if (range == null || range.getLower() < 0 || range.getUpper() < 0) { - continue; - } - ret.put(size, range); + if (counts != null) { + mBlockCountRange = POSITIVE_INTEGERS.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); } - return ret; - } - - private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) { - Pair<Size, Size> range = Utils.parseSizeRange(o); - if (range != null) { - try { - return Pair.create( - Range.create(range.first.getWidth(), range.second.getWidth()), - Range.create(range.first.getHeight(), range.second.getHeight())); - } catch (IllegalArgumentException e) { - Log.w(TAG, "could not parse size range '" + o + "'"); - } + if (blockRates != null) { + mBlocksPerSecondRange = POSITIVE_LONGS.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); } - return null; - } - - /** @hide */ - public static int equivalentVP9Level(MediaFormat info) { - final Map<String, Object> map = info.getMap(); - - Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8)); - int BS = blockSize.getWidth() * blockSize.getHeight(); - - Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null); - int FS = counts == null ? 0 : BS * counts.getUpper(); - - Range<Long> blockRates = - Utils.parseLongRange(map.get("blocks-per-second-range"), null); - long SR = blockRates == null ? 0 : BS * blockRates.getUpper(); - - Pair<Range<Integer>, Range<Integer>> dimensionRanges = - parseWidthHeightRanges(map.get("size-range")); - int D = dimensionRanges == null ? 0 : Math.max( - dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper()); - - Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); - int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000); - - if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512) - return CodecProfileLevel.VP9Level1; - if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768) - return CodecProfileLevel.VP9Level11; - if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960) - return CodecProfileLevel.VP9Level2; - if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344) - return CodecProfileLevel.VP9Level21; - if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048) - return CodecProfileLevel.VP9Level3; - if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752) - return CodecProfileLevel.VP9Level31; - if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160) - return CodecProfileLevel.VP9Level4; - if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160) - return CodecProfileLevel.VP9Level41; - if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384) - return CodecProfileLevel.VP9Level5; - if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384) - return CodecProfileLevel.VP9Level51; - if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384) - return CodecProfileLevel.VP9Level52; - if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832) - return CodecProfileLevel.VP9Level6; - if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832) - return CodecProfileLevel.VP9Level61; - if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832) - return CodecProfileLevel.VP9Level62; - // returning largest level - return CodecProfileLevel.VP9Level62; - } - - private void parseFromInfo(MediaFormat info) { - final Map<String, Object> map = info.getMap(); - Size blockSize = new Size(mBlockWidth, mBlockHeight); - Size alignment = new Size(mWidthAlignment, mHeightAlignment); - Range<Integer> counts = null, widths = null, heights = null; - Range<Integer> frameRates = null, bitRates = null; - Range<Long> blockRates = null; - Range<Rational> ratios = null, blockRatios = null; - - blockSize = Utils.parseSize(map.get("block-size"), blockSize); - alignment = Utils.parseSize(map.get("alignment"), alignment); - counts = Utils.parseIntRange(map.get("block-count-range"), null); - blockRates = - Utils.parseLongRange(map.get("blocks-per-second-range"), null); - mMeasuredFrameRates = getMeasuredFrameRates(map); - mPerformancePoints = getPerformancePoints(map); - Pair<Range<Integer>, Range<Integer>> sizeRanges = - parseWidthHeightRanges(map.get("size-range")); - if (sizeRanges != null) { - widths = sizeRanges.first; - heights = sizeRanges.second; + if (blockRatios != null) { + mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); } - // for now this just means using the smaller max size as 2nd - // upper limit. - // for now we are keeping the profile specific "width/height - // in macroblocks" limits. - if (map.containsKey("feature-can-swap-width-height")) { - if (widths != null) { - mSmallerDimensionUpperLimit = - Math.min(widths.getUpper(), heights.getUpper()); - widths = heights = widths.extend(heights); + if (ratios != null) { + mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); + } + if (frameRates != null) { + mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); + } + if (bitRates != null) { + // only allow bitrate override if unsupported profiles were encountered + if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { + mBitrateRange = BITRATE_RANGE.intersect(bitRates); } else { - Log.w(TAG, "feature can-swap-width-height is best used with size-range"); - mSmallerDimensionUpperLimit = - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); - mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); + mBitrateRange = mBitrateRange.intersect(bitRates); } } - - 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); + } 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) { - 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; - } + mFrameRateRange = mFrameRateRange.intersect(frameRates); } - bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); if (bitRates != null) { - try { - bitRates = bitRates.intersect(BITRATE_RANGE); - } catch (IllegalArgumentException e) { - Log.w(TAG, "bitrate range (" + bitRates - + ") is out of limits: " + BITRATE_RANGE); - bitRates = null; - } + mBitrateRange = mBitrateRange.intersect(bitRates); } + } + updateLimits(); + } - checkPowerOfTwo( - blockSize.getWidth(), "block-size width must be power of two"); - checkPowerOfTwo( - blockSize.getHeight(), "block-size height must be power of two"); + 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; + } - checkPowerOfTwo( - alignment.getWidth(), "alignment width must be power of two"); - checkPowerOfTwo( - alignment.getHeight(), "alignment height must be power of two"); + 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"); - // update block-size and alignment - applyMacroBlockLimits( - Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, - Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(), - alignment.getWidth(), alignment.getHeight()); - - if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) { - // codec supports profiles that we don't know. - // Use supplied values clipped to platform limits - if (widths != null) { - mWidthRange = getSizeRange().intersect(widths); - } - if (heights != null) { - mHeightRange = getSizeRange().intersect(heights); - } - if (counts != null) { - mBlockCountRange = POSITIVE_INTEGERS.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = POSITIVE_LONGS.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); - } - if (ratios != null) { - mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); - } - if (frameRates != null) { - mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); - } - if (bitRates != null) { - // only allow bitrate override if unsupported profiles were encountered - if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { - mBitrateRange = BITRATE_RANGE.intersect(bitRates); - } else { - mBitrateRange = mBitrateRange.intersect(bitRates); - } - } - } else { - // no unsupported profile/levels, so restrict values to known limits - if (widths != null) { - mWidthRange = mWidthRange.intersect(widths); - } - if (heights != null) { - mHeightRange = mHeightRange.intersect(heights); - } - if (counts != null) { - mBlockCountRange = mBlockCountRange.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); - } - if (ratios != null) { - mAspectRatioRange = mAspectRatioRange.intersect(ratios); - } - if (frameRates != null) { - mFrameRateRange = mFrameRateRange.intersect(frameRates); - } - if (bitRates != null) { - mBitrateRange = mBitrateRange.intersect(bitRates); - } - } - updateLimits(); + 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); } - 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; - } + mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); + mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); - 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"); + mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); + mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); + } - 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); - } + private void updateLimits() { + // pixels -> blocks <- counts + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Utils.factorRange(mWidthRange, mBlockWidth)); + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Utils.factorRange(mHeightRange, mBlockHeight)); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); + mBlockCountRange = mBlockCountRange.intersect( + Range.create( + mHorizontalBlockRange.getLower() + * mVerticalBlockRange.getLower(), + mHorizontalBlockRange.getUpper() + * mVerticalBlockRange.getUpper())); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + new Rational( + mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), + new Rational( + mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); + + // blocks -> pixels + mWidthRange = mWidthRange.intersect( + (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, + mHorizontalBlockRange.getUpper() * mBlockWidth); + mHeightRange = mHeightRange.intersect( + (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, + mVerticalBlockRange.getUpper() * mBlockHeight); + mAspectRatioRange = mAspectRatioRange.intersect( + new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), + new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); + + mSmallerDimensionUpperLimit = Math.min( + mSmallerDimensionUpperLimit, + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); + + // blocks -> rate + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), + mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); + mFrameRateRange = mFrameRateRange.intersect( + (int)(mBlocksPerSecondRange.getLower() + / mBlockCountRange.getUpper()), + (int)(mBlocksPerSecondRange.getUpper() + / (double)mBlockCountRange.getLower())); + } - mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); - mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); + private void applyMacroBlockLimits( + int maxHorizontalBlocks, int maxVerticalBlocks, + int maxBlocks, long maxBlocksPerSecond, + int blockWidth, int blockHeight, + int widthAlignment, int heightAlignment) { + applyMacroBlockLimits( + 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */, + maxHorizontalBlocks, maxVerticalBlocks, + maxBlocks, maxBlocksPerSecond, + blockWidth, blockHeight, widthAlignment, heightAlignment); + } - mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); - mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); - } + private void applyMacroBlockLimits( + int minHorizontalBlocks, int minVerticalBlocks, + int maxHorizontalBlocks, int maxVerticalBlocks, + int maxBlocks, long maxBlocksPerSecond, + int blockWidth, int blockHeight, + int widthAlignment, int heightAlignment) { + applyAlignment(widthAlignment, heightAlignment); + applyBlockLimits( + blockWidth, blockHeight, Range.create(1, maxBlocks), + Range.create(1L, maxBlocksPerSecond), + Range.create( + new Rational(1, maxVerticalBlocks), + new Rational(maxHorizontalBlocks, 1))); + mHorizontalBlockRange = + mHorizontalBlockRange.intersect( + Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)), + maxHorizontalBlocks / (mBlockWidth / blockWidth)); + mVerticalBlockRange = + mVerticalBlockRange.intersect( + Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)), + maxVerticalBlocks / (mBlockHeight / blockHeight)); + } - private void updateLimits() { - // pixels -> blocks <- counts - mHorizontalBlockRange = mHorizontalBlockRange.intersect( - Utils.factorRange(mWidthRange, mBlockWidth)); - mHorizontalBlockRange = mHorizontalBlockRange.intersect( - Range.create( - mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), - mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); - mVerticalBlockRange = mVerticalBlockRange.intersect( - Utils.factorRange(mHeightRange, mBlockHeight)); - mVerticalBlockRange = mVerticalBlockRange.intersect( - Range.create( - mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), - mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); - mBlockCountRange = mBlockCountRange.intersect( - Range.create( - mHorizontalBlockRange.getLower() - * mVerticalBlockRange.getLower(), - mHorizontalBlockRange.getUpper() - * mVerticalBlockRange.getUpper())); - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( - new Rational( - mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), - new Rational( - mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); - - // blocks -> pixels - mWidthRange = mWidthRange.intersect( - (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, - mHorizontalBlockRange.getUpper() * mBlockWidth); - mHeightRange = mHeightRange.intersect( - (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, - mVerticalBlockRange.getUpper() * mBlockHeight); - mAspectRatioRange = mAspectRatioRange.intersect( - new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), - new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); - - mSmallerDimensionUpperLimit = Math.min( - mSmallerDimensionUpperLimit, - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); - - // blocks -> rate - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( - mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), - mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); - mFrameRateRange = mFrameRateRange.intersect( - (int)(mBlocksPerSecondRange.getLower() - / mBlockCountRange.getUpper()), - (int)(mBlocksPerSecondRange.getUpper() - / (double)mBlockCountRange.getLower())); - } + private void applyLevelLimits() { + long maxBlocksPerSecond = 0; + int maxBlocks = 0; + int maxBps = 0; + int maxDPBBlocks = 0; + + int errors = ERROR_NONE_SUPPORTED; + CodecProfileLevel[] profileLevels = mParent.profileLevels; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + maxDPBBlocks = 396; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, DPB = 0; + boolean supported = true; + switch (profileLevel.level) { + case CodecProfileLevel.AVCLevel1: + MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; + case CodecProfileLevel.AVCLevel1b: + MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; + case CodecProfileLevel.AVCLevel11: + MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; + case CodecProfileLevel.AVCLevel12: + MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; + case CodecProfileLevel.AVCLevel13: + MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; + case CodecProfileLevel.AVCLevel2: + MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; + case CodecProfileLevel.AVCLevel21: + MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; + case CodecProfileLevel.AVCLevel22: + MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel3: + MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel31: + MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; + case CodecProfileLevel.AVCLevel32: + MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; + case CodecProfileLevel.AVCLevel4: + MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel41: + MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel42: + MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; + case CodecProfileLevel.AVCLevel5: + MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; + case CodecProfileLevel.AVCLevel51: + MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; + case CodecProfileLevel.AVCLevel52: + MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; + case CodecProfileLevel.AVCLevel6: + MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break; + case CodecProfileLevel.AVCLevel61: + MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break; + case CodecProfileLevel.AVCLevel62: + MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.AVCProfileConstrainedHigh: + case CodecProfileLevel.AVCProfileHigh: + BR *= 1250; break; + case CodecProfileLevel.AVCProfileHigh10: + BR *= 3000; break; + case CodecProfileLevel.AVCProfileExtended: + case CodecProfileLevel.AVCProfileHigh422: + case CodecProfileLevel.AVCProfileHigh444: + Log.w(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + // fall through - treat as base profile + case CodecProfileLevel.AVCProfileConstrainedBaseline: + case CodecProfileLevel.AVCProfileBaseline: + case CodecProfileLevel.AVCProfileMain: + BR *= 1000; break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + BR *= 1000; + } + if (supported) { + errors &= ~ERROR_NONE_SUPPORTED; + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR, maxBps); + maxDPBBlocks = Math.max(maxDPBBlocks, DPB); + } - private void applyMacroBlockLimits( - int maxHorizontalBlocks, int maxVerticalBlocks, - int maxBlocks, long maxBlocksPerSecond, - int blockWidth, int blockHeight, - int widthAlignment, int heightAlignment) { + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); applyMacroBlockLimits( - 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */, - maxHorizontalBlocks, maxVerticalBlocks, + maxLengthInBlocks, maxLengthInBlocks, maxBlocks, maxBlocksPerSecond, - blockWidth, blockHeight, widthAlignment, heightAlignment); - } - - private void applyMacroBlockLimits( - int minHorizontalBlocks, int minVerticalBlocks, - int maxHorizontalBlocks, int maxVerticalBlocks, - int maxBlocks, long maxBlocksPerSecond, - int blockWidth, int blockHeight, - int widthAlignment, int heightAlignment) { - applyAlignment(widthAlignment, heightAlignment); - applyBlockLimits( - blockWidth, blockHeight, Range.create(1, maxBlocks), - Range.create(1L, maxBlocksPerSecond), - Range.create( - new Rational(1, maxVerticalBlocks), - new Rational(maxHorizontalBlocks, 1))); - mHorizontalBlockRange = - mHorizontalBlockRange.intersect( - Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)), - maxHorizontalBlocks / (mBlockWidth / blockWidth)); - mVerticalBlockRange = - mVerticalBlockRange.intersect( - Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)), - maxVerticalBlocks / (mBlockHeight / blockHeight)); - } - - private void applyLevelLimits() { - long maxBlocksPerSecond = 0; - int maxBlocks = 0; - int maxBps = 0; - int maxDPBBlocks = 0; - - int errors = ERROR_NONE_SUPPORTED; - CodecProfileLevel[] profileLevels = mParent.getProfileLevels(); - String mime = mParent.getMimeType(); - - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - maxDPBBlocks = 396; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, DPB = 0; - boolean supported = true; - switch (profileLevel.level) { - case CodecProfileLevel.AVCLevel1: - MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; - case CodecProfileLevel.AVCLevel1b: - MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; - case CodecProfileLevel.AVCLevel11: - MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; - case CodecProfileLevel.AVCLevel12: - MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; - case CodecProfileLevel.AVCLevel13: - MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; - case CodecProfileLevel.AVCLevel2: - MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; - case CodecProfileLevel.AVCLevel21: - MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; - case CodecProfileLevel.AVCLevel22: - MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel3: - MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel31: - MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; - case CodecProfileLevel.AVCLevel32: - MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; - case CodecProfileLevel.AVCLevel4: - MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel41: - MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel42: - MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; - case CodecProfileLevel.AVCLevel5: - MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; - case CodecProfileLevel.AVCLevel51: - MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; - case CodecProfileLevel.AVCLevel52: - MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; - case CodecProfileLevel.AVCLevel6: - MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break; - case CodecProfileLevel.AVCLevel61: - MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break; - case CodecProfileLevel.AVCLevel62: - MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.AVCProfileConstrainedHigh: - case CodecProfileLevel.AVCProfileHigh: - BR *= 1250; break; - case CodecProfileLevel.AVCProfileHigh10: - BR *= 3000; break; - case CodecProfileLevel.AVCProfileExtended: - case CodecProfileLevel.AVCProfileHigh422: - case CodecProfileLevel.AVCProfileHigh444: - Log.w(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - // fall through - treat as base profile - case CodecProfileLevel.AVCProfileConstrainedBaseline: - case CodecProfileLevel.AVCProfileBaseline: - case CodecProfileLevel.AVCProfileMain: - BR *= 1000; break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - BR *= 1000; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR, maxBps); - maxDPBBlocks = Math.max(maxDPBBlocks, DPB); + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; + boolean supported = true; + switch (profileLevel.profile) { + case CodecProfileLevel.MPEG2ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG2LevelML: + FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG2ProfileMain: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG2LevelLL: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; + case CodecProfileLevel.MPEG2LevelML: + FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; + case CodecProfileLevel.MPEG2LevelH14: + FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break; + case CodecProfileLevel.MPEG2LevelHL: + FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break; + case CodecProfileLevel.MPEG2LevelHP: + FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG2Profile422: + case CodecProfileLevel.MPEG2ProfileSNR: + case CodecProfileLevel.MPEG2ProfileSpatial: + case CodecProfileLevel.MPEG2ProfileHigh: + Log.i(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; - boolean supported = true; - switch (profileLevel.profile) { - case CodecProfileLevel.MPEG2ProfileSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG2LevelML: - FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG2ProfileMain: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG2LevelLL: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; - case CodecProfileLevel.MPEG2LevelML: - FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; - case CodecProfileLevel.MPEG2LevelH14: - FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break; - case CodecProfileLevel.MPEG2LevelHL: - FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break; - case CodecProfileLevel.MPEG2LevelHP: - FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG2Profile422: - case CodecProfileLevel.MPEG2ProfileSNR: - case CodecProfileLevel.MPEG2ProfileSpatial: - case CodecProfileLevel.MPEG2ProfileHigh: - Log.i(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); + if (supported) { + errors &= ~ERROR_NONE_SUPPORTED; } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = mFrameRateRange.intersect(12, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; - boolean strict = false; // true: W, H and FR are individual max limits - boolean supported = true; - switch (profileLevel.profile) { - case CodecProfileLevel.MPEG4ProfileSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - strict = true; - FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level0b: - strict = true; - FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level4a: - FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break; - case CodecProfileLevel.MPEG4Level5: - FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break; - case CodecProfileLevel.MPEG4Level6: - FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileAdvancedSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; - case CodecProfileLevel.MPEG4Level3b: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break; - case CodecProfileLevel.MPEG4Level4: - FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; - case CodecProfileLevel.MPEG4Level5: - FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileMain: // 2-4 - case CodecProfileLevel.MPEG4ProfileNbit: // 2 - case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 - case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 - case CodecProfileLevel.MPEG4ProfileCore: // 1-2 - case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 - case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 - case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 - - // Studio profiles are not supported by our codecs. - - // Only profiles that can decode simple object types are considered. - // The following profiles are not able to. - case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 - case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 - case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 - case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 - Log.i(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - if (strict) { - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); - } else { - // assuming max 60 fps frame rate and 1:2 aspect ratio - int maxDim = (int)Math.sqrt(FS * 2); - maxWidth = Math.max(maxDim, maxWidth); - maxHeight = Math.max(maxDim, maxHeight); - maxRate = Math.max(Math.max(FR, 60), maxRate); - } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = mFrameRateRange.intersect(12, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; + boolean strict = false; // true: W, H and FR are individual max limits + boolean supported = true; + switch (profileLevel.profile) { + case CodecProfileLevel.MPEG4ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + strict = true; + FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level0b: + strict = true; + FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level4a: + FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break; + case CodecProfileLevel.MPEG4Level5: + FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break; + case CodecProfileLevel.MPEG4Level6: + FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileAdvancedSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; + case CodecProfileLevel.MPEG4Level3b: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break; + case CodecProfileLevel.MPEG4Level4: + FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; + case CodecProfileLevel.MPEG4Level5: + FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileMain: // 2-4 + case CodecProfileLevel.MPEG4ProfileNbit: // 2 + case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 + case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 + case CodecProfileLevel.MPEG4ProfileCore: // 1-2 + case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 + case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 + case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 + + // Studio profiles are not supported by our codecs. + + // Only profiles that can decode simple object types are considered. + // The following profiles are not able to. + case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 + case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 + case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 + case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 + Log.i(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = mFrameRateRange.intersect(12, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - int minWidth = maxWidth, minHeight = maxHeight; - int minAlignment = 16; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight; - boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF) - switch (profileLevel.level) { - case CodecProfileLevel.H263Level10: - strict = true; // only supports sQCIF & QCIF - FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level20: - strict = true; // only supports sQCIF, QCIF & CIF - FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break; - case CodecProfileLevel.H263Level30: - strict = true; // only supports sQCIF, QCIF & CIF - FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level40: - strict = true; // only supports sQCIF, QCIF & CIF - FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level45: - // only implies level 10 support - strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline - || profileLevel.profile - == CodecProfileLevel.H263ProfileBackwardCompatible; - if (!strict) { - minW = 1; minH = 1; minAlignment = 4; - } - FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level50: - // only supports 50fps for H > 15 - minW = 1; minH = 1; minAlignment = 4; - FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level60: - // only supports 50fps for H > 15 - minW = 1; minH = 1; minAlignment = 4; - FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level70: - // only supports 50fps for H > 30 - minW = 1; minH = 1; minAlignment = 4; - FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; - default: - Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile - + "/" + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.H263ProfileBackwardCompatible: - case CodecProfileLevel.H263ProfileBaseline: - case CodecProfileLevel.H263ProfileH320Coding: - case CodecProfileLevel.H263ProfileHighCompression: - case CodecProfileLevel.H263ProfileHighLatency: - case CodecProfileLevel.H263ProfileInterlace: - case CodecProfileLevel.H263ProfileInternet: - case CodecProfileLevel.H263ProfileISWV2: - case CodecProfileLevel.H263ProfileISWV3: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - if (strict) { - // Strict levels define sub-QCIF min size and enumerated sizes. We - // cannot express support for "only sQCIF & QCIF (& CIF)" using - // VideoCapabilities but we can express "only QCIF (& CIF)", so set - // minimume size at QCIF.minW = 8; minH = 6; - minW = 11; minH = 9; - } else { - // any support for non-strict levels (including unrecognized profiles or - // levels) allow custom frame size support beyond supported limits - // (other than bitrate) - mAllowMbOverride = true; - } + if (supported) { errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(W * H, maxBlocks); - maxBps = Math.max(BR * 64000, maxBps); + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + if (strict) { maxWidth = Math.max(W, maxWidth); maxHeight = Math.max(H, maxHeight); maxRate = Math.max(FR, maxRate); - minWidth = Math.min(minW, minWidth); - minHeight = Math.min(minH, minHeight); + } else { + // assuming max 60 fps frame rate and 1:2 aspect ratio + int maxDim = (int)Math.sqrt(FS * 2); + maxWidth = Math.max(maxDim, maxWidth); + maxHeight = Math.max(maxDim, maxHeight); + maxRate = Math.max(Math.max(FR, 60), maxRate); } - // unless we encountered custom frame size support, limit size to QCIF and CIF - // using aspect ratio. - if (!mAllowMbOverride) { - mBlockAspectRatioRange = - Range.create(new Rational(11, 9), new Rational(11, 9)); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = mFrameRateRange.intersect(12, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + int minWidth = maxWidth, minHeight = maxHeight; + int minAlignment = 16; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight; + boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF) + switch (profileLevel.level) { + case CodecProfileLevel.H263Level10: + strict = true; // only supports sQCIF & QCIF + FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level20: + strict = true; // only supports sQCIF, QCIF & CIF + FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break; + case CodecProfileLevel.H263Level30: + strict = true; // only supports sQCIF, QCIF & CIF + FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level40: + strict = true; // only supports sQCIF, QCIF & CIF + FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level45: + // only implies level 10 support + strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline + || profileLevel.profile == + CodecProfileLevel.H263ProfileBackwardCompatible; + if (!strict) { + minW = 1; minH = 1; minAlignment = 4; + } + FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level50: + // only supports 50fps for H > 15 + minW = 1; minH = 1; minAlignment = 4; + FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level60: + // only supports 50fps for H > 15 + minW = 1; minH = 1; minAlignment = 4; + FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level70: + // only supports 50fps for H > 30 + minW = 1; minH = 1; minAlignment = 4; + FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; + default: + Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile + + "/" + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - applyMacroBlockLimits( - minWidth, minHeight, - maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - minAlignment /* widthAlignment */, minAlignment /* heightAlignment */); - mFrameRateRange = Range.create(1, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) { - maxBlocks = Integer.MAX_VALUE; - maxBlocksPerSecond = Integer.MAX_VALUE; - - // TODO: set to 100Mbps for now, need a number for VP8 - maxBps = 100000000; - - // profile levels are not indicative for VPx, but verify - // them nonetheless - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.level) { - case CodecProfileLevel.VP8Level_Version0: - case CodecProfileLevel.VP8Level_Version1: - case CodecProfileLevel.VP8Level_Version2: - case CodecProfileLevel.VP8Level_Version3: - break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.VP8ProfileMain: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - errors &= ~ERROR_NONE_SUPPORTED; + 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; } - - final int blockSize = 16; - applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, - maxBlocks, maxBlocksPerSecond, blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { - maxBlocksPerSecond = 829440; - maxBlocks = 36864; - maxBps = 200000; - int maxDim = 512; - - for (CodecProfileLevel profileLevel: profileLevels) { - long SR = 0; // luma sample rate - int FS = 0; // luma picture size - int BR = 0; // bit rate kbps - int D = 0; // luma dimension - switch (profileLevel.level) { - case CodecProfileLevel.VP9Level1: - SR = 829440; FS = 36864; BR = 200; D = 512; break; - case CodecProfileLevel.VP9Level11: - SR = 2764800; FS = 73728; BR = 800; D = 768; break; - case CodecProfileLevel.VP9Level2: - SR = 4608000; FS = 122880; BR = 1800; D = 960; break; - case CodecProfileLevel.VP9Level21: - SR = 9216000; FS = 245760; BR = 3600; D = 1344; break; - case CodecProfileLevel.VP9Level3: - SR = 20736000; FS = 552960; BR = 7200; D = 2048; break; - case CodecProfileLevel.VP9Level31: - SR = 36864000; FS = 983040; BR = 12000; D = 2752; break; - case CodecProfileLevel.VP9Level4: - SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break; - case CodecProfileLevel.VP9Level41: - SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break; - case CodecProfileLevel.VP9Level5: - SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break; - case CodecProfileLevel.VP9Level51: - SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break; - case CodecProfileLevel.VP9Level52: - SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break; - case CodecProfileLevel.VP9Level6: - SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break; - case CodecProfileLevel.VP9Level61: - SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break; - case CodecProfileLevel.VP9Level62: - SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.VP9Profile0: - case CodecProfileLevel.VP9Profile1: - case CodecProfileLevel.VP9Profile2: - case CodecProfileLevel.VP9Profile3: - case CodecProfileLevel.VP9Profile2HDR: - case CodecProfileLevel.VP9Profile3HDR: - case CodecProfileLevel.VP9Profile2HDR10Plus: - case CodecProfileLevel.VP9Profile3HDR10Plus: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxDim = Math.max(D, maxDim); + if (strict) { + // Strict levels define sub-QCIF min size and enumerated sizes. We cannot + // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities + // but we can express "only QCIF (& CIF)", so set minimume size at QCIF. + // minW = 8; minH = 6; + minW = 11; minH = 9; + } else { + // any support for non-strict levels (including unrecognized profiles or + // levels) allow custom frame size support beyond supported limits + // (other than bitrate) + mAllowMbOverride = true; } - - final int blockSize = 8; - int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); - maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); - maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); - - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { - // CTBs are at least 8x8 so use 8x8 block size - maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks - maxBlocksPerSecond = maxBlocks * 15; - maxBps = 128000; - for (CodecProfileLevel profileLevel: profileLevels) { - double FR = 0; - int FS = 0; - int BR = 0; - switch (profileLevel.level) { - /* The HEVC spec talks only in a very convoluted manner about the - existence of levels 1-3.1 for High tier, which could also be - understood as 'decoders and encoders should treat these levels - as if they were Main tier', so we do that. */ - case CodecProfileLevel.HEVCMainTierLevel1: - case CodecProfileLevel.HEVCHighTierLevel1: - FR = 15; FS = 36864; BR = 128; break; - case CodecProfileLevel.HEVCMainTierLevel2: - case CodecProfileLevel.HEVCHighTierLevel2: - FR = 30; FS = 122880; BR = 1500; break; - case CodecProfileLevel.HEVCMainTierLevel21: - case CodecProfileLevel.HEVCHighTierLevel21: - FR = 30; FS = 245760; BR = 3000; break; - case CodecProfileLevel.HEVCMainTierLevel3: - case CodecProfileLevel.HEVCHighTierLevel3: - FR = 30; FS = 552960; BR = 6000; break; - case CodecProfileLevel.HEVCMainTierLevel31: - case CodecProfileLevel.HEVCHighTierLevel31: - FR = 33.75; FS = 983040; BR = 10000; break; - case CodecProfileLevel.HEVCMainTierLevel4: - FR = 30; FS = 2228224; BR = 12000; break; - case CodecProfileLevel.HEVCHighTierLevel4: - FR = 30; FS = 2228224; BR = 30000; break; - case CodecProfileLevel.HEVCMainTierLevel41: - FR = 60; FS = 2228224; BR = 20000; break; - case CodecProfileLevel.HEVCHighTierLevel41: - FR = 60; FS = 2228224; BR = 50000; break; - case CodecProfileLevel.HEVCMainTierLevel5: - FR = 30; FS = 8912896; BR = 25000; break; - case CodecProfileLevel.HEVCHighTierLevel5: - FR = 30; FS = 8912896; BR = 100000; break; - case CodecProfileLevel.HEVCMainTierLevel51: - FR = 60; FS = 8912896; BR = 40000; break; - case CodecProfileLevel.HEVCHighTierLevel51: - FR = 60; FS = 8912896; BR = 160000; break; - case CodecProfileLevel.HEVCMainTierLevel52: - FR = 120; FS = 8912896; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel52: - FR = 120; FS = 8912896; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel6: - FR = 30; FS = 35651584; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel6: - FR = 30; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel61: - FR = 60; FS = 35651584; BR = 120000; break; - case CodecProfileLevel.HEVCHighTierLevel61: - FR = 60; FS = 35651584; BR = 480000; break; - case CodecProfileLevel.HEVCMainTierLevel62: - FR = 120; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCHighTierLevel62: - FR = 120; FS = 35651584; BR = 800000; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.HEVCProfileMain: - case CodecProfileLevel.HEVCProfileMain10: - case CodecProfileLevel.HEVCProfileMainStill: - case CodecProfileLevel.HEVCProfileMain10HDR10: - case CodecProfileLevel.HEVCProfileMain10HDR10Plus: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - - /* DPB logic: - if (width * height <= FS / 4) DPB = 16; - else if (width * height <= FS / 2) DPB = 12; - else if (width * height <= FS * 0.75) DPB = 8; - else DPB = 6; - */ - - FS >>= 6; // convert pixels to blocks - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(W * H, maxBlocks); + maxBps = Math.max(BR * 64000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + minWidth = Math.min(minW, minWidth); + minHeight = Math.min(minH, minHeight); + } + // unless we encountered custom frame size support, limit size to QCIF and CIF + // using aspect ratio. + if (!mAllowMbOverride) { + mBlockAspectRatioRange = + Range.create(new Rational(11, 9), new Rational(11, 9)); + } + applyMacroBlockLimits( + minWidth, minHeight, + maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + minAlignment /* widthAlignment */, minAlignment /* heightAlignment */); + mFrameRateRange = Range.create(1, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) { + maxBlocks = Integer.MAX_VALUE; + maxBlocksPerSecond = Integer.MAX_VALUE; + + // TODO: set to 100Mbps for now, need a number for VP8 + maxBps = 100000000; + + // profile levels are not indicative for VPx, but verify + // them nonetheless + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.level) { + case CodecProfileLevel.VP8Level_Version0: + case CodecProfileLevel.VP8Level_Version1: + case CodecProfileLevel.VP8Level_Version2: + case CodecProfileLevel.VP8Level_Version3: + break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 8 /* blockWidth */, 8 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) { - maxBlocksPerSecond = 829440; - maxBlocks = 36864; - maxBps = 200000; - int maxDim = 512; - - // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec, - // corresponding to the definitions in - // "AV1 Bitstream & Decoding Process Specification", Annex A - // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/ - for (CodecProfileLevel profileLevel: profileLevels) { - long SR = 0; // luma sample rate - int FS = 0; // luma picture size - int BR = 0; // bit rate kbps - int D = 0; // luma D - switch (profileLevel.level) { - case CodecProfileLevel.AV1Level2: - SR = 5529600; FS = 147456; BR = 1500; D = 2048; break; - case CodecProfileLevel.AV1Level21: - case CodecProfileLevel.AV1Level22: - case CodecProfileLevel.AV1Level23: - SR = 10454400; FS = 278784; BR = 3000; D = 2816; break; - - case CodecProfileLevel.AV1Level3: - SR = 24969600; FS = 665856; BR = 6000; D = 4352; break; - case CodecProfileLevel.AV1Level31: - case CodecProfileLevel.AV1Level32: - case CodecProfileLevel.AV1Level33: - SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break; - - case CodecProfileLevel.AV1Level4: - SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break; - case CodecProfileLevel.AV1Level41: - case CodecProfileLevel.AV1Level42: - case CodecProfileLevel.AV1Level43: - SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break; - - case CodecProfileLevel.AV1Level5: - SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break; - case CodecProfileLevel.AV1Level51: - SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break; - case CodecProfileLevel.AV1Level52: - SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break; - case CodecProfileLevel.AV1Level53: - SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break; - - case CodecProfileLevel.AV1Level6: - SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break; - case CodecProfileLevel.AV1Level61: - SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break; - case CodecProfileLevel.AV1Level62: - SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break; - case CodecProfileLevel.AV1Level63: - SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break; - - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.AV1ProfileMain8: - case CodecProfileLevel.AV1ProfileMain10: - case CodecProfileLevel.AV1ProfileMain10HDR10: - case CodecProfileLevel.AV1ProfileMain10HDR10Plus: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxDim = Math.max(D, maxDim); + switch (profileLevel.profile) { + case CodecProfileLevel.VP8ProfileMain: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - - final int blockSize = 8; - int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); - maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); - maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else { - Log.w(TAG, "Unsupported mime " + mime); - // using minimal bitrate here. should be overridden by - // info from media_codecs.xml - maxBps = 64000; - errors |= ERROR_UNSUPPORTED; + errors &= ~ERROR_NONE_SUPPORTED; } - mBitrateRange = Range.create(1, maxBps); - mParent.mError |= errors; - } - } - /* package private */ static final class VideoCapsNativeImpl implements VideoCapsIntf { - private long mNativeContext; // accessed by native methods - - private Range<Integer> mBitrateRange; - private Range<Integer> mHeightRange; - private Range<Integer> mWidthRange; - private Range<Integer> mFrameRateRange; - private List<PerformancePoint> mPerformancePoints; - - private int mWidthAlignment; - private int mHeightAlignment; - - // Used by JNI to construct Java VideoCapsNativeImpl - /** package private */ VideoCapsNativeImpl(Range<Integer> bitrateRange, - Range<Integer> widthRange, Range<Integer> heightRange, - Range<Integer> frameRateRange, List<PerformancePoint> performancePoints, - int widthAlignment, int heightAlignment) { - mBitrateRange = new Range<Integer>(bitrateRange.getLower(), - bitrateRange.getUpper()); - mWidthRange = new Range<Integer>(widthRange.getLower(), widthRange.getUpper()); - mHeightRange = new Range<Integer>(heightRange.getLower(), heightRange.getUpper()); - mFrameRateRange = new Range<Integer>(frameRateRange.getLower(), - frameRateRange.getUpper()); - mPerformancePoints = new ArrayList<PerformancePoint>(); - for (PerformancePoint pp : performancePoints) { - mPerformancePoints.add(new PerformancePoint(pp)); + final int blockSize = 16; + applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, + maxBlocks, maxBlocksPerSecond, blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { + maxBlocksPerSecond = 829440; + maxBlocks = 36864; + maxBps = 200000; + int maxDim = 512; + + for (CodecProfileLevel profileLevel: profileLevels) { + long SR = 0; // luma sample rate + int FS = 0; // luma picture size + int BR = 0; // bit rate kbps + int D = 0; // luma dimension + switch (profileLevel.level) { + case CodecProfileLevel.VP9Level1: + SR = 829440; FS = 36864; BR = 200; D = 512; break; + case CodecProfileLevel.VP9Level11: + SR = 2764800; FS = 73728; BR = 800; D = 768; break; + case CodecProfileLevel.VP9Level2: + SR = 4608000; FS = 122880; BR = 1800; D = 960; break; + case CodecProfileLevel.VP9Level21: + SR = 9216000; FS = 245760; BR = 3600; D = 1344; break; + case CodecProfileLevel.VP9Level3: + SR = 20736000; FS = 552960; BR = 7200; D = 2048; break; + case CodecProfileLevel.VP9Level31: + SR = 36864000; FS = 983040; BR = 12000; D = 2752; break; + case CodecProfileLevel.VP9Level4: + SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break; + case CodecProfileLevel.VP9Level41: + SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break; + case CodecProfileLevel.VP9Level5: + SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break; + case CodecProfileLevel.VP9Level51: + SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break; + case CodecProfileLevel.VP9Level52: + SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break; + case CodecProfileLevel.VP9Level6: + SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break; + case CodecProfileLevel.VP9Level61: + SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break; + case CodecProfileLevel.VP9Level62: + SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.VP9Profile0: + case CodecProfileLevel.VP9Profile1: + case CodecProfileLevel.VP9Profile2: + case CodecProfileLevel.VP9Profile3: + case CodecProfileLevel.VP9Profile2HDR: + case CodecProfileLevel.VP9Profile3HDR: + case CodecProfileLevel.VP9Profile2HDR10Plus: + case CodecProfileLevel.VP9Profile3HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxDim = Math.max(D, maxDim); } - mWidthAlignment = widthAlignment; - mHeightAlignment = heightAlignment; - } - - /* no public constructor */ - private VideoCapsNativeImpl() { } - - public Range<Integer> getBitrateRange() { - return mBitrateRange; - } - - public Range<Integer> getSupportedWidths() { - return mWidthRange; - } - public Range<Integer> getSupportedHeights() { - return mHeightRange; - } - - public int getWidthAlignment() { - return mWidthAlignment; - } - - public int getHeightAlignment() { - return mHeightAlignment; - } + final int blockSize = 8; + int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); + maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); - /** @hide */ - public int getSmallerDimensionUpperLimit() { - return native_getSmallerDimensionUpperLimit(); - } - - public Range<Integer> getSupportedFrameRates() { - return mFrameRateRange; - } - - @Nullable - public List<PerformancePoint> getSupportedPerformancePoints() { - return mPerformancePoints; - } - - public Range<Integer> getSupportedWidthsFor(int height) { - return native_getSupportedWidthsFor(height); - } - - public Range<Integer> getSupportedHeightsFor(int width) { - return native_getSupportedHeightsFor(width); - } - - public Range<Double> getSupportedFrameRatesFor(int width, int height) { - return native_getSupportedFrameRatesFor(width, height); - } - - /** @throws IllegalArgumentException if the video size is not supported. */ - @Nullable - public Range<Double> getAchievableFrameRatesFor(int width, int height) { - return native_getAchievableFrameRatesFor(width, height); - } - - public boolean areSizeAndRateSupported(int width, int height, double frameRate) { - return native_areSizeAndRateSupported(width, height, frameRate); - } - - public boolean isSizeSupported(int width, int height) { - return native_isSizeSupported(width, height); - } - - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - throw new UnsupportedOperationException( - "Java Implementation should not call native implemenatation"); - } - - private native Range<Integer> native_getSupportedWidthsFor(int height); - private native Range<Integer> native_getSupportedHeightsFor(int width); - private native Range<Double> native_getSupportedFrameRatesFor(int width, int height); - private native Range<Double> native_getAchievableFrameRatesFor(int width, int height); - private native boolean native_areSizeAndRateSupported( - int width, int height, double frameRate); - private native boolean native_isSizeSupported(int width, int height); - private native int native_getSmallerDimensionUpperLimit(); - - private static native void native_init(); - - static { - System.loadLibrary("media_jni"); - native_init(); - } - } - - private VideoCapsIntf mImpl; - - /** @hide */ - public static VideoCapabilities create( - MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - VideoCapsLegacyImpl impl = VideoCapsLegacyImpl.create(info, parent); - VideoCapabilities caps = new VideoCapabilities(impl); - return caps; - } - - /* package private */ VideoCapabilities(VideoCapsIntf impl) { - mImpl = impl; - } - - /* no public constructor */ - private VideoCapabilities() { } - - /** - * Returns the range of supported bitrates in bits/second. - */ - public Range<Integer> getBitrateRange() { - return mImpl.getBitrateRange(); - } - - /** - * Returns the range of supported video widths. - * <p class=note> - * 32-bit processes will not support resolutions larger than 4096x4096 due to - * the limited address space. - */ - public Range<Integer> getSupportedWidths() { - return mImpl.getSupportedWidths(); - } - - /** - * Returns the range of supported video heights. - * <p class=note> - * 32-bit processes will not support resolutions larger than 4096x4096 due to - * the limited address space. - */ - public Range<Integer> getSupportedHeights() { - return mImpl.getSupportedHeights(); - } - - /** - * Returns the alignment requirement for video width (in pixels). - * - * This is a power-of-2 value that video width must be a - * multiple of. - */ - public int getWidthAlignment() { - return mImpl.getWidthAlignment(); - } - - /** - * Returns the alignment requirement for video height (in pixels). - * - * This is a power-of-2 value that video height must be a - * multiple of. - */ - public int getHeightAlignment() { - return mImpl.getWidthAlignment(); - } - - /** - * 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 mImpl.getSmallerDimensionUpperLimit(); - } - - /** - * Returns the range of supported frame rates. - * <p> - * This is not a performance indicator. Rather, it expresses the - * limits specified in the coding standard, based on the complexities - * of encoding material for later playback at a certain frame rate, - * or the decoding of such material in non-realtime. - */ - public Range<Integer> getSupportedFrameRates() { - return mImpl.getSupportedFrameRates(); - } - - /** - * Returns the range of supported video widths for a video height. - * @param height the height of the video - */ - public Range<Integer> getSupportedWidthsFor(int height) { - return mImpl.getSupportedWidthsFor(height); - } + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { + // CTBs are at least 8x8 so use 8x8 block size + maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks + maxBlocksPerSecond = maxBlocks * 15; + maxBps = 128000; + for (CodecProfileLevel profileLevel: profileLevels) { + double FR = 0; + int FS = 0; + int BR = 0; + switch (profileLevel.level) { + /* The HEVC spec talks only in a very convoluted manner about the + existence of levels 1-3.1 for High tier, which could also be + understood as 'decoders and encoders should treat these levels + as if they were Main tier', so we do that. */ + case CodecProfileLevel.HEVCMainTierLevel1: + case CodecProfileLevel.HEVCHighTierLevel1: + FR = 15; FS = 36864; BR = 128; break; + case CodecProfileLevel.HEVCMainTierLevel2: + case CodecProfileLevel.HEVCHighTierLevel2: + FR = 30; FS = 122880; BR = 1500; break; + case CodecProfileLevel.HEVCMainTierLevel21: + case CodecProfileLevel.HEVCHighTierLevel21: + FR = 30; FS = 245760; BR = 3000; break; + case CodecProfileLevel.HEVCMainTierLevel3: + case CodecProfileLevel.HEVCHighTierLevel3: + FR = 30; FS = 552960; BR = 6000; break; + case CodecProfileLevel.HEVCMainTierLevel31: + case CodecProfileLevel.HEVCHighTierLevel31: + FR = 33.75; FS = 983040; BR = 10000; break; + case CodecProfileLevel.HEVCMainTierLevel4: + FR = 30; FS = 2228224; BR = 12000; break; + case CodecProfileLevel.HEVCHighTierLevel4: + FR = 30; FS = 2228224; BR = 30000; break; + case CodecProfileLevel.HEVCMainTierLevel41: + FR = 60; FS = 2228224; BR = 20000; break; + case CodecProfileLevel.HEVCHighTierLevel41: + FR = 60; FS = 2228224; BR = 50000; break; + case CodecProfileLevel.HEVCMainTierLevel5: + FR = 30; FS = 8912896; BR = 25000; break; + case CodecProfileLevel.HEVCHighTierLevel5: + FR = 30; FS = 8912896; BR = 100000; break; + case CodecProfileLevel.HEVCMainTierLevel51: + FR = 60; FS = 8912896; BR = 40000; break; + case CodecProfileLevel.HEVCHighTierLevel51: + FR = 60; FS = 8912896; BR = 160000; break; + case CodecProfileLevel.HEVCMainTierLevel52: + FR = 120; FS = 8912896; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel52: + FR = 120; FS = 8912896; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel6: + FR = 30; FS = 35651584; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel6: + FR = 30; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel61: + FR = 60; FS = 35651584; BR = 120000; break; + case CodecProfileLevel.HEVCHighTierLevel61: + FR = 60; FS = 35651584; BR = 480000; break; + case CodecProfileLevel.HEVCMainTierLevel62: + FR = 120; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCHighTierLevel62: + FR = 120; FS = 35651584; BR = 800000; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.HEVCProfileMain: + case CodecProfileLevel.HEVCProfileMain10: + case CodecProfileLevel.HEVCProfileMainStill: + case CodecProfileLevel.HEVCProfileMain10HDR10: + case CodecProfileLevel.HEVCProfileMain10HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } - /** - * Returns the range of supported video heights for a video width - * @param width the width of the video - */ - public Range<Integer> getSupportedHeightsFor(int width) { - return mImpl.getSupportedHeightsFor(width); - } + /* DPB logic: + if (width * height <= FS / 4) DPB = 16; + else if (width * height <= FS / 2) DPB = 12; + else if (width * height <= FS * 0.75) DPB = 8; + else DPB = 6; + */ + + FS >>= 6; // convert pixels to blocks + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + } - /** - * 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. + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 8 /* blockWidth */, 8 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) { + maxBlocksPerSecond = 829440; + maxBlocks = 36864; + maxBps = 200000; + int maxDim = 512; + + // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec, + // corresponding to the definitions in + // "AV1 Bitstream & Decoding Process Specification", Annex A + // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/ + for (CodecProfileLevel profileLevel: profileLevels) { + long SR = 0; // luma sample rate + int FS = 0; // luma picture size + int BR = 0; // bit rate kbps + int D = 0; // luma D + switch (profileLevel.level) { + case CodecProfileLevel.AV1Level2: + SR = 5529600; FS = 147456; BR = 1500; D = 2048; break; + case CodecProfileLevel.AV1Level21: + case CodecProfileLevel.AV1Level22: + case CodecProfileLevel.AV1Level23: + SR = 10454400; FS = 278784; BR = 3000; D = 2816; break; + + case CodecProfileLevel.AV1Level3: + SR = 24969600; FS = 665856; BR = 6000; D = 4352; break; + case CodecProfileLevel.AV1Level31: + case CodecProfileLevel.AV1Level32: + case CodecProfileLevel.AV1Level33: + SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break; + + case CodecProfileLevel.AV1Level4: + SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break; + case CodecProfileLevel.AV1Level41: + case CodecProfileLevel.AV1Level42: + case CodecProfileLevel.AV1Level43: + SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break; + + case CodecProfileLevel.AV1Level5: + SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break; + case CodecProfileLevel.AV1Level51: + SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break; + case CodecProfileLevel.AV1Level52: + SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break; + case CodecProfileLevel.AV1Level53: + SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break; + + case CodecProfileLevel.AV1Level6: + SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break; + case CodecProfileLevel.AV1Level61: + SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break; + case CodecProfileLevel.AV1Level62: + SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break; + case CodecProfileLevel.AV1Level63: + SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break; + + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.AV1ProfileMain8: + case CodecProfileLevel.AV1ProfileMain10: + case CodecProfileLevel.AV1ProfileMain10HDR10: + case CodecProfileLevel.AV1ProfileMain10HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxDim = Math.max(D, maxDim); + } - * @param width the width of the video - * @param height the height of the video - */ - public Range<Double> getSupportedFrameRatesFor(int width, int height) { - return mImpl.getSupportedFrameRatesFor(width, height); + final int blockSize = 8; + int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); + maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else { + Log.w(TAG, "Unsupported mime " + mime); + // using minimal bitrate here. should be overriden by + // info from media_codecs.xml + maxBps = 64000; + errors |= ERROR_UNSUPPORTED; + } + mBitrateRange = Range.create(1, maxBps); + mParent.mError |= errors; } + } + /** + * A class that supports querying the encoding capabilities of a codec. + */ + public static final class EncoderCapabilities { /** - * Returns the range of achievable video frame rates for a video size. - * May return {@code null}, if the codec did not publish any measurement - * data. - * <p> - * This is a performance estimate provided by the device manufacturer based on statistical - * sampling of full-speed decoding and encoding measurements in various configurations - * of common video sizes supported by the codec. As such it should only be used to - * compare individual codecs on the device. The value is not suitable for comparing - * different devices or even different android releases for the same device. - * <p> - * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range - * corresponds to the fastest frame rates achieved in the tested configurations. As - * such, it should not be used to gauge guaranteed or even average codec performance - * on the device. - * <p> - * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range - * corresponds closer to sustained performance <em>in tested configurations</em>. - * One can expect to achieve sustained performance higher than the lower limit more than - * 50% of the time, and higher than half of the lower limit at least 90% of the time - * <em>in tested configurations</em>. - * Conversely, one can expect performance lower than twice the upper limit at least - * 90% of the time. - * <p class=note> - * Tested configurations use a single active codec. For use cases where multiple - * codecs are active, applications can expect lower and in most cases significantly lower - * performance. - * <p class=note> - * The returned range value is interpolated from the nearest frame size(s) tested. - * Codec performance is severely impacted by other activity on the device as well - * as environmental factors (such as battery level, temperature or power source), and can - * vary significantly even in a steady environment. - * <p class=note> - * Use this method in cases where only codec performance matters, e.g. to evaluate if - * a codec has any chance of meeting a performance target. Codecs are listed - * in {@link MediaCodecList} in the preferred order as defined by the device - * manufacturer. As such, applications should use the first suitable codec in the - * list to achieve the best balance between power use and performance. - * - * @param width the width of the video - * @param height the height of the video + * Returns the supported range of quality values. * - * @throws IllegalArgumentException if the video size is not supported. + * Quality is implementation-specific. As a general rule, a higher quality + * setting results in a better image quality and a lower compression ratio. */ - @Nullable - public Range<Double> getAchievableFrameRatesFor(int width, int height) { - return mImpl.getAchievableFrameRatesFor(width, height); + public Range<Integer> getQualityRange() { + return mQualityRange; } /** - * Returns the supported performance points. May return {@code null} if the codec did not - * publish any performance point information (e.g. the vendor codecs have not been updated - * to the latest android release). May return an empty list if the codec published that - * if does not guarantee any performance points. - * <p> - * This is a performance guarantee provided by the device manufacturer for hardware codecs - * based on hardware capabilities of the device. + * Returns the supported range of encoder complexity values. * <p> - * The returned list is sorted first by decreasing number of pixels, then by decreasing - * width, and finally by decreasing frame rate. - * Performance points assume a single active codec. For use cases where multiple - * codecs are active, should use that highest pixel count, and add the frame rates of - * each individual codec. - * <p class=note> - * 32-bit processes will not support resolutions larger than 4096x4096 due to - * the limited address space, but performance points will be presented as is. - * In other words, even though a component publishes a performance point for - * a resolution higher than 4096x4096, it does not mean that the resolution is supported - * for 32-bit processes. - */ - @Nullable - public List<PerformancePoint> getSupportedPerformancePoints() { - return mImpl.getSupportedPerformancePoints(); - } - - /** - * Returns whether a given video size ({@code width} and - * {@code height}) and {@code frameRate} combination is supported. - */ - public boolean areSizeAndRateSupported(int width, int height, double frameRate) { - return mImpl.areSizeAndRateSupported(width, height, frameRate); - } - - /** - * Returns whether a given video size ({@code width} and - * {@code height}) is supported. - */ - public boolean isSizeSupported(int width, int height) { - return mImpl.isSizeSupported(width, height); - } - - /** - * @hide - * @throws java.lang.ClassCastException - * @throws java.lang.UnsupportedOperationException + * 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 boolean supportsFormat(MediaFormat format) { - return mImpl.supportsFormat(format); + public Range<Integer> getComplexityRange() { + return mComplexityRange; } - } - - /** - * A class that supports querying the encoding capabilities of a codec. - */ - public static final class EncoderCapabilities { - private static final String TAG = "EncoderCapabilities"; /** Constant quality mode */ public static final int BITRATE_MODE_CQ = 0; @@ -4575,314 +3874,188 @@ public final class MediaCodecInfo { /** Constant bitrate mode with frame drops */ public static final int BITRATE_MODE_CBR_FD = 3; - /* package private */ interface EncoderCapsIntf { - public Range<Integer> getQualityRange(); - - public Range<Integer> getComplexityRange(); - - public boolean isBitrateModeSupported(int mode); - - public void getDefaultFormat(MediaFormat format); - - public boolean supportsFormat(MediaFormat format); + 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), + new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false) + }; + + private static int parseBitrateMode(String mode) { + for (Feature feat: bitrates) { + if (feat.mName.equalsIgnoreCase(mode)) { + return feat.mValue; + } + } + return 0; } - /* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf { - private CodecCapabilities.CodecCapsLegacyImpl mParent; + /** + * Query whether a bitrate mode is supported. + */ + public boolean isBitrateModeSupported(int mode) { + for (Feature feat: bitrates) { + if (mode == feat.mValue) { + return (mBitControl & (1 << mode)) != 0; + } + } + return false; + } - private Range<Integer> mQualityRange; - private Range<Integer> mComplexityRange; + private Range<Integer> mQualityRange; + private Range<Integer> mComplexityRange; + private CodecCapabilities mParent; - public Range<Integer> getQualityRange() { - return mQualityRange; - } + /* no public constructor */ + private EncoderCapabilities() { } - public Range<Integer> getComplexityRange() { - return mComplexityRange; - } + /** @hide */ + public static EncoderCapabilities create( + MediaFormat info, CodecCapabilities parent) { + EncoderCapabilities caps = new EncoderCapabilities(); + caps.init(info, parent); + return caps; + } - 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), - new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false) - }; + private void init(MediaFormat info, CodecCapabilities parent) { + // no support for complexity or quality yet + mParent = parent; + mComplexityRange = Range.create(0, 0); + mQualityRange = Range.create(0, 0); + mBitControl = (1 << BITRATE_MODE_VBR); - private static int parseBitrateMode(String mode) { - for (Feature feat: bitrates) { - if (feat.mName.equalsIgnoreCase(mode)) { - return feat.mValue; - } - } - return 0; - } + applyLevelLimits(); + parseFromInfo(info); + } - public boolean isBitrateModeSupported(int mode) { - for (Feature feat: bitrates) { - if (mode == feat.mValue) { - return (mBitControl & (1 << mode)) != 0; - } - } - return false; + private void applyLevelLimits() { + String mime = mParent.getMimeType(); + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + mComplexityRange = Range.create(0, 8); + mBitControl = (1 << BITRATE_MODE_CQ); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + mBitControl = (1 << BITRATE_MODE_CBR); } + } - /* no public constructor */ - private EncoderCapsLegacyImpl() { } + private int mBitControl; + private Integer mDefaultComplexity; + private Integer mDefaultQuality; + private String mQualityScale; - /** @hide */ - public static EncoderCapsLegacyImpl create( - MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { - Log.d(TAG, "Legacy implementation is called while native flag is on."); - } + private void parseFromInfo(MediaFormat info) { + Map<String, Object> map = info.getMap(); - EncoderCapsLegacyImpl caps = new EncoderCapsLegacyImpl(); - caps.init(info, parent); - return caps; + if (info.containsKey("complexity-range")) { + mComplexityRange = Utils + .parseIntRange(info.getString("complexity-range"), mComplexityRange); + // TODO should we limit this to level limits? } - - private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl 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); + if (info.containsKey("quality-range")) { + mQualityRange = Utils + .parseIntRange(info.getString("quality-range"), mQualityRange); } - - private void applyLevelLimits() { - String mime = mParent.getMimeType(); - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - mComplexityRange = Range.create(0, 8); - mBitControl = (1 << BITRATE_MODE_CQ); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - mBitControl = (1 << BITRATE_MODE_CBR); + if (info.containsKey("feature-bitrate-modes")) { + mBitControl = 0; + for (String mode: info.getString("feature-bitrate-modes").split(",")) { + mBitControl |= (1 << parseBitrateMode(mode)); } } - private int mBitControl; - private Integer mDefaultComplexity; - private Integer mDefaultQuality; - private String mQualityScale; - - private void parseFromInfo(MediaFormat info) { - Map<String, Object> map = info.getMap(); - - if (info.containsKey("complexity-range")) { - mComplexityRange = Utils - .parseIntRange(info.getString("complexity-range"), mComplexityRange); - // TODO should we limit this to level limits? - } - if (info.containsKey("quality-range")) { - mQualityRange = Utils - .parseIntRange(info.getString("quality-range"), mQualityRange); - } - if (info.containsKey("feature-bitrate-modes")) { - mBitControl = 0; - for (String mode: info.getString("feature-bitrate-modes").split(",")) { - mBitControl |= (1 << parseBitrateMode(mode)); - } - } + try { + mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); + } catch (NumberFormatException e) { } - try { - mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); - } catch (NumberFormatException e) { } + try { + mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); + } catch (NumberFormatException e) { } - try { - mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); - } catch (NumberFormatException e) { } + mQualityScale = (String)map.get("quality-scale"); + } - 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); } - - 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.getProfileLevels()) { - if (pl.profile == profile) { - profile = null; - break; - } - } - ok = profile == null; - } - return ok; + if (ok && quality != null) { + ok = mQualityRange.contains(quality); } - - /** @hide */ - public void getDefaultFormat(MediaFormat format) { - // don't list trivial quality/complexity as default for now - if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) - && mDefaultQuality != null) { - format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); - } - if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) - && mDefaultComplexity != null) { - format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); - } - // bitrates are listed in order of preference - for (Feature feat: bitrates) { - if ((mBitControl & (1 << feat.mValue)) != 0) { - format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); + if (ok && profile != null) { + for (CodecProfileLevel pl: mParent.profileLevels) { + if (pl.profile == profile) { + profile = null; break; } } + ok = profile == null; } + return ok; + } - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - final String mime = mParent.getMimeType(); - - Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); - if (mode != null && !isBitrateModeSupported(mode)) { - return false; - } - - Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); - if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { - Integer flacComplexity = - (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); - if (complexity == null) { - complexity = flacComplexity; - } else if (flacComplexity != null && !complexity.equals(flacComplexity)) { - throw new IllegalArgumentException( - "conflicting values for complexity and " - + "flac-compression-level"); - } - } - - // other audio parameters - Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); - if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { - Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); - if (profile == null) { - profile = aacProfile; - } else if (aacProfile != null && !aacProfile.equals(profile)) { - throw new IllegalArgumentException( - "conflicting values for profile and aac-profile"); - } + /** @hide */ + public void getDefaultFormat(MediaFormat format) { + // don't list trivial quality/complexity as default for now + if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) + && mDefaultQuality != null) { + format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); + } + if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) + && mDefaultComplexity != null) { + format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); + } + // bitrates are listed in order of preference + for (Feature feat: bitrates) { + if ((mBitControl & (1 << feat.mValue)) != 0) { + format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); + break; } - - Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); - - return supports(complexity, quality, profile); } } - /* package private */ static final class EncoderCapsNativeImpl implements EncoderCapsIntf { - private long mNativeContext; // accessed by native methods - - private Range<Integer> mQualityRange; - private Range<Integer> mComplexityRange; - - /* no public constructor */ - private EncoderCapsNativeImpl() { } - - // Constructor called from native - /* package private */ EncoderCapsNativeImpl(Range<Integer> qualityRange, - Range<Integer> complexityRange) { - mQualityRange = qualityRange; - mComplexityRange = complexityRange; - } - - public Range<Integer> getQualityRange() { - return mQualityRange; - } - - public Range<Integer> getComplexityRange() { - return mComplexityRange; - } - - public boolean isBitrateModeSupported(int mode) { - return native_isBitrateModeSupported(mode); - } + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + final String mime = mParent.getMimeType(); - // This API is for internal Java implementation only. Should not be called. - public void getDefaultFormat(MediaFormat format) { - throw new UnsupportedOperationException( - "Java Implementation should not call native implemenatation"); + Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); + if (mode != null && !isBitrateModeSupported(mode)) { + return false; } - // This API is for internal Java implementation only. Should not be called. - public boolean supportsFormat(MediaFormat format) { - throw new UnsupportedOperationException( - "Java Implementation should not call native implemenatation"); + Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); + if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { + Integer flacComplexity = + (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); + if (complexity == null) { + complexity = flacComplexity; + } else if (flacComplexity != null && !complexity.equals(flacComplexity)) { + throw new IllegalArgumentException( + "conflicting values for complexity and " + + "flac-compression-level"); + } } - private native boolean native_isBitrateModeSupported(int mode); - private static native void native_init(); - - static { - System.loadLibrary("media_jni"); - native_init(); + // other audio parameters + Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); + if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { + Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); + if (profile == null) { + profile = aacProfile; + } else if (aacProfile != null && !aacProfile.equals(profile)) { + throw new IllegalArgumentException( + "conflicting values for profile and aac-profile"); + } } - } - - private EncoderCapsIntf mImpl; - - /** @hide */ - public static EncoderCapabilities create( - MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) { - EncoderCapsLegacyImpl impl = EncoderCapsLegacyImpl.create(info, parent); - EncoderCapabilities caps = new EncoderCapabilities(impl); - return caps; - } - /* package private */ EncoderCapabilities(EncoderCapsIntf impl) { - mImpl = impl; - } + Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); - /** - * Returns the supported range of quality values. - * - * Quality is implementation-specific. As a general rule, a higher quality - * setting results in a better image quality and a lower compression ratio. - */ - public Range<Integer> getQualityRange() { - return mImpl.getQualityRange(); - } - - /** - * Returns the supported range of encoder complexity values. - * <p> - * Some codecs may support multiple complexity levels, where higher - * complexity values use more encoder tools (e.g. perform more - * intensive calculations) to improve the quality or the compression - * ratio. Use a lower value to save power and/or time. - */ - public Range<Integer> getComplexityRange() { - return mImpl.getComplexityRange(); - } - - /** - * Query whether a bitrate mode is supported. - */ - public boolean isBitrateModeSupported(int mode) { - return mImpl.isBitrateModeSupported(mode); - } - - /** @hide */ - public void getDefaultFormat(MediaFormat format) { - mImpl.getDefaultFormat(format); - } - - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - return mImpl.supportsFormat(format); + return supports(complexity, quality, profile); } }; @@ -5787,19 +4960,4 @@ public final class MediaCodecInfo { mName, mCanonicalName, mFlags, caps.toArray(new CodecCapabilities[caps.size()])); } - - /* package private */ class GenericHelper { - private static Range<Integer> constructIntegerRange(int lower, int upper) { - return Range.create(Integer.valueOf(lower), Integer.valueOf(upper)); - } - - private static Range<Double> constructDoubleRange(double lower, double upper) { - return Range.create(Double.valueOf(lower), Double.valueOf(upper)); - } - - private static List<VideoCapabilities.PerformancePoint> - constructPerformancePointList(VideoCapabilities.PerformancePoint[] array) { - return Arrays.asList(array); - } - } } diff --git a/media/jni/Android.bp b/media/jni/Android.bp index af545d5a4bc4..f09dc7218d7d 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -25,7 +25,6 @@ cc_library_shared { min_sdk_version: "", srcs: [ - "android_media_CodecCapabilities.cpp", "android_media_ImageWriter.cpp", "android_media_ImageReader.cpp", "android_media_JetPlayer.cpp", @@ -65,7 +64,6 @@ cc_library_shared { "libbinder", "libmedia", "libmedia_codeclist", - "libmedia_codeclist_capabilities", "libmedia_jni_utils", "libmedia_omx", "libmediametrics", diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp deleted file mode 100644 index c7c4833abf96..000000000000 --- a/media/jni/android_media_CodecCapabilities.cpp +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * Copyright 2024, 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. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "MediaCodec-JNI" - -#include "android_media_CodecCapabilities.h" -#include "android_media_Streams.h" -#include "android_runtime/AndroidRuntime.h" -#include "jni.h" - -#include <media/AudioCapabilities.h> -#include <media/CodecCapabilities.h> -#include <media/EncoderCapabilities.h> -#include <media/VideoCapabilities.h> -#include <media/stagefright/foundation/ADebug.h> -#include <media/stagefright/foundation/AMessage.h> -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedLocalRef.h> -#include <utils/Log.h> - -namespace android { - -struct fields_t { - jfieldID audioCapsContext; - jfieldID videoCapsContext; - jfieldID encoderCapsContext; - jfieldID codecCapsContext; -}; -static fields_t fields; - -// JCodecCapabilities - -JCodecCapabilities::JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps) - : mCodecCaps(codecCaps) {} - -std::shared_ptr<CodecCapabilities> JCodecCapabilities::getCodecCaps() const { - return mCodecCaps; -} - -int32_t JCodecCapabilities::getMaxSupportedInstances() const { - return mCodecCaps->getMaxSupportedInstances(); -} - -std::string JCodecCapabilities::getMediaType() const { - return mCodecCaps->getMediaType(); -} - -bool JCodecCapabilities::isFeatureRequired(const std::string& name) const { - return mCodecCaps->isFeatureRequired(name); -} - -bool JCodecCapabilities::isFeatureSupported(const std::string& name) const { - return mCodecCaps->isFeatureSupported(name); -} - -bool JCodecCapabilities::isFormatSupported(const sp<AMessage> &format) const { - return mCodecCaps->isFormatSupported(format); -} - -bool JCodecCapabilities::isRegular() const { - return mCodecCaps->isRegular(); -} - -std::vector<std::string> JCodecCapabilities::validFeatures() const { - return mCodecCaps->validFeatures(); -} - -// Setter - -static sp<JCodecCapabilities> setCodecCapabilities(JNIEnv *env, jobject thiz, - const sp<JCodecCapabilities>& jCodecCaps) { - sp<JCodecCapabilities> old - = (JCodecCapabilities*)env->GetLongField(thiz, fields.codecCapsContext); - if (jCodecCaps != NULL) { - jCodecCaps->incStrong(thiz); - } - if (old != NULL) { - old->decStrong(thiz); - } - env->SetLongField(thiz, fields.codecCapsContext, (jlong)jCodecCaps.get()); - return old; -} - -// Getters - -static AudioCapabilities* getAudioCapabilities(JNIEnv *env, jobject thiz) { - AudioCapabilities* const p = (AudioCapabilities*)env->GetLongField( - thiz, fields.audioCapsContext); - return p; -} - -static VideoCapabilities* getVideoCapabilities(JNIEnv *env, jobject thiz) { - VideoCapabilities* const p = (VideoCapabilities*)env->GetLongField( - thiz, fields.videoCapsContext); - return p; -} - -static EncoderCapabilities* getEncoderCapabilities(JNIEnv *env, jobject thiz) { - EncoderCapabilities* const p = (EncoderCapabilities*)env->GetLongField( - thiz, fields.encoderCapsContext); - return p; -} - -static sp<JCodecCapabilities> getCodecCapabilities(JNIEnv *env, jobject thiz) { - JCodecCapabilities* const p = (JCodecCapabilities*)env->GetLongField( - thiz, fields.codecCapsContext); - return sp<JCodecCapabilities>(p); -} - -// Utils - -static jobject convertToJavaIntRange(JNIEnv *env, const Range<int32_t>& range) { - jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper"); - jmethodID constructIntegerRangeID = env->GetStaticMethodID(helperClazz, "constructIntegerRange", - "(II)Landroid/util/Range;"); - jobject jRange = env->CallStaticObjectMethod(helperClazz, constructIntegerRangeID, - range.lower(), range.upper()); - - return jRange; -} - -static jobject convertToJavaDoubleRange(JNIEnv *env, const Range<double>& range) { - jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper"); - jmethodID constructDoubleRangeID = env->GetStaticMethodID(helperClazz, "constructDoubleRange", - "(DD)Landroid/util/Range;"); - jobject jRange = env->CallStaticObjectMethod(helperClazz, constructDoubleRangeID, - range.lower(), range.upper()); - return jRange; -} - -static jobjectArray convertToJavaIntRangeArray(JNIEnv *env, - const std::vector<Range<int32_t>>& ranges) { - jclass rangeClazz = env->FindClass("android/util/Range"); - CHECK(rangeClazz != NULL); - jobjectArray jRanges = env->NewObjectArray(ranges.size(), rangeClazz, NULL); - for (int i = 0; i < ranges.size(); i++) { - Range<int32_t> range = ranges.at(i); - jobject jRange = convertToJavaIntRange(env, range); - env->SetObjectArrayElement(jRanges, i, jRange); - env->DeleteLocalRef(jRange); - jRange = NULL; - } - return jRanges; -} - -// Converters between Java objects and native instances - -// The Java AudioCapabilities object keep bitrateRange, sampleRates, sampleRateRanges -// and inputChannelRanges in it to prevent reconstruction when called the getters functions. -static jobject convertToJavaAudioCapabilities( - JNIEnv *env, std::shared_ptr<AudioCapabilities> audioCaps) { - if (audioCaps == nullptr) { - return NULL; - } - - // construct Java bitrateRange - const Range<int32_t>& bitrateRange = audioCaps->getBitrateRange(); - jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange); - - // construct Java sampleRates array - const std::vector<int32_t>& sampleRates = audioCaps->getSupportedSampleRates(); - jintArray jSampleRates = env->NewIntArray(sampleRates.size()); - for (size_t i = 0; i < sampleRates.size(); ++i) { - jint val = sampleRates.at(i); - env->SetIntArrayRegion(jSampleRates, i, 1, &val); - } - - // construct Java sampleRateRanges - const std::vector<Range<int32_t>>& sampleRateRanges = audioCaps->getSupportedSampleRateRanges(); - jobjectArray jSampleRateRanges = convertToJavaIntRangeArray(env, sampleRateRanges); - - // construct Java inputChannelRanges - const std::vector<Range<int32_t>>& inputChannelRanges = audioCaps->getInputChannelCountRanges(); - jobjectArray jInputChannelRanges = convertToJavaIntRangeArray(env, inputChannelRanges); - - // construct Java AudioCapsNativeImpl - jclass audioCapsImplClazz - = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl"); - CHECK(audioCapsImplClazz != NULL); - jmethodID audioCapsImplConstructID = env->GetMethodID(audioCapsImplClazz, "<init>", - "(Landroid/util/Range;" - "[I" - "[Landroid/util/Range;" - "[Landroid/util/Range;)V"); - jobject jAudioCapsImpl = env->NewObject(audioCapsImplClazz, audioCapsImplConstructID, - jBitrateRange, jSampleRates, jSampleRateRanges, jInputChannelRanges); - // The native AudioCapabilities won't be destructed until process ends. - env->SetLongField(jAudioCapsImpl, fields.audioCapsContext, (jlong)audioCaps.get()); - - // construct Java AudioCapabilities - jclass audioCapsClazz - = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities"); - CHECK(audioCapsClazz != NULL); - jmethodID audioCapsConstructID = env->GetMethodID(audioCapsClazz, "<init>", - "(Landroid/media/MediaCodecInfo$AudioCapabilities$AudioCapsIntf;)V"); - jobject jAudioCaps = env->NewObject(audioCapsClazz, audioCapsConstructID, jAudioCapsImpl); - - env->DeleteLocalRef(jBitrateRange); - jBitrateRange = NULL; - - env->DeleteLocalRef(jSampleRates); - jSampleRates = NULL; - - env->DeleteLocalRef(jSampleRateRanges); - jSampleRateRanges = NULL; - - env->DeleteLocalRef(jInputChannelRanges); - jInputChannelRanges = NULL; - - env->DeleteLocalRef(jAudioCapsImpl); - jAudioCapsImpl = NULL; - - return jAudioCaps; -} - -// convert native PerformancePoints to Java objects -static jobject convertToJavaPerformancePoints(JNIEnv *env, - const std::vector<VideoCapabilities::PerformancePoint>& performancePoints) { - jclass performancePointClazz = env->FindClass( - "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint"); - CHECK(performancePointClazz != NULL); - jmethodID performancePointConstructID = env->GetMethodID(performancePointClazz, "<init>", - "(IIIJII)V"); - - jobjectArray jPerformancePoints = env->NewObjectArray(performancePoints.size(), - performancePointClazz, NULL); - int i = 0; - for (auto it = performancePoints.begin(); it != performancePoints.end(); ++it, ++i) { - jobject jPerformancePoint = env->NewObject(performancePointClazz, - performancePointConstructID, it->getWidth(), - it->getHeight(), it->getMaxFrameRate(), - it->getMaxMacroBlockRate(), it->getBlockSize().getWidth(), - it->getBlockSize().getHeight()); - - env->SetObjectArrayElement(jPerformancePoints, i, jPerformancePoint); - - env->DeleteLocalRef(jPerformancePoint); - } - - jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper"); - CHECK(helperClazz != NULL); - jmethodID asListID = env->GetStaticMethodID(helperClazz, "constructPerformancePointList", - "([Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Ljava/util/List;"); - CHECK(asListID != NULL); - jobject jList = env->CallStaticObjectMethod(helperClazz, asListID, jPerformancePoints); - - return jList; -} - -static VideoCapabilities::PerformancePoint convertToNativePerformancePoint( - JNIEnv *env, jobject pp) { - if (pp == NULL) { - jniThrowException(env, "java/lang/NullPointerException", NULL); - } - - jclass clazz = env->FindClass( - "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint"); - CHECK(clazz != NULL); - CHECK(env->IsInstanceOf(pp, clazz)); - - jmethodID getWidthID = env->GetMethodID(clazz, "getWidth", "()I"); - CHECK(getWidthID != NULL); - jint width = env->CallIntMethod(pp, getWidthID); - - jmethodID getHeightID = env->GetMethodID(clazz, "getHeight", "()I"); - CHECK(getHeightID != NULL); - jint height = env->CallIntMethod(pp, getHeightID); - - jmethodID getMaxFrameRateID = env->GetMethodID(clazz, "getMaxFrameRate", "()I"); - CHECK(getMaxFrameRateID != NULL); - jint maxFrameRate = env->CallIntMethod(pp, getMaxFrameRateID); - - jmethodID getMaxMacroBlockRateID = env->GetMethodID(clazz, "getMaxMacroBlockRate", "()J"); - CHECK(getMaxMacroBlockRateID != NULL); - jlong maxMacroBlockRate = env->CallLongMethod(pp, getMaxMacroBlockRateID); - - jmethodID getBlockWidthID = env->GetMethodID(clazz, "getBlockWidth", "()I"); - CHECK(getBlockWidthID != NULL); - jint blockWidth = env->CallIntMethod(pp, getBlockWidthID); - - jmethodID getBlockHeightID = env->GetMethodID(clazz, "getBlockHeight", "()I"); - CHECK(getBlockHeightID != NULL); - jint blockHeight = env->CallIntMethod(pp, getBlockHeightID); - - return VideoCapabilities::PerformancePoint(VideoSize(blockWidth, blockHeight), - width, height, maxFrameRate, maxMacroBlockRate); -} - -static jobject convertToJavaVideoCapabilities(JNIEnv *env, - std::shared_ptr<VideoCapabilities> videoCaps) { - if (videoCaps == nullptr) { - return NULL; - } - - // get Java bitrateRange - const Range<int32_t>& bitrateRange = videoCaps->getBitrateRange(); - jobject jBitrateRange = convertToJavaIntRange(env, bitrateRange); - - // get Java widthRange - const Range<int32_t>& widthRange = videoCaps->getSupportedWidths(); - jobject jWidthRange = convertToJavaIntRange(env, widthRange); - - // get Java heightRange - const Range<int32_t>& heightRange = videoCaps->getSupportedHeights(); - jobject jHeightRange = convertToJavaIntRange(env, heightRange); - - // get Java frameRateRange - const Range<int32_t>& frameRateRange = videoCaps->getSupportedFrameRates(); - jobject jFrameRateRange = convertToJavaIntRange(env, frameRateRange); - - // get Java performancePoints - const std::vector<VideoCapabilities::PerformancePoint>& performancePoints - = videoCaps->getSupportedPerformancePoints(); - jobject jPerformancePoints = convertToJavaPerformancePoints(env, performancePoints); - - // get width alignment - int32_t widthAlignment = videoCaps->getWidthAlignment(); - - // get height alignment - int32_t heightAlignment = videoCaps->getHeightAlignment(); - - // get Java VideoCapsNativeImpl - jclass videoCapsImplClazz = env->FindClass( - "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl"); - CHECK(videoCapsImplClazz != NULL); - jmethodID videoCapsImplConstructID = env->GetMethodID(videoCapsImplClazz, "<init>", - "(Landroid/util/Range;" - "Landroid/util/Range;" - "Landroid/util/Range;" - "Landroid/util/Range;" - "Ljava/util/List;II)V"); - jobject jVideoCapsImpl = env->NewObject(videoCapsImplClazz, videoCapsImplConstructID, - jBitrateRange, jWidthRange, jHeightRange, jFrameRateRange, jPerformancePoints, - widthAlignment, heightAlignment); - // The native VideoCapabilities won't be destructed until process ends. - env->SetLongField(jVideoCapsImpl, fields.videoCapsContext, (jlong)videoCaps.get()); - - // get Java VideoCapabilities - jclass videoCapsClazz - = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities"); - CHECK(videoCapsClazz != NULL); - jmethodID videoCapsConstructID = env->GetMethodID(videoCapsClazz, "<init>", - "(Landroid/media/MediaCodecInfo$VideoCapabilities$VideoCapsIntf;)V"); - jobject jVideoCaps = env->NewObject(videoCapsClazz, videoCapsConstructID, jVideoCapsImpl); - - env->DeleteLocalRef(jBitrateRange); - jBitrateRange = NULL; - - env->DeleteLocalRef(jWidthRange); - jWidthRange = NULL; - - env->DeleteLocalRef(jHeightRange); - jHeightRange = NULL; - - env->DeleteLocalRef(jFrameRateRange); - jFrameRateRange = NULL; - - env->DeleteLocalRef(jPerformancePoints); - jPerformancePoints = NULL; - - env->DeleteLocalRef(jVideoCapsImpl); - jVideoCapsImpl = NULL; - - return jVideoCaps; -} - -static jobject convertToJavaEncoderCapabilities(JNIEnv *env, - std::shared_ptr<EncoderCapabilities> encoderCaps) { - if (encoderCaps == nullptr) { - return NULL; - } - - // get quality range - const Range<int>& qualityRange = encoderCaps->getQualityRange(); - jobject jQualityRange = convertToJavaIntRange(env, qualityRange); - - // get complexity range - const Range<int>& complexityRange = encoderCaps->getComplexityRange(); - jobject jComplexityRange = convertToJavaIntRange(env, complexityRange); - - // construct java EncoderCapsNativeImpl - jclass encoderCapsImplClazz = env->FindClass( - "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl"); - CHECK(encoderCapsImplClazz != NULL); - jmethodID encoderCapsImplConstructID = env->GetMethodID(encoderCapsImplClazz, "<init>", - "(Landroid/util/Range;Landroid/util/Range;)V"); - jobject jEncoderCapsImpl = env->NewObject(encoderCapsImplClazz, encoderCapsImplConstructID, - jQualityRange, jComplexityRange); - // The native EncoderCapabilities won't be destructed until process ends. - env->SetLongField(jEncoderCapsImpl, fields.encoderCapsContext, (jlong)encoderCaps.get()); - - // construct java EncoderCapabilities object - jclass encoderCapsClazz - = env->FindClass("android/media/MediaCodecInfo$EncoderCapabilities"); - CHECK(encoderCapsClazz != NULL); - jmethodID encoderCapsConstructID = env->GetMethodID(encoderCapsClazz, "<init>", - "(Landroid/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsIntf;)V"); - jobject jEncoderCaps = env->NewObject(encoderCapsClazz, encoderCapsConstructID, - jEncoderCapsImpl); - - env->DeleteLocalRef(jQualityRange); - jQualityRange = NULL; - - env->DeleteLocalRef(jComplexityRange); - jComplexityRange = NULL; - - env->DeleteLocalRef(jEncoderCapsImpl); - jEncoderCapsImpl = NULL; - - return jEncoderCaps; -} - -// Java CodecCapsNativeImpl keeps the defaultFormat, profileLevels, colorFormats, audioCapabilities, -// videoCapabilities and encoderCapabilities in it to prevent reconsturction when called by getter. -static jobject convertToJavaCodecCapsNativeImpl( - JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) { - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return NULL; - } - - // Construct defaultFormat - sp<AMessage> defaultFormat = codecCaps->getDefaultFormat(); - - jobject formatMap = NULL; - if (ConvertMessageToMap(env, defaultFormat, &formatMap)) { - return NULL; - } - - ScopedLocalRef<jclass> mediaFormatClass{env, env->FindClass("android/media/MediaFormat")}; - ScopedLocalRef<jobject> jDefaultFormat{env, env->NewObject( - mediaFormatClass.get(), - env->GetMethodID(mediaFormatClass.get(), "<init>", "(Ljava/util/Map;)V"), - formatMap)}; - - env->DeleteLocalRef(formatMap); - formatMap = NULL; - - // Construct Java ProfileLevelArray - std::vector<ProfileLevel> profileLevels = codecCaps->getProfileLevels(); - - jclass profileLevelClazz = - env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel"); - CHECK(profileLevelClazz != NULL); - - jobjectArray profileLevelArray = - env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL); - - jfieldID profileField = - env->GetFieldID(profileLevelClazz, "profile", "I"); - jfieldID levelField = - env->GetFieldID(profileLevelClazz, "level", "I"); - - for (size_t i = 0; i < profileLevels.size(); ++i) { - const ProfileLevel &src = profileLevels.at(i); - - jobject profileLevelObj = env->AllocObject(profileLevelClazz); - - env->SetIntField(profileLevelObj, profileField, src.mProfile); - env->SetIntField(profileLevelObj, levelField, src.mLevel); - - env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj); - - env->DeleteLocalRef(profileLevelObj); - profileLevelObj = NULL; - } - - // Construct ColorFormatArray - std::vector<uint32_t> colorFormats = codecCaps->getColorFormats(); - - jintArray colorFormatsArray = env->NewIntArray(colorFormats.size()); - env->SetIntArrayRegion(colorFormatsArray, 0, colorFormats.size(), - reinterpret_cast<jint*>(colorFormats.data())); - - // Construct and set AudioCapabilities - std::shared_ptr<AudioCapabilities> audioCaps = codecCaps->getAudioCapabilities(); - jobject jAudioCaps = convertToJavaAudioCapabilities(env, audioCaps); - - // Set VideoCapabilities - std::shared_ptr<VideoCapabilities> videoCaps = codecCaps->getVideoCapabilities(); - jobject jVideoCaps = convertToJavaVideoCapabilities(env, videoCaps); - - // Set EncoderCapabilities - std::shared_ptr<EncoderCapabilities> encoderCaps = codecCaps->getEncoderCapabilities(); - jobject jEncoderCaps = convertToJavaEncoderCapabilities(env, encoderCaps); - - // Construct CodecCapsNativeImpl - jclass codecCapsImplClazz = - env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl"); - CHECK(codecCapsImplClazz != NULL); - jmethodID codecCapsImplConstructID = env->GetMethodID(codecCapsImplClazz, "<init>", - "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[I" - "Landroid/media/MediaFormat;" - "Landroid/media/MediaCodecInfo$AudioCapabilities;" - "Landroid/media/MediaCodecInfo$VideoCapabilities;" - "Landroid/media/MediaCodecInfo$EncoderCapabilities;)V"); - jobject javaCodecCapsImpl = env->NewObject(codecCapsImplClazz, codecCapsImplConstructID, - profileLevelArray, colorFormatsArray, jDefaultFormat.get(), - jAudioCaps, jVideoCaps, jEncoderCaps); - - // Construct JCodecCapabilities and hold the codecCaps in it - sp<JCodecCapabilities> jCodecCaps = sp<JCodecCapabilities>::make(codecCaps); - setCodecCapabilities(env, javaCodecCapsImpl, jCodecCaps); - - env->DeleteLocalRef(profileLevelArray); - profileLevelArray = NULL; - - env->DeleteLocalRef(colorFormatsArray); - colorFormatsArray = NULL; - - env->DeleteLocalRef(jAudioCaps); - jAudioCaps = NULL; - - env->DeleteLocalRef(jVideoCaps); - jVideoCaps = NULL; - - env->DeleteLocalRef(jEncoderCaps); - jEncoderCaps = NULL; - - return javaCodecCapsImpl; -} - -jobject convertToJavaCodecCapabiliites( - JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps) { - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return NULL; - } - - jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps); - - // Construct CodecCapabilities - jclass codecCapsClazz = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities"); - CHECK(codecCapsClazz != NULL); - - jmethodID codecCapsConstructID = env->GetMethodID(codecCapsClazz, "<init>", - "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V"); - jobject javaCodecCaps = env->NewObject(codecCapsClazz, codecCapsConstructID, javaCodecCapsImpl); - - return javaCodecCaps; -} - -} // namespace android - -// ---------------------------------------------------------------------------- - -using namespace android; - -// AudioCapabilities - -static void android_media_AudioCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { - jclass audioCapsImplClazz - = env->FindClass("android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl"); - if (audioCapsImplClazz == NULL) { - return; - } - - fields.audioCapsContext = env->GetFieldID(audioCapsImplClazz, "mNativeContext", "J"); - if (fields.audioCapsContext == NULL) { - return; - } - - env->DeleteLocalRef(audioCapsImplClazz); -} - -static jint android_media_AudioCapabilities_getMaxInputChannelCount(JNIEnv *env, jobject thiz) { - AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz); - if (audioCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - int32_t maxInputChannelCount = audioCaps->getMaxInputChannelCount(); - return maxInputChannelCount; -} - -static jint android_media_AudioCapabilities_getMinInputChannelCount(JNIEnv *env, jobject thiz) { - AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz); - if (audioCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - int32_t minInputChannelCount = audioCaps->getMinInputChannelCount(); - return minInputChannelCount; -} - -static jboolean android_media_AudioCapabilities_isSampleRateSupported(JNIEnv *env, jobject thiz, - int sampleRate) { - AudioCapabilities* const audioCaps = getAudioCapabilities(env, thiz); - if (audioCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - bool res = audioCaps->isSampleRateSupported(sampleRate); - return res; -} - -// PerformancePoint - -static jboolean android_media_VideoCapabilities_PerformancePoint_covers(JNIEnv *env, jobject thiz, - jobject other) { - VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz); - VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other); - - bool res = pp0.covers(pp1); - return res; -} - -static jboolean android_media_VideoCapabilities_PerformancePoint_equals(JNIEnv *env, jobject thiz, - jobject other) { - VideoCapabilities::PerformancePoint pp0 = convertToNativePerformancePoint(env, thiz); - VideoCapabilities::PerformancePoint pp1 = convertToNativePerformancePoint(env, other); - - bool res = pp0.equals(pp1); - return res; -} - -// VideoCapabilities - -static void android_media_VideoCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { - jclass clazz - = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl"); - if (clazz == NULL) { - return; - } - - fields.videoCapsContext = env->GetFieldID(clazz, "mNativeContext", "J"); - if (fields.videoCapsContext == NULL) { - return; - } - - env->DeleteLocalRef(clazz); -} - -static jboolean android_media_VideoCapabilities_areSizeAndRateSupported(JNIEnv *env, jobject thiz, - int32_t width, int32_t height, double frameRate) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - bool res = videoCaps->areSizeAndRateSupported(width, height, frameRate); - return res; -} - -static jboolean android_media_VideoCapabilities_isSizeSupported(JNIEnv *env, jobject thiz, - int32_t width, int32_t height) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - bool res = videoCaps->isSizeSupported(width, height); - return res; -} - -static jobject android_media_VideoCapabilities_getAchievableFrameRatesFor(JNIEnv *env, jobject thiz, - int32_t width, int32_t height) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - std::optional<Range<double>> frameRates = videoCaps->getAchievableFrameRatesFor(width, height); - if (!frameRates) { - return NULL; - } - jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value()); - return jFrameRates; -} - -static jobject android_media_VideoCapabilities_getSupportedFrameRatesFor(JNIEnv *env, jobject thiz, - int32_t width, int32_t height) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - std::optional<Range<double>> frameRates = videoCaps->getSupportedFrameRatesFor(width, height); - if (!frameRates) { - return NULL; - } - jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value()); - return jFrameRates; -} - -static jobject android_media_VideoCapabilities_getSupportedWidthsFor(JNIEnv *env, jobject thiz, - int32_t height) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - std::optional<Range<int32_t>> supportedWidths = videoCaps->getSupportedWidthsFor(height); - if (!supportedWidths) { - return NULL; - } - jobject jSupportedWidths = convertToJavaIntRange(env, supportedWidths.value()); - - return jSupportedWidths; -} - -static jobject android_media_VideoCapabilities_getSupportedHeightsFor(JNIEnv *env, jobject thiz, - int32_t width) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - std::optional<Range<int32_t>> supportedHeights = videoCaps->getSupportedHeightsFor(width); - if (!supportedHeights) { - return NULL; - } - jobject jSupportedHeights = convertToJavaIntRange(env, supportedHeights.value()); - - return jSupportedHeights; -} - -static jint android_media_VideoCapabilities_getSmallerDimensionUpperLimit(JNIEnv *env, - jobject thiz) { - VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); - if (videoCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - int smallerDimensionUpperLimit = videoCaps->getSmallerDimensionUpperLimit(); - return smallerDimensionUpperLimit; -} - -// EncoderCapabilities - -static void android_media_EncoderCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { - jclass clazz = env->FindClass( - "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl"); - if (clazz == NULL) { - return; - } - - fields.encoderCapsContext = env->GetFieldID(clazz, "mNativeContext", "J"); - if (fields.encoderCapsContext == NULL) { - return; - } - - env->DeleteLocalRef(clazz); -} - -static jboolean android_media_EncoderCapabilities_isBitrateModeSupported(JNIEnv *env, jobject thiz, - int mode) { - EncoderCapabilities* const encoderCaps = getEncoderCapabilities(env, thiz); - if (encoderCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - bool res = encoderCaps->isBitrateModeSupported(mode); - return res; -} - -// CodecCapabilities - -static void android_media_CodecCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { - jclass codecCapsClazz - = env->FindClass("android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl"); - if (codecCapsClazz == NULL) { - return; - } - - fields.codecCapsContext = env->GetFieldID(codecCapsClazz, "mNativeContext", "J"); - if (fields.codecCapsContext == NULL) { - return; - } - - env->DeleteLocalRef(codecCapsClazz); -} - -static jobject android_media_CodecCapabilities_createFromProfileLevel(JNIEnv *env, - jobject /* thiz */, jstring mediaType, jint profile, jint level) { - if (mediaType == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return NULL; - } - - const char *mediaTypeStr = env->GetStringUTFChars(mediaType, nullptr); - if (mediaTypeStr == nullptr) { - return NULL; - } - - std::shared_ptr<CodecCapabilities> codecCaps = CodecCapabilities::CreateFromProfileLevel( - mediaTypeStr, profile, level); - - jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, codecCaps); - - env->ReleaseStringUTFChars(mediaType, mediaTypeStr); - - return javaCodecCapsImpl; -} - -static jobject android_media_CodecCapabilities_native_dup(JNIEnv *env, jobject thiz) { - sp<JCodecCapabilities> jCodecCaps = getCodecCapabilities(env, thiz); - - // As the CodecCaps objects are ready ony, it is ok to use the default copy constructor. - // The duplicate CodecCaps will share the same subobjects with the existing one. - // The lifetime of subobjects are managed by the shared pointer and sp. - std::shared_ptr<CodecCapabilities> duplicate - = std::make_shared<CodecCapabilities>(*(jCodecCaps->getCodecCaps())); - - jobject javaCodecCapsImpl = convertToJavaCodecCapsNativeImpl(env, duplicate); - - return javaCodecCapsImpl; -} - -static void android_media_CodecCapabilities_native_finalize(JNIEnv *env, jobject thiz) { - ALOGV("native_finalize"); - setCodecCapabilities(env, thiz, NULL); -} - -static jint android_media_CodecCapabilities_getMaxSupportedInstances(JNIEnv *env, jobject thiz) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return 0; - } - - int maxSupportedInstances = codecCaps->getMaxSupportedInstances(); - return maxSupportedInstances; -} - -static jstring android_media_CodecCapabilities_getMimeType(JNIEnv *env, jobject thiz) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - std::string mediaType = codecCaps->getMediaType(); - return env->NewStringUTF(mediaType.c_str()); -} - -static jboolean android_media_CodecCapabilities_isFeatureRequired( - JNIEnv *env, jobject thiz, jstring name) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return false; - } - - if (name == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return -ENOENT; - } - - const char *nameStr = env->GetStringUTFChars(name, NULL); - if (nameStr == NULL) { - // Out of memory exception already pending. - return -ENOENT; - } - - bool isFeatureRequired = codecCaps->isFeatureRequired(nameStr); - - env->ReleaseStringUTFChars(name, nameStr); - - return isFeatureRequired; -} - -static jboolean android_media_CodecCapabilities_isFeatureSupported( - JNIEnv *env, jobject thiz, jstring name) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return false; - } - - if (name == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return -ENOENT; - } - - const char *nameStr = env->GetStringUTFChars(name, NULL); - if (nameStr == NULL) { - // Out of memory exception already pending. - return -ENOENT; - } - - bool isFeatureSupported = codecCaps->isFeatureSupported(nameStr); - - env->ReleaseStringUTFChars(name, nameStr); - - return isFeatureSupported; -} - -static jboolean android_media_CodecCapabilities_isFormatSupported(JNIEnv *env, jobject thiz, - jobjectArray keys, jobjectArray values) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return false; - } - - sp<AMessage> format; - status_t err = ConvertKeyValueArraysToMessage(env, keys, values, &format); - if (err != OK) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return -ENOENT;; - } - - return codecCaps->isFormatSupported(format); -} - -static jboolean android_media_CodecCapabilities_isRegular(JNIEnv *env, jobject thiz) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return false; - } - - bool res = codecCaps->isRegular(); - return res; -} - -static jobjectArray android_media_CodecCapabilities_validFeatures(JNIEnv *env, jobject thiz) { - sp<JCodecCapabilities> codecCaps = getCodecCapabilities(env, thiz); - if (codecCaps == nullptr) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); - return NULL; - } - - std::vector<std::string> features = codecCaps->validFeatures(); - - jclass stringClazz = env->FindClass("java/lang/String"); - CHECK(stringClazz != NULL); - jobjectArray jFeatures = env->NewObjectArray(features.size(), stringClazz, NULL); - for (int i = 0; i < features.size(); i++) { - jstring jFeature = env->NewStringUTF(features.at(i).c_str()); - env->SetObjectArrayElement(jFeatures, i, jFeature); - env->DeleteLocalRef(jFeature); - jFeature = NULL; - } - - return jFeatures; -} - -// ---------------------------------------------------------------------------- - -static const JNINativeMethod gAudioCapsMethods[] = { - {"native_init", "()V", (void *)android_media_AudioCapabilities_native_init}, - {"native_getMaxInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMaxInputChannelCount}, - {"native_getMinInputChannelCount", "()I", (void *)android_media_AudioCapabilities_getMinInputChannelCount}, - {"native_isSampleRateSupported", "(I)Z", (void *)android_media_AudioCapabilities_isSampleRateSupported} -}; - -static const JNINativeMethod gPerformancePointMethods[] = { - {"native_covers", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_covers}, - {"native_equals", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_equals}, -}; - -static const JNINativeMethod gVideoCapsMethods[] = { - {"native_init", "()V", (void *)android_media_VideoCapabilities_native_init}, - {"native_areSizeAndRateSupported", "(IID)Z", (void *)android_media_VideoCapabilities_areSizeAndRateSupported}, - {"native_isSizeSupported", "(II)Z", (void *)android_media_VideoCapabilities_isSizeSupported}, - {"native_getAchievableFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getAchievableFrameRatesFor}, - {"native_getSupportedFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedFrameRatesFor}, - {"native_getSupportedWidthsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedWidthsFor}, - {"native_getSupportedHeightsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedHeightsFor}, - {"native_getSmallerDimensionUpperLimit", "()I", (void *)android_media_VideoCapabilities_getSmallerDimensionUpperLimit} -}; - -static const JNINativeMethod gEncoderCapsMethods[] = { - {"native_init", "()V", (void *)android_media_EncoderCapabilities_native_init}, - {"native_isBitrateModeSupported", "(I)Z", (void *)android_media_EncoderCapabilities_isBitrateModeSupported} -}; - -static const JNINativeMethod gCodecCapsMethods[] = { - { "native_init", "()V", (void *)android_media_CodecCapabilities_native_init }, - { "native_createFromProfileLevel", "(Ljava/lang/String;II)Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_createFromProfileLevel }, - { "native_dup", "()Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl;", (void *)android_media_CodecCapabilities_native_dup }, - { "native_finalize", "()V", (void *)android_media_CodecCapabilities_native_finalize }, - { "native_getMaxSupportedInstances", "()I", (void *)android_media_CodecCapabilities_getMaxSupportedInstances }, - { "native_getMimeType", "()Ljava/lang/String;", (void *)android_media_CodecCapabilities_getMimeType }, - { "native_isFeatureRequired", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureRequired }, - { "native_isFeatureSupported", "(Ljava/lang/String;)Z", (void *)android_media_CodecCapabilities_isFeatureSupported }, - { "native_isFormatSupported", "([Ljava/lang/String;[Ljava/lang/Object;)Z", (void *)android_media_CodecCapabilities_isFormatSupported }, - { "native_isRegular", "()Z", (void *)android_media_CodecCapabilities_isRegular }, - { "native_validFeatures", "()[Ljava/lang/String;", (void *)android_media_CodecCapabilities_validFeatures }, -}; - -int register_android_media_CodecCapabilities(JNIEnv *env) { - int result = AndroidRuntime::registerNativeMethods(env, - "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl", - gAudioCapsMethods, NELEM(gAudioCapsMethods)); - if (result != JNI_OK) { - return result; - } - - result = AndroidRuntime::registerNativeMethods(env, - "android/media/MediaCodecInfo$VideoCapabilities$PerformancePoint", - gPerformancePointMethods, NELEM(gPerformancePointMethods)); - if (result != JNI_OK) { - return result; - } - - result = AndroidRuntime::registerNativeMethods(env, - "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl", - gVideoCapsMethods, NELEM(gVideoCapsMethods)); - if (result != JNI_OK) { - return result; - } - - result = AndroidRuntime::registerNativeMethods(env, - "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl", - gEncoderCapsMethods, NELEM(gEncoderCapsMethods)); - if (result != JNI_OK) { - return result; - } - - result = AndroidRuntime::registerNativeMethods(env, - "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsNativeImpl", - gCodecCapsMethods, NELEM(gCodecCapsMethods)); - return result; -}
\ No newline at end of file diff --git a/media/jni/android_media_CodecCapabilities.h b/media/jni/android_media_CodecCapabilities.h deleted file mode 100644 index 4d7dc6a483c2..000000000000 --- a/media/jni/android_media_CodecCapabilities.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024, 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. - */ - -#ifndef _ANDROID_MEDIA_CODECCAPABILITIES_H_ -#define _ANDROID_MEDIA_CODECCAPABILITIES_H_ - -#include "jni.h" - -#include <media/CodecCapabilities.h> - -namespace android { - -struct JCodecCapabilities : public RefBase { - JCodecCapabilities(std::shared_ptr<CodecCapabilities> codecCaps); - - std::shared_ptr<CodecCapabilities> getCodecCaps() const; - - int32_t getMaxSupportedInstances() const; - std::string getMediaType() const; - bool isFeatureRequired(const std::string& name) const; - bool isFeatureSupported(const std::string& name) const; - bool isFormatSupported(const sp<AMessage> &format) const; - bool isRegular() const; - std::vector<std::string> validFeatures() const; - -private: - std::shared_ptr<CodecCapabilities> mCodecCaps; -}; - -jobject convertToJavaCodecCapabiliites( - JNIEnv *env, std::shared_ptr<CodecCapabilities> codecCaps); - -} - -#endif // _ANDROID_MEDIA_CODECCAPABILITIES_H_
\ No newline at end of file diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 1790670903a4..8419ce761a4a 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -16,14 +16,12 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaCodec-JNI" -#include <android_media_codec.h> #include <utils/Log.h> #include <type_traits> #include "android_media_MediaCodec.h" -#include "android_media_CodecCapabilities.h" #include "android_media_MediaCodecLinearBlock.h" #include "android_media_MediaCrypto.h" #include "android_media_MediaDescrambler.h" @@ -140,8 +138,6 @@ static struct { static struct { jclass capsClazz; jmethodID capsCtorId; - jclass cpasImplClazz; - jmethodID capsImplCtorId; jclass profileLevelClazz; jfieldID profileField; jfieldID levelField; @@ -1000,12 +996,10 @@ static jobject getCodecCapabilitiesObject( env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val); } - jobject javaCodecCapsImpl = env->NewObject( - gCodecInfo.cpasImplClazz, gCodecInfo.capsImplCtorId, + return env->NewObject( + gCodecInfo.capsClazz, gCodecInfo.capsCtorId, profileLevelArray.get(), colorFormatsArray.get(), isEncoder, defaultFormatRef.get(), detailsRef.get()); - - return env->NewObject(gCodecInfo.capsClazz, gCodecInfo.capsCtorId, javaCodecCapsImpl); } status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const { @@ -1033,18 +1027,11 @@ status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const env->NewObjectArray(mediaTypes.size(), gCodecInfo.capsClazz, NULL)); for (size_t i = 0; i < mediaTypes.size(); i++) { - jobject jCodecCaps = NULL; - if (android::media::codec::provider_->native_capabilites()) { - const std::shared_ptr<CodecCapabilities> codecCaps - = codecInfo->getCodecCapsFor(mediaTypes[i].c_str()); - jCodecCaps = convertToJavaCodecCapabiliites(env, codecCaps); - } else { - const sp<MediaCodecInfo::Capabilities> caps = - codecInfo->getCapabilitiesFor(mediaTypes[i].c_str()); - jCodecCaps = getCodecCapabilitiesObject( - env, mediaTypes[i].c_str(), isEncoder, caps); - } - ScopedLocalRef<jobject> capsObj(env, jCodecCaps); + const sp<MediaCodecInfo::Capabilities> caps = + codecInfo->getCapabilitiesFor(mediaTypes[i].c_str()); + + ScopedLocalRef<jobject> capsObj(env, getCodecCapabilitiesObject( + env, mediaTypes[i].c_str(), isEncoder, caps)); env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get()); } @@ -3890,20 +3877,10 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get()); method = env->GetMethodID(clazz.get(), "<init>", - "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V"); - CHECK(method != NULL); - gCodecInfo.capsCtorId = method; - - clazz.reset(env->FindClass( - "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl")); - CHECK(clazz.get() != NULL); - gCodecInfo.cpasImplClazz = (jclass)env->NewGlobalRef(clazz.get()); - - method = env->GetMethodID(clazz.get(), "<init>", "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ" "Ljava/util/Map;Ljava/util/Map;)V"); CHECK(method != NULL); - gCodecInfo.capsImplCtorId = method; + gCodecInfo.capsCtorId = method; clazz.reset(env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel")); CHECK(clazz.get() != NULL); diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp index 3522b35539ab..07866ac34e4c 100644 --- a/media/jni/android_media_MediaCodecList.cpp +++ b/media/jni/android_media_MediaCodecList.cpp @@ -16,9 +16,6 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaCodec-JNI" - -#include <android_media_codec.h> - #include <utils/Log.h> #include <media/stagefright/foundation/ADebug.h> @@ -35,7 +32,6 @@ #include "android_runtime/AndroidRuntime.h" #include "jni.h" #include <nativehelper/JNIHelp.h> -#include "android_media_CodecCapabilities.h" #include "android_media_Streams.h" using namespace android; @@ -249,113 +245,95 @@ static jobject android_media_MediaCodecList_getCodecCapabilities( return NULL; } - jobject caps; - if (android::media::codec::provider_->native_capabilites()) { - std::shared_ptr<CodecCapabilities> codecCaps = info.info->getCodecCapsFor(typeStr); - caps = android::convertToJavaCodecCapabiliites(env, codecCaps); - } else { - Vector<MediaCodecInfo::ProfileLevel> profileLevels; - Vector<uint32_t> colorFormats; - - sp<AMessage> defaultFormat = new AMessage(); - defaultFormat->setString("mime", typeStr); - - // TODO query default-format also from codec/codec list - const sp<MediaCodecInfo::Capabilities> &capabilities = - info.info->getCapabilitiesFor(typeStr); - env->ReleaseStringUTFChars(type, typeStr); - typeStr = NULL; - if (capabilities == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return NULL; - } - - capabilities->getSupportedColorFormats(&colorFormats); - capabilities->getSupportedProfileLevels(&profileLevels); - sp<AMessage> details = capabilities->getDetails(); - bool isEncoder = info.info->isEncoder(); - - jobject defaultFormatObj = NULL; - if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) { - return NULL; - } + Vector<MediaCodecInfo::ProfileLevel> profileLevels; + Vector<uint32_t> colorFormats; - jobject infoObj = NULL; - if (ConvertMessageToMap(env, details, &infoObj)) { - env->DeleteLocalRef(defaultFormatObj); - return NULL; - } + sp<AMessage> defaultFormat = new AMessage(); + defaultFormat->setString("mime", typeStr); - jclass capsImplClazz = env->FindClass( - "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl"); - CHECK(capsImplClazz != NULL); + // TODO query default-format also from codec/codec list + const sp<MediaCodecInfo::Capabilities> &capabilities = + info.info->getCapabilitiesFor(typeStr); + env->ReleaseStringUTFChars(type, typeStr); + typeStr = NULL; + if (capabilities == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } - jclass profileLevelClazz = - env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel"); - CHECK(profileLevelClazz != NULL); + capabilities->getSupportedColorFormats(&colorFormats); + capabilities->getSupportedProfileLevels(&profileLevels); + sp<AMessage> details = capabilities->getDetails(); + bool isEncoder = info.info->isEncoder(); - jobjectArray profileLevelArray = - env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL); + jobject defaultFormatObj = NULL; + if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) { + return NULL; + } - jfieldID profileField = - env->GetFieldID(profileLevelClazz, "profile", "I"); + jobject infoObj = NULL; + if (ConvertMessageToMap(env, details, &infoObj)) { + env->DeleteLocalRef(defaultFormatObj); + return NULL; + } - jfieldID levelField = - env->GetFieldID(profileLevelClazz, "level", "I"); + jclass capsClazz = + env->FindClass("android/media/MediaCodecInfo$CodecCapabilities"); + CHECK(capsClazz != NULL); - for (size_t i = 0; i < profileLevels.size(); ++i) { - const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i); + jclass profileLevelClazz = + env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel"); + CHECK(profileLevelClazz != NULL); - jobject profileLevelObj = env->AllocObject(profileLevelClazz); + jobjectArray profileLevelArray = + env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL); - env->SetIntField(profileLevelObj, profileField, src.mProfile); - env->SetIntField(profileLevelObj, levelField, src.mLevel); + jfieldID profileField = + env->GetFieldID(profileLevelClazz, "profile", "I"); - env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj); + jfieldID levelField = + env->GetFieldID(profileLevelClazz, "level", "I"); - env->DeleteLocalRef(profileLevelObj); - profileLevelObj = NULL; - } + for (size_t i = 0; i < profileLevels.size(); ++i) { + const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i); - jintArray colorFormatsArray = env->NewIntArray(colorFormats.size()); + jobject profileLevelObj = env->AllocObject(profileLevelClazz); - for (size_t i = 0; i < colorFormats.size(); ++i) { - jint val = colorFormats.itemAt(i); - env->SetIntArrayRegion(colorFormatsArray, i, 1, &val); - } + env->SetIntField(profileLevelObj, profileField, src.mProfile); + env->SetIntField(profileLevelObj, levelField, src.mLevel); - jmethodID capsImplConstructID = env->GetMethodID(capsImplClazz, "<init>", - "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ" - "Ljava/util/Map;Ljava/util/Map;)V"); + env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj); - jobject capsImpl = env->NewObject(capsImplClazz, capsImplConstructID, - profileLevelArray, colorFormatsArray, isEncoder, - defaultFormatObj, infoObj); + env->DeleteLocalRef(profileLevelObj); + profileLevelObj = NULL; + } - jclass capsClazz = env->FindClass( - "android/media/MediaCodecInfo$CodecCapabilities"); - CHECK(capsClazz != NULL); + jintArray colorFormatsArray = env->NewIntArray(colorFormats.size()); - jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>", - "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V"); + for (size_t i = 0; i < colorFormats.size(); ++i) { + jint val = colorFormats.itemAt(i); + env->SetIntArrayRegion(colorFormatsArray, i, 1, &val); + } - caps = env->NewObject(capsClazz, capsConstructID, capsImpl); + jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>", + "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ" + "Ljava/util/Map;Ljava/util/Map;)V"); - env->DeleteLocalRef(profileLevelArray); - profileLevelArray = NULL; + jobject caps = env->NewObject(capsClazz, capsConstructID, + profileLevelArray, colorFormatsArray, isEncoder, + defaultFormatObj, infoObj); - env->DeleteLocalRef(colorFormatsArray); - colorFormatsArray = NULL; + env->DeleteLocalRef(profileLevelArray); + profileLevelArray = NULL; - env->DeleteLocalRef(defaultFormatObj); - defaultFormatObj = NULL; + env->DeleteLocalRef(colorFormatsArray); + colorFormatsArray = NULL; - env->DeleteLocalRef(infoObj); - infoObj = NULL; + env->DeleteLocalRef(defaultFormatObj); + defaultFormatObj = NULL; - env->DeleteLocalRef(capsImpl); - capsImpl = NULL; - } + env->DeleteLocalRef(infoObj); + infoObj = NULL; return caps; } diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index f159282ed2c2..a94230014437 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1479,7 +1479,6 @@ static int register_android_media_MediaPlayer(JNIEnv *env) extern int register_android_media_ImageReader(JNIEnv *env); extern int register_android_media_ImageWriter(JNIEnv *env); extern int register_android_media_JetPlayer(JNIEnv *env); -extern int register_android_media_CodecCapabilities(JNIEnv *env); extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_Drm(JNIEnv *env); extern int register_android_media_Descrambler(JNIEnv *env); @@ -1594,11 +1593,6 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } - if (register_android_media_CodecCapabilities(env) < 0) { - ALOGE("ERROR: CodecCapabilities native registration failed"); - goto bail; - } - if (register_android_media_Crypto(env) < 0) { ALOGE("ERROR: MediaCodec native registration failed"); goto bail; |