summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Songyue Han <songyueh@google.com> 2024-05-15 18:24:37 +0000
committer Songyue Han <songyueh@google.com> 2024-12-18 22:57:40 +0000
commit736e038e23b2e5dafc92149d297d451b948ce72e (patch)
treee7fcb8257386bb8f17a9fbb9737d12710c8ebb9b
parentb58417cc4992e791531d3f063062e5b9931d3d26 (diff)
Java and JNI support for native CodecCapabilities.
Bug: b/306023029 Test: atest MediaCodecCapabilitiesTest Change-Id: I34c10bf742bd9094c8fe35583bef6c63aa123167
-rw-r--r--media/java/android/media/MediaCodecInfo.java1099
-rw-r--r--media/jni/android_media_CodecCapabilities.cpp610
-rw-r--r--media/jni/android_media_CodecCapabilities.h47
-rw-r--r--media/jni/android_media_MediaCodec.cpp39
-rw-r--r--media/jni/android_media_MediaCodecList.cpp154
5 files changed, 1445 insertions, 504 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 06d428212c34..69b26fd9e073 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -244,12 +244,7 @@ public final class MediaCodecInfo {
* {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
*/
public static final class CodecCapabilities {
- public CodecCapabilities() {
- }
-
- // CLASSIFICATION
- private String mMime;
- private int mMaxSupportedInstances;
+ private static final String TAG = "CodecCapabilities";
// LEGACY FIELDS
@@ -627,12 +622,6 @@ public final class MediaCodecInfo {
*/
public int[] colorFormats; // NOTE this array is modifiable by user
- // FEATURES
-
- private int mFlagsSupported;
- private int mFlagsRequired;
- private int mFlagsVerified;
-
/**
* <b>video decoder only</b>: codec supports seamless resolution changes.
*/
@@ -822,122 +811,680 @@ public final class MediaCodecInfo {
@FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
public static final String FEATURE_DetachedSurface = "detached-surface";
- /**
- * Query codec feature capabilities.
- * <p>
- * These features are supported to be used by the codec. These
- * include optional features that can be turned on, as well as
- * features that are always on.
- */
- public final boolean isFeatureSupported(String name) {
- return checkFeature(name, mFlagsSupported);
- }
+ /** package private */ interface CodecCapsIntf {
+ public CodecCapsIntf dup();
- /**
- * Query codec feature requirements.
- * <p>
- * These features are required to be used by the codec, and as such,
- * they are always turned on.
- */
- public final boolean isFeatureRequired(String name) {
- return checkFeature(name, mFlagsRequired);
+ 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();
}
- // 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));
+ /* 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;
+ }
}
- if (GetFlag(() -> android.media.codec.Flags.nullOutputSurface())) {
- features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true));
+ }
+
+ /** @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;
+ }
- // feature to exclude codec from REGULAR codec list
- features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true));
+ private Feature[] getValidFeatures() {
+ return FeatureList.getFeatures(isEncoder());
+ }
- return features.toArray(new Feature[0]);
- };
+ private boolean checkFeature(String name, int flags) {
+ for (Feature feat: getValidFeatures()) {
+ if (feat.mName.equals(name)) {
+ return (flags & feat.mValue) != 0;
+ }
+ }
+ return false;
+ }
- private static Feature[] decoderFeatures = getDecoderFeatures();
+ public boolean isRegular() {
+ // regular codecs only require default features
+ for (Feature feat: getValidFeatures()) {
+ if (!feat.mDefault && isFeatureRequired(feat.mName)) {
+ return false;
+ }
+ }
+ return true;
+ }
- private static Feature[] getEncoderFeatures() {
- ArrayList<Feature> features = new ArrayList();
+ public final boolean isFormatSupported(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ final String mime = (String) map.get(MediaFormat.KEY_MIME);
- 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));
+ // mime must match if present
+ if (mime != null && !mMime.equalsIgnoreCase(mime)) {
+ return false;
}
- if (GetFlag(() -> android.media.codec.Flags.regionOfInterest())) {
- features.add(new Feature(FEATURE_Roi, (1 << 7), true));
+
+ // 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;
+ }
}
- // feature to exclude codec from REGULAR codec list
- features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true));
+ Integer profile = (Integer) map.get(MediaFormat.KEY_PROFILE);
+ Integer level = (Integer) map.get(MediaFormat.KEY_LEVEL);
- return features.toArray(new Feature[0]);
- };
+ 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;
- private static Feature[] encoderFeatures = getEncoderFeatures();
+ // 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);
- public static Feature[] getFeatures(boolean isEncoder) {
- if (isEncoder) {
- return encoderFeatures;
- } else {
- return decoderFeatures;
+ 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;
}
- }
- /** @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;
+ 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;
}
- return res;
- }
- private Feature[] getValidFeatures() {
- return FeatureList.getFeatures(isEncoder());
+ 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
+ }
+ }
}
- private boolean checkFeature(String name, int flags) {
- for (Feature feat: getValidFeatures()) {
- if (feat.mName.equals(name)) {
- return (flags & feat.mValue) != 0;
+ /* 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();
}
- return false;
+ }
+
+ 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>
+ * These features are supported to be used by the codec. These
+ * include optional features that can be turned on, as well as
+ * features that are always on.
+ */
+ public final boolean isFeatureSupported(String name) {
+ return mImpl.isFeatureSupported(name);
+ }
+
+ /**
+ * Query codec feature requirements.
+ * <p>
+ * These features are required to be used by the codec, and as such,
+ * they are always turned on.
+ */
+ public final boolean isFeatureRequired(String name) {
+ return mImpl.isFeatureRequired(name);
}
/** @hide */
public boolean isRegular() {
- // regular codecs only require default features
- for (Feature feat: getValidFeatures()) {
- if (!feat.mDefault && isFeatureRequired(feat.mName)) {
- return false;
- }
- }
- return true;
+ return mImpl.isRegular();
}
/**
@@ -1046,201 +1593,22 @@ public final class MediaCodecInfo {
* and feature requests.
*/
public final boolean isFormatSupported(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- final String mime = (String)map.get(MediaFormat.KEY_MIME);
-
- // mime must match if present
- if (mime != null && !mMime.equalsIgnoreCase(mime)) {
- return false;
- }
-
- // check feature support
- for (Feature feat: getValidFeatures()) {
- 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.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: 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;
+ return mImpl.isFormatSupported(format);
}
- // 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 mDefaultFormat;
+ return mImpl.getDefaultFormat();
}
/**
* Returns the mime type for which this codec-capability object was created.
*/
public String getMimeType() {
- return mMime;
+ return mImpl.getMimeType();
}
/**
@@ -1252,157 +1620,28 @@ public final class MediaCodecInfo {
* resources at time of use.
*/
public int getMaxSupportedInstances() {
- return mMaxSupportedInstances;
- }
-
- private boolean isAudio() {
- return mAudioCaps != null;
+ return mImpl.getMaxSupportedInstances();
}
/**
* Returns the audio capabilities or {@code null} if this is not an audio codec.
*/
public AudioCapabilities getAudioCapabilities() {
- return mAudioCaps;
- }
-
- private boolean isEncoder() {
- return mEncoderCaps != null;
+ return mImpl.getAudioCapabilities();
}
/**
* Returns the encoding capabilities or {@code null} if this is not an encoder.
*/
public EncoderCapabilities getEncoderCapabilities() {
- return mEncoderCaps;
- }
-
- private boolean isVideo() {
- return mVideoCaps != null;
+ return mImpl.getEncoderCapabilities();
}
/**
* Returns the video capabilities or {@code null} if this is not a video codec.
*/
public VideoCapabilities 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.VideoCapsLegacyImpl.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
- }
+ return mImpl.getVideoCapabilities();
}
}
@@ -1433,7 +1672,7 @@ public final class MediaCodecInfo {
}
/* package private */ static final class AudioCapsLegacyImpl implements AudioCapsIntf {
- private CodecCapabilities mParent;
+ private CodecCapabilities.CodecCapsLegacyImpl mParent;
private Range<Integer> mBitrateRange;
private int[] mSampleRates;
@@ -1485,7 +1724,7 @@ public final class MediaCodecInfo {
private AudioCapsLegacyImpl() { }
public static AudioCapsLegacyImpl create(
- MediaFormat info, CodecCapabilities parent) {
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
Log.d(TAG, "Legacy implementation is called while native flag is on.");
}
@@ -1495,7 +1734,7 @@ public final class MediaCodecInfo {
return caps;
}
- private void init(MediaFormat info, CodecCapabilities parent) {
+ private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
mParent = parent;
initWithPlatformLimits();
applyLevelLimits();
@@ -1576,7 +1815,7 @@ public final class MediaCodecInfo {
int[] sampleRates = null;
Range<Integer> sampleRateRange = null, bitRates = null;
int maxChannels = MAX_INPUT_CHANNEL_COUNT;
- CodecProfileLevel[] profileLevels = mParent.profileLevels;
+ CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
String mime = mParent.getMimeType();
if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
@@ -1811,7 +2050,7 @@ public final class MediaCodecInfo {
return false;
}
- if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
+ if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
return false;
}
@@ -1903,7 +2142,7 @@ public final class MediaCodecInfo {
/** @hide */
public static AudioCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
AudioCapsLegacyImpl impl = AudioCapsLegacyImpl.create(info, parent);
AudioCapabilities caps = new AudioCapabilities(impl);
return caps;
@@ -2492,7 +2731,7 @@ public final class MediaCodecInfo {
MediaFormat.KEY_BIT_RATE,
MediaFormat.KEY_MIME);
- private CodecCapabilities mParent;
+ private CodecCapabilities.CodecCapsLegacyImpl mParent;
private Range<Integer> mBitrateRange;
private Range<Integer> mHeightRange;
@@ -2520,7 +2759,7 @@ public final class MediaCodecInfo {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static VideoCapsLegacyImpl create(
- MediaFormat info, CodecCapabilities parent) {
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
Log.d(TAG, "Legacy implementation is called while native flag is on.");
}
@@ -2530,7 +2769,7 @@ public final class MediaCodecInfo {
return caps;
}
- private void init(MediaFormat info, CodecCapabilities parent) {
+ private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
mParent = parent;
initWithPlatformLimits();
applyLevelLimits();
@@ -2765,7 +3004,7 @@ public final class MediaCodecInfo {
return false;
}
- if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) {
+ if (!CodecCapabilities.CodecCapsLegacyImpl.supportsBitrate(mBitrateRange, format)) {
return false;
}
@@ -3280,7 +3519,7 @@ public final class MediaCodecInfo {
int maxDPBBlocks = 0;
int errors = ERROR_NONE_SUPPORTED;
- CodecProfileLevel[] profileLevels = mParent.profileLevels;
+ CodecProfileLevel[] profileLevels = mParent.getProfileLevels();
String mime = mParent.getMimeType();
if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
@@ -4087,7 +4326,7 @@ public final class MediaCodecInfo {
/** @hide */
public static VideoCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
VideoCapsLegacyImpl impl = VideoCapsLegacyImpl.create(info, parent);
VideoCapabilities caps = new VideoCapabilities(impl);
return caps;
@@ -4336,7 +4575,7 @@ public final class MediaCodecInfo {
}
/* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf {
- private CodecCapabilities mParent;
+ private CodecCapabilities.CodecCapsLegacyImpl mParent;
private Range<Integer> mQualityRange;
private Range<Integer> mComplexityRange;
@@ -4379,7 +4618,7 @@ public final class MediaCodecInfo {
/** @hide */
public static EncoderCapsLegacyImpl create(
- MediaFormat info, CodecCapabilities parent) {
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) {
Log.d(TAG, "Legacy implementation is called while native flag is on.");
}
@@ -4389,7 +4628,7 @@ public final class MediaCodecInfo {
return caps;
}
- private void init(MediaFormat info, CodecCapabilities parent) {
+ private void init(MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
// no support for complexity or quality yet
mParent = parent;
mComplexityRange = Range.create(0, 0);
@@ -4459,7 +4698,7 @@ public final class MediaCodecInfo {
ok = mQualityRange.contains(quality);
}
if (ok && profile != null) {
- for (CodecProfileLevel pl: mParent.profileLevels) {
+ for (CodecProfileLevel pl: mParent.getProfileLevels()) {
if (pl.profile == profile) {
profile = null;
break;
@@ -4584,7 +4823,7 @@ public final class MediaCodecInfo {
/** @hide */
public static EncoderCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
+ MediaFormat info, CodecCapabilities.CodecCapsLegacyImpl parent) {
EncoderCapsLegacyImpl impl = EncoderCapsLegacyImpl.create(info, parent);
EncoderCapabilities caps = new EncoderCapabilities(impl);
return caps;
diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp
index 29695d60eeb2..df0c826d8d87 100644
--- a/media/jni/android_media_CodecCapabilities.cpp
+++ b/media/jni/android_media_CodecCapabilities.cpp
@@ -17,14 +17,20 @@
//#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 {
@@ -32,9 +38,59 @@ 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) {
@@ -55,6 +111,12 @@ static EncoderCapabilities* getEncoderCapabilities(JNIEnv *env, jobject thiz) {
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) {
@@ -76,8 +138,125 @@ static jobject convertToJavaDoubleRange(JNIEnv *env, const Range<double>& range)
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) {
@@ -117,6 +296,260 @@ static VideoCapabilities::PerformancePoint convertToNativePerformancePoint(
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
// ----------------------------------------------------------------------------
@@ -342,6 +775,167 @@ static jboolean android_media_EncoderCapabilities_isBitrateModeSupported(JNIEnv
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[] = {
@@ -372,6 +966,19 @@ static const JNINativeMethod gEncoderCapsMethods[] = {
{"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",
@@ -401,5 +1008,8 @@ int register_android_media_CodecCapabilities(JNIEnv *env) {
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
new file mode 100644
index 000000000000..5cca0b503740
--- /dev/null
+++ b/media/jni/android_media_CodecCapabilities.h
@@ -0,0 +1,47 @@
+/*
+ * 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 fc184fe5c872..695361e60622 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,12 +16,14 @@
//#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"
@@ -138,6 +140,8 @@ static struct {
static struct {
jclass capsClazz;
jmethodID capsCtorId;
+ jclass cpasImplClazz;
+ jmethodID capsImplCtorId;
jclass profileLevelClazz;
jfieldID profileField;
jfieldID levelField;
@@ -996,10 +1000,12 @@ static jobject getCodecCapabilitiesObject(
env->SetIntArrayRegion(colorFormatsArray.get(), i, 1, &val);
}
- return env->NewObject(
- gCodecInfo.capsClazz, gCodecInfo.capsCtorId,
+ jobject javaCodecCapsImpl = env->NewObject(
+ gCodecInfo.cpasImplClazz, gCodecInfo.capsImplCtorId,
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 {
@@ -1027,11 +1033,18 @@ status_t JMediaCodec::getCodecInfo(JNIEnv *env, jobject *codecInfoObject) const
env->NewObjectArray(mediaTypes.size(), gCodecInfo.capsClazz, NULL));
for (size_t i = 0; i < mediaTypes.size(); i++) {
- const sp<MediaCodecInfo::Capabilities> caps =
- codecInfo->getCapabilitiesFor(mediaTypes[i].c_str());
-
- ScopedLocalRef<jobject> capsObj(env, getCodecCapabilitiesObject(
- env, mediaTypes[i].c_str(), isEncoder, caps));
+ 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);
env->SetObjectArrayElement(capsArrayObj.get(), i, capsObj.get());
}
@@ -3877,10 +3890,20 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
gCodecInfo.capsClazz = (jclass)env->NewGlobalRef(clazz.get());
method = env->GetMethodID(clazz.get(), "<init>",
+ "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
+ CHECK(method != NULL);
+ gCodecInfo.capsCtorId = method;
+
+ clazz.reset(env->FindClass(
+ "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl"));
+ CHECK(clazz.get() != NULL);
+ gCodecInfo.cpasImplClazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ method = env->GetMethodID(clazz.get(), "<init>",
"([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
"Ljava/util/Map;Ljava/util/Map;)V");
CHECK(method != NULL);
- gCodecInfo.capsCtorId = method;
+ gCodecInfo.capsImplCtorId = 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 07866ac34e4c..3522b35539ab 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -16,6 +16,9 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaCodec-JNI"
+
+#include <android_media_codec.h>
+
#include <utils/Log.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -32,6 +35,7 @@
#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;
@@ -245,95 +249,113 @@ static jobject android_media_MediaCodecList_getCodecCapabilities(
return NULL;
}
- Vector<MediaCodecInfo::ProfileLevel> profileLevels;
- Vector<uint32_t> colorFormats;
+ jobject caps;
+ if (android::media::codec::provider_->native_capabilites()) {
+ std::shared_ptr<CodecCapabilities> codecCaps = info.info->getCodecCapsFor(typeStr);
+ caps = android::convertToJavaCodecCapabiliites(env, codecCaps);
+ } else {
+ Vector<MediaCodecInfo::ProfileLevel> profileLevels;
+ Vector<uint32_t> colorFormats;
+
+ sp<AMessage> defaultFormat = new AMessage();
+ defaultFormat->setString("mime", typeStr);
+
+ // TODO query default-format also from codec/codec list
+ const sp<MediaCodecInfo::Capabilities> &capabilities =
+ info.info->getCapabilitiesFor(typeStr);
+ env->ReleaseStringUTFChars(type, typeStr);
+ typeStr = NULL;
+ if (capabilities == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return NULL;
+ }
- sp<AMessage> defaultFormat = new AMessage();
- defaultFormat->setString("mime", typeStr);
+ capabilities->getSupportedColorFormats(&colorFormats);
+ capabilities->getSupportedProfileLevels(&profileLevels);
+ sp<AMessage> details = capabilities->getDetails();
+ bool isEncoder = info.info->isEncoder();
- // 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;
- }
+ jobject defaultFormatObj = NULL;
+ if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
+ return NULL;
+ }
- capabilities->getSupportedColorFormats(&colorFormats);
- capabilities->getSupportedProfileLevels(&profileLevels);
- sp<AMessage> details = capabilities->getDetails();
- bool isEncoder = info.info->isEncoder();
+ jobject infoObj = NULL;
+ if (ConvertMessageToMap(env, details, &infoObj)) {
+ env->DeleteLocalRef(defaultFormatObj);
+ return NULL;
+ }
- jobject defaultFormatObj = NULL;
- if (ConvertMessageToMap(env, defaultFormat, &defaultFormatObj)) {
- return NULL;
- }
+ jclass capsImplClazz = env->FindClass(
+ "android/media/MediaCodecInfo$CodecCapabilities$CodecCapsLegacyImpl");
+ CHECK(capsImplClazz != NULL);
- jobject infoObj = NULL;
- if (ConvertMessageToMap(env, details, &infoObj)) {
- env->DeleteLocalRef(defaultFormatObj);
- return NULL;
- }
+ jclass profileLevelClazz =
+ env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
+ CHECK(profileLevelClazz != NULL);
- jclass capsClazz =
- env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
- CHECK(capsClazz != NULL);
+ jobjectArray profileLevelArray =
+ env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
- jclass profileLevelClazz =
- env->FindClass("android/media/MediaCodecInfo$CodecProfileLevel");
- CHECK(profileLevelClazz != NULL);
+ jfieldID profileField =
+ env->GetFieldID(profileLevelClazz, "profile", "I");
- jobjectArray profileLevelArray =
- env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
+ jfieldID levelField =
+ env->GetFieldID(profileLevelClazz, "level", "I");
- jfieldID profileField =
- env->GetFieldID(profileLevelClazz, "profile", "I");
+ for (size_t i = 0; i < profileLevels.size(); ++i) {
+ const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
- jfieldID levelField =
- env->GetFieldID(profileLevelClazz, "level", "I");
+ jobject profileLevelObj = env->AllocObject(profileLevelClazz);
- for (size_t i = 0; i < profileLevels.size(); ++i) {
- const MediaCodecInfo::ProfileLevel &src = profileLevels.itemAt(i);
+ env->SetIntField(profileLevelObj, profileField, src.mProfile);
+ env->SetIntField(profileLevelObj, levelField, src.mLevel);
- jobject profileLevelObj = env->AllocObject(profileLevelClazz);
+ env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
- env->SetIntField(profileLevelObj, profileField, src.mProfile);
- env->SetIntField(profileLevelObj, levelField, src.mLevel);
+ env->DeleteLocalRef(profileLevelObj);
+ profileLevelObj = NULL;
+ }
- env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj);
+ jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
- env->DeleteLocalRef(profileLevelObj);
- profileLevelObj = NULL;
- }
+ for (size_t i = 0; i < colorFormats.size(); ++i) {
+ jint val = colorFormats.itemAt(i);
+ env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
+ }
- jintArray colorFormatsArray = env->NewIntArray(colorFormats.size());
+ jmethodID capsImplConstructID = env->GetMethodID(capsImplClazz, "<init>",
+ "([Landroid/media/MediaCodecInfo$CodecProfileLevel;[IZ"
+ "Ljava/util/Map;Ljava/util/Map;)V");
- for (size_t i = 0; i < colorFormats.size(); ++i) {
- jint val = colorFormats.itemAt(i);
- env->SetIntArrayRegion(colorFormatsArray, i, 1, &val);
- }
+ 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$CodecProfileLevel;[IZ"
- "Ljava/util/Map;Ljava/util/Map;)V");
+ jmethodID capsConstructID = env->GetMethodID(capsClazz, "<init>",
+ "(Landroid/media/MediaCodecInfo$CodecCapabilities$CodecCapsIntf;)V");
- jobject caps = env->NewObject(capsClazz, capsConstructID,
- profileLevelArray, colorFormatsArray, isEncoder,
- defaultFormatObj, infoObj);
+ caps = env->NewObject(capsClazz, capsConstructID, capsImpl);
- env->DeleteLocalRef(profileLevelArray);
- profileLevelArray = NULL;
+ env->DeleteLocalRef(profileLevelArray);
+ profileLevelArray = NULL;
- env->DeleteLocalRef(colorFormatsArray);
- colorFormatsArray = NULL;
+ env->DeleteLocalRef(colorFormatsArray);
+ colorFormatsArray = NULL;
- env->DeleteLocalRef(defaultFormatObj);
- defaultFormatObj = NULL;
+ env->DeleteLocalRef(defaultFormatObj);
+ defaultFormatObj = NULL;
+
+ env->DeleteLocalRef(infoObj);
+ infoObj = NULL;
- env->DeleteLocalRef(infoObj);
- infoObj = NULL;
+ env->DeleteLocalRef(capsImpl);
+ capsImpl = NULL;
+ }
return caps;
}