Merge "Add checkServerTrusted with OCSP and TlsData parameters to RootTrustManager" into main
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index f3bb514..727dcba 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -4,6 +4,8 @@
# PowerManager
per-file IPowerManager.aidl = file:/services/core/java/com/android/server/power/OWNERS
+per-file IScreenTimeoutPolicyListener.aidl = file:/services/core/java/com/android/server/power/OWNERS
+per-file IWakeLockCallback.aidl = file:/services/core/java/com/android/server/power/OWNERS
per-file PowerManager.java = file:/services/core/java/com/android/server/power/OWNERS
per-file PowerManagerInternal.java = file:/services/core/java/com/android/server/power/OWNERS
diff --git a/core/java/android/security/net/config/OWNERS b/core/java/android/security/net/config/OWNERS
index 85ce3c6..e945ff9 100644
--- a/core/java/android/security/net/config/OWNERS
+++ b/core/java/android/security/net/config/OWNERS
@@ -1,5 +1,6 @@
-# Bug component: 36824
-set noparent
+# Bug component: 1479456
-cbrubaker@google.com
+bessiej@google.com
brambonne@google.com
+sandrom@google.com
+tweek@google.com
diff --git a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
index 7755000..3ed902f 100644
--- a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
+++ b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS
@@ -1,3 +1,5 @@
# TODO(b/274465475): Migrate LatencyTracker testing to its own module
marcinoc@google.com
ilkos@google.com
+jjaggi@google.com
+nicomazz@google.com
diff --git a/core/tests/coretests/src/android/net/http/OWNERS b/core/tests/coretests/src/android/net/http/OWNERS
new file mode 100644
index 0000000..c93a419
--- /dev/null
+++ b/core/tests/coretests/src/android/net/http/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/net/http/OWNERS
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 6d84e70..b91a5b5 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -673,6 +673,7 @@
return config != null ? config.getDuration() : DURATION_NOT_SET;
}
+ @Nullable
private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
// if no volume shaper config is available, return null
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 69b26fd..782db35 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -244,7 +244,12 @@
* {@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 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,656 +822,6 @@
@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();
- }
-
- /* 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;
- }
-
- // 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;
- }
- return true;
- }
-
- private static boolean supportsBitrate(
- Range<Integer> bitrateRange, MediaFormat format) {
- Map<String, Object> map = format.getMap();
-
- // consider max bitrate over average bitrate for support
- Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
- Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
- if (bitrate == null) {
- bitrate = maxBitrate;
- } else if (maxBitrate != null) {
- bitrate = Math.max(bitrate, maxBitrate);
- }
-
- if (bitrate != null && bitrate > 0) {
- return bitrateRange.contains(bitrate);
- }
-
- return true;
- }
-
- private boolean supportsProfileLevel(int profile, Integer level) {
- for (CodecProfileLevel pl: 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;
- }
- }
-
- // MPEG4 levels are not completely ordered:
- // Level1 support only implies Level0 (and not Level0b) support
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
- && level > CodecProfileLevel.MPEG4Level0) {
- continue;
- }
- }
-
- // HEVC levels incorporate both tiers and levels. Verify tier support.
- if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
- boolean supportsHighTier =
- (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
- boolean checkingHighTier
- = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
- // high tier levels are only supported by other high tier levels
- if (checkingHighTier && !supportsHighTier) {
- continue;
- }
- }
-
- if (pl.level >= level) {
- // if we recognize the listed profile/level, we must also recognize the
- // profile/level arguments.
- if (createFromProfileLevel(mMime, profile, pl.level) != null) {
- return createFromProfileLevel(mMime, profile, level) != null;
- }
- return true;
- }
- }
- return false;
- }
-
- 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;
- }
- 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);
- }
-
- 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
- }
- }
- }
-
- /* 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;
- }
-
- 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();
- }
-
- /** @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>
@@ -1469,7 +830,7 @@
* features that are always on.
*/
public final boolean isFeatureSupported(String name) {
- return mImpl.isFeatureSupported(name);
+ return checkFeature(name, mFlagsSupported);
}
/**
@@ -1479,12 +840,104 @@
* they are always turned on.
*/
public final boolean isFeatureRequired(String name) {
- return mImpl.isFeatureRequired(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;
}
/** @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,22 +1046,200 @@
* and feature requests.
*/
public final boolean isFormatSupported(MediaFormat format) {
- return mImpl.isFormatSupported(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;
+ }
+
+ // 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;
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ if (mAudioCaps != null && !mAudioCaps.supportsFormat(format)) {
+ return false;
+ }
+ if (mVideoCaps != null && !mVideoCaps.supportsFormat(format)) {
+ return false;
+ }
+ if (mEncoderCaps != null && !mEncoderCaps.supportsFormat(format)) {
+ return false;
+ }
+ return true;
}
+ private static boolean supportsBitrate(
+ Range<Integer> bitrateRange, MediaFormat format) {
+ Map<String, Object> map = format.getMap();
+
+ // consider max bitrate over average bitrate for support
+ Integer maxBitrate = (Integer)map.get(MediaFormat.KEY_MAX_BIT_RATE);
+ Integer bitrate = (Integer)map.get(MediaFormat.KEY_BIT_RATE);
+ if (bitrate == null) {
+ bitrate = maxBitrate;
+ } else if (maxBitrate != null) {
+ bitrate = Math.max(bitrate, maxBitrate);
+ }
+
+ if (bitrate != null && bitrate > 0) {
+ return bitrateRange.contains(bitrate);
+ }
+
+ return true;
+ }
+
+ private boolean supportsProfileLevel(int profile, Integer level) {
+ for (CodecProfileLevel pl: profileLevels) {
+ if (pl.profile != profile) {
+ continue;
+ }
+
+ // 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;
+ }
+ }
+
+ // MPEG4 levels are not completely ordered:
+ // Level1 support only implies Level0 (and not Level0b) support
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+ if (pl.level != level && pl.level == CodecProfileLevel.MPEG4Level1
+ && level > CodecProfileLevel.MPEG4Level0) {
+ continue;
+ }
+ }
+
+ // HEVC levels incorporate both tiers and levels. Verify tier support.
+ if (mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ boolean supportsHighTier =
+ (pl.level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+ boolean checkingHighTier = (level & CodecProfileLevel.HEVCHighTierLevels) != 0;
+ // high tier levels are only supported by other high tier levels
+ if (checkingHighTier && !supportsHighTier) {
+ continue;
+ }
+ }
+
+ if (pl.level >= level) {
+ // if we recognize the listed profile/level, we must also recognize the
+ // profile/level arguments.
+ if (createFromProfileLevel(mMime, profile, pl.level) != null) {
+ return createFromProfileLevel(mMime, profile, level) != null;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // errors while reading profile levels - accessed from sister capabilities
+ int mError;
+
+ private static final String TAG = "CodecCapabilities";
+
+ // NEW-STYLE CAPABILITIES
+ private AudioCapabilities mAudioCaps;
+ private VideoCapabilities mVideoCaps;
+ private EncoderCapabilities mEncoderCaps;
+ private MediaFormat mDefaultFormat;
+
/**
* Returns a MediaFormat object with default values for configurations that have
* defaults.
*/
public MediaFormat getDefaultFormat() {
- return mImpl.getDefaultFormat();
+ return mDefaultFormat;
}
/**
* Returns the mime type for which this codec-capability object was created.
*/
public String getMimeType() {
- return mImpl.getMimeType();
+ return mMime;
}
/**
@@ -1620,28 +1251,157 @@
* resources at time of use.
*/
public int getMaxSupportedInstances() {
- return mImpl.getMaxSupportedInstances();
+ return mMaxSupportedInstances;
+ }
+
+ private boolean isAudio() {
+ return mAudioCaps != null;
}
/**
* Returns the audio capabilities or {@code null} if this is not an audio codec.
*/
public AudioCapabilities getAudioCapabilities() {
- return mImpl.getAudioCapabilities();
+ return mAudioCaps;
+ }
+
+ private boolean isEncoder() {
+ return mEncoderCaps != null;
}
/**
* Returns the encoding capabilities or {@code null} if this is not an encoder.
*/
public EncoderCapabilities getEncoderCapabilities() {
- return mImpl.getEncoderCapabilities();
+ return mEncoderCaps;
+ }
+
+ private boolean isVideo() {
+ return mVideoCaps != null;
}
/**
* Returns the video capabilities or {@code null} if this is not a video codec.
*/
public VideoCapabilities getVideoCapabilities() {
- return mImpl.getVideoCapabilities();
+ return mVideoCaps;
+ }
+
+ /** @hide */
+ public CodecCapabilities dup() {
+ CodecCapabilities caps = new CodecCapabilities();
+
+ // profileLevels and colorFormats may be modified by client.
+ caps.profileLevels = Arrays.copyOf(profileLevels, profileLevels.length);
+ caps.colorFormats = Arrays.copyOf(colorFormats, colorFormats.length);
+
+ caps.mMime = mMime;
+ caps.mMaxSupportedInstances = mMaxSupportedInstances;
+ caps.mFlagsRequired = mFlagsRequired;
+ caps.mFlagsSupported = mFlagsSupported;
+ caps.mFlagsVerified = mFlagsVerified;
+ caps.mAudioCaps = mAudioCaps;
+ caps.mVideoCaps = mVideoCaps;
+ caps.mEncoderCaps = mEncoderCaps;
+ caps.mDefaultFormat = mDefaultFormat;
+ caps.mCapabilitiesInfo = mCapabilitiesInfo;
+
+ return caps;
+ }
+
+ /**
+ * Retrieve the codec capabilities for a certain {@code mime type}, {@code
+ * profile} and {@code level}. If the type, or profile-level combination
+ * is not understood by the framework, it returns null.
+ * <p class=note> In {@link android.os.Build.VERSION_CODES#M}, calling this
+ * method without calling any method of the {@link MediaCodecList} class beforehand
+ * results in a {@link NullPointerException}.</p>
+ */
+ public static CodecCapabilities createFromProfileLevel(
+ String mime, int profile, int level) {
+ CodecProfileLevel pl = new CodecProfileLevel();
+ pl.profile = profile;
+ pl.level = level;
+ MediaFormat defaultFormat = new MediaFormat();
+ defaultFormat.setString(MediaFormat.KEY_MIME, mime);
+
+ CodecCapabilities ret = new CodecCapabilities(
+ new CodecProfileLevel[] { pl }, new int[0], true /* encoder */,
+ defaultFormat, new MediaFormat() /* info */);
+ if (ret.mError != 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /* 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));
+ }
+
+ 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
+ }
}
}
@@ -1650,516 +1410,20 @@
*/
public static final class AudioCapabilities {
private static final String TAG = "AudioCapabilities";
+ private CodecCapabilities mParent;
+ private Range<Integer> mBitrateRange;
- /* package private */ interface AudioCapsIntf {
- public Range<Integer> getBitrateRange();
+ private int[] mSampleRates;
+ private Range<Integer>[] mSampleRateRanges;
+ private Range<Integer>[] mInputChannelRanges;
- 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);
- }
-
- 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;
- }
- }
- 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;
- }
- }
- 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.");
- }
-
- AudioCapsLegacyImpl caps = new AudioCapsLegacyImpl();
- caps.init(info, parent);
- return caps;
- }
-
- private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl 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;
- }
-
- public boolean isSampleRateSupported(int sampleRate) {
- return supports(sampleRate, null);
- }
-
- /** modifies rates */
- private void limitSampleRates(int[] rates) {
- Arrays.sort(rates);
- ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
- for (int rate: rates) {
- if (supports(rate, null /* channels */)) {
- ranges.add(Range.create(rate, rate));
- }
- }
- mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
- createDiscreteSampleRates();
- }
-
- private void createDiscreteSampleRates() {
- mSampleRates = new int[mSampleRateRanges.length];
- for (int i = 0; i < mSampleRateRanges.length; i++) {
- mSampleRates[i] = mSampleRateRanges[i].getLower();
- }
- }
-
- /** modifies rateRanges */
- private void limitSampleRates(Range<Integer>[] rateRanges) {
- sortDistinctRanges(rateRanges);
- mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
-
- // check if all values are discrete
- for (Range<Integer> range: mSampleRateRanges) {
- if (!range.getLower().equals(range.getUpper())) {
- mSampleRates = null;
- return;
- }
- }
- createDiscreteSampleRates();
- }
-
- private void applyLevelLimits() {
- int[] sampleRates = null;
- Range<Integer> sampleRateRange = null, bitRates = null;
- int maxChannels = MAX_INPUT_CHANNEL_COUNT;
- 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);
- }
-
- 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) {
- // 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) {
- 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.CodecCapsLegacyImpl.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;
- }
- }
-
- /* package private */ static final class AudioCapsNativeImpl implements AudioCapsIntf {
- private long mNativeContext; // accessed by native methods
-
- private Range<Integer> mBitrateRange;
- private int[] mSampleRates;
- private Range<Integer>[] mSampleRateRanges;
- private Range<Integer>[] mInputChannelRanges;
-
- /**
- * 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;
- }
-
- /* no public constructor */
- private AudioCapsNativeImpl() { }
-
- 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);
- }
-
- public Range<Integer>[] getInputChannelCountRanges() {
- return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
- }
-
- public int getMaxInputChannelCount() {
- return native_getMaxInputChannelCount();
- }
-
- public int getMinInputChannelCount() {
- return native_getMinInputChannelCount();
- }
-
- public boolean isSampleRateSupported(int sampleRate) {
- return native_isSampleRateSupported(sampleRate);
- }
-
- // 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");
- }
-
- // 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");
- }
-
- private native int native_getMaxInputChannelCount();
- private native int native_getMinInputChannelCount();
- private native boolean native_isSampleRateSupported(int sampleRate);
- private static native void native_init();
-
- static {
- System.loadLibrary("media_jni");
- native_init();
- }
- }
-
- 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;
- }
-
- /* package private */ AudioCapabilities(AudioCapsIntf impl) {
- mImpl = impl;
- }
-
- /* 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 @@
* {@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 @@
* 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 @@
*/
@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 @@
*/
@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 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 @@
}
/**
- * 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 @@
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 @@
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 @@
* @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 @@
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,1812 +2519,6 @@
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();
-
- public int getWidthAlignment();
-
- public int getHeightAlignment();
-
- public int getSmallerDimensionUpperLimit();
-
- public Range<Integer> getSupportedFrameRates();
-
- public Range<Integer> getSupportedWidthsFor(int height);
-
- public Range<Integer> getSupportedHeightsFor(int width);
-
- public Range<Double> getSupportedFrameRatesFor(int width, int height);
-
- public Range<Double> getAchievableFrameRatesFor(int width, int height);
-
- public boolean areSizeAndRateSupported(int width, int height, double frameRate);
-
- public boolean isSizeSupported(int width, int height);
-
- public boolean supportsFormat(MediaFormat format);
-
- public List<PerformancePoint> getSupportedPerformancePoints();
- }
-
- /* 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.");
- }
-
- VideoCapsLegacyImpl caps = new VideoCapsLegacyImpl();
- caps.init(info, parent);
- return caps;
- }
-
- private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
- mParent = parent;
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- updateLimits();
- }
-
- 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 mSmallerDimensionUpperLimit;
- }
-
- public Range<Integer> getSupportedFrameRates() {
- return mFrameRateRange;
- }
-
- 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");
- }
- }
-
- 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");
- }
- }
-
- 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);
- }
-
- /** @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);
- }
-
- @Nullable
- public List<PerformancePoint> getSupportedPerformancePoints() {
- return mPerformancePoints;
- }
-
- public boolean areSizeAndRateSupported(
- int width, int height, double frameRate) {
- return supports(width, height, frameRate);
- }
-
- public boolean isSizeSupported(int width, int height) {
- return supports(width, height, null);
- }
-
- private boolean supports(Integer width, Integer height, Number rate) {
- boolean ok = true;
-
- if (ok && width != null) {
- ok = mWidthRange.contains(width)
- && (width % mWidthAlignment == 0);
- }
- if (ok && height != null) {
- ok = mHeightRange.contains(height)
- && (height % mHeightAlignment == 0);
- }
- if (ok && rate != null) {
- ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
- }
- if (ok && height != null && width != null) {
- ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
-
- final int widthInBlocks = Utils.divUp(width, mBlockWidth);
- final int heightInBlocks = Utils.divUp(height, mBlockHeight);
- final int blockCount = widthInBlocks * heightInBlocks;
- ok = ok && mBlockCountRange.contains(blockCount)
- && mBlockAspectRatioRange.contains(
- new Rational(widthInBlocks, heightInBlocks))
- && mAspectRatioRange.contains(new Rational(width, height));
- if (ok && rate != null) {
- double blocksPerSec = blockCount * rate.doubleValue();
- ok = mBlocksPerSecondRange.contains(
- Utils.longRangeFor(blocksPerSec));
- }
- }
- return ok;
- }
-
- /**
- * @hide
- * @throws java.lang.ClassCastException */
- public boolean supportsFormat(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
- Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
- Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
-
- if (!supports(width, height, rate)) {
- return false;
- }
-
- if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
- return false;
- }
-
- // we ignore color-format for now as it is not reliably reported by codec
- return true;
- }
-
- /** @hide */
- public Size getBlockSize() {
- return new Size(mBlockWidth, mBlockHeight);
- }
-
- /** @hide */
- public Range<Integer> getBlockCountRange() {
- return mBlockCountRange;
- }
-
- /** @hide */
- public Range<Long> getBlocksPerSecondRange() {
- return mBlocksPerSecondRange;
- }
-
- /** @hide */
- public Range<Rational> getAspectRatioRange(boolean blocks) {
- return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
- }
-
- private void initWithPlatformLimits() {
- mBitrateRange = BITRATE_RANGE;
-
- mWidthRange = 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;
-
- mBlockAspectRatioRange = POSITIVE_RATIONALS;
- mAspectRatioRange = POSITIVE_RATIONALS;
-
- mWidthAlignment = 1;
- mHeightAlignment = 1;
- mBlockWidth = 1;
- mBlockHeight = 1;
- mSmallerDimensionUpperLimit = getSizeRange().getUpper();
- }
-
- private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
- Vector<PerformancePoint> ret = new Vector<>();
- final String prefix = "performance-point-";
- Set<String> keys = map.keySet();
- for (String key : keys) {
- // looking for: performance-point-WIDTHxHEIGHT-range
- if (!key.startsWith(prefix)) {
- continue;
- }
- String subKey = key.substring(prefix.length());
- if (subKey.equals("none") && ret.size() == 0) {
- // This means that component knowingly did not publish performance points.
- // This is different from when the component forgot to publish performance
- // points.
- return Collections.unmodifiableList(ret);
- }
- String[] temp = key.split("-");
- if (temp.length != 4) {
- continue;
- }
- String sizeStr = temp[2];
- Size size = Utils.parseSize(sizeStr, null);
- if (size == null || size.getWidth() * size.getHeight() <= 0) {
- continue;
- }
- Range<Long> range = Utils.parseLongRange(map.get(key), null);
- if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
- continue;
- }
- PerformancePoint given = new PerformancePoint(
- size.getWidth(), size.getHeight(), range.getLower().intValue(),
- range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
- PerformancePoint rotated = new PerformancePoint(
- size.getHeight(), size.getWidth(), range.getLower().intValue(),
- range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
- ret.add(given);
- if (!given.covers(rotated)) {
- ret.add(rotated);
- }
- }
-
- // check if the component specified no performance point indication
- if (ret.size() == 0) {
- return 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));
-
- 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);
- }
- return ret;
- }
-
- private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
- Pair<Size, Size> range = Utils.parseSizeRange(o);
- if (range != null) {
- try {
- return Pair.create(
- Range.create(range.first.getWidth(), range.second.getWidth()),
- Range.create(range.first.getHeight(), range.second.getHeight()));
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "could not parse size range '" + o + "'");
- }
- }
- return null;
- }
-
- /** @hide */
- public static int equivalentVP9Level(MediaFormat info) {
- final Map<String, Object> map = info.getMap();
-
- Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
- int BS = blockSize.getWidth() * blockSize.getHeight();
-
- Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
- int FS = counts == null ? 0 : BS * counts.getUpper();
-
- Range<Long> blockRates =
- Utils.parseLongRange(map.get("blocks-per-second-range"), null);
- long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
-
- Pair<Range<Integer>, Range<Integer>> dimensionRanges =
- parseWidthHeightRanges(map.get("size-range"));
- int D = dimensionRanges == null ? 0 : Math.max(
- dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
-
- Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
- int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
-
- if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512)
- return CodecProfileLevel.VP9Level1;
- if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768)
- return CodecProfileLevel.VP9Level11;
- if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960)
- return CodecProfileLevel.VP9Level2;
- if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344)
- return CodecProfileLevel.VP9Level21;
- if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048)
- return CodecProfileLevel.VP9Level3;
- if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752)
- return CodecProfileLevel.VP9Level31;
- if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160)
- return CodecProfileLevel.VP9Level4;
- if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160)
- return CodecProfileLevel.VP9Level41;
- if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384)
- return CodecProfileLevel.VP9Level5;
- if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384)
- return CodecProfileLevel.VP9Level51;
- if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384)
- return CodecProfileLevel.VP9Level52;
- if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
- return CodecProfileLevel.VP9Level6;
- if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
- return CodecProfileLevel.VP9Level61;
- if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
- return CodecProfileLevel.VP9Level62;
- // returning largest level
- return CodecProfileLevel.VP9Level62;
- }
-
- private void parseFromInfo(MediaFormat info) {
- final Map<String, Object> map = info.getMap();
- Size blockSize = new Size(mBlockWidth, mBlockHeight);
- Size alignment = new Size(mWidthAlignment, mHeightAlignment);
- Range<Integer> counts = null, widths = null, heights = null;
- Range<Integer> frameRates = null, bitRates = null;
- Range<Long> blockRates = null;
- Range<Rational> ratios = null, blockRatios = null;
-
- blockSize = Utils.parseSize(map.get("block-size"), blockSize);
- alignment = Utils.parseSize(map.get("alignment"), alignment);
- counts = Utils.parseIntRange(map.get("block-count-range"), null);
- blockRates =
- Utils.parseLongRange(map.get("blocks-per-second-range"), null);
- mMeasuredFrameRates = getMeasuredFrameRates(map);
- 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);
- }
- }
-
- ratios = Utils.parseRationalRange(
- map.get("block-aspect-ratio-range"), null);
- blockRatios = Utils.parseRationalRange(
- map.get("pixel-aspect-ratio-range"), null);
- frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
- if (frameRates != null) {
- try {
- frameRates = frameRates.intersect(FRAME_RATE_RANGE);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "frame rate range (" + frameRates
- + ") is out of limits: " + FRAME_RATE_RANGE);
- frameRates = null;
- }
- }
- bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
- if (bitRates != null) {
- try {
- bitRates = bitRates.intersect(BITRATE_RANGE);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "bitrate range (" + bitRates
- + ") is out of limits: " + BITRATE_RANGE);
- bitRates = null;
- }
- }
-
- checkPowerOfTwo(
- blockSize.getWidth(), "block-size width must be power of two");
- checkPowerOfTwo(
- blockSize.getHeight(), "block-size height must be power of two");
-
- checkPowerOfTwo(
- alignment.getWidth(), "alignment width must be power of two");
- checkPowerOfTwo(
- alignment.getHeight(), "alignment height must be power of two");
-
- // update block-size and alignment
- applyMacroBlockLimits(
- Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
- Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
- alignment.getWidth(), alignment.getHeight());
-
- if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
- // codec supports profiles that we don't know.
- // Use supplied values clipped to platform limits
- if (widths != null) {
- mWidthRange = 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();
- }
-
- private void applyBlockLimits(
- int blockWidth, int blockHeight,
- Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
- checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
- checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
-
- final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
- final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
-
- // factor will always be a power-of-2
- int factor =
- newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
- if (factor != 1) {
- mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
- mBlocksPerSecondRange = Utils.factorRange(
- mBlocksPerSecondRange, factor);
- mBlockAspectRatioRange = Utils.scaleRange(
- mBlockAspectRatioRange,
- newBlockHeight / mBlockHeight,
- newBlockWidth / mBlockWidth);
- mHorizontalBlockRange = Utils.factorRange(
- mHorizontalBlockRange, newBlockWidth / mBlockWidth);
- mVerticalBlockRange = Utils.factorRange(
- mVerticalBlockRange, newBlockHeight / mBlockHeight);
- }
- factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
- if (factor != 1) {
- counts = Utils.factorRange(counts, factor);
- rates = Utils.factorRange(rates, factor);
- ratios = Utils.scaleRange(
- ratios, newBlockHeight / blockHeight,
- newBlockWidth / blockWidth);
- }
- mBlockCountRange = mBlockCountRange.intersect(counts);
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
- mBlockWidth = newBlockWidth;
- mBlockHeight = newBlockHeight;
- }
-
- private void applyAlignment(int widthAlignment, int heightAlignment) {
- checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
- checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
-
- if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
- // maintain assumption that 0 < alignment <= block-size
- applyBlockLimits(
- Math.max(widthAlignment, mBlockWidth),
- Math.max(heightAlignment, mBlockHeight),
- POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
- }
-
- mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
- mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
-
- mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
- mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
- }
-
- private void updateLimits() {
- // pixels -> blocks <- counts
- mHorizontalBlockRange = mHorizontalBlockRange.intersect(
- Utils.factorRange(mWidthRange, mBlockWidth));
- mHorizontalBlockRange = mHorizontalBlockRange.intersect(
- Range.create(
- mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
- mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
- mVerticalBlockRange = mVerticalBlockRange.intersect(
- Utils.factorRange(mHeightRange, mBlockHeight));
- mVerticalBlockRange = mVerticalBlockRange.intersect(
- Range.create(
- mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
- mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
- mBlockCountRange = mBlockCountRange.intersect(
- Range.create(
- mHorizontalBlockRange.getLower()
- * mVerticalBlockRange.getLower(),
- mHorizontalBlockRange.getUpper()
- * mVerticalBlockRange.getUpper()));
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
- new Rational(
- mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
- new Rational(
- mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
-
- // blocks -> pixels
- mWidthRange = mWidthRange.intersect(
- (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
- mHorizontalBlockRange.getUpper() * mBlockWidth);
- mHeightRange = mHeightRange.intersect(
- (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
- mVerticalBlockRange.getUpper() * mBlockHeight);
- mAspectRatioRange = mAspectRatioRange.intersect(
- new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
- new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
-
- mSmallerDimensionUpperLimit = Math.min(
- mSmallerDimensionUpperLimit,
- Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
-
- // blocks -> rate
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
- mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
- mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
- mFrameRateRange = mFrameRateRange.intersect(
- (int)(mBlocksPerSecondRange.getLower()
- / mBlockCountRange.getUpper()),
- (int)(mBlocksPerSecondRange.getUpper()
- / (double)mBlockCountRange.getLower()));
- }
-
- private void applyMacroBlockLimits(
- int maxHorizontalBlocks, int maxVerticalBlocks,
- int maxBlocks, long maxBlocksPerSecond,
- int blockWidth, int blockHeight,
- int widthAlignment, int heightAlignment) {
- applyMacroBlockLimits(
- 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
- maxHorizontalBlocks, maxVerticalBlocks,
- maxBlocks, maxBlocksPerSecond,
- blockWidth, blockHeight, widthAlignment, heightAlignment);
- }
-
- private void applyMacroBlockLimits(
- int minHorizontalBlocks, int minVerticalBlocks,
- int maxHorizontalBlocks, int maxVerticalBlocks,
- int maxBlocks, long maxBlocksPerSecond,
- int blockWidth, int blockHeight,
- int widthAlignment, int heightAlignment) {
- applyAlignment(widthAlignment, heightAlignment);
- applyBlockLimits(
- blockWidth, blockHeight, Range.create(1, maxBlocks),
- Range.create(1L, maxBlocksPerSecond),
- Range.create(
- new Rational(1, maxVerticalBlocks),
- new Rational(maxHorizontalBlocks, 1)));
- mHorizontalBlockRange =
- mHorizontalBlockRange.intersect(
- Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
- maxHorizontalBlocks / (mBlockWidth / blockWidth));
- mVerticalBlockRange =
- mVerticalBlockRange.intersect(
- Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
- maxVerticalBlocks / (mBlockHeight / blockHeight));
- }
-
- private void applyLevelLimits() {
- long maxBlocksPerSecond = 0;
- int maxBlocks = 0;
- int maxBps = 0;
- int maxDPBBlocks = 0;
-
- int errors = ERROR_NONE_SUPPORTED;
- CodecProfileLevel[] profileLevels = mParent.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);
- }
-
- int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
- applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
- boolean supported = true;
- switch (profileLevel.profile) {
- case CodecProfileLevel.MPEG2ProfileSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG2LevelML:
- FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG2ProfileMain:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG2LevelLL:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break;
- case CodecProfileLevel.MPEG2LevelML:
- FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
- case CodecProfileLevel.MPEG2LevelH14:
- FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break;
- case CodecProfileLevel.MPEG2LevelHL:
- FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break;
- case CodecProfileLevel.MPEG2LevelHP:
- FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG2Profile422:
- case CodecProfileLevel.MPEG2ProfileSNR:
- case CodecProfileLevel.MPEG2ProfileSpatial:
- case CodecProfileLevel.MPEG2ProfileHigh:
- Log.i(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- if (supported) {
- errors &= ~ERROR_NONE_SUPPORTED;
- }
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- }
- applyMacroBlockLimits(maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
- boolean strict = false; // true: W, H and FR are individual max limits
- boolean supported = true;
- switch (profileLevel.profile) {
- case CodecProfileLevel.MPEG4ProfileSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG4Level0:
- strict = true;
- FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
- case CodecProfileLevel.MPEG4Level1:
- FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
- case CodecProfileLevel.MPEG4Level0b:
- strict = true;
- FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
- case CodecProfileLevel.MPEG4Level2:
- FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
- case CodecProfileLevel.MPEG4Level3:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
- case CodecProfileLevel.MPEG4Level4a:
- FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
- case CodecProfileLevel.MPEG4Level5:
- FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
- case CodecProfileLevel.MPEG4Level6:
- FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG4Level0:
- case CodecProfileLevel.MPEG4Level1:
- FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
- case CodecProfileLevel.MPEG4Level2:
- FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
- case CodecProfileLevel.MPEG4Level3:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
- case CodecProfileLevel.MPEG4Level3b:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break;
- case CodecProfileLevel.MPEG4Level4:
- FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
- case CodecProfileLevel.MPEG4Level5:
- FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG4ProfileMain: // 2-4
- case CodecProfileLevel.MPEG4ProfileNbit: // 2
- case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
- case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
- case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
- case CodecProfileLevel.MPEG4ProfileCore: // 1-2
- case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
- case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
- case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
-
- // Studio profiles are not supported by our codecs.
-
- // Only profiles that can decode simple object types are considered.
- // The following profiles are not able to.
- case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
- case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
- case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
- case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
- case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
- Log.i(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- if (supported) {
- errors &= ~ERROR_NONE_SUPPORTED;
- }
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- if (strict) {
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- } else {
- // assuming max 60 fps frame rate and 1:2 aspect ratio
- int maxDim = (int)Math.sqrt(FS * 2);
- maxWidth = Math.max(maxDim, maxWidth);
- maxHeight = Math.max(maxDim, maxHeight);
- maxRate = Math.max(Math.max(FR, 60), maxRate);
- }
- }
- applyMacroBlockLimits(maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- int minWidth = maxWidth, minHeight = maxHeight;
- int minAlignment = 16;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
- boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
- switch (profileLevel.level) {
- case CodecProfileLevel.H263Level10:
- strict = true; // only supports sQCIF & QCIF
- FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level20:
- strict = true; // only supports sQCIF, QCIF & CIF
- FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break;
- case CodecProfileLevel.H263Level30:
- strict = true; // only supports sQCIF, QCIF & CIF
- FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level40:
- strict = true; // only supports sQCIF, QCIF & CIF
- FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level45:
- // only implies level 10 support
- strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
- || profileLevel.profile ==
- CodecProfileLevel.H263ProfileBackwardCompatible;
- if (!strict) {
- minW = 1; minH = 1; minAlignment = 4;
- }
- FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level50:
- // only supports 50fps for H > 15
- minW = 1; minH = 1; minAlignment = 4;
- FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
- case CodecProfileLevel.H263Level60:
- // only supports 50fps for H > 15
- minW = 1; minH = 1; minAlignment = 4;
- FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
- case CodecProfileLevel.H263Level70:
- // only supports 50fps for H > 30
- minW = 1; minH = 1; minAlignment = 4;
- FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
- default:
- Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
- + "/" + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.H263ProfileBackwardCompatible:
- case CodecProfileLevel.H263ProfileBaseline:
- case CodecProfileLevel.H263ProfileH320Coding:
- case CodecProfileLevel.H263ProfileHighCompression:
- case CodecProfileLevel.H263ProfileHighLatency:
- case CodecProfileLevel.H263ProfileInterlace:
- case CodecProfileLevel.H263ProfileInternet:
- case CodecProfileLevel.H263ProfileISWV2:
- case CodecProfileLevel.H263ProfileISWV3:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- if (strict) {
- // Strict levels define sub-QCIF min size and enumerated sizes. We
- // cannot express support for "only sQCIF & QCIF (& CIF)" using
- // VideoCapabilities but we can express "only QCIF (& CIF)", so set
- // minimume size at QCIF.minW = 8; minH = 6;
- minW = 11; minH = 9;
- } else {
- // any support for non-strict levels (including unrecognized profiles or
- // levels) allow custom frame size support beyond supported limits
- // (other than bitrate)
- mAllowMbOverride = true;
- }
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(W * H, maxBlocks);
- maxBps = Math.max(BR * 64000, maxBps);
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- minWidth = Math.min(minW, minWidth);
- minHeight = Math.min(minH, minHeight);
- }
- // unless we encountered custom frame size support, limit size to QCIF and CIF
- // using aspect ratio.
- if (!mAllowMbOverride) {
- mBlockAspectRatioRange =
- Range.create(new Rational(11, 9), new Rational(11, 9));
- }
- applyMacroBlockLimits(
- minWidth, minHeight,
- maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
- mFrameRateRange = Range.create(1, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
- maxBlocks = Integer.MAX_VALUE;
- maxBlocksPerSecond = Integer.MAX_VALUE;
-
- // TODO: set to 100Mbps for now, need a number for VP8
- maxBps = 100000000;
-
- // profile levels are not indicative for VPx, but verify
- // them nonetheless
- for (CodecProfileLevel profileLevel: profileLevels) {
- switch (profileLevel.level) {
- case CodecProfileLevel.VP8Level_Version0:
- case CodecProfileLevel.VP8Level_Version1:
- case CodecProfileLevel.VP8Level_Version2:
- case CodecProfileLevel.VP8Level_Version3:
- break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.VP8ProfileMain:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- errors &= ~ERROR_NONE_SUPPORTED;
- }
-
- final int blockSize = 16;
- applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
- maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
- maxBlocksPerSecond = 829440;
- maxBlocks = 36864;
- maxBps = 200000;
- int maxDim = 512;
-
- for (CodecProfileLevel profileLevel: profileLevels) {
- long SR = 0; // luma sample rate
- int FS = 0; // luma picture size
- int BR = 0; // bit rate kbps
- int D = 0; // luma dimension
- switch (profileLevel.level) {
- case CodecProfileLevel.VP9Level1:
- SR = 829440; FS = 36864; BR = 200; D = 512; break;
- case CodecProfileLevel.VP9Level11:
- SR = 2764800; FS = 73728; BR = 800; D = 768; break;
- case CodecProfileLevel.VP9Level2:
- SR = 4608000; FS = 122880; BR = 1800; D = 960; break;
- case CodecProfileLevel.VP9Level21:
- SR = 9216000; FS = 245760; BR = 3600; D = 1344; break;
- case CodecProfileLevel.VP9Level3:
- SR = 20736000; FS = 552960; BR = 7200; D = 2048; break;
- case CodecProfileLevel.VP9Level31:
- SR = 36864000; FS = 983040; BR = 12000; D = 2752; break;
- case CodecProfileLevel.VP9Level4:
- SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break;
- case CodecProfileLevel.VP9Level41:
- SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break;
- case CodecProfileLevel.VP9Level5:
- SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break;
- case CodecProfileLevel.VP9Level51:
- SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break;
- case CodecProfileLevel.VP9Level52:
- SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break;
- case CodecProfileLevel.VP9Level6:
- SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break;
- case CodecProfileLevel.VP9Level61:
- SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
- case CodecProfileLevel.VP9Level62:
- SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.VP9Profile0:
- case CodecProfileLevel.VP9Profile1:
- case CodecProfileLevel.VP9Profile2:
- case CodecProfileLevel.VP9Profile3:
- case CodecProfileLevel.VP9Profile2HDR:
- case CodecProfileLevel.VP9Profile3HDR:
- 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);
- }
-
- 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);
- }
-
- 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);
- }
-
- 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;
- }
- 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));
- }
- 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();
- }
-
- 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);
- }
-
- /**
- * 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);
- }
-
- /**
- * 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) {
- return mImpl.getSupportedFrameRatesFor(width, height);
- }
-
- /**
- * 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) {
- return mImpl.getAchievableFrameRatesFor(width, height);
- }
-
/**
* 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
@@ -4518,15 +2542,16 @@
*/
@Nullable
public List<PerformancePoint> getSupportedPerformancePoints() {
- return mImpl.getSupportedPerformancePoints();
+ return mPerformancePoints;
}
/**
* 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);
+ public boolean areSizeAndRateSupported(
+ int width, int height, double frameRate) {
+ return supports(width, height, frameRate);
}
/**
@@ -4534,16 +2559,1282 @@
* {@code height}) is supported.
*/
public boolean isSizeSupported(int width, int height) {
- return mImpl.isSizeSupported(width, height);
+ return supports(width, height, null);
}
+ private boolean supports(Integer width, Integer height, Number rate) {
+ boolean ok = true;
+
+ if (ok && width != null) {
+ ok = mWidthRange.contains(width)
+ && (width % mWidthAlignment == 0);
+ }
+ if (ok && height != null) {
+ ok = mHeightRange.contains(height)
+ && (height % mHeightAlignment == 0);
+ }
+ if (ok && rate != null) {
+ ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue()));
+ }
+ if (ok && height != null && width != null) {
+ ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+ final int blockCount = widthInBlocks * heightInBlocks;
+ ok = ok && mBlockCountRange.contains(blockCount)
+ && mBlockAspectRatioRange.contains(
+ new Rational(widthInBlocks, heightInBlocks))
+ && mAspectRatioRange.contains(new Rational(width, height));
+ if (ok && rate != null) {
+ double blocksPerSec = blockCount * rate.doubleValue();
+ ok = mBlocksPerSecondRange.contains(
+ Utils.longRangeFor(blocksPerSec));
+ }
+ }
+ return ok;
+ }
+
+ /* 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);
+
/**
* @hide
- * @throws java.lang.ClassCastException
- * @throws java.lang.UnsupportedOperationException
- */
+ * @throws java.lang.ClassCastException */
public boolean supportsFormat(MediaFormat format) {
- return mImpl.supportsFormat(format);
+ final Map<String, Object> map = format.getMap();
+ Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+ Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+ Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE);
+
+ if (!supports(width, height, rate)) {
+ return false;
+ }
+
+ if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
+ return false;
+ }
+
+ // we ignore color-format for now as it is not reliably reported by codec
+ return true;
+ }
+
+ /* no public constructor */
+ private VideoCapabilities() { }
+
+ /** @hide */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public static VideoCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ VideoCapabilities caps = new VideoCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ private void init(MediaFormat info, CodecCapabilities parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ updateLimits();
+ }
+
+ /** @hide */
+ public Size getBlockSize() {
+ return new Size(mBlockWidth, mBlockHeight);
+ }
+
+ /** @hide */
+ public Range<Integer> getBlockCountRange() {
+ return mBlockCountRange;
+ }
+
+ /** @hide */
+ public Range<Long> getBlocksPerSecondRange() {
+ return mBlocksPerSecondRange;
+ }
+
+ /** @hide */
+ public Range<Rational> getAspectRatioRange(boolean blocks) {
+ return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
+ }
+
+ private void initWithPlatformLimits() {
+ mBitrateRange = BITRATE_RANGE;
+
+ mWidthRange = 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;
+
+ mBlockAspectRatioRange = POSITIVE_RATIONALS;
+ mAspectRatioRange = POSITIVE_RATIONALS;
+
+ mWidthAlignment = 1;
+ mHeightAlignment = 1;
+ mBlockWidth = 1;
+ mBlockHeight = 1;
+ mSmallerDimensionUpperLimit = getSizeRange().getUpper();
+ }
+
+ private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) {
+ Vector<PerformancePoint> ret = new Vector<>();
+ final String prefix = "performance-point-";
+ Set<String> keys = map.keySet();
+ for (String key : keys) {
+ // looking for: performance-point-WIDTHxHEIGHT-range
+ if (!key.startsWith(prefix)) {
+ continue;
+ }
+ String subKey = key.substring(prefix.length());
+ if (subKey.equals("none") && ret.size() == 0) {
+ // This means that component knowingly did not publish performance points.
+ // This is different from when the component forgot to publish performance
+ // points.
+ return Collections.unmodifiableList(ret);
+ }
+ String[] temp = key.split("-");
+ if (temp.length != 4) {
+ continue;
+ }
+ String sizeStr = temp[2];
+ Size size = Utils.parseSize(sizeStr, null);
+ if (size == null || size.getWidth() * size.getHeight() <= 0) {
+ continue;
+ }
+ Range<Long> range = Utils.parseLongRange(map.get(key), null);
+ if (range == null || range.getLower() < 0 || range.getUpper() < 0) {
+ continue;
+ }
+ PerformancePoint given = new PerformancePoint(
+ size.getWidth(), size.getHeight(), range.getLower().intValue(),
+ range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+ PerformancePoint rotated = new PerformancePoint(
+ size.getHeight(), size.getWidth(), range.getLower().intValue(),
+ range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight));
+ ret.add(given);
+ if (!given.covers(rotated)) {
+ ret.add(rotated);
+ }
+ }
+
+ // check if the component specified no performance point indication
+ if (ret.size() == 0) {
+ return 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));
+
+ 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);
+ }
+ return ret;
+ }
+
+ private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) {
+ Pair<Size, Size> range = Utils.parseSizeRange(o);
+ if (range != null) {
+ try {
+ return Pair.create(
+ Range.create(range.first.getWidth(), range.second.getWidth()),
+ Range.create(range.first.getHeight(), range.second.getHeight()));
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "could not parse size range '" + o + "'");
+ }
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static int equivalentVP9Level(MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+
+ Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8));
+ int BS = blockSize.getWidth() * blockSize.getHeight();
+
+ Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null);
+ int FS = counts == null ? 0 : BS * counts.getUpper();
+
+ Range<Long> blockRates =
+ Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+ long SR = blockRates == null ? 0 : BS * blockRates.getUpper();
+
+ Pair<Range<Integer>, Range<Integer>> dimensionRanges =
+ parseWidthHeightRanges(map.get("size-range"));
+ int D = dimensionRanges == null ? 0 : Math.max(
+ dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper());
+
+ Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+ int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000);
+
+ if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512)
+ return CodecProfileLevel.VP9Level1;
+ if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768)
+ return CodecProfileLevel.VP9Level11;
+ if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960)
+ return CodecProfileLevel.VP9Level2;
+ if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344)
+ return CodecProfileLevel.VP9Level21;
+ if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048)
+ return CodecProfileLevel.VP9Level3;
+ if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752)
+ return CodecProfileLevel.VP9Level31;
+ if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160)
+ return CodecProfileLevel.VP9Level4;
+ if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160)
+ return CodecProfileLevel.VP9Level41;
+ if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384)
+ return CodecProfileLevel.VP9Level5;
+ if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384)
+ return CodecProfileLevel.VP9Level51;
+ if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384)
+ return CodecProfileLevel.VP9Level52;
+ if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832)
+ return CodecProfileLevel.VP9Level6;
+ if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832)
+ return CodecProfileLevel.VP9Level61;
+ if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832)
+ return CodecProfileLevel.VP9Level62;
+ // returning largest level
+ return CodecProfileLevel.VP9Level62;
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+ Size blockSize = new Size(mBlockWidth, mBlockHeight);
+ Size alignment = new Size(mWidthAlignment, mHeightAlignment);
+ Range<Integer> counts = null, widths = null, heights = null;
+ Range<Integer> frameRates = null, bitRates = null;
+ Range<Long> blockRates = null;
+ Range<Rational> ratios = null, blockRatios = null;
+
+ blockSize = Utils.parseSize(map.get("block-size"), blockSize);
+ alignment = Utils.parseSize(map.get("alignment"), alignment);
+ counts = Utils.parseIntRange(map.get("block-count-range"), null);
+ blockRates =
+ Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+ mMeasuredFrameRates = getMeasuredFrameRates(map);
+ 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);
+ }
+ }
+
+ ratios = Utils.parseRationalRange(
+ map.get("block-aspect-ratio-range"), null);
+ blockRatios = Utils.parseRationalRange(
+ map.get("pixel-aspect-ratio-range"), null);
+ frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
+ if (frameRates != null) {
+ try {
+ frameRates = frameRates.intersect(FRAME_RATE_RANGE);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "frame rate range (" + frameRates
+ + ") is out of limits: " + FRAME_RATE_RANGE);
+ frameRates = null;
+ }
+ }
+ bitRates = Utils.parseIntRange(map.get("bitrate-range"), null);
+ if (bitRates != null) {
+ try {
+ bitRates = bitRates.intersect(BITRATE_RANGE);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "bitrate range (" + bitRates
+ + ") is out of limits: " + BITRATE_RANGE);
+ bitRates = null;
+ }
+ }
+
+ checkPowerOfTwo(
+ blockSize.getWidth(), "block-size width must be power of two");
+ checkPowerOfTwo(
+ blockSize.getHeight(), "block-size height must be power of two");
+
+ checkPowerOfTwo(
+ alignment.getWidth(), "alignment width must be power of two");
+ checkPowerOfTwo(
+ alignment.getHeight(), "alignment height must be power of two");
+
+ // update block-size and alignment
+ applyMacroBlockLimits(
+ Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
+ alignment.getWidth(), alignment.getHeight());
+
+ if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) {
+ // codec supports profiles that we don't know.
+ // Use supplied values clipped to platform limits
+ if (widths != null) {
+ mWidthRange = 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();
+ }
+
+ private void applyBlockLimits(
+ int blockWidth, int blockHeight,
+ Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
+ checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
+ checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
+
+ final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
+ final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
+
+ // factor will always be a power-of-2
+ int factor =
+ newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
+ if (factor != 1) {
+ mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
+ mBlocksPerSecondRange = Utils.factorRange(
+ mBlocksPerSecondRange, factor);
+ mBlockAspectRatioRange = Utils.scaleRange(
+ mBlockAspectRatioRange,
+ newBlockHeight / mBlockHeight,
+ newBlockWidth / mBlockWidth);
+ mHorizontalBlockRange = Utils.factorRange(
+ mHorizontalBlockRange, newBlockWidth / mBlockWidth);
+ mVerticalBlockRange = Utils.factorRange(
+ mVerticalBlockRange, newBlockHeight / mBlockHeight);
+ }
+ factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
+ if (factor != 1) {
+ counts = Utils.factorRange(counts, factor);
+ rates = Utils.factorRange(rates, factor);
+ ratios = Utils.scaleRange(
+ ratios, newBlockHeight / blockHeight,
+ newBlockWidth / blockWidth);
+ }
+ mBlockCountRange = mBlockCountRange.intersect(counts);
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
+ mBlockWidth = newBlockWidth;
+ mBlockHeight = newBlockHeight;
+ }
+
+ private void applyAlignment(int widthAlignment, int heightAlignment) {
+ checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
+ checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
+
+ if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
+ // maintain assumption that 0 < alignment <= block-size
+ applyBlockLimits(
+ Math.max(widthAlignment, mBlockWidth),
+ Math.max(heightAlignment, mBlockHeight),
+ POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+ }
+
+ mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+ mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+
+ mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+ mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+ }
+
+ private void updateLimits() {
+ // pixels -> blocks <- counts
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Utils.factorRange(mWidthRange, mBlockWidth));
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Range.create(
+ mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
+ mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Utils.factorRange(mHeightRange, mBlockHeight));
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Range.create(
+ mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
+ mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
+ mBlockCountRange = mBlockCountRange.intersect(
+ Range.create(
+ mHorizontalBlockRange.getLower()
+ * mVerticalBlockRange.getLower(),
+ mHorizontalBlockRange.getUpper()
+ * mVerticalBlockRange.getUpper()));
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ new Rational(
+ mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
+ new Rational(
+ mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
+
+ // blocks -> pixels
+ mWidthRange = mWidthRange.intersect(
+ (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
+ mHorizontalBlockRange.getUpper() * mBlockWidth);
+ mHeightRange = mHeightRange.intersect(
+ (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
+ mVerticalBlockRange.getUpper() * mBlockHeight);
+ mAspectRatioRange = mAspectRatioRange.intersect(
+ new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
+ new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
+
+ mSmallerDimensionUpperLimit = Math.min(
+ mSmallerDimensionUpperLimit,
+ Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
+
+ // blocks -> rate
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
+ mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
+ mFrameRateRange = mFrameRateRange.intersect(
+ (int)(mBlocksPerSecondRange.getLower()
+ / mBlockCountRange.getUpper()),
+ (int)(mBlocksPerSecondRange.getUpper()
+ / (double)mBlockCountRange.getLower()));
+ }
+
+ private void applyMacroBlockLimits(
+ int maxHorizontalBlocks, int maxVerticalBlocks,
+ int maxBlocks, long maxBlocksPerSecond,
+ int blockWidth, int blockHeight,
+ int widthAlignment, int heightAlignment) {
+ applyMacroBlockLimits(
+ 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */,
+ maxHorizontalBlocks, maxVerticalBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ blockWidth, blockHeight, widthAlignment, heightAlignment);
+ }
+
+ private void applyMacroBlockLimits(
+ int minHorizontalBlocks, int minVerticalBlocks,
+ int maxHorizontalBlocks, int maxVerticalBlocks,
+ int maxBlocks, long maxBlocksPerSecond,
+ int blockWidth, int blockHeight,
+ int widthAlignment, int heightAlignment) {
+ applyAlignment(widthAlignment, heightAlignment);
+ applyBlockLimits(
+ blockWidth, blockHeight, Range.create(1, maxBlocks),
+ Range.create(1L, maxBlocksPerSecond),
+ Range.create(
+ new Rational(1, maxVerticalBlocks),
+ new Rational(maxHorizontalBlocks, 1)));
+ mHorizontalBlockRange =
+ mHorizontalBlockRange.intersect(
+ Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)),
+ maxHorizontalBlocks / (mBlockWidth / blockWidth));
+ mVerticalBlockRange =
+ mVerticalBlockRange.intersect(
+ Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)),
+ maxVerticalBlocks / (mBlockHeight / blockHeight));
+ }
+
+ private void applyLevelLimits() {
+ long maxBlocksPerSecond = 0;
+ int maxBlocks = 0;
+ int maxBps = 0;
+ int maxDPBBlocks = 0;
+
+ int errors = ERROR_NONE_SUPPORTED;
+ CodecProfileLevel[] profileLevels = mParent.profileLevels;
+ String mime = mParent.getMimeType();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ maxDPBBlocks = 396;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, DPB = 0;
+ boolean supported = true;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.AVCLevel1:
+ MBPS = 1485; FS = 99; BR = 64; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel1b:
+ MBPS = 1485; FS = 99; BR = 128; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel11:
+ MBPS = 3000; FS = 396; BR = 192; DPB = 900; break;
+ case CodecProfileLevel.AVCLevel12:
+ MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel13:
+ MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel2:
+ MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel21:
+ MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break;
+ case CodecProfileLevel.AVCLevel22:
+ MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel3:
+ MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel31:
+ MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break;
+ case CodecProfileLevel.AVCLevel32:
+ MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break;
+ case CodecProfileLevel.AVCLevel4:
+ MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel41:
+ MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel42:
+ MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break;
+ case CodecProfileLevel.AVCLevel5:
+ MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break;
+ case CodecProfileLevel.AVCLevel51:
+ MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break;
+ case CodecProfileLevel.AVCLevel52:
+ MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
+ 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);
+ }
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG2ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG2LevelML:
+ FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG2ProfileMain:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG2LevelLL:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break;
+ case CodecProfileLevel.MPEG2LevelML:
+ FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break;
+ case CodecProfileLevel.MPEG2LevelH14:
+ FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break;
+ case CodecProfileLevel.MPEG2LevelHL:
+ FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break;
+ case CodecProfileLevel.MPEG2LevelHP:
+ FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG2Profile422:
+ case CodecProfileLevel.MPEG2ProfileSNR:
+ case CodecProfileLevel.MPEG2ProfileSpatial:
+ case CodecProfileLevel.MPEG2ProfileHigh:
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean strict = false; // true: W, H and FR are individual max limits
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG4ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ strict = true;
+ FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level0b:
+ strict = true;
+ FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level4a:
+ FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break;
+ case CodecProfileLevel.MPEG4Level5:
+ FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break;
+ case CodecProfileLevel.MPEG4Level6:
+ FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
+ case CodecProfileLevel.MPEG4Level3b:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break;
+ case CodecProfileLevel.MPEG4Level4:
+ FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
+ case CodecProfileLevel.MPEG4Level5:
+ FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileMain: // 2-4
+ case CodecProfileLevel.MPEG4ProfileNbit: // 2
+ case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCore: // 1-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
+ case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
+ case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
+
+ // Studio profiles are not supported by our codecs.
+
+ // Only profiles that can decode simple object types are considered.
+ // The following profiles are not able to.
+ case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
+ case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
+ case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ if (strict) {
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ } else {
+ // assuming max 60 fps frame rate and 1:2 aspect ratio
+ int maxDim = (int)Math.sqrt(FS * 2);
+ maxWidth = Math.max(maxDim, maxWidth);
+ maxHeight = Math.max(maxDim, maxHeight);
+ maxRate = Math.max(Math.max(FR, 60), maxRate);
+ }
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ int minWidth = maxWidth, minHeight = maxHeight;
+ int minAlignment = 16;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight;
+ boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF)
+ switch (profileLevel.level) {
+ case CodecProfileLevel.H263Level10:
+ strict = true; // only supports sQCIF & QCIF
+ FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level20:
+ strict = true; // only supports sQCIF, QCIF & CIF
+ FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break;
+ case CodecProfileLevel.H263Level30:
+ strict = true; // only supports sQCIF, QCIF & CIF
+ FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level40:
+ strict = true; // only supports sQCIF, QCIF & CIF
+ FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level45:
+ // only implies level 10 support
+ strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline
+ || profileLevel.profile ==
+ CodecProfileLevel.H263ProfileBackwardCompatible;
+ if (!strict) {
+ minW = 1; minH = 1; minAlignment = 4;
+ }
+ FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level50:
+ // only supports 50fps for H > 15
+ minW = 1; minH = 1; minAlignment = 4;
+ FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level60:
+ // only supports 50fps for H > 15
+ minW = 1; minH = 1; minAlignment = 4;
+ FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level70:
+ // only supports 50fps for H > 30
+ minW = 1; minH = 1; minAlignment = 4;
+ FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
+ + "/" + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.H263ProfileBackwardCompatible:
+ case CodecProfileLevel.H263ProfileBaseline:
+ case CodecProfileLevel.H263ProfileH320Coding:
+ case CodecProfileLevel.H263ProfileHighCompression:
+ case CodecProfileLevel.H263ProfileHighLatency:
+ case CodecProfileLevel.H263ProfileInterlace:
+ case CodecProfileLevel.H263ProfileInternet:
+ case CodecProfileLevel.H263ProfileISWV2:
+ case CodecProfileLevel.H263ProfileISWV3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (strict) {
+ // Strict levels define sub-QCIF min size and enumerated sizes. We cannot
+ // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities
+ // but we can express "only QCIF (& CIF)", so set minimume size at QCIF.
+ // minW = 8; minH = 6;
+ minW = 11; minH = 9;
+ } else {
+ // any support for non-strict levels (including unrecognized profiles or
+ // levels) allow custom frame size support beyond supported limits
+ // (other than bitrate)
+ mAllowMbOverride = true;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(W * H, maxBlocks);
+ maxBps = Math.max(BR * 64000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ minWidth = Math.min(minW, minWidth);
+ minHeight = Math.min(minH, minHeight);
+ }
+ // unless we encountered custom frame size support, limit size to QCIF and CIF
+ // using aspect ratio.
+ if (!mAllowMbOverride) {
+ mBlockAspectRatioRange =
+ Range.create(new Rational(11, 9), new Rational(11, 9));
+ }
+ applyMacroBlockLimits(
+ minWidth, minHeight,
+ maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ minAlignment /* widthAlignment */, minAlignment /* heightAlignment */);
+ mFrameRateRange = Range.create(1, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) {
+ maxBlocks = Integer.MAX_VALUE;
+ maxBlocksPerSecond = Integer.MAX_VALUE;
+
+ // TODO: set to 100Mbps for now, need a number for VP8
+ maxBps = 100000000;
+
+ // profile levels are not indicative for VPx, but verify
+ // them nonetheless
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ switch (profileLevel.level) {
+ case CodecProfileLevel.VP8Level_Version0:
+ case CodecProfileLevel.VP8Level_Version1:
+ case CodecProfileLevel.VP8Level_Version2:
+ case CodecProfileLevel.VP8Level_Version3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.VP8ProfileMain:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+
+ final int blockSize = 16;
+ applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
+ maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ maxBlocksPerSecond = 829440;
+ maxBlocks = 36864;
+ maxBps = 200000;
+ int maxDim = 512;
+
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ long SR = 0; // luma sample rate
+ int FS = 0; // luma picture size
+ int BR = 0; // bit rate kbps
+ int D = 0; // luma dimension
+ switch (profileLevel.level) {
+ case CodecProfileLevel.VP9Level1:
+ SR = 829440; FS = 36864; BR = 200; D = 512; break;
+ case CodecProfileLevel.VP9Level11:
+ SR = 2764800; FS = 73728; BR = 800; D = 768; break;
+ case CodecProfileLevel.VP9Level2:
+ SR = 4608000; FS = 122880; BR = 1800; D = 960; break;
+ case CodecProfileLevel.VP9Level21:
+ SR = 9216000; FS = 245760; BR = 3600; D = 1344; break;
+ case CodecProfileLevel.VP9Level3:
+ SR = 20736000; FS = 552960; BR = 7200; D = 2048; break;
+ case CodecProfileLevel.VP9Level31:
+ SR = 36864000; FS = 983040; BR = 12000; D = 2752; break;
+ case CodecProfileLevel.VP9Level4:
+ SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break;
+ case CodecProfileLevel.VP9Level41:
+ SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break;
+ case CodecProfileLevel.VP9Level5:
+ SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break;
+ case CodecProfileLevel.VP9Level51:
+ SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break;
+ case CodecProfileLevel.VP9Level52:
+ SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break;
+ case CodecProfileLevel.VP9Level6:
+ SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break;
+ case CodecProfileLevel.VP9Level61:
+ SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break;
+ case CodecProfileLevel.VP9Level62:
+ SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.VP9Profile0:
+ case CodecProfileLevel.VP9Profile1:
+ case CodecProfileLevel.VP9Profile2:
+ case CodecProfileLevel.VP9Profile3:
+ case CodecProfileLevel.VP9Profile2HDR:
+ case CodecProfileLevel.VP9Profile3HDR:
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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;
}
}
@@ -4551,288 +3842,6 @@
* 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;
- /** Variable bitrate mode */
- public static final int BITRATE_MODE_VBR = 1;
- /** Constant bitrate mode */
- public static final int BITRATE_MODE_CBR = 2;
- /** 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);
- }
-
- /* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf {
- private CodecCapabilities.CodecCapsLegacyImpl mParent;
-
- private Range<Integer> mQualityRange;
- private Range<Integer> mComplexityRange;
-
- public Range<Integer> getQualityRange() {
- return mQualityRange;
- }
-
- public Range<Integer> getComplexityRange() {
- return mComplexityRange;
- }
-
- 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;
- }
-
- public boolean isBitrateModeSupported(int mode) {
- for (Feature feat: bitrates) {
- if (mode == feat.mValue) {
- return (mBitControl & (1 << mode)) != 0;
- }
- }
- return false;
- }
-
- /* no public constructor */
- private EncoderCapsLegacyImpl() { }
-
- /** @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.");
- }
-
- EncoderCapsLegacyImpl caps = new EncoderCapsLegacyImpl();
- caps.init(info, parent);
- return caps;
- }
-
- 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);
- }
-
- private void applyLevelLimits() {
- String mime = mParent.getMimeType();
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
- mComplexityRange = Range.create(0, 8);
- mBitControl = (1 << BITRATE_MODE_CQ);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
- mBitControl = (1 << BITRATE_MODE_CBR);
- }
- }
-
- private int mBitControl;
- private Integer mDefaultComplexity;
- private Integer mDefaultQuality;
- private String mQualityScale;
-
- private void parseFromInfo(MediaFormat info) {
- Map<String, Object> map = info.getMap();
-
- if (info.containsKey("complexity-range")) {
- mComplexityRange = Utils
- .parseIntRange(info.getString("complexity-range"), mComplexityRange);
- // TODO should we limit this to level limits?
- }
- if (info.containsKey("quality-range")) {
- mQualityRange = Utils
- .parseIntRange(info.getString("quality-range"), mQualityRange);
- }
- if (info.containsKey("feature-bitrate-modes")) {
- 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");
- }
-
- 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;
- }
-
- /** @hide */
- public void getDefaultFormat(MediaFormat format) {
- // don't list trivial quality/complexity as default for now
- if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
- && mDefaultQuality != null) {
- format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
- }
- if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
- && mDefaultComplexity != null) {
- format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
- }
- // bitrates are listed in order of preference
- for (Feature feat: bitrates) {
- if ((mBitControl & (1 << feat.mValue)) != 0) {
- format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
- break;
- }
- }
- }
-
- /** @hide */
- public boolean supportsFormat(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- final String mime = mParent.getMimeType();
-
- Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
- if (mode != null && !isBitrateModeSupported(mode)) {
- return false;
- }
-
- Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
- if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
- Integer flacComplexity =
- (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
- if (complexity == null) {
- complexity = flacComplexity;
- } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
- throw new IllegalArgumentException(
- "conflicting values for complexity and " +
- "flac-compression-level");
- }
- }
-
- // other audio parameters
- Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
- if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
- Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
- if (profile == null) {
- profile = aacProfile;
- } else if (aacProfile != null && !aacProfile.equals(profile)) {
- throw new IllegalArgumentException(
- "conflicting values for profile and aac-profile");
- }
- }
-
- Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
-
- return supports(complexity, quality, profile);
- }
- }
-
- /* package private */ static final class EncoderCapsNativeImpl implements EncoderCapsIntf {
- private long mNativeContext; // accessed by native methods
-
- private Range<Integer> mQualityRange;
- private Range<Integer> mComplexityRange;
-
- /* no public constructor */
- private EncoderCapsNativeImpl() { }
-
- // Constructor called from native
- /* package private */ EncoderCapsNativeImpl(Range<Integer> qualityRange,
- Range<Integer> complexityRange) {
- mQualityRange = qualityRange;
- mComplexityRange = complexityRange;
- }
-
- public Range<Integer> getQualityRange() {
- return mQualityRange;
- }
-
- public Range<Integer> getComplexityRange() {
- return mComplexityRange;
- }
-
- public boolean isBitrateModeSupported(int mode) {
- return native_isBitrateModeSupported(mode);
- }
-
- // 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");
- }
-
- // 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");
- }
-
- private native boolean native_isBitrateModeSupported(int mode);
- private static native void native_init();
-
- 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.
*
@@ -4840,7 +3849,7 @@
* setting results in a better image quality and a lower compression ratio.
*/
public Range<Integer> getQualityRange() {
- return mImpl.getQualityRange();
+ return mQualityRange;
}
/**
@@ -4852,24 +3861,200 @@
* ratio. Use a lower value to save power and/or time.
*/
public Range<Integer> getComplexityRange() {
- return mImpl.getComplexityRange();
+ return mComplexityRange;
+ }
+
+ /** Constant quality mode */
+ public static final int BITRATE_MODE_CQ = 0;
+ /** Variable bitrate mode */
+ public static final int BITRATE_MODE_VBR = 1;
+ /** Constant bitrate mode */
+ public static final int BITRATE_MODE_CBR = 2;
+ /** Constant bitrate mode with frame drops */
+ public static final int BITRATE_MODE_CBR_FD = 3;
+
+ 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;
}
/**
* Query whether a bitrate mode is supported.
*/
public boolean isBitrateModeSupported(int mode) {
- return mImpl.isBitrateModeSupported(mode);
+ for (Feature feat: bitrates) {
+ if (mode == feat.mValue) {
+ return (mBitControl & (1 << mode)) != 0;
+ }
+ }
+ return false;
+ }
+
+ private Range<Integer> mQualityRange;
+ private Range<Integer> mComplexityRange;
+ private CodecCapabilities mParent;
+
+ /* no public constructor */
+ private EncoderCapabilities() { }
+
+ /** @hide */
+ public static EncoderCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ EncoderCapabilities caps = new EncoderCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ private void init(MediaFormat info, CodecCapabilities parent) {
+ // no support for complexity or quality yet
+ mParent = parent;
+ mComplexityRange = Range.create(0, 0);
+ mQualityRange = Range.create(0, 0);
+ mBitControl = (1 << BITRATE_MODE_VBR);
+
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ private void applyLevelLimits() {
+ String mime = mParent.getMimeType();
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ mComplexityRange = Range.create(0, 8);
+ mBitControl = (1 << BITRATE_MODE_CQ);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+ mBitControl = (1 << BITRATE_MODE_CBR);
+ }
+ }
+
+ private int mBitControl;
+ private Integer mDefaultComplexity;
+ private Integer mDefaultQuality;
+ private String mQualityScale;
+
+ private void parseFromInfo(MediaFormat info) {
+ Map<String, Object> map = info.getMap();
+
+ if (info.containsKey("complexity-range")) {
+ mComplexityRange = Utils
+ .parseIntRange(info.getString("complexity-range"), mComplexityRange);
+ // TODO should we limit this to level limits?
+ }
+ if (info.containsKey("quality-range")) {
+ mQualityRange = Utils
+ .parseIntRange(info.getString("quality-range"), mQualityRange);
+ }
+ if (info.containsKey("feature-bitrate-modes")) {
+ 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");
+ }
+
+ private boolean supports(
+ Integer complexity, Integer quality, Integer profile) {
+ boolean ok = true;
+ if (ok && complexity != null) {
+ ok = mComplexityRange.contains(complexity);
+ }
+ if (ok && quality != null) {
+ ok = mQualityRange.contains(quality);
+ }
+ if (ok && profile != null) {
+ for (CodecProfileLevel pl: mParent.profileLevels) {
+ if (pl.profile == profile) {
+ profile = null;
+ break;
+ }
+ }
+ ok = profile == null;
+ }
+ return ok;
}
/** @hide */
public void getDefaultFormat(MediaFormat format) {
- mImpl.getDefaultFormat(format);
+ // don't list trivial quality/complexity as default for now
+ if (!mQualityRange.getUpper().equals(mQualityRange.getLower())
+ && mDefaultQuality != null) {
+ format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality);
+ }
+ if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower())
+ && mDefaultComplexity != null) {
+ format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity);
+ }
+ // bitrates are listed in order of preference
+ for (Feature feat: bitrates) {
+ if ((mBitControl & (1 << feat.mValue)) != 0) {
+ format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue);
+ break;
+ }
+ }
}
/** @hide */
public boolean supportsFormat(MediaFormat format) {
- return mImpl.supportsFormat(format);
+ final Map<String, Object> map = format.getMap();
+ final String mime = mParent.getMimeType();
+
+ Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE);
+ if (mode != null && !isBitrateModeSupported(mode)) {
+ return false;
+ }
+
+ Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY);
+ if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) {
+ Integer flacComplexity =
+ (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL);
+ if (complexity == null) {
+ complexity = flacComplexity;
+ } else if (flacComplexity != null && !complexity.equals(flacComplexity)) {
+ throw new IllegalArgumentException(
+ "conflicting values for complexity and " +
+ "flac-compression-level");
+ }
+ }
+
+ // other audio parameters
+ Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE);
+ if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) {
+ Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE);
+ if (profile == null) {
+ profile = aacProfile;
+ } else if (aacProfile != null && !aacProfile.equals(profile)) {
+ throw new IllegalArgumentException(
+ "conflicting values for profile and aac-profile");
+ }
+ }
+
+ Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+ return supports(complexity, quality, profile);
}
};
@@ -5641,19 +4826,4 @@
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 af545d5..f09dc72 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -25,7 +25,6 @@
min_sdk_version: "",
srcs: [
- "android_media_CodecCapabilities.cpp",
"android_media_ImageWriter.cpp",
"android_media_ImageReader.cpp",
"android_media_JetPlayer.cpp",
@@ -65,7 +64,6 @@
"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 df0c826..0000000
--- 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 5cca0b5..0000000
--- 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 1790670..8419ce7 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 {
jclass capsClazz;
jmethodID capsCtorId;
- jclass cpasImplClazz;
- jmethodID capsImplCtorId;
jclass profileLevelClazz;
jfieldID profileField;
jfieldID levelField;
@@ -1000,12 +996,10 @@
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 @@
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 @@
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 3522b35..07866ac 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,114 +245,96 @@
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;
+ Vector<MediaCodecInfo::ProfileLevel> profileLevels;
+ Vector<uint32_t> colorFormats;
- sp<AMessage> defaultFormat = new AMessage();
- defaultFormat->setString("mime", typeStr);
+ 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;
- }
-
- jobject infoObj = NULL;
- if (ConvertMessageToMap(env, details, &infoObj)) {
- env->DeleteLocalRef(defaultFormatObj);
- return NULL;
- }
-
- jclass capsImplClazz = env->FindClass(
- "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl");
- CHECK(capsImplClazz != NULL);
-
- 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 MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(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;
- }
-
- jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
-
- for (size_t i = 0; i < colorFormats.size(); ++i) {
- jint val = colorFormats.itemAt(i);
- env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
- }
-
- jmethodID capsImplConstructID = env->GetMethodID(capsImplClazz, "<init>",
- "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
- "Ljava/util/Map;Ljava/util/Map;)V");
-
- jobject capsImpl = env->NewObject(capsImplClazz, capsImplConstructID,
- profileLevelArray, colorFormatsArray, isEncoder,
- defaultFormatObj, infoObj);
-
- jclass capsClazz = env->FindClass(
- "android/media/MediaCodecInfo$CodecCapabilities");
- CHECK(capsClazz != NULL);
-
- jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
- "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
-
- caps = env->NewObject(capsClazz, capsConstructID, capsImpl);
-
- env->DeleteLocalRef(profileLevelArray);
- profileLevelArray = NULL;
-
- env->DeleteLocalRef(colorFormatsArray);
- colorFormatsArray = NULL;
-
- env->DeleteLocalRef(defaultFormatObj);
- defaultFormatObj = NULL;
-
- env->DeleteLocalRef(infoObj);
- infoObj = NULL;
-
- env->DeleteLocalRef(capsImpl);
- capsImpl = 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;
}
+ 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;
+ }
+
+ jobject infoObj = NULL;
+ if (ConvertMessageToMap(env, details, &infoObj)) {
+ env->DeleteLocalRef(defaultFormatObj);
+ return NULL;
+ }
+
+ jclass capsClazz =
+ env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
+ CHECK(capsClazz != NULL);
+
+ 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 MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(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;
+ }
+
+ jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+
+ for (size_t i = 0; i < colorFormats.size(); ++i) {
+ jint val = colorFormats.itemAt(i);
+ env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
+ }
+
+ jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+ "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
+ "Ljava/util/Map;Ljava/util/Map;)V");
+
+ jobject caps = env->NewObject(capsClazz, capsConstructID,
+ profileLevelArray, colorFormatsArray, isEncoder,
+ defaultFormatObj, infoObj);
+
+ env->DeleteLocalRef(profileLevelArray);
+ profileLevelArray = NULL;
+
+ env->DeleteLocalRef(colorFormatsArray);
+ colorFormatsArray = NULL;
+
+ env->DeleteLocalRef(defaultFormatObj);
+ defaultFormatObj = 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 fcba150..d05ee55 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1465,7 +1465,6 @@
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 @@
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;
diff --git a/nfc/tests/src/android/nfc/NfcManagerTest.java b/nfc/tests/src/android/nfc/NfcManagerTest.java
new file mode 100644
index 0000000..06314cc
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcManagerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class NfcManagerTest {
+
+ private MockitoSession mMockitoSession;
+ private NfcManager mNfcManager;
+ @Mock
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(NfcAdapter.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ when(NfcAdapter.getNfcAdapter(any())).thenReturn(mock(NfcAdapter.class));
+ when(mContext.getApplicationContext()).thenReturn(mContext);
+ mNfcManager = new NfcManager(mContext);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testGetDefaultAdapter() {
+ NfcAdapter nfcAdapter = mNfcManager.getDefaultAdapter();
+ assertThat(nfcAdapter).isNotNull();
+ }
+}
diff --git a/nfc/tests/src/android/nfc/NfcRoutingTableEntryTest.java b/nfc/tests/src/android/nfc/NfcRoutingTableEntryTest.java
new file mode 100644
index 0000000..a90a716
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcRoutingTableEntryTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.nfc;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class NfcRoutingTableEntryTest {
+
+ @Test
+ public void testAidEntry_GetAid() {
+ String expectedAid = "A00000061A02";
+ RoutingTableAidEntry entry = new RoutingTableAidEntry(1, expectedAid, 0);
+
+ assertEquals(expectedAid, entry.getAid());
+ }
+
+ @Test
+ public void testProtocolEntry_GetProtocol() {
+ RoutingTableProtocolEntry entry =
+ new RoutingTableProtocolEntry(1, RoutingTableProtocolEntry.PROTOCOL_T1T, 0);
+
+ assertEquals(RoutingTableProtocolEntry.PROTOCOL_T1T, entry.getProtocol());
+ }
+
+ @Test
+ public void testSystemCodeEntry_GetSystemCode() {
+ byte[] expectedSystemCode = {0x01, 0x02, 0x03};
+ RoutingTableSystemCodeEntry entry =
+ new RoutingTableSystemCodeEntry(1, expectedSystemCode, 0);
+
+ assertArrayEquals(expectedSystemCode, entry.getSystemCode());
+ }
+
+ @Test
+ public void testTechnologyEntry_GetTechnology_A() {
+ RoutingTableTechnologyEntry entry =
+ new RoutingTableTechnologyEntry(1, RoutingTableTechnologyEntry.TECHNOLOGY_A, 0);
+
+ assertEquals(RoutingTableTechnologyEntry.TECHNOLOGY_A, entry.getTechnology());
+ }
+}
diff --git a/nfc/tests/src/android/nfc/OemLogItemsTest.java b/nfc/tests/src/android/nfc/OemLogItemsTest.java
new file mode 100644
index 0000000..21ef804
--- /dev/null
+++ b/nfc/tests/src/android/nfc/OemLogItemsTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.nfc;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Instant;
+
+@RunWith(JUnit4.class)
+public final class OemLogItemsTest {
+
+ @Test
+ public void testGetAction() {
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_RF_FIELD_STATE_CHANGED)
+ .build();
+ assertEquals(OemLogItems.LOG_ACTION_RF_FIELD_STATE_CHANGED, item.getAction());
+ }
+
+ @Test
+ public void testGetEvent() {
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_NFC_TOGGLE)
+ .setCallingEvent(OemLogItems.EVENT_ENABLE)
+ .build();
+ assertEquals(OemLogItems.EVENT_ENABLE, item.getEvent());
+ }
+
+ @Test
+ public void testGetCallingPid() {
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_NFC_TOGGLE)
+ .setCallingPid(1234)
+ .build();
+ assertEquals(1234, item.getCallingPid());
+ }
+
+ @Test
+ public void testGetCommandApdu() {
+ byte[] commandApdu = {0x01, 0x02, 0x03};
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_HCE_DATA)
+ .setApduCommand(commandApdu)
+ .build();
+ assertArrayEquals(commandApdu, item.getCommandApdu());
+ }
+
+ @Test
+ public void testGetResponseApdu() {
+ byte[] responseApdu = {0x04, 0x05, 0x06};
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_HCE_DATA)
+ .setApduResponse(responseApdu)
+ .build();
+ assertArrayEquals(responseApdu, item.getResponseApdu());
+ }
+
+ @Test
+ public void testGetRfFieldEventTimeMillis() {
+ Instant expectedTime = Instant.ofEpochSecond(1688768000, 123456789);
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_RF_FIELD_STATE_CHANGED)
+ .setRfFieldOnTime(expectedTime)
+ .build();
+ assertEquals(expectedTime, item.getRfFieldEventTimeMillis());
+ }
+
+ @Test
+ public void testGetTag() {
+ Tag mockTag = mock(Tag.class);
+ OemLogItems item = new OemLogItems.Builder(OemLogItems.LOG_ACTION_TAG_DETECTED)
+ .setTag(mockTag)
+ .build();
+ assertEquals(mockTag, item.getTag());
+ }
+}
diff --git a/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
new file mode 100644
index 0000000..a215835
--- /dev/null
+++ b/nfc/tests/src/android/nfc/cardemulation/CardemulationTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.nfc.cardemulation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.nfc.Constants;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class CardemulationTest {
+
+ private CardEmulation mCardEmulation;
+ @Mock
+ private Context mContext;
+ @Mock
+ private INfcCardEmulation mINfcCardEmulation;
+ @Mock
+ private NfcAdapter mNfcAdapter;
+ @Mock
+ private PackageManager mPackageManager;
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setUp() {
+ mMockitoSession = ExtendedMockito.mockitoSession()
+ .mockStatic(NfcAdapter.class)
+ .mockStatic(Settings.Secure.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION))
+ .thenReturn(true);
+ when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ assertThat(mNfcAdapter).isNotNull();
+ when(mNfcAdapter.getCardEmulationService()).thenReturn(mINfcCardEmulation);
+ when(mNfcAdapter.getContext()).thenReturn(mContext);
+ mCardEmulation = CardEmulation.getInstance(mNfcAdapter);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testIsDefaultServiceForCategory() throws RemoteException {
+ ComponentName componentName = mock(ComponentName.class);
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ when(mINfcCardEmulation.isDefaultServiceForCategory(1, componentName,
+ "payment")).thenReturn(true);
+ boolean result = mCardEmulation.isDefaultServiceForCategory(componentName,
+ "payment");
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation).isDefaultServiceForCategory(1, componentName,
+ "payment");
+
+ }
+
+ @Test
+ public void testIsDefaultServiceForAid() throws RemoteException {
+ ComponentName componentName = mock(ComponentName.class);
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ when(mINfcCardEmulation.isDefaultServiceForAid(1, componentName,
+ "payment")).thenReturn(true);
+ boolean result = mCardEmulation.isDefaultServiceForAid(componentName,
+ "payment");
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation).isDefaultServiceForAid(1, componentName,
+ "payment");
+ }
+
+ @Test
+ public void testCategoryAllowsForegroundPreference() throws Settings.SettingNotFoundException {
+ when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
+ RoleManager roleManager = mock(RoleManager.class);
+ when(roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)).thenReturn(false);
+ when(mContext.getSystemService(RoleManager.class)).thenReturn(roleManager);
+ ContentResolver contentResolver = mock(ContentResolver.class);
+ when(mContext.getContentResolver()).thenReturn(contentResolver);
+ when(Settings.Secure.getInt(contentResolver, Constants
+ .SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND)).thenReturn(1);
+ boolean result = mCardEmulation.categoryAllowsForegroundPreference("payment");
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void testGetSelectionModeForCategory() throws RemoteException {
+ when(mINfcCardEmulation.isDefaultPaymentRegistered()).thenReturn(true);
+ int result = mCardEmulation.getSelectionModeForCategory("payment");
+ assertThat(result).isEqualTo(0);
+ }
+
+ @Test
+ public void testSetShouldDefaultToObserveModeForService() throws RemoteException {
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ ComponentName componentName = mock(ComponentName.class);
+ when(mINfcCardEmulation.setShouldDefaultToObserveModeForService(1, componentName, true))
+ .thenReturn(true);
+ boolean result = mCardEmulation
+ .setShouldDefaultToObserveModeForService(componentName, true);
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation).setShouldDefaultToObserveModeForService(1, componentName, true);
+ }
+
+ @Test
+ public void testRegisterPollingLoopFilterForService()throws RemoteException {
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ ComponentName componentName = mock(ComponentName.class);
+ when(mINfcCardEmulation.registerPollingLoopFilterForService(anyInt(),
+ any(), anyString(), anyBoolean())).thenReturn(true);
+ boolean result = mCardEmulation.registerPollingLoopFilterForService(componentName,
+ "A0000000041010", true);
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation)
+ .registerPollingLoopFilterForService(anyInt(), any(), anyString(), anyBoolean());
+ }
+
+ @Test
+ public void testRemovePollingLoopFilterForService()throws RemoteException {
+ UserHandle userHandle = mock(UserHandle.class);
+ when(userHandle.getIdentifier()).thenReturn(1);
+ when(mContext.getUser()).thenReturn(userHandle);
+ ComponentName componentName = mock(ComponentName.class);
+ when(mINfcCardEmulation.removePollingLoopFilterForService(anyInt(), any(), anyString()))
+ .thenReturn(true);
+ boolean result = mCardEmulation
+ .removePollingLoopFilterForService(componentName, "A0000000041010");
+ assertThat(result).isTrue();
+ verify(mINfcCardEmulation).removePollingLoopFilterForService(anyInt(), any(), anyString());
+ }
+}
diff --git a/nfc/tests/src/android/nfc/dta/NfcDtaTest.java b/nfc/tests/src/android/nfc/dta/NfcDtaTest.java
new file mode 100644
index 0000000..38fb7ef
--- /dev/null
+++ b/nfc/tests/src/android/nfc/dta/NfcDtaTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.dta;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.nfc.INfcDta;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class NfcDtaTest {
+ private final String mServiceName = "serviceName";
+ private final int mServiceSap = 1;
+ private final int mMiu = 1;
+ private final int mRwSize = 1;
+ private final int mTestCaseId = 1;
+ @Mock
+ private NfcAdapter mMockNfcAdapter;
+ @Mock
+ private INfcDta mMockService;
+ @Mock
+ private Context mMockContext;
+
+ private NfcDta mNfcDta;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockNfcAdapter.getContext()).thenReturn(mMockContext);
+ when(mMockNfcAdapter.getNfcDtaInterface()).thenReturn(mMockService);
+
+ mNfcDta = NfcDta.getInstance(mMockNfcAdapter);
+ }
+
+ @Test
+ public void testEnableData() throws RemoteException {
+ assertTrue(mNfcDta.enableDta());
+ verify(mMockService).enableDta();
+ }
+
+ @Test
+ public void testEnableDataWithRemoteException() throws RemoteException {
+ doThrow(new RemoteException()).when(mMockService).enableDta();
+
+ assertFalse(mNfcDta.enableDta());
+ verify(mMockService).enableDta();
+ }
+
+ @Test
+ public void testDisableData() throws RemoteException {
+ assertTrue(mNfcDta.disableDta());
+ verify(mMockService).disableDta();
+ }
+
+ @Test
+ public void testDisableDataWithRemoteException() throws RemoteException {
+ doThrow(new RemoteException()).when(mMockService).disableDta();
+
+ assertFalse(mNfcDta.disableDta());
+ verify(mMockService).disableDta();
+ }
+
+ @Test
+ public void testEnableServer() throws RemoteException {
+ when(mMockService.enableServer(mServiceName, mServiceSap, mMiu, mRwSize,
+ mTestCaseId)).thenReturn(true);
+
+ mNfcDta.enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+ verify(mMockService).enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+ }
+
+ @Test
+ public void testEnableServerWithRemoteException() throws RemoteException {
+ doThrow(new RemoteException()).when(mMockService).enableServer(mServiceName, mServiceSap,
+ mMiu,
+ mRwSize, mTestCaseId);
+
+ mNfcDta.enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+ verify(mMockService).enableServer(mServiceName, mServiceSap, mMiu, mRwSize, mTestCaseId);
+ }
+
+ @Test
+ public void testDisableServer() throws RemoteException {
+ assertTrue(mNfcDta.disableServer());
+ verify(mMockService).disableServer();
+ }
+
+ @Test
+ public void testDisableServerWithRemoteException() throws RemoteException {
+ doThrow(new RemoteException()).when(mMockService).disableServer();
+
+ assertFalse(mNfcDta.disableServer());
+ verify(mMockService).disableServer();
+ }
+
+ @Test
+ public void testEnableClient() throws RemoteException {
+ when(mMockService.enableClient(mServiceName, mMiu, mRwSize, mTestCaseId)).thenReturn(true);
+
+ mNfcDta.enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+ verify(mMockService).enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+ }
+
+ @Test
+ public void testEnableClientWithRemoteException() throws RemoteException {
+ doThrow(new RemoteException()).when(mMockService).enableClient(mServiceName, mMiu, mRwSize,
+ mTestCaseId);
+
+ mNfcDta.enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+ verify(mMockService).enableClient(mServiceName, mMiu, mRwSize, mTestCaseId);
+ }
+
+ @Test
+ public void testDisableClient() throws RemoteException {
+ assertTrue(mNfcDta.disableClient());
+ verify(mMockService).disableClient();
+ }
+
+ @Test
+ public void testDisableClientWithRemoteException() throws RemoteException {
+ doThrow(new RemoteException()).when(mMockService).disableClient();
+
+ assertFalse(mNfcDta.disableClient());
+ verify(mMockService).disableClient();
+ }
+
+ @Test
+ public void testRegisterMessageService() throws RemoteException {
+ String msgServiceName = "sampleServiceName";
+ when(mMockService.registerMessageService(msgServiceName)).thenReturn(true);
+
+ mNfcDta.registerMessageService(msgServiceName);
+ verify(mMockService).registerMessageService(msgServiceName);
+ }
+
+ @Test
+ public void testRegisterMessageServiceWithRemoteException() throws RemoteException {
+ String msgServiceName = "sampleServiceName";
+ doThrow(new RemoteException()).when(mMockService).registerMessageService(msgServiceName);
+
+ assertFalse(mNfcDta.registerMessageService(msgServiceName));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testGetInstanceWithNullPointerException() {
+ NfcDta.getInstance(null);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testGetInstanceWithUnsupportedOperationExceptionForNfcAdapterContext() {
+ when(mMockNfcAdapter.getContext()).thenReturn(null);
+
+ NfcDta.getInstance(mMockNfcAdapter);
+ }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcBTest.java b/nfc/tests/src/android/nfc/tech/NfcBTest.java
new file mode 100644
index 0000000..98d6070
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcBTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcB.EXTRA_APPDATA;
+import static android.nfc.tech.NfcB.EXTRA_PROTINFO;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcBTest {
+ private final byte[] mSampleAppDate = new byte[] {1, 2, 3};
+ private final byte[] mSampleProtInfo = new byte[] {3, 2, 1};
+ @Mock
+ private Tag mMockTag;
+ @Mock
+ private Bundle mMockBundle;
+ @Mock
+ private INfcTag mMockTagService;
+ private NfcB mNfcB;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ when(mMockBundle.getByteArray(EXTRA_APPDATA)).thenReturn(mSampleAppDate);
+ when(mMockBundle.getByteArray(EXTRA_PROTINFO)).thenReturn(mSampleProtInfo);
+ when(mMockTag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+ mNfcB = new NfcB(mMockTag);
+ }
+
+ @Test
+ public void testGetApplicationData() {
+ assertNotNull(mNfcB.getApplicationData());
+ }
+
+ @Test
+ public void testGetProtocolInfo() {
+ assertNotNull(mNfcB.getProtocolInfo());
+ }
+
+ @Test
+ public void testGetNfcBInstance() {
+ Tag tag = mock(Tag.class);
+ when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(true);
+ when(tag.getTechExtras(TagTechnology.NFC_B)).thenReturn(mMockBundle);
+
+ assertNotNull(NfcB.get(tag));
+ verify(tag).hasTech(TagTechnology.NFC_B);
+ verify(tag).getTechExtras(TagTechnology.NFC_B);
+ }
+
+ @Test
+ public void testGetNfcBNullInstance() {
+ Tag tag = mock(Tag.class);
+ when(tag.hasTech(TagTechnology.NFC_B)).thenReturn(false);
+
+ assertNull(NfcB.get(tag));
+ verify(tag).hasTech(TagTechnology.NFC_B);
+ verify(tag, never()).getTechExtras(TagTechnology.NFC_B);
+ }
+
+
+ @Test
+ public void testTransceive() throws IOException, RemoteException {
+ byte[] sampleData = new byte[] {1, 2, 3, 4, 5};
+ TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+ when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_B);
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTag.getServiceHandle()).thenReturn(1);
+ when(mMockTagService.transceive(1, sampleData, true))
+ .thenReturn(mockTransceiveResult);
+ when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+ mNfcB.transceive(sampleData);
+ verify(mMockTag).getTagService();
+ verify(mMockTag).getServiceHandle();
+ }
+
+ @Test
+ public void testGetMaxTransceiveLength() throws RemoteException {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_B)).thenReturn(1);
+
+ mNfcB.getMaxTransceiveLength();
+ verify(mMockTag).getTagService();
+ }
+}
diff --git a/nfc/tests/src/android/nfc/tech/NfcFTest.java b/nfc/tests/src/android/nfc/tech/NfcFTest.java
new file mode 100644
index 0000000..31a6943
--- /dev/null
+++ b/nfc/tests/src/android/nfc/tech/NfcFTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.tech;
+
+import static android.nfc.tech.NfcF.EXTRA_PMM;
+import static android.nfc.tech.NfcF.EXTRA_SC;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.nfc.ErrorCodes;
+import android.nfc.INfcTag;
+import android.nfc.Tag;
+import android.nfc.TransceiveResult;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+
+public class NfcFTest {
+ private final byte[] mSampleSystemCode = new byte[] {1, 2, 3};
+ private final byte[] mSampleManufacturer = new byte[] {3, 2, 1};
+ @Mock
+ private Tag mMockTag;
+ @Mock
+ private INfcTag mMockTagService;
+ @Mock
+ private Bundle mMockBundle;
+ private NfcF mNfcF;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ when(mMockBundle.getByteArray(EXTRA_SC)).thenReturn(mSampleSystemCode);
+ when(mMockBundle.getByteArray(EXTRA_PMM)).thenReturn(mSampleManufacturer);
+ when(mMockTag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+
+ mNfcF = new NfcF(mMockTag);
+ }
+
+ @Test
+ public void testGetSystemCode() {
+ assertNotNull(mNfcF.getSystemCode());
+ }
+
+ @Test
+ public void testGetManufacturer() {
+ assertNotNull(mNfcF.getManufacturer());
+ }
+
+ @Test
+ public void testGetNfcFInstanceWithTech() {
+ Tag tag = mock(Tag.class);
+ when(tag.getTechExtras(TagTechnology.NFC_F)).thenReturn(mMockBundle);
+ when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(true);
+
+ assertNotNull(NfcF.get(tag));
+ verify(tag).getTechExtras(TagTechnology.NFC_F);
+ verify(tag).hasTech(TagTechnology.NFC_F);
+ }
+
+ @Test
+ public void testGetNfcFInstanceWithoutTech() {
+ Tag tag = mock(Tag.class);
+ when(tag.hasTech(TagTechnology.NFC_F)).thenReturn(false);
+
+ assertNull(NfcF.get(tag));
+ verify(tag).hasTech(TagTechnology.NFC_F);
+ verify(tag, never()).getTechExtras(TagTechnology.NFC_F);
+ }
+
+ @Test
+ public void testTransceive() throws IOException, RemoteException {
+ byte[] sampleData = new byte[]{1, 2, 3, 4, 5};
+ TransceiveResult mockTransceiveResult = mock(TransceiveResult.class);
+ when(mMockTag.getConnectedTechnology()).thenReturn(TagTechnology.NFC_F);
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTag.getServiceHandle()).thenReturn(1);
+ when(mMockTagService.transceive(1, sampleData, true))
+ .thenReturn(mockTransceiveResult);
+ when(mockTransceiveResult.getResponseOrThrow()).thenReturn(sampleData);
+
+ mNfcF.transceive(sampleData);
+ verify(mMockTag).getTagService();
+ verify(mMockTag).getServiceHandle();
+ }
+
+ @Test
+ public void testGetMaxTransceiveLength() throws RemoteException {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ when(mMockTagService.getMaxTransceiveLength(TagTechnology.NFC_F)).thenReturn(1);
+
+ mNfcF.getMaxTransceiveLength();
+ verify(mMockTag).getTagService();
+ }
+
+ @Test
+ public void testGetTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenReturn(2000);
+
+ assertEquals(2000, mNfcF.getTimeout());
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).getTimeout(TagTechnology.NFC_F);
+ } catch (Exception e) {
+ fail("Unexpected exception during valid getTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetTimeoutRemoteException() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.getTimeout(TagTechnology.NFC_F)).thenThrow(new RemoteException());
+
+ assertEquals(0, mNfcF.getTimeout());
+ } catch (Exception e) {
+ fail("Unexpected exception during RemoteException in getTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenReturn(
+ ErrorCodes.SUCCESS);
+
+ mNfcF.setTimeout(1000);
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+ } catch (Exception e) {
+ fail("Unexpected exception during valid setTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeoutInvalidTimeout() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_F, -1)).thenReturn(
+ ErrorCodes.ERROR_TIMEOUT);
+
+ assertThrows(IllegalArgumentException.class, () -> mNfcF.setTimeout(-1));
+ } catch (Exception e) {
+ fail("Unexpected exception during invalid setTimeout: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetTimeoutRemoteException() {
+ when(mMockTag.getTagService()).thenReturn(mMockTagService);
+ try {
+ when(mMockTagService.setTimeout(TagTechnology.NFC_F, 1000)).thenThrow(
+ new RemoteException());
+
+ mNfcF.setTimeout(1000);
+ verify(mMockTag).getTagService();
+ verify(mMockTagService).setTimeout(TagTechnology.NFC_F, 1000);
+ } catch (Exception e) {
+ fail("Unexpected exception during RemoteException in setTimeout: " + e.getMessage());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index c15cf34..6858e29 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -31,6 +31,7 @@
per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
+per-file GestureLauncherService.java = file:/INPUT_OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index 2ec1aed..5bd9c05 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -112,23 +112,10 @@
private final ActivityManagerService mService;
private final Handler mKillHandler;
- private static final int CGROUP_V1 = 0;
- private static final int CGROUP_V2 = 1;
- private static final String[] CGROUP_PATH_PREFIXES = {
- "/acct/uid_" /* cgroup v1 */,
- "/sys/fs/cgroup/uid_" /* cgroup v2 */
- };
- private static final String CGROUP_PID_PREFIX = "/pid_";
- private static final String CGROUP_PROCS = "/cgroup.procs";
-
- @VisibleForTesting
- int mCgroupVersion = CGROUP_V1;
-
PhantomProcessList(final ActivityManagerService service) {
mService = service;
mKillHandler = service.mProcessList.sKillHandler;
mInjector = new Injector();
- probeCgroupVersion();
}
@VisibleForTesting
@@ -157,9 +144,15 @@
final int appPid = app.getPid();
InputStream input = mCgroupProcsFds.get(appPid);
if (input == null) {
- final String path = getCgroupFilePath(app.info.uid, appPid);
+ String path = null;
try {
+ path = getCgroupFilePath(app.info.uid, appPid);
input = mInjector.openCgroupProcs(path);
+ } catch (IllegalArgumentException e) {
+ if (DEBUG_PROCESSES) {
+ Slog.w(TAG, "Unable to obtain cgroup.procs path ", e);
+ }
+ return;
} catch (FileNotFoundException | SecurityException e) {
if (DEBUG_PROCESSES) {
Slog.w(TAG, "Unable to open " + path, e);
@@ -207,18 +200,9 @@
}
}
- private void probeCgroupVersion() {
- for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) {
- if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) {
- mCgroupVersion = i;
- break;
- }
- }
- }
-
@VisibleForTesting
String getCgroupFilePath(int uid, int pid) {
- return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS;
+ return nativeGetCgroupProcsPath(uid, pid);
}
static String getProcessName(int pid) {
@@ -605,4 +589,7 @@
return PhantomProcessList.getProcessName(pid);
}
}
+
+ private static native String nativeGetCgroupProcsPath(int uid, int pid)
+ throws IllegalArgumentException;
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 4d5bce5..fedfe51 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -199,7 +199,9 @@
for (AudioPlaybackConfiguration apc : players) {
final VolumeShaper.Configuration volShaper =
mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
- fa.addFade(apc, /* skipRamp= */ false, volShaper);
+ if (volShaper != null) {
+ fa.addFade(apc, /* skipRamp= */ false, volShaper);
+ }
}
}
}
@@ -249,7 +251,7 @@
final VolumeShaper.Configuration volShaper =
mFadeConfigurations.getFadeOutVolumeShaperConfig(apc.getAudioAttributes());
final FadedOutApp fa = mUidToFadedAppsMap.get(apc.getClientUid());
- if (fa == null) {
+ if (fa == null || volShaper == null) {
return;
}
fa.addFade(apc, /* skipRamp= */ true, volShaper);
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index e80a86d..4fd026a 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -285,14 +285,20 @@
}
private void updateCacheFile(String cacheFilename, byte[] data) {
+ AtomicFile atomicCachedFile = null;
+ FileOutputStream fos = null;
try {
- final AtomicFile atomicCachedFile =
+ atomicCachedFile =
new AtomicFile(new File(mDataStoragePath, cacheFilename));
- final FileOutputStream fos = atomicCachedFile.startWrite();
+ fos = atomicCachedFile.startWrite();
fos.write(data);
atomicCachedFile.finishWrite(fos);
} catch (IOException e) {
Slog.e(TAG, "Failed to write current data to cached file", e);
+ if (fos != null) {
+ atomicCachedFile.failWrite(fos);
+ }
+ return;
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 2c03902..97ae6be 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -79,6 +79,7 @@
":lib_oomConnection_native",
":lib_anrTimer_native",
":lib_lazilyRegisteredServices_native",
+ ":lib_phantomProcessList_native",
],
include_dirs: [
@@ -264,3 +265,10 @@
"com_android_server_vr_VrManagerService.cpp",
],
}
+
+filegroup {
+ name: "lib_phantomProcessList_native",
+ srcs: [
+ "com_android_server_am_PhantomProcessList.cpp",
+ ],
+}
diff --git a/services/core/jni/com_android_server_am_PhantomProcessList.cpp b/services/core/jni/com_android_server_am_PhantomProcessList.cpp
new file mode 100644
index 0000000..0c5e6d8
--- /dev/null
+++ b/services/core/jni/com_android_server_am_PhantomProcessList.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <processgroup/processgroup.h>
+
+namespace android {
+namespace {
+
+jstring getCgroupProcsPath(JNIEnv* env, jobject clazz, jint uid, jint pid) {
+ if (uid < 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "uid is negative: %d", uid);
+ return nullptr;
+ }
+
+ std::string path;
+ if (!CgroupGetAttributePathForProcess("CgroupProcs", uid, pid, path)) {
+ path.clear();
+ }
+
+ return env->NewStringUTF(path.c_str());
+}
+
+const JNINativeMethod sMethods[] = {
+ {"nativeGetCgroupProcsPath", "(II)Ljava/lang/String;", (void*)getCgroupProcsPath},
+};
+
+} // anonymous namespace
+
+int register_android_server_am_PhantomProcessList(JNIEnv* env) {
+ const char* className = "com/android/server/am/PhantomProcessList";
+ return jniRegisterNativeMethods(env, className, sMethods, NELEM(sMethods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df37ec3..a12eb31 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -70,6 +70,7 @@
int register_com_android_server_SystemClockTime(JNIEnv* env);
int register_android_server_display_smallAreaDetectionController(JNIEnv* env);
int register_com_android_server_accessibility_BrailleDisplayConnection(JNIEnv* env);
+int register_android_server_am_PhantomProcessList(JNIEnv* env);
// Note: Consider adding new JNI entrypoints for optional services to
// LazyJniRegistrar instead, and relying on lazy registration.
@@ -135,5 +136,6 @@
register_com_android_server_SystemClockTime(env);
register_android_server_display_smallAreaDetectionController(env);
register_com_android_server_accessibility_BrailleDisplayConnection(env);
+ register_android_server_am_PhantomProcessList(env);
return JNI_VERSION_1_4;
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index d2c91ff..232bb83 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -286,14 +286,21 @@
return@forEach
}
var newFlags = oldFlags
+ val isSystemOrInstalled =
+ packageState.isSystem || packageState.getUserStateOrDefault(userId).isInstalled
newFlags =
if (
- newFlags.hasBits(PermissionFlags.ROLE) ||
- newFlags.hasBits(PermissionFlags.PREGRANT)
+ isSystemOrInstalled && (
+ newFlags.hasBits(PermissionFlags.ROLE) ||
+ newFlags.hasBits(PermissionFlags.PREGRANT)
+ )
) {
newFlags or PermissionFlags.RUNTIME_GRANTED
} else {
- newFlags andInv PermissionFlags.RUNTIME_GRANTED
+ newFlags andInv (
+ PermissionFlags.RUNTIME_GRANTED or PermissionFlags.ROLE or
+ PermissionFlags.PREGRANT
+ )
}
newFlags = newFlags andInv USER_SETTABLE_MASK
if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
index 1237095..8b357862d 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt
@@ -72,7 +72,8 @@
} else {
mockPackageState(
APP_ID_1,
- mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0))
+ mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)),
+ true
)
}
setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags)
diff --git a/services/tests/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
index 94d4b95..03bd73c 100644
--- a/services/tests/mockingservicestests/jni/Android.bp
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -24,6 +24,7 @@
":lib_freezer_native",
":lib_oomConnection_native",
":lib_lazilyRegisteredServices_native",
+ ":lib_phantomProcessList_native",
"onload.cpp",
],
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
index 9b4c817..30fa7de 100644
--- a/services/tests/mockingservicestests/jni/onload.cpp
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -28,6 +28,7 @@
int register_android_server_am_Freezer(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_utils_LazyJniRegistrar(JNIEnv* env);
+int register_android_server_am_PhantomProcessList(JNIEnv* env);
};
using namespace android;
@@ -46,5 +47,6 @@
register_android_server_am_Freezer(env);
register_android_server_am_OomConnection(env);
register_android_server_utils_LazyJniRegistrar(env);
+ register_android_server_am_PhantomProcessList(env);
return JNI_VERSION_1_4;
}
diff --git a/services/tests/servicestests/src/com/android/server/OWNERS b/services/tests/servicestests/src/com/android/server/OWNERS
index d8a9400..69feb1d 100644
--- a/services/tests/servicestests/src/com/android/server/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/OWNERS
@@ -6,5 +6,6 @@
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
per-file BatteryServiceTest.java = file:platform/hardware/interfaces:/health/OWNERS
per-file GestureLauncherServiceTest.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
+per-file GestureLauncherServiceTest.java = file:/INPUT_OWNERS
per-file PinnerServiceTest.java = file:/apct-tests/perftests/OWNERS
per-file SecurityStateTest.java = file:/SECURITY_STATE_OWNERS