summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Songyue Han <songyueh@google.com> 2024-12-19 03:50:33 -0800
committer Gerrit Code Review <noreply-gerritcodereview@google.com> 2024-12-19 03:50:33 -0800
commit5f2f051b3a5692986d9b1fcba7f8f1c48da9e5ad (patch)
treea28150ff12af1ab7992ded62f7d2be1c244a8f2a
parentbd4463c58b1b7c848b3a50823bdfef20811fc617 (diff)
parentbe726fb4c593f9efc3d4bba88ddbc28e5910df5f (diff)
Merge changes from topic "revert-3093180-EQDDQCLEND" into main
* changes: Revert "Java and JNI support for native AudioCapabilities." Revert "Java and JNI support for native PerformancePoint." Revert "Java and JNI support for native VideoCapabilities." Revert "Java and JNI support for native EncoderCapabilities." Revert "Java and JNI support for native CodecCapabilities."
-rw-r--r--media/java/android/media/MediaCodecInfo.java5716
-rw-r--r--media/jni/Android.bp2
-rw-r--r--media/jni/android_media_CodecCapabilities.cpp1015
-rw-r--r--media/jni/android_media_CodecCapabilities.h47
-rw-r--r--media/jni/android_media_MediaCodec.cpp39
-rw-r--r--media/jni/android_media_MediaCodecList.cpp154
-rw-r--r--media/jni/android_media_MediaPlayer.cpp6
7 files changed, 2517 insertions, 4462 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 69b26fd9e073..782db358bf9f 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -244,7 +244,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
@@ -622,6 +627,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.
*/
@@ -811,680 +822,122 @@ public final class MediaCodecInfo {
@FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
public static final String FEATURE_DetachedSurface = "detached-surface";
- /** package private */ interface CodecCapsIntf {
- public CodecCapsIntf dup();
-
- public boolean isFeatureSupported(String name);
-
- public boolean isFeatureRequired(String name);
-
- public boolean isFormatSupported(MediaFormat format);
-
- public MediaFormat getDefaultFormat();
-
- public String getMimeType();
-
- public int getMaxSupportedInstances();
-
- public AudioCapabilities getAudioCapabilities();
-
- public VideoCapabilities getVideoCapabilities();
-
- public EncoderCapabilities getEncoderCapabilities();
-
- public boolean isRegular();
-
- public CodecProfileLevel[] getProfileLevels();
-
- public 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 final boolean isFeatureSupported(String name) {
- return checkFeature(name, mFlagsSupported);
- }
-
- public final 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 final boolean isFormatSupported(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- final String mime = (String) map.get(MediaFormat.KEY_MIME);
-
- // mime must match if present
- if (mime != null && !mMime.equalsIgnoreCase(mime)) {
- return false;
- }
-
- // check feature support
- for (Feature feat: getValidFeatures()) {
- 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 final boolean isFeatureSupported(String name) {
- return native_isFeatureSupported(name);
- }
-
- public final boolean isFeatureRequired(String name) {
- return native_isFeatureRequired(name);
- }
-
- public boolean isRegular() {
- return native_isRegular();
- }
-
- public final 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 final boolean native_isFeatureRequired(String name);
- private native final boolean native_isFeatureSupported(String name);
- private native final boolean native_isFormatSupported(@Nullable String[] keys,
- @Nullable Object[] values);
- private native boolean native_isRegular();
-
- 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();
- }
-
- /** package private */ CodecCapabilities(CodecCapsIntf impl) {
- mImpl = impl;
- profileLevels = mImpl.getProfileLevels();
- colorFormats = mImpl.getColorFormats();
+ return res;
}
- /** @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);
+ private Feature[] getValidFeatures() {
+ return FeatureList.getFeatures(isEncoder());
}
- /**
- * 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);
+ 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;
}
/**
@@ -1593,573 +1046,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();
- }
-
- /**
- * Returns the mime type for which this codec-capability object was created.
- */
- public String getMimeType() {
- return mImpl.getMimeType();
- }
+ final Map<String, Object> map = format.getMap();
+ final String mime = (String)map.get(MediaFormat.KEY_MIME);
- /**
- * 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;
}
/**
@@ -2168,7 +1432,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;
}
/**
@@ -2177,21 +1441,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);
}
/**
@@ -2211,7 +1461,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;
}
/**
@@ -2223,24 +1480,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;
}
}
@@ -2300,6 +1897,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
@@ -2329,24 +2224,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
@@ -2366,24 +2243,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();
@@ -2471,20 +2330,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) {
@@ -2538,18 +2383,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) {
@@ -2563,28 +2404,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);
@@ -2689,1869 +2519,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;
-
- mWidthRange = getSizeRange();
- mHeightRange = getSizeRange();
- mFrameRateRange = FRAME_RATE_RANGE;
-
- mHorizontalBlockRange = getSizeRange();
- mVerticalBlockRange = getSizeRange();
-
- // full positive ranges are supported as these get calculated
- mBlockCountRange = POSITIVE_INTEGERS;
- mBlocksPerSecondRange = POSITIVE_LONGS;
+ checkPowerOfTwo(
+ blockSize.getWidth(), "block-size width must be power of two");
+ checkPowerOfTwo(
+ blockSize.getHeight(), "block-size height must be power of two");
- mBlockAspectRatioRange = POSITIVE_RATIONALS;
- mAspectRatioRange = POSITIVE_RATIONALS;
+ checkPowerOfTwo(
+ alignment.getWidth(), "alignment width must be power of two");
+ checkPowerOfTwo(
+ alignment.getHeight(), "alignment height must be power of two");
- mWidthAlignment = 1;
- mHeightAlignment = 1;
- mBlockWidth = 1;
- mBlockHeight = 1;
- mSmallerDimensionUpperLimit = getSizeRange().getUpper();
- }
+ // 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());
- 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()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
}
- // 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 (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;
- }
-
- /** @hide */
- public int getSmallerDimensionUpperLimit() {
- return native_getSmallerDimensionUpperLimit();
- }
+ final int blockSize = 8;
+ int maxLengthInBlocks = Utils.divUp(maxDim, blockSize);
+ maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize);
+ maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize);
- 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;
@@ -4562,314 +3873,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;
+ try {
+ mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+ } catch (NumberFormatException e) { }
- private void parseFromInfo(MediaFormat info) {
- Map<String, Object> map = info.getMap();
+ try {
+ mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+ } catch (NumberFormatException e) { }
- 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 {
- 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;
- }
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = mParent.getMimeType();
- public boolean isBitrateModeSupported(int mode) {
- return native_isBitrateModeSupported(mode);
+ 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 void getDefaultFormat(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");
+ }
}
- // 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");
+ // 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 native boolean native_isBitrateModeSupported(int mode);
- private static native void native_init();
+ Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
- static {
- System.loadLibrary("media_jni");
- native_init();
- }
- }
-
- 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;
- }
-
- /**
- * 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);
}
};
@@ -5641,19 +4826,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 df0c826d8d87..000000000000
--- a/media/jni/android_media_CodecCapabilities.cpp
+++ /dev/null
@@ -1,1015 +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();
-}
-
-// 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 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 },
-};
-
-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 5cca0b503740..000000000000
--- a/media/jni/android_media_CodecCapabilities.h
+++ /dev/null
@@ -1,47 +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;
-
-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 fcba150f0db5..d05ee551c172 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1465,7 +1465,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);
@@ -1580,11 +1579,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;