diff options
| author | 2024-05-15 18:24:37 +0000 | |
|---|---|---|
| committer | 2024-12-18 22:57:40 +0000 | |
| commit | 736e038e23b2e5dafc92149d297d451b948ce72e (patch) | |
| tree | e7fcb8257386bb8f17a9fbb9737d12710c8ebb9b | |
| parent | b58417cc4992e791531d3f063062e5b9931d3d26 (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.java | 1099 | ||||
| -rw-r--r-- | media/jni/android_media_CodecCapabilities.cpp | 610 | ||||
| -rw-r--r-- | media/jni/android_media_CodecCapabilities.h | 47 | ||||
| -rw-r--r-- | media/jni/android_media_MediaCodec.cpp | 39 | ||||
| -rw-r--r-- | media/jni/android_media_MediaCodecList.cpp | 154 |
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; } |