diff options
| -rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 434 | ||||
| -rw-r--r-- | media/jni/android_media_CodecCapabilities.cpp | 49 |
2 files changed, 319 insertions, 164 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 5f79f4409fc7..995dcead492a 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -4312,27 +4312,7 @@ public final class MediaCodecInfo { * A class that supports querying the encoding capabilities of a codec. */ public static final class EncoderCapabilities { - /** - * Returns the supported range of quality values. - * - * Quality is implementation-specific. As a general rule, a higher quality - * setting results in a better image quality and a lower compression ratio. - */ - public Range<Integer> getQualityRange() { - return mQualityRange; - } - - /** - * Returns the supported range of encoder complexity values. - * <p> - * Some codecs may support multiple complexity levels, where higher - * complexity values use more encoder tools (e.g. perform more - * intensive calculations) to improve the quality or the compression - * ratio. Use a lower value to save power and/or time. - */ - public Range<Integer> getComplexityRange() { - return mComplexityRange; - } + private static final String TAG = "EncoderCapabilities"; /** Constant quality mode */ public static final int BITRATE_MODE_CQ = 0; @@ -4343,188 +4323,314 @@ public final class MediaCodecInfo { /** Constant bitrate mode with frame drops */ public static final int BITRATE_MODE_CBR_FD = 3; - private static final Feature[] bitrates = new Feature[] { - new Feature("VBR", BITRATE_MODE_VBR, true), - new Feature("CBR", BITRATE_MODE_CBR, false), - new Feature("CQ", BITRATE_MODE_CQ, false), - new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false) - }; - - private static int parseBitrateMode(String mode) { - for (Feature feat: bitrates) { - if (feat.mName.equalsIgnoreCase(mode)) { - return feat.mValue; - } - } - return 0; - } + /* package private */ interface EncoderCapsIntf { + public Range<Integer> getQualityRange(); - /** - * Query whether a bitrate mode is supported. - */ - public boolean isBitrateModeSupported(int mode) { - for (Feature feat: bitrates) { - if (mode == feat.mValue) { - return (mBitControl & (1 << mode)) != 0; - } - } - return false; - } + public Range<Integer> getComplexityRange(); - private Range<Integer> mQualityRange; - private Range<Integer> mComplexityRange; - private CodecCapabilities mParent; + public boolean isBitrateModeSupported(int mode); - /* no public constructor */ - private EncoderCapabilities() { } + public void getDefaultFormat(MediaFormat format); - /** @hide */ - public static EncoderCapabilities create( - MediaFormat info, CodecCapabilities parent) { - EncoderCapabilities caps = new EncoderCapabilities(); - caps.init(info, parent); - return caps; + public boolean supportsFormat(MediaFormat format); } - private void init(MediaFormat info, CodecCapabilities parent) { - // no support for complexity or quality yet - mParent = parent; - mComplexityRange = Range.create(0, 0); - mQualityRange = Range.create(0, 0); - mBitControl = (1 << BITRATE_MODE_VBR); + /* package private */ static final class EncoderCapsLegacyImpl implements EncoderCapsIntf { + private CodecCapabilities mParent; - applyLevelLimits(); - parseFromInfo(info); - } + private Range<Integer> mQualityRange; + private Range<Integer> mComplexityRange; - private void applyLevelLimits() { - String mime = mParent.getMimeType(); - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - mComplexityRange = Range.create(0, 8); - mBitControl = (1 << BITRATE_MODE_CQ); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - mBitControl = (1 << BITRATE_MODE_CBR); + public Range<Integer> getQualityRange() { + return mQualityRange; + } + + public Range<Integer> getComplexityRange() { + return mComplexityRange; + } + + private static final Feature[] bitrates = new Feature[] { + new Feature("VBR", BITRATE_MODE_VBR, true), + new Feature("CBR", BITRATE_MODE_CBR, false), + new Feature("CQ", BITRATE_MODE_CQ, false), + new Feature("CBR-FD", BITRATE_MODE_CBR_FD, false) + }; + + private static int parseBitrateMode(String mode) { + for (Feature feat: bitrates) { + if (feat.mName.equalsIgnoreCase(mode)) { + return feat.mValue; + } + } + return 0; + } + + public boolean isBitrateModeSupported(int mode) { + for (Feature feat: bitrates) { + if (mode == feat.mValue) { + return (mBitControl & (1 << mode)) != 0; + } + } + return false; } - } - private int mBitControl; - private Integer mDefaultComplexity; - private Integer mDefaultQuality; - private String mQualityScale; + /* no public constructor */ + private EncoderCapsLegacyImpl() { } - private void parseFromInfo(MediaFormat info) { - Map<String, Object> map = info.getMap(); + /** @hide */ + public static EncoderCapsLegacyImpl create( + MediaFormat info, CodecCapabilities parent) { + if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { + Log.d(TAG, "Legacy implementation is called while native flag is on."); + } - if (info.containsKey("complexity-range")) { - mComplexityRange = Utils - .parseIntRange(info.getString("complexity-range"), mComplexityRange); - // TODO should we limit this to level limits? + EncoderCapsLegacyImpl caps = new EncoderCapsLegacyImpl(); + caps.init(info, parent); + return caps; } - if (info.containsKey("quality-range")) { - mQualityRange = Utils - .parseIntRange(info.getString("quality-range"), mQualityRange); + + private void init(MediaFormat info, CodecCapabilities parent) { + // no support for complexity or quality yet + mParent = parent; + mComplexityRange = Range.create(0, 0); + mQualityRange = Range.create(0, 0); + mBitControl = (1 << BITRATE_MODE_VBR); + + applyLevelLimits(); + parseFromInfo(info); } - if (info.containsKey("feature-bitrate-modes")) { - mBitControl = 0; - for (String mode: info.getString("feature-bitrate-modes").split(",")) { - mBitControl |= (1 << parseBitrateMode(mode)); + + private void applyLevelLimits() { + String mime = mParent.getMimeType(); + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + mComplexityRange = Range.create(0, 8); + mBitControl = (1 << BITRATE_MODE_CQ); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + mBitControl = (1 << BITRATE_MODE_CBR); } } - try { - mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); - } catch (NumberFormatException e) { } + private int mBitControl; + private Integer mDefaultComplexity; + private Integer mDefaultQuality; + private String mQualityScale; - try { - mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); - } catch (NumberFormatException e) { } + private void parseFromInfo(MediaFormat info) { + Map<String, Object> map = info.getMap(); - mQualityScale = (String)map.get("quality-scale"); - } + if (info.containsKey("complexity-range")) { + mComplexityRange = Utils + .parseIntRange(info.getString("complexity-range"), mComplexityRange); + // TODO should we limit this to level limits? + } + if (info.containsKey("quality-range")) { + mQualityRange = Utils + .parseIntRange(info.getString("quality-range"), mQualityRange); + } + if (info.containsKey("feature-bitrate-modes")) { + mBitControl = 0; + for (String mode: info.getString("feature-bitrate-modes").split(",")) { + mBitControl |= (1 << parseBitrateMode(mode)); + } + } + + try { + mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); + } catch (NumberFormatException e) { } + + try { + mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); + } catch (NumberFormatException e) { } - private boolean supports( - Integer complexity, Integer quality, Integer profile) { - boolean ok = true; - if (ok && complexity != null) { - ok = mComplexityRange.contains(complexity); + mQualityScale = (String)map.get("quality-scale"); } - if (ok && quality != null) { - ok = mQualityRange.contains(quality); + + private boolean supports( + Integer complexity, Integer quality, Integer profile) { + boolean ok = true; + if (ok && complexity != null) { + ok = mComplexityRange.contains(complexity); + } + if (ok && quality != null) { + ok = mQualityRange.contains(quality); + } + if (ok && profile != null) { + for (CodecProfileLevel pl: mParent.profileLevels) { + if (pl.profile == profile) { + profile = null; + break; + } + } + ok = profile == null; + } + return ok; } - if (ok && profile != null) { - for (CodecProfileLevel pl: mParent.profileLevels) { - if (pl.profile == profile) { - profile = null; + + /** @hide */ + public void getDefaultFormat(MediaFormat format) { + // don't list trivial quality/complexity as default for now + if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) + && mDefaultQuality != null) { + format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); + } + if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) + && mDefaultComplexity != null) { + format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); + } + // bitrates are listed in order of preference + for (Feature feat: bitrates) { + if ((mBitControl & (1 << feat.mValue)) != 0) { + format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); break; } } - ok = profile == null; } - return ok; - } - /** @hide */ - public void getDefaultFormat(MediaFormat format) { - // don't list trivial quality/complexity as default for now - if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) - && mDefaultQuality != null) { - format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); - } - if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) - && mDefaultComplexity != null) { - format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); - } - // bitrates are listed in order of preference - for (Feature feat: bitrates) { - if ((mBitControl & (1 << feat.mValue)) != 0) { - format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); - break; + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + final String mime = mParent.getMimeType(); + + Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); + if (mode != null && !isBitrateModeSupported(mode)) { + return false; + } + + Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); + if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { + Integer flacComplexity = + (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); + if (complexity == null) { + complexity = flacComplexity; + } else if (flacComplexity != null && !complexity.equals(flacComplexity)) { + throw new IllegalArgumentException( + "conflicting values for complexity and " + + "flac-compression-level"); + } + } + + // other audio parameters + Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); + if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { + Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); + if (profile == null) { + profile = aacProfile; + } else if (aacProfile != null && !aacProfile.equals(profile)) { + throw new IllegalArgumentException( + "conflicting values for profile and aac-profile"); + } } + + Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); + + return supports(complexity, quality, profile); } } - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - final String mime = mParent.getMimeType(); + /* package private */ static final class EncoderCapsNativeImpl implements EncoderCapsIntf { + private long mNativeContext; // accessed by native methods - Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); - if (mode != null && !isBitrateModeSupported(mode)) { - return false; + private Range<Integer> mQualityRange; + private Range<Integer> mComplexityRange; + + /* no public constructor */ + private EncoderCapsNativeImpl() { } + + // Constructor called from native + /* package private */ EncoderCapsNativeImpl(Range<Integer> qualityRange, + Range<Integer> complexityRange) { + mQualityRange = qualityRange; + mComplexityRange = complexityRange; } - Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); - if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { - Integer flacComplexity = - (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); - if (complexity == null) { - complexity = flacComplexity; - } else if (flacComplexity != null && !complexity.equals(flacComplexity)) { - throw new IllegalArgumentException( - "conflicting values for complexity and " + - "flac-compression-level"); - } + public Range<Integer> getQualityRange() { + return mQualityRange; } - // other audio parameters - Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); - if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { - Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); - if (profile == null) { - profile = aacProfile; - } else if (aacProfile != null && !aacProfile.equals(profile)) { - throw new IllegalArgumentException( - "conflicting values for profile and aac-profile"); - } + public Range<Integer> getComplexityRange() { + return mComplexityRange; + } + + public boolean isBitrateModeSupported(int mode) { + return native_isBitrateModeSupported(mode); } - Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); + // This API is for internal Java implementation only. Should not be called. + public void getDefaultFormat(MediaFormat format) { + throw new UnsupportedOperationException( + "Java Implementation should not call native implemenatation"); + } - return supports(complexity, quality, profile); + // This API is for internal Java implementation only. Should not be called. + public boolean supportsFormat(MediaFormat format) { + throw new UnsupportedOperationException( + "Java Implementation should not call native implemenatation"); + } + + private native boolean native_isBitrateModeSupported(int mode); + private static native void native_init(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } + } + + private EncoderCapsIntf mImpl; + + /** @hide */ + public static EncoderCapabilities create( + MediaFormat info, CodecCapabilities parent) { + EncoderCapsLegacyImpl impl = EncoderCapsLegacyImpl.create(info, parent); + EncoderCapabilities caps = new EncoderCapabilities(impl); + return caps; + } + + /* package private */ EncoderCapabilities(EncoderCapsIntf impl) { + mImpl = impl; + } + + /** + * Returns the supported range of quality values. + * + * Quality is implementation-specific. As a general rule, a higher quality + * setting results in a better image quality and a lower compression ratio. + */ + public Range<Integer> getQualityRange() { + return mImpl.getQualityRange(); + } + + /** + * Returns the supported range of encoder complexity values. + * <p> + * Some codecs may support multiple complexity levels, where higher + * complexity values use more encoder tools (e.g. perform more + * intensive calculations) to improve the quality or the compression + * ratio. Use a lower value to save power and/or time. + */ + public Range<Integer> getComplexityRange() { + return mImpl.getComplexityRange(); + } + + /** + * Query whether a bitrate mode is supported. + */ + public boolean isBitrateModeSupported(int mode) { + return mImpl.isBitrateModeSupported(mode); + } + + /** @hide */ + public void getDefaultFormat(MediaFormat format) { + mImpl.getDefaultFormat(format); + } + + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + return mImpl.supportsFormat(format); } }; diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp index ccaa140845dc..29695d60eeb2 100644 --- a/media/jni/android_media_CodecCapabilities.cpp +++ b/media/jni/android_media_CodecCapabilities.cpp @@ -21,6 +21,7 @@ #include "jni.h" #include <media/AudioCapabilities.h> +#include <media/EncoderCapabilities.h> #include <media/VideoCapabilities.h> #include <media/stagefright/foundation/ADebug.h> #include <nativehelper/JNIHelp.h> @@ -30,6 +31,7 @@ namespace android { struct fields_t { jfieldID audioCapsContext; jfieldID videoCapsContext; + jfieldID encoderCapsContext; }; static fields_t fields; @@ -47,6 +49,12 @@ static VideoCapabilities* getVideoCapabilities(JNIEnv *env, jobject thiz) { return p; } +static EncoderCapabilities* getEncoderCapabilities(JNIEnv *env, jobject thiz) { + EncoderCapabilities* const p = (EncoderCapabilities*)env->GetLongField( + thiz, fields.encoderCapsContext); + return p; +} + // Utils static jobject convertToJavaIntRange(JNIEnv *env, const Range<int32_t>& range) { @@ -305,6 +313,35 @@ static jint android_media_VideoCapabilities_getSmallerDimensionUpperLimit(JNIEnv return smallerDimensionUpperLimit; } +// EncoderCapabilities + +static void android_media_EncoderCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { + jclass clazz = env->FindClass( + "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl"); + if (clazz == NULL) { + return; + } + + fields.encoderCapsContext = env->GetFieldID(clazz, "mNativeContext", "J"); + if (fields.encoderCapsContext == NULL) { + return; + } + + env->DeleteLocalRef(clazz); +} + +static jboolean android_media_EncoderCapabilities_isBitrateModeSupported(JNIEnv *env, jobject thiz, + int mode) { + EncoderCapabilities* const encoderCaps = getEncoderCapabilities(env, thiz); + if (encoderCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + bool res = encoderCaps->isBitrateModeSupported(mode); + return res; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gAudioCapsMethods[] = { @@ -330,6 +367,11 @@ static const JNINativeMethod gVideoCapsMethods[] = { {"native_getSmallerDimensionUpperLimit", "()I", (void *)android_media_VideoCapabilities_getSmallerDimensionUpperLimit} }; +static const JNINativeMethod gEncoderCapsMethods[] = { + {"native_init", "()V", (void *)android_media_EncoderCapabilities_native_init}, + {"native_isBitrateModeSupported", "(I)Z", (void *)android_media_EncoderCapabilities_isBitrateModeSupported} +}; + int register_android_media_CodecCapabilities(JNIEnv *env) { int result = AndroidRuntime::registerNativeMethods(env, "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl", @@ -352,5 +394,12 @@ int register_android_media_CodecCapabilities(JNIEnv *env) { return result; } + result = AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodecInfo$EncoderCapabilities$EncoderCapsNativeImpl", + gEncoderCapsMethods, NELEM(gEncoderCapsMethods)); + if (result != JNI_OK) { + return result; + } + return result; }
\ No newline at end of file |