Merge "Fixed a flash of the preview image when going to the dialer" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index 6fbb5ce..2ff971b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14526,7 +14526,6 @@
method public abstract long getTimestamp();
method public abstract int getWidth();
method public void setCropRect(android.graphics.Rect);
- field protected android.graphics.Rect mCropRect;
}
public static abstract class Image.Plane {
@@ -14658,7 +14657,6 @@
}
public static final class MediaCodec.CodecException extends java.lang.IllegalStateException {
- ctor public MediaCodec.CodecException(int, int, java.lang.String);
method public int getErrorCode();
method public boolean isRecoverable();
method public boolean isTransient();
@@ -14690,14 +14688,22 @@
method public final boolean isEncoder();
}
+ public static final class MediaCodecInfo.AudioCapabilities {
+ method public android.util.Range<java.lang.Integer> getBitrateRange();
+ method public int getMaxInputChannelCount();
+ method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
+ method public int[] getSupportedSampleRates();
+ method public boolean isSampleRateSupported(int);
+ }
+
public static final class MediaCodecInfo.CodecCapabilities {
ctor public MediaCodecInfo.CodecCapabilities();
- method public static final android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int);
- method public final android.media.MediaCodecInfo.CodecCapabilities.AudioCapabilities getAudioCapabilities();
- method public final android.media.MediaFormat getDefaultFormat();
- method public final android.media.MediaCodecInfo.CodecCapabilities.EncoderCapabilities getEncoderCapabilities();
- method public final java.lang.String getMime();
- method public final android.media.MediaCodecInfo.CodecCapabilities.VideoCapabilities getVideoCapabilities();
+ method public static android.media.MediaCodecInfo.CodecCapabilities CreateFromProfileLevel(java.lang.String, int, int);
+ method public android.media.MediaCodecInfo.AudioCapabilities getAudioCapabilities();
+ method public android.media.MediaFormat getDefaultFormat();
+ method public android.media.MediaCodecInfo.EncoderCapabilities getEncoderCapabilities();
+ method public java.lang.String getMimeType();
+ method public android.media.MediaCodecInfo.VideoCapabilities getVideoCapabilities();
method public final boolean isFeatureRequired(java.lang.String);
method public final boolean isFeatureSupported(java.lang.String);
method public final boolean isFormatSupported(android.media.MediaFormat);
@@ -14755,39 +14761,6 @@
field public android.media.MediaCodecInfo.CodecProfileLevel[] profileLevels;
}
- public static final class MediaCodecInfo.CodecCapabilities.AudioCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities {
- method public final int getMaxInputChannelCount();
- method public final android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
- method public final int[] getSupportedSampleRates();
- method public final boolean isSampleRateSupported(int);
- }
-
- public static class MediaCodecInfo.CodecCapabilities.BaseCapabilities {
- method public final android.util.Range<java.lang.Integer> getBitrateRange();
- }
-
- public static final class MediaCodecInfo.CodecCapabilities.EncoderCapabilities {
- method public final android.util.Range<java.lang.Integer> getComplexityRange();
- method public final android.util.Range<java.lang.Integer> getQualityRange();
- method public final boolean isBitrateModeSupported(int);
- field public static final int BITRATE_MODE_CBR = 2; // 0x2
- field public static final int BITRATE_MODE_CQ = 0; // 0x0
- field public static final int BITRATE_MODE_VBR = 1; // 0x1
- }
-
- public static final class MediaCodecInfo.CodecCapabilities.VideoCapabilities extends android.media.MediaCodecInfo.CodecCapabilities.BaseCapabilities {
- method public final boolean areSizeAndRateSupported(int, int, double);
- method public final int getHeightAlignment();
- method public final android.util.Range<java.lang.Integer> getSupportedFrameRates();
- method public final android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int);
- method public final android.util.Range<java.lang.Integer> getSupportedHeights();
- method public final android.util.Range<java.lang.Integer> getSupportedHeightsFor(int);
- method public final android.util.Range<java.lang.Integer> getSupportedWidths();
- method public final android.util.Range<java.lang.Integer> getSupportedWidthsFor(int);
- method public final int getWidthAlignment();
- method public final boolean isSizeSupported(int, int);
- }
-
public static final class MediaCodecInfo.CodecProfileLevel {
ctor public MediaCodecInfo.CodecProfileLevel();
field public static final int AACObjectELD = 39; // 0x27
@@ -14902,12 +14875,34 @@
field public int profile;
}
+ public static final class MediaCodecInfo.EncoderCapabilities {
+ method public android.util.Range<java.lang.Integer> getComplexityRange();
+ method public boolean isBitrateModeSupported(int);
+ field public static final int BITRATE_MODE_CBR = 2; // 0x2
+ field public static final int BITRATE_MODE_CQ = 0; // 0x0
+ field public static final int BITRATE_MODE_VBR = 1; // 0x1
+ }
+
+ public static final class MediaCodecInfo.VideoCapabilities {
+ method public boolean areSizeAndRateSupported(int, int, double);
+ method public android.util.Range<java.lang.Integer> getBitrateRange();
+ method public int getHeightAlignment();
+ method public android.util.Range<java.lang.Integer> getSupportedFrameRates();
+ method public android.util.Range<java.lang.Double> getSupportedFrameRatesFor(int, int);
+ method public android.util.Range<java.lang.Integer> getSupportedHeights();
+ method public android.util.Range<java.lang.Integer> getSupportedHeightsFor(int);
+ method public android.util.Range<java.lang.Integer> getSupportedWidths();
+ method public android.util.Range<java.lang.Integer> getSupportedWidthsFor(int);
+ method public int getWidthAlignment();
+ method public boolean isSizeSupported(int, int);
+ }
+
public final class MediaCodecList {
ctor public MediaCodecList(int);
method public final java.lang.String findDecoderForFormat(android.media.MediaFormat);
method public final java.lang.String findEncoderForFormat(android.media.MediaFormat);
- method public static final int getCodecCount();
- method public static final android.media.MediaCodecInfo getCodecInfoAt(int);
+ method public static final deprecated int getCodecCount();
+ method public static final deprecated android.media.MediaCodecInfo getCodecInfoAt(int);
method public final android.media.MediaCodecInfo[] getCodecInfos();
field public static final int ALL_CODECS = 1; // 0x1
field public static final int REGULAR_CODECS = 0; // 0x0
@@ -15026,11 +15021,13 @@
method public static final android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String);
method public static final android.media.MediaFormat createVideoFormat(java.lang.String, int, int);
method public final java.nio.ByteBuffer getByteBuffer(java.lang.String);
+ method public boolean getFeatureEnabled(java.lang.String);
method public final float getFloat(java.lang.String);
method public final int getInteger(java.lang.String);
method public final long getLong(java.lang.String);
method public final java.lang.String getString(java.lang.String);
method public final void setByteBuffer(java.lang.String, java.nio.ByteBuffer);
+ method public void setFeatureEnabled(java.lang.String, boolean);
method public final void setFloat(java.lang.String, float);
method public final void setInteger(java.lang.String, int);
method public final void setLong(java.lang.String, long);
@@ -15052,7 +15049,6 @@
field public static final java.lang.String KEY_COLOR_FORMAT = "color-format";
field public static final java.lang.String KEY_COMPLEXITY = "complexity";
field public static final java.lang.String KEY_DURATION = "durationUs";
- field public static final java.lang.String KEY_FEATURE_ = "feature-";
field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
field public static final java.lang.String KEY_HEIGHT = "height";
@@ -15068,7 +15064,6 @@
field public static final java.lang.String KEY_MIME = "mime";
field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
- field public static final java.lang.String KEY_QUALITY = "quality";
field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate";
field public static final java.lang.String KEY_TEMPORAL_LAYERING = "ts-schema";
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 2856edb..522e45d 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -123,7 +123,7 @@
*/
public abstract long getTimestamp();
- protected Rect mCropRect;
+ private Rect mCropRect;
/**
* Get the crop rectangle associated with this frame.
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 96e6ab9..032f07f 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -665,7 +665,7 @@
* Thrown when an internal codec error occurs.
*/
public final static class CodecException extends IllegalStateException {
- public CodecException(int errorCode, int actionCode, String detailMessage) {
+ CodecException(int errorCode, int actionCode, String detailMessage) {
super(detailMessage);
mErrorCode = errorCode;
mActionCode = actionCode;
@@ -1729,7 +1729,7 @@
if (cropRect != null) {
cropRect.offset(-xOffset, -yOffset);
}
- mCropRect = cropRect;
+ setCropRect(cropRect);
// save offsets and info
mXOffset = xOffset;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index acb2186..323a3e3 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -70,7 +70,7 @@
mIsEncoder = isEncoder;
mCaps = new HashMap<String, CodecCapabilities>();
for (CodecCapabilities c: caps) {
- mCaps.put(c.getMime(), c);
+ mCaps.put(c.getMimeType(), c);
}
}
@@ -98,6 +98,43 @@
return types;
}
+ private static int checkPowerOfTwo(int value, String message) {
+ if ((value & (value - 1)) != 0) {
+ throw new IllegalArgumentException(message);
+ }
+ return value;
+ }
+
+ private static class Feature {
+ public String mName;
+ public int mValue;
+ public boolean mDefault;
+ public Feature(String name, int value, boolean def) {
+ mName = name;
+ mValue = value;
+ mDefault = def;
+ }
+ }
+
+ // COMMON CONSTANTS
+ private static final Range<Integer> POSITIVE_INTEGERS =
+ Range.create(1, Integer.MAX_VALUE);
+ private static final Range<Long> POSITIVE_LONGS =
+ Range.create(1l, Long.MAX_VALUE);
+ private static final Range<Rational> POSITIVE_RATIONALS =
+ Range.create(new Rational(1, Integer.MAX_VALUE),
+ new Rational(Integer.MAX_VALUE, 1));
+ private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
+ private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+
+ // found stuff that is not supported by framework (=> this should not happen)
+ private static final int ERROR_UNRECOGNIZED = (1 << 0);
+ // found profile/level for which we don't have capability estimates
+ private static final int ERROR_UNSUPPORTED = (1 << 1);
+ // have not found any profile/level for which we don't have capability estimate
+ private static final int ERROR_NONE_SUPPORTED = (1 << 2);
+
+
/**
* Encapsulates the capabilities of a given codec component.
* For example, what profile/level combinations it supports and what colorspaces
@@ -222,17 +259,6 @@
return checkFeature(name, mFlagsRequired);
}
- private static class Feature {
- public String mName;
- public int mValue;
- public boolean mDefault;
- public Feature(String name, int value, boolean def) {
- mName = name;
- mValue = value;
- mDefault = def;
- }
- }
-
private static final Feature[] decoderFeatures = {
new Feature(FEATURE_AdaptivePlayback, (1 << 0), true),
new Feature(FEATURE_SecurePlayback, (1 << 1), false),
@@ -312,1311 +338,54 @@
return true;
}
- // errors while reading profile levels
- private int mError;
- // found stuff that is not supported by framework (=> this should not happen)
- private static final int ERROR_UNRECOGNIZED = (1 << 0);
- // found profile/level for which we don't have capability estimates
- private static final int ERROR_UNSUPPORTED = (1 << 1);
- // have not found any profile/level for which we don't have capability estimate
- private static final int ERROR_NONE_SUPPORTED = (1 << 2);
-
-
- // UTILITY METHODS
- private static final Range<Integer> POSITIVE_INTEGERS =
- Range.create(1, Integer.MAX_VALUE);
- private static final Range<Long> POSITIVE_LONGS =
- Range.create(1l, Long.MAX_VALUE);
- private static final Range<Rational> POSITIVE_RATIONALS =
- Range.create(new Rational(1, Integer.MAX_VALUE),
- new Rational(Integer.MAX_VALUE, 1));
- private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768);
- private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960);
+ // 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 final MediaFormat getDefaultFormat() {
+ public MediaFormat getDefaultFormat() {
return mDefaultFormat;
}
- private MediaFormat mDefaultFormat;
/**
* Returns the mime type for which this codec-capability object was created.
*/
- public final String getMime() {
+ public String getMimeType() {
return mMime;
}
- /**
- * Returns the encoding capabilities or {@code null} if this is not an encoder.
- */
- public final EncoderCapabilities getEncoderCapabilities() {
- return mEncoderCaps;
+ private boolean isAudio() {
+ return mAudioCaps != null;
}
- private EncoderCapabilities mEncoderCaps;
+
+ /**
+ * 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;
}
/**
- * A class that supports querying the encoding capabilities of a codec.
+ * Returns the encoding capabilities or {@code null} if this is not an encoder.
*/
- public static final class EncoderCapabilities {
- /**
- * Returns the supported range of quality values.
- */
- public final 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 final Range<Integer> getComplexityRange() {
- return mComplexityRange;
- }
-
- /** Constant quality mode */
- public static final int BITRATE_MODE_CQ = 0;
- /** Variable bitrate mode */
- public static final int BITRATE_MODE_VBR = 1;
- /** Constant bitrate mode */
- public static final int BITRATE_MODE_CBR = 2;
-
- 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)
- };
-
- private static int parseBitrateMode(String mode) {
- for (Feature feat: bitrates) {
- if (feat.mName.equalsIgnoreCase(mode)) {
- return feat.mValue;
- }
- }
- return 0;
- }
-
- /**
- * Query whether a bitrate mode is supported.
- */
- public final boolean isBitrateModeSupported(int mode) {
- for (Feature feat: bitrates) {
- if (mode == feat.mValue) {
- return (mBitControl & (1 << mode)) != 0;
- }
- }
- return false;
- }
-
- private Range<Integer> mQualityRange;
- private Range<Integer> mComplexityRange;
- private CodecCapabilities mParent;
-
- /* no public constructor */
- private EncoderCapabilities() { }
-
- /** @hide */
- public static EncoderCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- EncoderCapabilities caps = new EncoderCapabilities();
- caps.init(info, parent);
- return caps;
- }
-
- /** @hide */
- public void init(MediaFormat info, CodecCapabilities parent) {
- // no support for complexity or quality yet
- mParent = parent;
- mComplexityRange = Range.create(0, 0);
- mQualityRange = Range.create(0, 0);
- mBitControl = (1 << BITRATE_MODE_VBR);
-
- applyLevelLimits();
- parseFromInfo(info);
- }
-
- private void applyLevelLimits() {
- String mime = mParent.getMime();
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
- mComplexityRange = Range.create(0, 8);
- mBitControl = (1 << BITRATE_MODE_CQ);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
- mBitControl = (1 << BITRATE_MODE_CBR);
- }
- }
-
- private int mBitControl;
- private Integer mDefaultComplexity;
- private Integer mDefaultQuality;
- private String mQualityScale;
-
- private void parseFromInfo(MediaFormat info) {
- Map<String, Object> map = info.getMap();
-
- if (info.containsKey("complexity-range")) {
- mComplexityRange = Utils
- .parseIntRange(info.getString("complexity-range"), mComplexityRange);
- // TODO should we limit this to level limits?
- }
- if (info.containsKey("quality-range")) {
- mQualityRange = Utils
- .parseIntRange(info.getString("quality-range"), mQualityRange);
- }
- if (info.containsKey("feature-bitrate-control")) {
- for (String mode: info.getString("feature-bitrate-control").split(",")) {
- mBitControl |= parseBitrateMode(mode);
- }
- }
-
- try {
- mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
- } catch (NumberFormatException e) { }
-
- try {
- mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
- } catch (NumberFormatException e) { }
-
- mQualityScale = (String)map.get("quality-scale");
- }
-
- private boolean supports(
- Integer complexity, Integer quality, Integer profile) {
- boolean ok = true;
- if (ok && complexity != null) {
- ok = mComplexityRange.contains(complexity);
- }
- if (ok && quality != null) {
- ok = mQualityRange.contains(quality);
- }
- if (ok && profile != null) {
- for (CodecProfileLevel pl: mParent.profileLevels) {
- if (pl.profile == profile) {
- profile = null;
- break;
- }
- }
- ok = profile == null;
- }
- return ok;
- }
-
- /** @hide */
- public void setDefaultFormat(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.getMime();
-
- 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 != 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 != profile) {
- throw new IllegalArgumentException(
- "conflicting values for profile and aac-profile");
- }
- }
-
- Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
-
- return supports(complexity, quality, profile);
- }
- };
-
- /**
- * A class that supports querying basic capabilities of a codec.
- */
- public static class BaseCapabilities {
- /**
- * Returns the range of supported bitrates in bits/second.
- */
- public final Range<Integer> getBitrateRange() {
- return mBitrateRange;
- }
-
- /** @hide */
- protected Range<Integer> mBitrateRange;
-
- /** @hide */
- protected CodecCapabilities mParent;
-
- /** @hide */
- protected BaseCapabilities() {
- }
-
- /** @hide */
- protected void init(MediaFormat info, CodecCapabilities parent) {
- mParent = parent;
- mBitrateRange = Range.create(0, Integer.MAX_VALUE);
- }
+ public EncoderCapabilities getEncoderCapabilities() {
+ return mEncoderCaps;
}
- /**
- * A class that supports querying the video capabilities of a codec.
- */
- public static final class VideoCapabilities extends BaseCapabilities {
- private static final String TAG = "VideoCapabilities";
- private Range<Integer> mHeightRange;
- private Range<Integer> mWidthRange;
- private Range<Integer> mBlockCountRange;
- private Range<Integer> mHorizontalBlockRange;
- private Range<Integer> mVerticalBlockRange;
- private Range<Rational> mAspectRatioRange;
- private Range<Rational> mBlockAspectRatioRange;
- private Range<Long> mBlocksPerSecondRange;
- private Range<Integer> mFrameRateRange;
-
- private int mBlockWidth;
- private int mBlockHeight;
- private int mWidthAlignment;
- private int mHeightAlignment;
- private int mSmallerDimensionUpperLimit;
-
- /**
- * Returns the range of supported video widths.
- */
- public final Range<Integer> getSupportedWidths() {
- return mWidthRange;
- }
-
- /**
- * Returns the range of supported video heights.
- */
- public final Range<Integer> getSupportedHeights() {
- return mHeightRange;
- }
-
- /**
- * Returns the alignment requirement for video width.
- */
- public final int getWidthAlignment() {
- return mWidthAlignment;
- }
-
- /**
- * Returns the alignment requirement for video height.
- */
- public final int getHeightAlignment() {
- return mHeightAlignment;
- }
-
- /**
- * Return the upper limit on the smaller dimension of width or height.
- * <p></p>
- * Some codecs have a limit on the smaller dimension, whether it be
- * the width or the height. E.g. a codec may only be able to handle
- * up to 1920x1080 both in landscape and portrait mode (1080x1920).
- * In this case the maximum width and height are both 1920, but the
- * smaller dimension limit will be 1080. For other codecs, this is
- * {@code Math.min(getSupportedWidths().getUpper(),
- * getSupportedHeights().getUpper())}.
- *
- * @hide
- */
- public int getSmallerDimensionUpperLimit() {
- return mSmallerDimensionUpperLimit;
- }
-
- /**
- * Returns the range of supported frame rates.
- * <p>
- * This is not a performance indicator. Rather, it expresses the
- * limits specified in the coding standard, based on the complexities
- * of encoding material for later playback at a certain frame rate,
- * or the decoding of such material in non-realtime.
- */
- public final Range<Integer> getSupportedFrameRates() {
- return mFrameRateRange;
- }
-
- /**
- * Returns the range of supported video widths for a video height.
- * @param height the height of the video
- */
- public final Range<Integer> getSupportedWidthsFor(int height) {
- try {
- Range<Integer> range = mWidthRange;
- if (!mHeightRange.contains(height)
- || (height % mHeightAlignment) != 0) {
- throw new IllegalArgumentException("unsupported height");
- }
- final int heightInBlocks = Utils.divUp(height, mBlockHeight);
-
- // constrain by block count and by block aspect ratio
- final int minWidthInBlocks = Math.max(
- Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
- (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
- * heightInBlocks));
- final int maxWidthInBlocks = Math.min(
- mBlockCountRange.getUpper() / heightInBlocks,
- (int)(mBlockAspectRatioRange.getUpper().doubleValue()
- * heightInBlocks));
- range = range.intersect(
- (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
- maxWidthInBlocks * mBlockWidth);
-
- // constrain by smaller dimension limit
- if (height > mSmallerDimensionUpperLimit) {
- range = range.intersect(1, mSmallerDimensionUpperLimit);
- }
-
- // constrain by aspect ratio
- range = range.intersect(
- (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
- * height),
- (int)(mAspectRatioRange.getUpper().doubleValue() * height));
- return range;
- } catch (IllegalArgumentException e) {
- // should not be here
- Log.w(TAG, "could not get supported widths for " + height , e);
- throw new IllegalArgumentException("unsupported height");
- }
- }
-
- /**
- * Returns the range of supported video heights for a video width
- * @param width the width of the video
- */
- public final Range<Integer> getSupportedHeightsFor(int width) {
- try {
- Range<Integer> range = mHeightRange;
- if (!mWidthRange.contains(width)
- || (width % mWidthAlignment) != 0) {
- throw new IllegalArgumentException("unsupported width");
- }
- final int widthInBlocks = Utils.divUp(width, mBlockWidth);
-
- // constrain by block count and by block aspect ratio
- final int minHeightInBlocks = Math.max(
- Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
- (int)Math.ceil(widthInBlocks /
- mBlockAspectRatioRange.getUpper().doubleValue()));
- final int maxHeightInBlocks = Math.min(
- mBlockCountRange.getUpper() / widthInBlocks,
- (int)(widthInBlocks /
- mBlockAspectRatioRange.getLower().doubleValue()));
- range = range.intersect(
- (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
- maxHeightInBlocks * mBlockHeight);
-
- // constrain by smaller dimension limit
- if (width > mSmallerDimensionUpperLimit) {
- range = range.intersect(1, mSmallerDimensionUpperLimit);
- }
-
- // constrain by aspect ratio
- range = range.intersect(
- (int)Math.ceil(width /
- mAspectRatioRange.getUpper().doubleValue()),
- (int)(width / mAspectRatioRange.getLower().doubleValue()));
- return range;
- } catch (IllegalArgumentException e) {
- // should not be here
- Log.w(TAG, "could not get supported heights for " + width , e);
- throw new IllegalArgumentException("unsupported width");
- }
- }
-
- /**
- * Returns the range of supported video frame rates for a video size.
- * <p>
- * This is not a performance indicator. Rather, it expresses the limits specified in
- * the coding standard, based on the complexities of encoding material of a given
- * size for later playback at a certain frame rate, or the decoding of such material
- * in non-realtime.
-
- * @param width the width of the video
- * @param height the height of the video
- */
- public final Range<Double> getSupportedFrameRatesFor(int width, int height) {
- Range<Integer> range = mHeightRange;
- if (!supports(width, height, null)) {
- throw new IllegalArgumentException("unsupported size");
- }
- final int blockCount =
- Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
-
- return Range.create(
- Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
- (double) mFrameRateRange.getLower()),
- Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
- (double) mFrameRateRange.getUpper()));
- }
-
- /**
- * Returns whether a given video size ({@code width} and
- * {@code height}) and {@code frameRate} combination is supported.
- */
- public final boolean areSizeAndRateSupported(
- int width, int height, double frameRate) {
- return supports(width, height, frameRate);
- }
-
- /**
- * Returns whether a given video size ({@code width} and
- * {@code height}) is supported.
- */
- public final boolean isSizeSupported(int width, int height) {
- return supports(width, height, null);
- }
-
- private final boolean supports(
- Integer width, Integer height, Double rate) {
- boolean ok = true;
-
- if (ok && width != null) {
- ok = mWidthRange.contains(width)
- && (width % mWidthAlignment == 0);
- }
- if (ok && height != null) {
- ok = mHeightRange.contains(height)
- && (height % mHeightAlignment == 0);
- }
- if (ok && rate != null) {
- ok = mFrameRateRange.contains(Utils.intRangeFor(rate));
- }
- if (ok && height != null && width != null) {
- ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
-
- final int widthInBlocks = Utils.divUp(width, mBlockWidth);
- final int heightInBlocks = Utils.divUp(height, mBlockHeight);
- final int blockCount = widthInBlocks * heightInBlocks;
- ok = ok && mBlockCountRange.contains(blockCount)
- && mBlockAspectRatioRange.contains(
- new Rational(widthInBlocks, heightInBlocks))
- && mAspectRatioRange.contains(new Rational(width, height));
- if (ok && rate != null) {
- double blocksPerSec = blockCount * rate;
- ok = mBlocksPerSecondRange.contains(
- Utils.longRangeFor(blocksPerSec));
- }
- }
- return ok;
- }
-
- /**
- * @hide
- * @throws java.lang.ClassCastException */
- public boolean supportsFormat(MediaFormat format) {
- final Map<String, Object> map = format.getMap();
- Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
- Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
- Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE);
-
- // we ignore color-format for now as it is not reliably reported by codec
-
- return supports(width, height, rate);
- }
-
- /* no public constructor */
- private VideoCapabilities() { }
-
- /** @hide */
- public static VideoCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- VideoCapabilities caps = new VideoCapabilities();
- caps.init(info, parent);
- return caps;
- }
-
- /** @hide */
- public void init(MediaFormat info, CodecCapabilities parent) {
- super.init(info, parent);
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- updateLimits();
- }
-
- /** @hide */
- public Size getBlockSize() {
- return new Size(mBlockWidth, mBlockHeight);
- }
-
- /** @hide */
- public Range<Integer> getBlockCountRange() {
- return mBlockCountRange;
- }
-
- /** @hide */
- public Range<Long> getBlocksPerSecondRange() {
- return mBlocksPerSecondRange;
- }
-
- /** @hide */
- public Range<Rational> getAspectRatioRange(boolean blocks) {
- return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
- }
-
- private void initWithPlatformLimits() {
- mWidthRange = SIZE_RANGE;
- mHeightRange = SIZE_RANGE;
- mFrameRateRange = FRAME_RATE_RANGE;
-
- mHorizontalBlockRange = SIZE_RANGE;
- mVerticalBlockRange = SIZE_RANGE;
-
- // full positive ranges are supported as these get calculated
- mBlockCountRange = POSITIVE_INTEGERS;
- mBlocksPerSecondRange = POSITIVE_LONGS;
-
- mBlockAspectRatioRange = POSITIVE_RATIONALS;
- mAspectRatioRange = POSITIVE_RATIONALS;
-
- // YUV 4:2:0 requires 2:2 alignment
- mWidthAlignment = 2;
- mHeightAlignment = 2;
- mBlockWidth = 2;
- mBlockHeight = 2;
- mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
- }
-
- private void parseFromInfo(MediaFormat info) {
- final Map<String, Object> map = info.getMap();
- Size blockSize = new Size(mBlockWidth, mBlockHeight);
- Size alignment = new Size(mWidthAlignment, mHeightAlignment);
- Range<Integer> counts = null, widths = null, heights = null;
- Range<Integer> frameRates = null;
- Range<Long> blockRates = null;
- Range<Rational> ratios = null, blockRatios = null;
-
- blockSize = Utils.parseSize(map.get("block-size"), blockSize);
- alignment = Utils.parseSize(map.get("alignment"), alignment);
- counts = Utils.parseIntRange(map.get("block-count-range"), null);
- blockRates =
- Utils.parseLongRange(map.get("blocks-per-second-range"), null);
- {
- Object o = map.get("size-range");
- Pair<Size, Size> sizeRange = Utils.parseSizeRange(o);
- if (sizeRange != null) {
- try {
- widths = Range.create(
- sizeRange.first.getWidth(),
- sizeRange.second.getWidth());
- heights = Range.create(
- sizeRange.first.getHeight(),
- sizeRange.second.getHeight());
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "could not parse size range '" + o + "'");
- widths = null;
- heights = null;
- }
- }
- }
- // for now this just means using the smaller max size as 2nd
- // upper limit.
- // for now we are keeping the profile specific "width/height
- // in macroblocks" limits.
- if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) {
- if (widths != null) {
- mSmallerDimensionUpperLimit =
- Math.min(widths.getUpper(), heights.getUpper());
- widths = heights = widths.extend(heights);
- } else {
- Log.w(TAG, "feature can-swap-width-height is best used with size-range");
- mSmallerDimensionUpperLimit =
- Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
- mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
- }
- }
-
- ratios = Utils.parseRationalRange(
- map.get("block-aspect-ratio-range"), null);
- blockRatios = Utils.parseRationalRange(
- map.get("pixel-aspect-ratio-range"), null);
- frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
- if (frameRates != null) {
- try {
- frameRates = frameRates.intersect(FRAME_RATE_RANGE);
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "frame rate range (" + frameRates
- + ") is out of limits: " + FRAME_RATE_RANGE);
- frameRates = null;
- }
- }
-
- checkPowerOfTwo(
- blockSize.getWidth(), "block-size width must be power of two");
- checkPowerOfTwo(
- blockSize.getHeight(), "block-size height must be power of two");
-
- checkPowerOfTwo(
- alignment.getWidth(), "alignment width must be power of two");
- checkPowerOfTwo(
- alignment.getHeight(), "alignment height must be power of two");
-
- // update block-size and alignment
- applyMacroBlockLimits(
- Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
- Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
- alignment.getWidth(), alignment.getHeight());
-
- if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
- // codec supports profiles that we don't know.
- // Use supplied values clipped to platform limits
- if (widths != null) {
- mWidthRange = SIZE_RANGE.intersect(widths);
- }
- if (heights != null) {
- mHeightRange = SIZE_RANGE.intersect(heights);
- }
- if (counts != null) {
- mBlockCountRange = POSITIVE_INTEGERS.intersect(
- Utils.factorRange(counts, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
- }
- if (blockRates != null) {
- mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
- Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
- }
- if (blockRatios != null) {
- mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
- Utils.scaleRange(blockRatios,
- mBlockHeight / blockSize.getHeight(),
- mBlockWidth / blockSize.getWidth()));
- }
- if (ratios != null) {
- mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
- }
- if (frameRates != null) {
- mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
- }
- } else {
- // no unsupported profile/levels, so restrict values to known limits
- if (widths != null) {
- mWidthRange = mWidthRange.intersect(widths);
- }
- if (heights != null) {
- mHeightRange = mHeightRange.intersect(heights);
- }
- if (counts != null) {
- mBlockCountRange = mBlockCountRange.intersect(
- Utils.factorRange(counts, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
- }
- if (blockRates != null) {
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
- Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
- / blockSize.getWidth() / blockSize.getHeight()));
- }
- if (blockRatios != null) {
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
- Utils.scaleRange(blockRatios,
- mBlockHeight / blockSize.getHeight(),
- mBlockWidth / blockSize.getWidth()));
- }
- if (ratios != null) {
- mAspectRatioRange = mAspectRatioRange.intersect(ratios);
- }
- if (frameRates != null) {
- mFrameRateRange = mFrameRateRange.intersect(frameRates);
- }
- }
- updateLimits();
- }
-
- private int checkPowerOfTwo(int value, String message) {
- if ((value & (value - 1)) != 0) {
- throw new IllegalArgumentException(message);
- }
- return value;
- }
-
- private void applyBlockLimits(
- int blockWidth, int blockHeight,
- Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
- checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
- checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
-
- final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
- final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
-
- // factor will always be a power-of-2
- int factor =
- newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
- if (factor != 1) {
- mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
- mBlocksPerSecondRange = Utils.factorRange(
- mBlocksPerSecondRange, factor);
- mBlockAspectRatioRange = Utils.scaleRange(
- mBlockAspectRatioRange,
- newBlockHeight / mBlockHeight,
- newBlockWidth / mBlockWidth);
- mHorizontalBlockRange = Utils.factorRange(
- mHorizontalBlockRange, newBlockWidth / mBlockWidth);
- mVerticalBlockRange = Utils.factorRange(
- mVerticalBlockRange, newBlockHeight / mBlockHeight);
- }
- factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
- if (factor != 1) {
- counts = Utils.factorRange(counts, factor);
- rates = Utils.factorRange(rates, factor);
- ratios = Utils.scaleRange(
- ratios, newBlockHeight / blockHeight,
- newBlockWidth / blockWidth);
- }
- mBlockCountRange = mBlockCountRange.intersect(counts);
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
- mBlockWidth = newBlockWidth;
- mBlockHeight = newBlockHeight;
- }
-
- private void applyAlignment(int widthAlignment, int heightAlignment) {
- checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
- checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
-
- if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
- // maintain assumption that 0 < alignment <= block-size
- applyBlockLimits(
- Math.max(widthAlignment, mBlockWidth),
- Math.max(heightAlignment, mBlockHeight),
- POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
- }
-
- mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
- mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
-
- mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
- mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
- }
-
- private void updateLimits() {
- // pixels -> blocks <- counts
- mHorizontalBlockRange = mHorizontalBlockRange.intersect(
- Utils.factorRange(mWidthRange, mBlockWidth));
- mHorizontalBlockRange = mHorizontalBlockRange.intersect(
- Range.create(
- mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
- mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
- mVerticalBlockRange = mVerticalBlockRange.intersect(
- Utils.factorRange(mHeightRange, mBlockHeight));
- mVerticalBlockRange = mVerticalBlockRange.intersect(
- Range.create(
- mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
- mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
- mBlockCountRange = mBlockCountRange.intersect(
- Range.create(
- mHorizontalBlockRange.getLower()
- * mVerticalBlockRange.getLower(),
- mHorizontalBlockRange.getUpper()
- * mVerticalBlockRange.getUpper()));
- mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
- new Rational(
- mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
- new Rational(
- mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
-
- // blocks -> pixels
- mWidthRange = mWidthRange.intersect(
- (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
- mHorizontalBlockRange.getUpper() * mBlockWidth);
- mHeightRange = mHeightRange.intersect(
- (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
- mVerticalBlockRange.getUpper() * mBlockHeight);
- mAspectRatioRange = mAspectRatioRange.intersect(
- new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
- new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
-
- mSmallerDimensionUpperLimit = Math.min(
- mSmallerDimensionUpperLimit,
- Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
-
- // blocks -> rate
- mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
- mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
- mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
- mFrameRateRange = mFrameRateRange.intersect(
- (int)(mBlocksPerSecondRange.getLower()
- / mBlockCountRange.getUpper()),
- (int)(mBlocksPerSecondRange.getUpper()
- / (double)mBlockCountRange.getLower()));
- }
-
- private void applyMacroBlockLimits(
- int maxHorizontalBlocks, int maxVerticalBlocks,
- int maxBlocks, long maxBlocksPerSecond,
- int blockWidth, int blockHeight,
- int widthAlignment, int heightAlignment) {
- applyAlignment(widthAlignment, heightAlignment);
- applyBlockLimits(
- blockWidth, blockHeight, Range.create(1, maxBlocks),
- Range.create(1L, maxBlocksPerSecond),
- Range.create(
- new Rational(1, maxVerticalBlocks),
- new Rational(maxHorizontalBlocks, 1)));
- mHorizontalBlockRange =
- mHorizontalBlockRange.intersect(
- 1, maxHorizontalBlocks / (mBlockWidth / blockWidth));
- mVerticalBlockRange =
- mVerticalBlockRange.intersect(
- 1, maxVerticalBlocks / (mBlockHeight / blockHeight));
- }
-
- private void applyLevelLimits() {
- int maxBlocksPerSecond = 0;
- int maxBlocks = 0;
- int maxBps = 0;
- int maxDPBBlocks = 0;
-
- int errors = ERROR_NONE_SUPPORTED;
- CodecProfileLevel[] profileLevels = mParent.profileLevels;
- String mime = mParent.getMime();
-
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- maxDPBBlocks = 396;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, DPB = 0;
- boolean supported = true;
- switch (profileLevel.level) {
- case CodecProfileLevel.AVCLevel1:
- MBPS = 1485; FS = 99; BR = 64; DPB = 396; break;
- case CodecProfileLevel.AVCLevel1b:
- MBPS = 1485; FS = 99; BR = 128; DPB = 396; break;
- case CodecProfileLevel.AVCLevel11:
- MBPS = 3000; FS = 396; BR = 192; DPB = 900; break;
- case CodecProfileLevel.AVCLevel12:
- MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break;
- case CodecProfileLevel.AVCLevel13:
- MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break;
- case CodecProfileLevel.AVCLevel2:
- MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break;
- case CodecProfileLevel.AVCLevel21:
- MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break;
- case CodecProfileLevel.AVCLevel22:
- MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break;
- case CodecProfileLevel.AVCLevel3:
- MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break;
- case CodecProfileLevel.AVCLevel31:
- MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break;
- case CodecProfileLevel.AVCLevel32:
- MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break;
- case CodecProfileLevel.AVCLevel4:
- MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break;
- case CodecProfileLevel.AVCLevel41:
- MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break;
- case CodecProfileLevel.AVCLevel42:
- MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break;
- case CodecProfileLevel.AVCLevel5:
- MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break;
- case CodecProfileLevel.AVCLevel51:
- MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break;
- case CodecProfileLevel.AVCLevel52:
- MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.AVCProfileHigh:
- BR *= 1250; break;
- case CodecProfileLevel.AVCProfileHigh10:
- BR *= 3000; break;
- case CodecProfileLevel.AVCProfileExtended:
- case CodecProfileLevel.AVCProfileHigh422:
- case CodecProfileLevel.AVCProfileHigh444:
- Log.w(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- // fall through - treat as base profile
- case CodecProfileLevel.AVCProfileBaseline:
- case CodecProfileLevel.AVCProfileMain:
- BR *= 1000; break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- BR *= 1000;
- }
- if (supported) {
- errors &= ~ERROR_NONE_SUPPORTED;
- }
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR, maxBps);
- maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
- }
-
- int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
- applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
- boolean supported = true;
- switch (profileLevel.profile) {
- case CodecProfileLevel.MPEG4ProfileSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG4Level0:
- FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
- case CodecProfileLevel.MPEG4Level1:
- FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
- case CodecProfileLevel.MPEG4Level0b:
- FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
- case CodecProfileLevel.MPEG4Level2:
- FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
- case CodecProfileLevel.MPEG4Level3:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
- case CodecProfileLevel.MPEG4Level4:
- case CodecProfileLevel.MPEG4Level4a:
- case CodecProfileLevel.MPEG4Level5:
- // While MPEG4 SP does not have level 4 or 5, some vendors
- // report it. Use the same limits as level 3, but mark as
- // unsupported.
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384;
- supported = false;
- break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
- switch (profileLevel.level) {
- case CodecProfileLevel.MPEG4Level0:
- case CodecProfileLevel.MPEG4Level1:
- FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
- case CodecProfileLevel.MPEG4Level2:
- FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
- case CodecProfileLevel.MPEG4Level3:
- FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
- // case CodecProfileLevel.MPEG4Level3b:
- // TODO: MPEG4 level 3b is not defined in OMX
- // MBPS = 11880; FS = 396; BR = 1500; break;
- case CodecProfileLevel.MPEG4Level4:
- case CodecProfileLevel.MPEG4Level4a:
- // TODO: MPEG4 level 4a is not defined in spec
- FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
- case CodecProfileLevel.MPEG4Level5:
- FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
- default:
- Log.w(TAG, "Unrecognized profile/level "
- + profileLevel.profile + "/"
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- break;
- case CodecProfileLevel.MPEG4ProfileMain: // 2-4
- case CodecProfileLevel.MPEG4ProfileNbit: // 2
- case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
- case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
- case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
- case CodecProfileLevel.MPEG4ProfileCore: // 1-2
- case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
- case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
- case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
- case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
- case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
- case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
- case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
- case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
- Log.i(TAG, "Unsupported profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNSUPPORTED;
- supported = false;
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- if (supported) {
- errors &= ~ERROR_NONE_SUPPORTED;
- }
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- }
- applyMacroBlockLimits(maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
- int maxWidth = 11, maxHeight = 9, maxRate = 15;
- maxBlocks = 99;
- maxBlocksPerSecond = 1485;
- maxBps = 64000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0;
- switch (profileLevel.level) {
- case CodecProfileLevel.H263Level10:
- FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level20:
- // only supports CIF, 0..QCIF
- FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level30:
- // only supports CIF, 0..QCIF
- FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level40:
- // only supports CIF, 0..QCIF
- FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level45:
- // only implies level 10 support
- FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
- case CodecProfileLevel.H263Level50:
- // only supports 50fps for H > 15
- FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
- case CodecProfileLevel.H263Level60:
- // only supports 50fps for H > 15
- FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
- case CodecProfileLevel.H263Level70:
- // only supports 50fps for H > 30
- FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
- default:
- Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
- + "/" + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.H263ProfileBackwardCompatible:
- case CodecProfileLevel.H263ProfileBaseline:
- case CodecProfileLevel.H263ProfileH320Coding:
- case CodecProfileLevel.H263ProfileHighCompression:
- case CodecProfileLevel.H263ProfileHighLatency:
- case CodecProfileLevel.H263ProfileInterlace:
- case CodecProfileLevel.H263ProfileInternet:
- case CodecProfileLevel.H263ProfileISWV2:
- case CodecProfileLevel.H263ProfileISWV3:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
- maxBlocks = Math.max(W * H, maxBlocks);
- maxBps = Math.max(BR * 64000, maxBps);
- maxWidth = Math.max(W, maxWidth);
- maxHeight = Math.max(H, maxHeight);
- maxRate = Math.max(FR, maxRate);
- }
- applyMacroBlockLimits(maxWidth, maxHeight,
- maxBlocks, maxBlocksPerSecond,
- 16 /* blockWidth */, 16 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- mFrameRateRange = Range.create(1, maxRate);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
- mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
- maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE;
-
- // TODO: set to 100Mbps for now, need a number for VPX
- maxBps = 100000000;
-
- // profile levels are not indicative for VPx, but verify
- // them nonetheless
- for (CodecProfileLevel profileLevel: profileLevels) {
- switch (profileLevel.level) {
- case CodecProfileLevel.VP8Level_Version0:
- case CodecProfileLevel.VP8Level_Version1:
- case CodecProfileLevel.VP8Level_Version2:
- case CodecProfileLevel.VP8Level_Version3:
- break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.VP8ProfileMain:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- errors &= ~ERROR_NONE_SUPPORTED;
- }
-
- final int blockSize =
- mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8;
- applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
- maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
- maxBlocks = 36864;
- maxBlocksPerSecond = maxBlocks * 15;
- maxBps = 128000;
- for (CodecProfileLevel profileLevel: profileLevels) {
- double FR = 0;
- int FS = 0;
- int BR = 0;
- switch (profileLevel.level) {
- case CodecProfileLevel.HEVCMainTierLevel1:
- case CodecProfileLevel.HEVCHighTierLevel1:
- FR = 15; FS = 36864; BR = 128; break;
- case CodecProfileLevel.HEVCMainTierLevel2:
- case CodecProfileLevel.HEVCHighTierLevel2:
- FR = 30; FS = 122880; BR = 1500; break;
- case CodecProfileLevel.HEVCMainTierLevel21:
- case CodecProfileLevel.HEVCHighTierLevel21:
- FR = 30; FS = 245760; BR = 3000; break;
- case CodecProfileLevel.HEVCMainTierLevel3:
- case CodecProfileLevel.HEVCHighTierLevel3:
- FR = 30; FS = 552960; BR = 6000; break;
- case CodecProfileLevel.HEVCMainTierLevel31:
- case CodecProfileLevel.HEVCHighTierLevel31:
- FR = 33.75; FS = 983040; BR = 10000; break;
- case CodecProfileLevel.HEVCMainTierLevel4:
- FR = 30; FS = 2228224; BR = 12000; break;
- case CodecProfileLevel.HEVCHighTierLevel4:
- FR = 30; FS = 2228224; BR = 30000; break;
- case CodecProfileLevel.HEVCMainTierLevel41:
- FR = 60; FS = 2228224; BR = 20000; break;
- case CodecProfileLevel.HEVCHighTierLevel41:
- FR = 60; FS = 2228224; BR = 50000; break;
- case CodecProfileLevel.HEVCMainTierLevel5:
- FR = 30; FS = 8912896; BR = 25000; break;
- case CodecProfileLevel.HEVCHighTierLevel5:
- FR = 30; FS = 8912896; BR = 100000; break;
- case CodecProfileLevel.HEVCMainTierLevel51:
- FR = 60; FS = 8912896; BR = 40000; break;
- case CodecProfileLevel.HEVCHighTierLevel51:
- FR = 60; FS = 8912896; BR = 160000; break;
- case CodecProfileLevel.HEVCMainTierLevel52:
- FR = 120; FS = 8912896; BR = 60000; break;
- case CodecProfileLevel.HEVCHighTierLevel52:
- FR = 120; FS = 8912896; BR = 240000; break;
- case CodecProfileLevel.HEVCMainTierLevel6:
- FR = 30; FS = 35651584; BR = 60000; break;
- case CodecProfileLevel.HEVCHighTierLevel6:
- FR = 30; FS = 35651584; BR = 240000; break;
- case CodecProfileLevel.HEVCMainTierLevel61:
- FR = 60; FS = 35651584; BR = 120000; break;
- case CodecProfileLevel.HEVCHighTierLevel61:
- FR = 60; FS = 35651584; BR = 480000; break;
- case CodecProfileLevel.HEVCMainTierLevel62:
- FR = 120; FS = 35651584; BR = 240000; break;
- case CodecProfileLevel.HEVCHighTierLevel62:
- FR = 120; FS = 35651584; BR = 800000; break;
- default:
- Log.w(TAG, "Unrecognized level "
- + profileLevel.level + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
- switch (profileLevel.profile) {
- case CodecProfileLevel.HEVCProfileMain:
- case CodecProfileLevel.HEVCProfileMain10:
- break;
- default:
- Log.w(TAG, "Unrecognized profile "
- + profileLevel.profile + " for " + mime);
- errors |= ERROR_UNRECOGNIZED;
- }
-
- /* DPB logic:
- if (width * height <= FS / 4) DPB = 16;
- else if (width * height <= FS / 2) DPB = 12;
- else if (width * height <= FS * 0.75) DPB = 8;
- else DPB = 6;
- */
-
- errors &= ~ERROR_NONE_SUPPORTED;
- maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
- maxBlocks = Math.max(FS, maxBlocks);
- maxBps = Math.max(BR * 1000, maxBps);
- }
-
- int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
- // CTBs are at least 8x8
- maxBlocks = Utils.divUp(maxBlocks, 8 * 8);
- maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8);
- maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8);
-
- applyMacroBlockLimits(
- maxLengthInBlocks, maxLengthInBlocks,
- maxBlocks, maxBlocksPerSecond,
- 8 /* blockWidth */, 8 /* blockHeight */,
- 1 /* widthAlignment */, 1 /* heightAlignment */);
- } else {
- Log.w(TAG, "Unsupported mime " + mime);
- // using minimal bitrate here. should be overriden by
- // info from media_codecs.xml
- maxBps = 64000;
- errors |= ERROR_UNSUPPORTED;
- }
- mBitrateRange = Range.create(1, maxBps);
- mParent.mError |= errors;
- }
- };
-
- VideoCapabilities mVideoCaps;
-
private boolean isVideo() {
return mVideoCaps != null;
}
@@ -1624,16 +393,28 @@
/**
* Returns the video capabilities or {@code null} if this is not a video codec.
*/
- public final VideoCapabilities getVideoCapabilities() {
+ public VideoCapabilities getVideoCapabilities() {
return mVideoCaps;
}
+ /** @hide */
+ public CodecCapabilities dup() {
+ return new CodecCapabilities(
+ // clone writable arrays
+ Arrays.copyOf(profileLevels, profileLevels.length),
+ Arrays.copyOf(colorFormats, colorFormats.length),
+ isEncoder(),
+ mFlagsVerified,
+ mDefaultFormat,
+ mCapabilitiesInfo);
+ }
+
/**
* 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.
*/
- public static final CodecCapabilities CreateFromProfileLevel(
+ public static CodecCapabilities CreateFromProfileLevel(
String mime, int profile, int level) {
CodecProfileLevel pl = new CodecProfileLevel();
pl.profile = profile;
@@ -1699,282 +480,1503 @@
// TODO restrict features by mFlagsVerified once all codecs reliably verify them
}
}
+ }
+
+ /**
+ * A class that supports querying the audio capabilities of a codec.
+ */
+ public static final class AudioCapabilities {
+ private static final String TAG = "AudioCapabilities";
+ private CodecCapabilities mParent;
+ private Range<Integer> mBitrateRange;
+
+ private int[] mSampleRates;
+ private Range<Integer>[] mSampleRateRanges;
+ private int mMaxInputChannelCount;
+
+ private static final int MAX_INPUT_CHANNEL_COUNT = 30;
/**
- * A class that supports querying the audio capabilities of a codec.
+ * Returns the range of supported bitrates in bits/second.
*/
- public static final class AudioCapabilities extends BaseCapabilities {
- private static final String TAG = "AudioCapabilities";
-
- private int[] mSampleRates;
- private Range<Integer>[] mSampleRateRanges;
- private int mMaxInputChannelCount;
-
- private static final int MAX_INPUT_CHANNEL_COUNT = 30;
-
- /**
- * Returns the array of supported sample rates if the codec
- * supports only discrete values. Otherwise, it returns
- * {@code null}. The array is sorted in ascending order.
- */
- public final int[] getSupportedSampleRates() {
- return Arrays.copyOf(mSampleRates, mSampleRates.length);
- }
-
- /**
- * Returns the array of supported sample rate ranges. The
- * array is sorted in ascending order, and the ranges are
- * distinct.
- */
- public final Range<Integer>[] getSupportedSampleRateRanges() {
- return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
- }
-
- /**
- * Returns the maximum number of input channels supported. The codec
- * supports any number of channels between 1 and this maximum value.
- */
- public final int getMaxInputChannelCount() {
- return mMaxInputChannelCount;
- }
-
- /* no public constructor */
- private AudioCapabilities() { }
-
- /** @hide */
- public static AudioCapabilities create(
- MediaFormat info, CodecCapabilities parent) {
- AudioCapabilities caps = new AudioCapabilities();
- caps.init(info, parent);
- return caps;
- }
-
- /** @hide */
- public void init(MediaFormat info, CodecCapabilities parent) {
- super.init(info, parent);
- initWithPlatformLimits();
- applyLevelLimits();
- parseFromInfo(info);
- }
-
- private void initWithPlatformLimits() {
- mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
- // mBitrateRange = Range.create(1, 320000);
- mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
- mSampleRates = null;
- }
-
- private boolean supports(Integer sampleRate, Integer inputChannels) {
- // channels and sample rates are checked orthogonally
- if (inputChannels != null &&
- (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
- return false;
- }
- if (sampleRate != null) {
- int ix = Utils.binarySearchDistinctRanges(
- mSampleRateRanges, sampleRate);
- if (ix < 0) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Query whether the sample rate is supported by the codec.
- */
- public final boolean isSampleRateSupported(int sampleRate) {
- return supports(sampleRate, null);
- }
-
- /** modifies rates */
- private void limitSampleRates(int[] rates) {
- Arrays.sort(rates);
- ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
- for (int rate: rates) {
- if (supports(rate, null /* channels */)) {
- ranges.add(Range.create(rate, rate));
- }
- }
- mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
- createDiscreteSampleRates();
- }
-
- private void createDiscreteSampleRates() {
- mSampleRates = new int[mSampleRateRanges.length];
- for (int i = 0; i < mSampleRateRanges.length; i++) {
- mSampleRates[i] = mSampleRateRanges[i].getLower();
- }
- }
-
- /** modifies rateRanges */
- private void limitSampleRates(Range<Integer>[] rateRanges) {
- sortDistinctRanges(rateRanges);
- mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
-
- // check if all values are discrete
- for (Range<Integer> range: mSampleRateRanges) {
- if (!range.getLower().equals(range.getUpper())) {
- mSampleRates = null;
- return;
- }
- }
- createDiscreteSampleRates();
- }
-
- private void applyLevelLimits() {
- int[] sampleRates = null;
- Range<Integer> sampleRateRange = null, bitRates = null;
- int maxChannels = 0;
- String mime = mParent.getMime();
-
- if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
- sampleRates = new int[] {
- 8000, 11025, 12000,
- 16000, 22050, 24000,
- 32000, 44100, 48000 };
- bitRates = Range.create(8000, 320000);
- maxChannels = 2;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
- sampleRates = new int[] { 8000 };
- bitRates = Range.create(4750, 12200);
- maxChannels = 1;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
- sampleRates = new int[] { 16000 };
- bitRates = Range.create(6600, 23850);
- maxChannels = 1;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
- sampleRates = new int[] {
- 7350, 8000,
- 11025, 12000, 16000,
- 22050, 24000, 32000,
- 44100, 48000, 64000,
- 88200, 96000 };
- bitRates = Range.create(8000, 510000);
- maxChannels = 48;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
- bitRates = Range.create(32000, 500000);
- sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
- maxChannels = 255;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
- bitRates = Range.create(6000, 510000);
- sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
- maxChannels = 255;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
- sampleRateRange = Range.create(1, 96000);
- bitRates = Range.create(1, 10000000);
- maxChannels = 8;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
- sampleRateRange = Range.create(1, 655350);
- // lossless codec, so bitrate is ignored
- maxChannels = 255;
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
- || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
- sampleRates = new int[] { 8000 };
- bitRates = Range.create(64000, 64000);
- // platform allows multiple channels for this format
- } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
- sampleRates = new int[] { 8000 };
- bitRates = Range.create(13000, 13000);
- maxChannels = 1;
- } else {
- Log.w(TAG, "Unsupported mime " + mime);
- mParent.mError |= ERROR_UNSUPPORTED;
- }
-
- // restrict ranges
- if (sampleRates != null) {
- limitSampleRates(sampleRates);
- } else if (sampleRateRange != null) {
- limitSampleRates(new Range[] { sampleRateRange });
- }
- applyLimits(maxChannels, bitRates);
- }
-
- private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
- mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
- .clamp(maxInputChannels);
- if (bitRates != null) {
- mBitrateRange = mBitrateRange.intersect(bitRates);
- }
- }
-
- private void parseFromInfo(MediaFormat info) {
- int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
- Range<Integer> bitRates = POSITIVE_INTEGERS;
-
- if (info.containsKey("sample-rate-ranges")) {
- String[] rateStrings = info.getString("sample-rate-ranges").split(",");
- Range<Integer>[] rateRanges = new Range[rateStrings.length];
- for (int i = 0; i < rateStrings.length; i++) {
- rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
- }
- limitSampleRates(rateRanges);
- }
- if (info.containsKey("max-channel-count")) {
- maxInputChannels = Utils.parseIntSafely(
- info.getString("max-channel-count"), maxInputChannels);
- }
- if (info.containsKey("bitrate-range")) {
- bitRates = bitRates.intersect(
- Utils.parseIntRange(info.getString("bitrate"), bitRates));
- }
- applyLimits(maxInputChannels, bitRates);
- }
-
- /** @hide */
- public void setDefaultFormat(MediaFormat format) {
- // report settings that have only a single choice
- if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
- format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
- }
- if (mMaxInputChannelCount == 1) {
- // mono-only format
- format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
- }
- if (mSampleRates != null && mSampleRates.length == 1) {
- format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
- }
- }
-
- /** @hide */
- public boolean supportsFormat(MediaFormat format) {
- Map<String, Object> map = format.getMap();
- Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
- Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
- if (!supports(sampleRate, channels)) {
- return false;
- }
-
- // nothing to do for:
- // KEY_CHANNEL_MASK: codecs don't get this
- // KEY_IS_ADTS: required feature for all AAC decoders
- return true;
- }
- };
-
- AudioCapabilities mAudioCaps;
- private boolean isAudio() {
- return mAudioCaps != null;
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
}
/**
- * Returns the audio capabilities or {@code null} if this is not an audio codec.
+ * Returns the array of supported sample rates if the codec
+ * supports only discrete values. Otherwise, it returns
+ * {@code null}. The array is sorted in ascending order.
*/
- public final AudioCapabilities getAudioCapabilities() {
- return mAudioCaps;
+ public int[] getSupportedSampleRates() {
+ return Arrays.copyOf(mSampleRates, mSampleRates.length);
+ }
+
+ /**
+ * Returns the array of supported sample rate ranges. The
+ * array is sorted in ascending order, and the ranges are
+ * distinct.
+ */
+ public Range<Integer>[] getSupportedSampleRateRanges() {
+ return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length);
+ }
+
+ /**
+ * Returns the maximum number of input channels supported. The codec
+ * supports any number of channels between 1 and this maximum value.
+ */
+ public int getMaxInputChannelCount() {
+ return mMaxInputChannelCount;
+ }
+
+ /* no public constructor */
+ private AudioCapabilities() { }
+
+ /** @hide */
+ public static AudioCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ AudioCapabilities caps = new AudioCapabilities();
+ caps.init(info, parent);
+ return caps;
}
/** @hide */
- public CodecCapabilities dup() {
- return new CodecCapabilities(
- // clone writable arrays
- Arrays.copyOf(profileLevels, profileLevels.length),
- Arrays.copyOf(colorFormats, colorFormats.length),
- isEncoder(),
- mFlagsVerified,
- mDefaultFormat,
- mCapabilitiesInfo);
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ private void initWithPlatformLimits() {
+ mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+ mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
+ // mBitrateRange = Range.create(1, 320000);
+ mSampleRateRanges = new Range[] { Range.create(8000, 96000) };
+ mSampleRates = null;
+ }
+
+ private boolean supports(Integer sampleRate, Integer inputChannels) {
+ // channels and sample rates are checked orthogonally
+ if (inputChannels != null &&
+ (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
+ return false;
+ }
+ if (sampleRate != null) {
+ int ix = Utils.binarySearchDistinctRanges(
+ mSampleRateRanges, sampleRate);
+ if (ix < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Query whether the sample rate is supported by the codec.
+ */
+ public boolean isSampleRateSupported(int sampleRate) {
+ return supports(sampleRate, null);
+ }
+
+ /** modifies rates */
+ private void limitSampleRates(int[] rates) {
+ Arrays.sort(rates);
+ ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
+ for (int rate: rates) {
+ if (supports(rate, null /* channels */)) {
+ ranges.add(Range.create(rate, rate));
+ }
+ }
+ mSampleRateRanges = ranges.toArray(new Range[ranges.size()]);
+ createDiscreteSampleRates();
+ }
+
+ private void createDiscreteSampleRates() {
+ mSampleRates = new int[mSampleRateRanges.length];
+ for (int i = 0; i < mSampleRateRanges.length; i++) {
+ mSampleRates[i] = mSampleRateRanges[i].getLower();
+ }
+ }
+
+ /** modifies rateRanges */
+ private void limitSampleRates(Range<Integer>[] rateRanges) {
+ sortDistinctRanges(rateRanges);
+ mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges);
+
+ // check if all values are discrete
+ for (Range<Integer> range: mSampleRateRanges) {
+ if (!range.getLower().equals(range.getUpper())) {
+ mSampleRates = null;
+ return;
+ }
+ }
+ createDiscreteSampleRates();
+ }
+
+ private void applyLevelLimits() {
+ int[] sampleRates = null;
+ Range<Integer> sampleRateRange = null, bitRates = null;
+ int maxChannels = 0;
+ String mime = mParent.getMimeType();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) {
+ sampleRates = new int[] {
+ 8000, 11025, 12000,
+ 16000, 22050, 24000,
+ 32000, 44100, 48000 };
+ bitRates = Range.create(8000, 320000);
+ maxChannels = 2;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(4750, 12200);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+ sampleRates = new int[] { 16000 };
+ bitRates = Range.create(6600, 23850);
+ maxChannels = 1;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+ sampleRates = new int[] {
+ 7350, 8000,
+ 11025, 12000, 16000,
+ 22050, 24000, 32000,
+ 44100, 48000, 64000,
+ 88200, 96000 };
+ bitRates = Range.create(8000, 510000);
+ maxChannels = 48;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
+ bitRates = Range.create(32000, 500000);
+ sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
+ bitRates = Range.create(6000, 510000);
+ sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 };
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) {
+ sampleRateRange = Range.create(1, 96000);
+ bitRates = Range.create(1, 10000000);
+ maxChannels = 8;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ sampleRateRange = Range.create(1, 655350);
+ // lossless codec, so bitrate is ignored
+ maxChannels = 255;
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(64000, 64000);
+ // platform allows multiple channels for this format
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+ sampleRates = new int[] { 8000 };
+ bitRates = Range.create(13000, 13000);
+ maxChannels = 1;
+ } else {
+ Log.w(TAG, "Unsupported mime " + mime);
+ mParent.mError |= ERROR_UNSUPPORTED;
+ }
+
+ // restrict ranges
+ if (sampleRates != null) {
+ limitSampleRates(sampleRates);
+ } else if (sampleRateRange != null) {
+ limitSampleRates(new Range[] { sampleRateRange });
+ }
+ applyLimits(maxChannels, bitRates);
+ }
+
+ private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
+ mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
+ .clamp(maxInputChannels);
+ if (bitRates != null) {
+ mBitrateRange = mBitrateRange.intersect(bitRates);
+ }
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+ Range<Integer> bitRates = POSITIVE_INTEGERS;
+
+ if (info.containsKey("sample-rate-ranges")) {
+ String[] rateStrings = info.getString("sample-rate-ranges").split(",");
+ Range<Integer>[] rateRanges = new Range[rateStrings.length];
+ for (int i = 0; i < rateStrings.length; i++) {
+ rateRanges[i] = Utils.parseIntRange(rateStrings[i], null);
+ }
+ limitSampleRates(rateRanges);
+ }
+ if (info.containsKey("max-channel-count")) {
+ maxInputChannels = Utils.parseIntSafely(
+ info.getString("max-channel-count"), maxInputChannels);
+ }
+ if (info.containsKey("bitrate-range")) {
+ bitRates = bitRates.intersect(
+ Utils.parseIntRange(info.getString("bitrate"), bitRates));
+ }
+ applyLimits(maxInputChannels, bitRates);
+ }
+
+ /** @hide */
+ public void setDefaultFormat(MediaFormat format) {
+ // report settings that have only a single choice
+ if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
+ format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
+ }
+ if (mMaxInputChannelCount == 1) {
+ // mono-only format
+ format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+ }
+ if (mSampleRates != null && mSampleRates.length == 1) {
+ format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]);
+ }
+ }
+
+ /** @hide */
+ public boolean supportsFormat(MediaFormat format) {
+ Map<String, Object> map = format.getMap();
+ Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE);
+ Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT);
+ if (!supports(sampleRate, channels)) {
+ return false;
+ }
+
+ // nothing to do for:
+ // KEY_CHANNEL_MASK: codecs don't get this
+ // KEY_IS_ADTS: required feature for all AAC decoders
+ return true;
+ }
+ }
+
+ /**
+ * A class that supports querying the video capabilities of a codec.
+ */
+ public static final class VideoCapabilities {
+ private static final String TAG = "VideoCapabilities";
+ private CodecCapabilities mParent;
+ private Range<Integer> mBitrateRange;
+
+ private Range<Integer> mHeightRange;
+ private Range<Integer> mWidthRange;
+ private Range<Integer> mBlockCountRange;
+ private Range<Integer> mHorizontalBlockRange;
+ private Range<Integer> mVerticalBlockRange;
+ private Range<Rational> mAspectRatioRange;
+ private Range<Rational> mBlockAspectRatioRange;
+ private Range<Long> mBlocksPerSecondRange;
+ private Range<Integer> mFrameRateRange;
+
+ private int mBlockWidth;
+ private int mBlockHeight;
+ private int mWidthAlignment;
+ private int mHeightAlignment;
+ private int mSmallerDimensionUpperLimit;
+
+ /**
+ * Returns the range of supported bitrates in bits/second.
+ */
+ public Range<Integer> getBitrateRange() {
+ return mBitrateRange;
+ }
+
+ /**
+ * Returns the range of supported video widths.
+ */
+ public Range<Integer> getSupportedWidths() {
+ return mWidthRange;
+ }
+
+ /**
+ * Returns the range of supported video heights.
+ */
+ public Range<Integer> getSupportedHeights() {
+ return mHeightRange;
+ }
+
+ /**
+ * Returns the alignment requirement for video width (in pixels).
+ *
+ * This is a power-of-2 value that video width must be a
+ * multiple of.
+ */
+ public int getWidthAlignment() {
+ return mWidthAlignment;
+ }
+
+ /**
+ * Returns the alignment requirement for video height (in pixels).
+ *
+ * This is a power-of-2 value that video height must be a
+ * multiple of.
+ */
+ public int getHeightAlignment() {
+ return mHeightAlignment;
+ }
+
+ /**
+ * Return the upper limit on the smaller dimension of width or height.
+ * <p></p>
+ * Some codecs have a limit on the smaller dimension, whether it be
+ * the width or the height. E.g. a codec may only be able to handle
+ * up to 1920x1080 both in landscape and portrait mode (1080x1920).
+ * In this case the maximum width and height are both 1920, but the
+ * smaller dimension limit will be 1080. For other codecs, this is
+ * {@code Math.min(getSupportedWidths().getUpper(),
+ * getSupportedHeights().getUpper())}.
+ *
+ * @hide
+ */
+ public int getSmallerDimensionUpperLimit() {
+ return mSmallerDimensionUpperLimit;
+ }
+
+ /**
+ * Returns the range of supported frame rates.
+ * <p>
+ * This is not a performance indicator. Rather, it expresses the
+ * limits specified in the coding standard, based on the complexities
+ * of encoding material for later playback at a certain frame rate,
+ * or the decoding of such material in non-realtime.
+ */
+ public Range<Integer> getSupportedFrameRates() {
+ return mFrameRateRange;
+ }
+
+ /**
+ * Returns the range of supported video widths for a video height.
+ * @param height the height of the video
+ */
+ public Range<Integer> getSupportedWidthsFor(int height) {
+ try {
+ Range<Integer> range = mWidthRange;
+ if (!mHeightRange.contains(height)
+ || (height % mHeightAlignment) != 0) {
+ throw new IllegalArgumentException("unsupported height");
+ }
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+
+ // constrain by block count and by block aspect ratio
+ final int minWidthInBlocks = Math.max(
+ Utils.divUp(mBlockCountRange.getLower(), heightInBlocks),
+ (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue()
+ * heightInBlocks));
+ final int maxWidthInBlocks = Math.min(
+ mBlockCountRange.getUpper() / heightInBlocks,
+ (int)(mBlockAspectRatioRange.getUpper().doubleValue()
+ * heightInBlocks));
+ range = range.intersect(
+ (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment,
+ maxWidthInBlocks * mBlockWidth);
+
+ // constrain by smaller dimension limit
+ if (height > mSmallerDimensionUpperLimit) {
+ range = range.intersect(1, mSmallerDimensionUpperLimit);
+ }
+
+ // constrain by aspect ratio
+ range = range.intersect(
+ (int)Math.ceil(mAspectRatioRange.getLower().doubleValue()
+ * height),
+ (int)(mAspectRatioRange.getUpper().doubleValue() * height));
+ return range;
+ } catch (IllegalArgumentException e) {
+ // should not be here
+ Log.w(TAG, "could not get supported widths for " + height , e);
+ throw new IllegalArgumentException("unsupported height");
+ }
+ }
+
+ /**
+ * Returns the range of supported video heights for a video width
+ * @param width the width of the video
+ */
+ public Range<Integer> getSupportedHeightsFor(int width) {
+ try {
+ Range<Integer> range = mHeightRange;
+ if (!mWidthRange.contains(width)
+ || (width % mWidthAlignment) != 0) {
+ throw new IllegalArgumentException("unsupported width");
+ }
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+
+ // constrain by block count and by block aspect ratio
+ final int minHeightInBlocks = Math.max(
+ Utils.divUp(mBlockCountRange.getLower(), widthInBlocks),
+ (int)Math.ceil(widthInBlocks /
+ mBlockAspectRatioRange.getUpper().doubleValue()));
+ final int maxHeightInBlocks = Math.min(
+ mBlockCountRange.getUpper() / widthInBlocks,
+ (int)(widthInBlocks /
+ mBlockAspectRatioRange.getLower().doubleValue()));
+ range = range.intersect(
+ (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment,
+ maxHeightInBlocks * mBlockHeight);
+
+ // constrain by smaller dimension limit
+ if (width > mSmallerDimensionUpperLimit) {
+ range = range.intersect(1, mSmallerDimensionUpperLimit);
+ }
+
+ // constrain by aspect ratio
+ range = range.intersect(
+ (int)Math.ceil(width /
+ mAspectRatioRange.getUpper().doubleValue()),
+ (int)(width / mAspectRatioRange.getLower().doubleValue()));
+ return range;
+ } catch (IllegalArgumentException e) {
+ // should not be here
+ Log.w(TAG, "could not get supported heights for " + width , e);
+ throw new IllegalArgumentException("unsupported width");
+ }
+ }
+
+ /**
+ * Returns the range of supported video frame rates for a video size.
+ * <p>
+ * This is not a performance indicator. Rather, it expresses the limits specified in
+ * the coding standard, based on the complexities of encoding material of a given
+ * size for later playback at a certain frame rate, or the decoding of such material
+ * in non-realtime.
+
+ * @param width the width of the video
+ * @param height the height of the video
+ */
+ public Range<Double> getSupportedFrameRatesFor(int width, int height) {
+ Range<Integer> range = mHeightRange;
+ if (!supports(width, height, null)) {
+ throw new IllegalArgumentException("unsupported size");
+ }
+ final int blockCount =
+ Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight);
+
+ return Range.create(
+ Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount,
+ (double) mFrameRateRange.getLower()),
+ Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount,
+ (double) mFrameRateRange.getUpper()));
+ }
+
+ /**
+ * Returns whether a given video size ({@code width} and
+ * {@code height}) and {@code frameRate} combination is supported.
+ */
+ public boolean areSizeAndRateSupported(
+ int width, int height, double frameRate) {
+ return supports(width, height, frameRate);
+ }
+
+ /**
+ * Returns whether a given video size ({@code width} and
+ * {@code height}) is supported.
+ */
+ public boolean isSizeSupported(int width, int height) {
+ return supports(width, height, null);
+ }
+
+ private boolean supports(
+ Integer width, Integer height, Double rate) {
+ boolean ok = true;
+
+ if (ok && width != null) {
+ ok = mWidthRange.contains(width)
+ && (width % mWidthAlignment == 0);
+ }
+ if (ok && height != null) {
+ ok = mHeightRange.contains(height)
+ && (height % mHeightAlignment == 0);
+ }
+ if (ok && rate != null) {
+ ok = mFrameRateRange.contains(Utils.intRangeFor(rate));
+ }
+ if (ok && height != null && width != null) {
+ ok = Math.min(height, width) <= mSmallerDimensionUpperLimit;
+
+ final int widthInBlocks = Utils.divUp(width, mBlockWidth);
+ final int heightInBlocks = Utils.divUp(height, mBlockHeight);
+ final int blockCount = widthInBlocks * heightInBlocks;
+ ok = ok && mBlockCountRange.contains(blockCount)
+ && mBlockAspectRatioRange.contains(
+ new Rational(widthInBlocks, heightInBlocks))
+ && mAspectRatioRange.contains(new Rational(width, height));
+ if (ok && rate != null) {
+ double blocksPerSec = blockCount * rate;
+ ok = mBlocksPerSecondRange.contains(
+ Utils.longRangeFor(blocksPerSec));
+ }
+ }
+ return ok;
+ }
+
+ /**
+ * @hide
+ * @throws java.lang.ClassCastException */
+ public boolean supportsFormat(MediaFormat format) {
+ final Map<String, Object> map = format.getMap();
+ Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH);
+ Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT);
+ Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE);
+
+ // we ignore color-format for now as it is not reliably reported by codec
+
+ return supports(width, height, rate);
+ }
+
+ /* no public constructor */
+ private VideoCapabilities() { }
+
+ /** @hide */
+ public static VideoCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ VideoCapabilities caps = new VideoCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ /** @hide */
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ mParent = parent;
+ initWithPlatformLimits();
+ applyLevelLimits();
+ parseFromInfo(info);
+ updateLimits();
+ }
+
+ /** @hide */
+ public Size getBlockSize() {
+ return new Size(mBlockWidth, mBlockHeight);
+ }
+
+ /** @hide */
+ public Range<Integer> getBlockCountRange() {
+ return mBlockCountRange;
+ }
+
+ /** @hide */
+ public Range<Long> getBlocksPerSecondRange() {
+ return mBlocksPerSecondRange;
+ }
+
+ /** @hide */
+ public Range<Rational> getAspectRatioRange(boolean blocks) {
+ return blocks ? mBlockAspectRatioRange : mAspectRatioRange;
+ }
+
+ private void initWithPlatformLimits() {
+ mBitrateRange = Range.create(0, Integer.MAX_VALUE);
+
+ mWidthRange = SIZE_RANGE;
+ mHeightRange = SIZE_RANGE;
+ mFrameRateRange = FRAME_RATE_RANGE;
+
+ mHorizontalBlockRange = SIZE_RANGE;
+ mVerticalBlockRange = SIZE_RANGE;
+
+ // full positive ranges are supported as these get calculated
+ mBlockCountRange = POSITIVE_INTEGERS;
+ mBlocksPerSecondRange = POSITIVE_LONGS;
+
+ mBlockAspectRatioRange = POSITIVE_RATIONALS;
+ mAspectRatioRange = POSITIVE_RATIONALS;
+
+ // YUV 4:2:0 requires 2:2 alignment
+ mWidthAlignment = 2;
+ mHeightAlignment = 2;
+ mBlockWidth = 2;
+ mBlockHeight = 2;
+ mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper();
+ }
+
+ private void parseFromInfo(MediaFormat info) {
+ final Map<String, Object> map = info.getMap();
+ Size blockSize = new Size(mBlockWidth, mBlockHeight);
+ Size alignment = new Size(mWidthAlignment, mHeightAlignment);
+ Range<Integer> counts = null, widths = null, heights = null;
+ Range<Integer> frameRates = null;
+ Range<Long> blockRates = null;
+ Range<Rational> ratios = null, blockRatios = null;
+
+ blockSize = Utils.parseSize(map.get("block-size"), blockSize);
+ alignment = Utils.parseSize(map.get("alignment"), alignment);
+ counts = Utils.parseIntRange(map.get("block-count-range"), null);
+ blockRates =
+ Utils.parseLongRange(map.get("blocks-per-second-range"), null);
+ {
+ Object o = map.get("size-range");
+ Pair<Size, Size> sizeRange = Utils.parseSizeRange(o);
+ if (sizeRange != null) {
+ try {
+ widths = Range.create(
+ sizeRange.first.getWidth(),
+ sizeRange.second.getWidth());
+ heights = Range.create(
+ sizeRange.first.getHeight(),
+ sizeRange.second.getHeight());
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "could not parse size range '" + o + "'");
+ widths = null;
+ heights = null;
+ }
+ }
+ }
+ // for now this just means using the smaller max size as 2nd
+ // upper limit.
+ // for now we are keeping the profile specific "width/height
+ // in macroblocks" limits.
+ if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) {
+ if (widths != null) {
+ mSmallerDimensionUpperLimit =
+ Math.min(widths.getUpper(), heights.getUpper());
+ widths = heights = widths.extend(heights);
+ } else {
+ Log.w(TAG, "feature can-swap-width-height is best used with size-range");
+ mSmallerDimensionUpperLimit =
+ Math.min(mWidthRange.getUpper(), mHeightRange.getUpper());
+ mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange);
+ }
+ }
+
+ ratios = Utils.parseRationalRange(
+ map.get("block-aspect-ratio-range"), null);
+ blockRatios = Utils.parseRationalRange(
+ map.get("pixel-aspect-ratio-range"), null);
+ frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null);
+ if (frameRates != null) {
+ try {
+ frameRates = frameRates.intersect(FRAME_RATE_RANGE);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "frame rate range (" + frameRates
+ + ") is out of limits: " + FRAME_RATE_RANGE);
+ frameRates = null;
+ }
+ }
+
+ checkPowerOfTwo(
+ blockSize.getWidth(), "block-size width must be power of two");
+ checkPowerOfTwo(
+ blockSize.getHeight(), "block-size height must be power of two");
+
+ checkPowerOfTwo(
+ alignment.getWidth(), "alignment width must be power of two");
+ checkPowerOfTwo(
+ alignment.getHeight(), "alignment height must be power of two");
+
+ // update block-size and alignment
+ applyMacroBlockLimits(
+ Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(),
+ alignment.getWidth(), alignment.getHeight());
+
+ if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
+ // codec supports profiles that we don't know.
+ // Use supplied values clipped to platform limits
+ if (widths != null) {
+ mWidthRange = SIZE_RANGE.intersect(widths);
+ }
+ if (heights != null) {
+ mHeightRange = SIZE_RANGE.intersect(heights);
+ }
+ if (counts != null) {
+ mBlockCountRange = POSITIVE_INTEGERS.intersect(
+ Utils.factorRange(counts, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRates != null) {
+ mBlocksPerSecondRange = POSITIVE_LONGS.intersect(
+ Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRatios != null) {
+ mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect(
+ Utils.scaleRange(blockRatios,
+ mBlockHeight / blockSize.getHeight(),
+ mBlockWidth / blockSize.getWidth()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates);
+ }
+ } else {
+ // no unsupported profile/levels, so restrict values to known limits
+ if (widths != null) {
+ mWidthRange = mWidthRange.intersect(widths);
+ }
+ if (heights != null) {
+ mHeightRange = mHeightRange.intersect(heights);
+ }
+ if (counts != null) {
+ mBlockCountRange = mBlockCountRange.intersect(
+ Utils.factorRange(counts, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRates != null) {
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ Utils.factorRange(blockRates, mBlockWidth * mBlockHeight
+ / blockSize.getWidth() / blockSize.getHeight()));
+ }
+ if (blockRatios != null) {
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ Utils.scaleRange(blockRatios,
+ mBlockHeight / blockSize.getHeight(),
+ mBlockWidth / blockSize.getWidth()));
+ }
+ if (ratios != null) {
+ mAspectRatioRange = mAspectRatioRange.intersect(ratios);
+ }
+ if (frameRates != null) {
+ mFrameRateRange = mFrameRateRange.intersect(frameRates);
+ }
+ }
+ updateLimits();
+ }
+
+ private void applyBlockLimits(
+ int blockWidth, int blockHeight,
+ Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) {
+ checkPowerOfTwo(blockWidth, "blockWidth must be a power of two");
+ checkPowerOfTwo(blockHeight, "blockHeight must be a power of two");
+
+ final int newBlockWidth = Math.max(blockWidth, mBlockWidth);
+ final int newBlockHeight = Math.max(blockHeight, mBlockHeight);
+
+ // factor will always be a power-of-2
+ int factor =
+ newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight;
+ if (factor != 1) {
+ mBlockCountRange = Utils.factorRange(mBlockCountRange, factor);
+ mBlocksPerSecondRange = Utils.factorRange(
+ mBlocksPerSecondRange, factor);
+ mBlockAspectRatioRange = Utils.scaleRange(
+ mBlockAspectRatioRange,
+ newBlockHeight / mBlockHeight,
+ newBlockWidth / mBlockWidth);
+ mHorizontalBlockRange = Utils.factorRange(
+ mHorizontalBlockRange, newBlockWidth / mBlockWidth);
+ mVerticalBlockRange = Utils.factorRange(
+ mVerticalBlockRange, newBlockHeight / mBlockHeight);
+ }
+ factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight;
+ if (factor != 1) {
+ counts = Utils.factorRange(counts, factor);
+ rates = Utils.factorRange(rates, factor);
+ ratios = Utils.scaleRange(
+ ratios, newBlockHeight / blockHeight,
+ newBlockWidth / blockWidth);
+ }
+ mBlockCountRange = mBlockCountRange.intersect(counts);
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates);
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios);
+ mBlockWidth = newBlockWidth;
+ mBlockHeight = newBlockHeight;
+ }
+
+ private void applyAlignment(int widthAlignment, int heightAlignment) {
+ checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two");
+ checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two");
+
+ if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) {
+ // maintain assumption that 0 < alignment <= block-size
+ applyBlockLimits(
+ Math.max(widthAlignment, mBlockWidth),
+ Math.max(heightAlignment, mBlockHeight),
+ POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS);
+ }
+
+ mWidthAlignment = Math.max(widthAlignment, mWidthAlignment);
+ mHeightAlignment = Math.max(heightAlignment, mHeightAlignment);
+
+ mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment);
+ mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment);
+ }
+
+ private void updateLimits() {
+ // pixels -> blocks <- counts
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Utils.factorRange(mWidthRange, mBlockWidth));
+ mHorizontalBlockRange = mHorizontalBlockRange.intersect(
+ Range.create(
+ mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(),
+ mBlockCountRange.getUpper() / mVerticalBlockRange.getLower()));
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Utils.factorRange(mHeightRange, mBlockHeight));
+ mVerticalBlockRange = mVerticalBlockRange.intersect(
+ Range.create(
+ mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(),
+ mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower()));
+ mBlockCountRange = mBlockCountRange.intersect(
+ Range.create(
+ mHorizontalBlockRange.getLower()
+ * mVerticalBlockRange.getLower(),
+ mHorizontalBlockRange.getUpper()
+ * mVerticalBlockRange.getUpper()));
+ mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(
+ new Rational(
+ mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()),
+ new Rational(
+ mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower()));
+
+ // blocks -> pixels
+ mWidthRange = mWidthRange.intersect(
+ (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment,
+ mHorizontalBlockRange.getUpper() * mBlockWidth);
+ mHeightRange = mHeightRange.intersect(
+ (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment,
+ mVerticalBlockRange.getUpper() * mBlockHeight);
+ mAspectRatioRange = mAspectRatioRange.intersect(
+ new Rational(mWidthRange.getLower(), mHeightRange.getUpper()),
+ new Rational(mWidthRange.getUpper(), mHeightRange.getLower()));
+
+ mSmallerDimensionUpperLimit = Math.min(
+ mSmallerDimensionUpperLimit,
+ Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()));
+
+ // blocks -> rate
+ mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(
+ mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(),
+ mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper());
+ mFrameRateRange = mFrameRateRange.intersect(
+ (int)(mBlocksPerSecondRange.getLower()
+ / mBlockCountRange.getUpper()),
+ (int)(mBlocksPerSecondRange.getUpper()
+ / (double)mBlockCountRange.getLower()));
+ }
+
+ private void applyMacroBlockLimits(
+ int maxHorizontalBlocks, int maxVerticalBlocks,
+ int maxBlocks, long maxBlocksPerSecond,
+ int blockWidth, int blockHeight,
+ int widthAlignment, int heightAlignment) {
+ applyAlignment(widthAlignment, heightAlignment);
+ applyBlockLimits(
+ blockWidth, blockHeight, Range.create(1, maxBlocks),
+ Range.create(1L, maxBlocksPerSecond),
+ Range.create(
+ new Rational(1, maxVerticalBlocks),
+ new Rational(maxHorizontalBlocks, 1)));
+ mHorizontalBlockRange =
+ mHorizontalBlockRange.intersect(
+ 1, maxHorizontalBlocks / (mBlockWidth / blockWidth));
+ mVerticalBlockRange =
+ mVerticalBlockRange.intersect(
+ 1, maxVerticalBlocks / (mBlockHeight / blockHeight));
+ }
+
+ private void applyLevelLimits() {
+ int maxBlocksPerSecond = 0;
+ int maxBlocks = 0;
+ int maxBps = 0;
+ int maxDPBBlocks = 0;
+
+ int errors = ERROR_NONE_SUPPORTED;
+ CodecProfileLevel[] profileLevels = mParent.profileLevels;
+ String mime = mParent.getMimeType();
+
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ maxDPBBlocks = 396;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, DPB = 0;
+ boolean supported = true;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.AVCLevel1:
+ MBPS = 1485; FS = 99; BR = 64; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel1b:
+ MBPS = 1485; FS = 99; BR = 128; DPB = 396; break;
+ case CodecProfileLevel.AVCLevel11:
+ MBPS = 3000; FS = 396; BR = 192; DPB = 900; break;
+ case CodecProfileLevel.AVCLevel12:
+ MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel13:
+ MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel2:
+ MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break;
+ case CodecProfileLevel.AVCLevel21:
+ MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break;
+ case CodecProfileLevel.AVCLevel22:
+ MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel3:
+ MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break;
+ case CodecProfileLevel.AVCLevel31:
+ MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break;
+ case CodecProfileLevel.AVCLevel32:
+ MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break;
+ case CodecProfileLevel.AVCLevel4:
+ MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel41:
+ MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break;
+ case CodecProfileLevel.AVCLevel42:
+ MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break;
+ case CodecProfileLevel.AVCLevel5:
+ MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break;
+ case CodecProfileLevel.AVCLevel51:
+ MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break;
+ case CodecProfileLevel.AVCLevel52:
+ MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.AVCProfileHigh:
+ BR *= 1250; break;
+ case CodecProfileLevel.AVCProfileHigh10:
+ BR *= 3000; break;
+ case CodecProfileLevel.AVCProfileExtended:
+ case CodecProfileLevel.AVCProfileHigh422:
+ case CodecProfileLevel.AVCProfileHigh444:
+ Log.w(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ // fall through - treat as base profile
+ case CodecProfileLevel.AVCProfileBaseline:
+ case CodecProfileLevel.AVCProfileMain:
+ BR *= 1000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ BR *= 1000;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR, maxBps);
+ maxDPBBlocks = Math.max(maxDPBBlocks, DPB);
+ }
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ boolean supported = true;
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.MPEG4ProfileSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break;
+ case CodecProfileLevel.MPEG4Level0b:
+ FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level4:
+ case CodecProfileLevel.MPEG4Level4a:
+ case CodecProfileLevel.MPEG4Level5:
+ // While MPEG4 SP does not have level 4 or 5, some vendors
+ // report it. Use the same limits as level 3, but mark as
+ // unsupported.
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileAdvancedSimple:
+ switch (profileLevel.level) {
+ case CodecProfileLevel.MPEG4Level0:
+ case CodecProfileLevel.MPEG4Level1:
+ FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break;
+ case CodecProfileLevel.MPEG4Level2:
+ FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break;
+ case CodecProfileLevel.MPEG4Level3:
+ FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break;
+ // case CodecProfileLevel.MPEG4Level3b:
+ // TODO: MPEG4 level 3b is not defined in OMX
+ // MBPS = 11880; FS = 396; BR = 1500; break;
+ case CodecProfileLevel.MPEG4Level4:
+ case CodecProfileLevel.MPEG4Level4a:
+ // TODO: MPEG4 level 4a is not defined in spec
+ FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break;
+ case CodecProfileLevel.MPEG4Level5:
+ FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level "
+ + profileLevel.profile + "/"
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ break;
+ case CodecProfileLevel.MPEG4ProfileMain: // 2-4
+ case CodecProfileLevel.MPEG4ProfileNbit: // 2
+ case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4
+ case CodecProfileLevel.MPEG4ProfileCore: // 1-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4
+ case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2
+ case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3
+ case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2
+ case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2
+ case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1
+ case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2
+ case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2
+ Log.i(TAG, "Unsupported profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNSUPPORTED;
+ supported = false;
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ if (supported) {
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = mFrameRateRange.intersect(12, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) {
+ int maxWidth = 11, maxHeight = 9, maxRate = 15;
+ maxBlocks = 99;
+ maxBlocksPerSecond = 1485;
+ maxBps = 64000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.H263Level10:
+ FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level20:
+ // only supports CIF, 0..QCIF
+ FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level30:
+ // only supports CIF, 0..QCIF
+ FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level40:
+ // only supports CIF, 0..QCIF
+ FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level45:
+ // only implies level 10 support
+ FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break;
+ case CodecProfileLevel.H263Level50:
+ // only supports 50fps for H > 15
+ FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level60:
+ // only supports 50fps for H > 15
+ FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break;
+ case CodecProfileLevel.H263Level70:
+ // only supports 50fps for H > 30
+ FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break;
+ default:
+ Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile
+ + "/" + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.H263ProfileBackwardCompatible:
+ case CodecProfileLevel.H263ProfileBaseline:
+ case CodecProfileLevel.H263ProfileH320Coding:
+ case CodecProfileLevel.H263ProfileHighCompression:
+ case CodecProfileLevel.H263ProfileHighLatency:
+ case CodecProfileLevel.H263ProfileInterlace:
+ case CodecProfileLevel.H263ProfileInternet:
+ case CodecProfileLevel.H263ProfileISWV2:
+ case CodecProfileLevel.H263ProfileISWV3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond);
+ maxBlocks = Math.max(W * H, maxBlocks);
+ maxBps = Math.max(BR * 64000, maxBps);
+ maxWidth = Math.max(W, maxWidth);
+ maxHeight = Math.max(H, maxHeight);
+ maxRate = Math.max(FR, maxRate);
+ }
+ applyMacroBlockLimits(maxWidth, maxHeight,
+ maxBlocks, maxBlocksPerSecond,
+ 16 /* blockWidth */, 16 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ mFrameRateRange = Range.create(1, maxRate);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+ mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+ maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE;
+
+ // TODO: set to 100Mbps for now, need a number for VPX
+ maxBps = 100000000;
+
+ // profile levels are not indicative for VPx, but verify
+ // them nonetheless
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ switch (profileLevel.level) {
+ case CodecProfileLevel.VP8Level_Version0:
+ case CodecProfileLevel.VP8Level_Version1:
+ case CodecProfileLevel.VP8Level_Version2:
+ case CodecProfileLevel.VP8Level_Version3:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.VP8ProfileMain:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ errors &= ~ERROR_NONE_SUPPORTED;
+ }
+
+ final int blockSize =
+ mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8;
+ applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE,
+ maxBlocks, maxBlocksPerSecond, blockSize, blockSize,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+ maxBlocks = 36864;
+ maxBlocksPerSecond = maxBlocks * 15;
+ maxBps = 128000;
+ for (CodecProfileLevel profileLevel: profileLevels) {
+ double FR = 0;
+ int FS = 0;
+ int BR = 0;
+ switch (profileLevel.level) {
+ case CodecProfileLevel.HEVCMainTierLevel1:
+ case CodecProfileLevel.HEVCHighTierLevel1:
+ FR = 15; FS = 36864; BR = 128; break;
+ case CodecProfileLevel.HEVCMainTierLevel2:
+ case CodecProfileLevel.HEVCHighTierLevel2:
+ FR = 30; FS = 122880; BR = 1500; break;
+ case CodecProfileLevel.HEVCMainTierLevel21:
+ case CodecProfileLevel.HEVCHighTierLevel21:
+ FR = 30; FS = 245760; BR = 3000; break;
+ case CodecProfileLevel.HEVCMainTierLevel3:
+ case CodecProfileLevel.HEVCHighTierLevel3:
+ FR = 30; FS = 552960; BR = 6000; break;
+ case CodecProfileLevel.HEVCMainTierLevel31:
+ case CodecProfileLevel.HEVCHighTierLevel31:
+ FR = 33.75; FS = 983040; BR = 10000; break;
+ case CodecProfileLevel.HEVCMainTierLevel4:
+ FR = 30; FS = 2228224; BR = 12000; break;
+ case CodecProfileLevel.HEVCHighTierLevel4:
+ FR = 30; FS = 2228224; BR = 30000; break;
+ case CodecProfileLevel.HEVCMainTierLevel41:
+ FR = 60; FS = 2228224; BR = 20000; break;
+ case CodecProfileLevel.HEVCHighTierLevel41:
+ FR = 60; FS = 2228224; BR = 50000; break;
+ case CodecProfileLevel.HEVCMainTierLevel5:
+ FR = 30; FS = 8912896; BR = 25000; break;
+ case CodecProfileLevel.HEVCHighTierLevel5:
+ FR = 30; FS = 8912896; BR = 100000; break;
+ case CodecProfileLevel.HEVCMainTierLevel51:
+ FR = 60; FS = 8912896; BR = 40000; break;
+ case CodecProfileLevel.HEVCHighTierLevel51:
+ FR = 60; FS = 8912896; BR = 160000; break;
+ case CodecProfileLevel.HEVCMainTierLevel52:
+ FR = 120; FS = 8912896; BR = 60000; break;
+ case CodecProfileLevel.HEVCHighTierLevel52:
+ FR = 120; FS = 8912896; BR = 240000; break;
+ case CodecProfileLevel.HEVCMainTierLevel6:
+ FR = 30; FS = 35651584; BR = 60000; break;
+ case CodecProfileLevel.HEVCHighTierLevel6:
+ FR = 30; FS = 35651584; BR = 240000; break;
+ case CodecProfileLevel.HEVCMainTierLevel61:
+ FR = 60; FS = 35651584; BR = 120000; break;
+ case CodecProfileLevel.HEVCHighTierLevel61:
+ FR = 60; FS = 35651584; BR = 480000; break;
+ case CodecProfileLevel.HEVCMainTierLevel62:
+ FR = 120; FS = 35651584; BR = 240000; break;
+ case CodecProfileLevel.HEVCHighTierLevel62:
+ FR = 120; FS = 35651584; BR = 800000; break;
+ default:
+ Log.w(TAG, "Unrecognized level "
+ + profileLevel.level + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+ switch (profileLevel.profile) {
+ case CodecProfileLevel.HEVCProfileMain:
+ case CodecProfileLevel.HEVCProfileMain10:
+ break;
+ default:
+ Log.w(TAG, "Unrecognized profile "
+ + profileLevel.profile + " for " + mime);
+ errors |= ERROR_UNRECOGNIZED;
+ }
+
+ /* DPB logic:
+ if (width * height <= FS / 4) DPB = 16;
+ else if (width * height <= FS / 2) DPB = 12;
+ else if (width * height <= FS * 0.75) DPB = 8;
+ else DPB = 6;
+ */
+
+ errors &= ~ERROR_NONE_SUPPORTED;
+ maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond);
+ maxBlocks = Math.max(FS, maxBlocks);
+ maxBps = Math.max(BR * 1000, maxBps);
+ }
+
+ int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8));
+ // CTBs are at least 8x8
+ maxBlocks = Utils.divUp(maxBlocks, 8 * 8);
+ maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8);
+ maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8);
+
+ applyMacroBlockLimits(
+ maxLengthInBlocks, maxLengthInBlocks,
+ maxBlocks, maxBlocksPerSecond,
+ 8 /* blockWidth */, 8 /* blockHeight */,
+ 1 /* widthAlignment */, 1 /* heightAlignment */);
+ } else {
+ Log.w(TAG, "Unsupported mime " + mime);
+ // using minimal bitrate here. should be overriden by
+ // info from media_codecs.xml
+ maxBps = 64000;
+ errors |= ERROR_UNSUPPORTED;
+ }
+ mBitrateRange = Range.create(1, maxBps);
+ mParent.mError |= errors;
+ }
+ }
+
+ /**
+ * A class that supports querying the encoding capabilities of a codec.
+ */
+ public static final class EncoderCapabilities {
+ /**
+ * Returns the supported range of quality values.
+ *
+ * @hide
+ */
+ 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;
+ }
+
+ /** Constant quality mode */
+ public static final int BITRATE_MODE_CQ = 0;
+ /** Variable bitrate mode */
+ public static final int BITRATE_MODE_VBR = 1;
+ /** Constant bitrate mode */
+ public static final int BITRATE_MODE_CBR = 2;
+
+ 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)
+ };
+
+ private static int parseBitrateMode(String mode) {
+ for (Feature feat: bitrates) {
+ if (feat.mName.equalsIgnoreCase(mode)) {
+ return feat.mValue;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Query whether a bitrate mode is supported.
+ */
+ public boolean isBitrateModeSupported(int mode) {
+ for (Feature feat: bitrates) {
+ if (mode == feat.mValue) {
+ return (mBitControl & (1 << mode)) != 0;
+ }
+ }
+ return false;
+ }
+
+ private Range<Integer> mQualityRange;
+ private Range<Integer> mComplexityRange;
+ private CodecCapabilities mParent;
+
+ /* no public constructor */
+ private EncoderCapabilities() { }
+
+ /** @hide */
+ public static EncoderCapabilities create(
+ MediaFormat info, CodecCapabilities parent) {
+ EncoderCapabilities caps = new EncoderCapabilities();
+ caps.init(info, parent);
+ return caps;
+ }
+
+ /** @hide */
+ public void init(MediaFormat info, CodecCapabilities parent) {
+ // no support for complexity or quality yet
+ mParent = parent;
+ mComplexityRange = Range.create(0, 0);
+ mQualityRange = Range.create(0, 0);
+ mBitControl = (1 << BITRATE_MODE_VBR);
+
+ applyLevelLimits();
+ parseFromInfo(info);
+ }
+
+ private void applyLevelLimits() {
+ String mime = mParent.getMimeType();
+ if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+ mComplexityRange = Range.create(0, 8);
+ mBitControl = (1 << BITRATE_MODE_CQ);
+ } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)
+ || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) {
+ mBitControl = (1 << BITRATE_MODE_CBR);
+ }
+ }
+
+ private int mBitControl;
+ private Integer mDefaultComplexity;
+ private Integer mDefaultQuality;
+ private String mQualityScale;
+
+ private void parseFromInfo(MediaFormat info) {
+ Map<String, Object> map = info.getMap();
+
+ if (info.containsKey("complexity-range")) {
+ mComplexityRange = Utils
+ .parseIntRange(info.getString("complexity-range"), mComplexityRange);
+ // TODO should we limit this to level limits?
+ }
+ if (info.containsKey("quality-range")) {
+ mQualityRange = Utils
+ .parseIntRange(info.getString("quality-range"), mQualityRange);
+ }
+ if (info.containsKey("feature-bitrate-control")) {
+ for (String mode: info.getString("feature-bitrate-control").split(",")) {
+ mBitControl |= parseBitrateMode(mode);
+ }
+ }
+
+ try {
+ mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default"));
+ } catch (NumberFormatException e) { }
+
+ try {
+ mDefaultQuality = Integer.parseInt((String)map.get("quality-default"));
+ } catch (NumberFormatException e) { }
+
+ mQualityScale = (String)map.get("quality-scale");
+ }
+
+ private boolean supports(
+ Integer complexity, Integer quality, Integer profile) {
+ boolean ok = true;
+ if (ok && complexity != null) {
+ ok = mComplexityRange.contains(complexity);
+ }
+ if (ok && quality != null) {
+ ok = mQualityRange.contains(quality);
+ }
+ if (ok && profile != null) {
+ for (CodecProfileLevel pl: mParent.profileLevels) {
+ if (pl.profile == profile) {
+ profile = null;
+ break;
+ }
+ }
+ ok = profile == null;
+ }
+ return ok;
+ }
+
+ /** @hide */
+ public void setDefaultFormat(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 != 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 != profile) {
+ throw new IllegalArgumentException(
+ "conflicting values for profile and aac-profile");
+ }
+ }
+
+ Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY);
+
+ return supports(complexity, quality, profile);
}
};
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index d74f22d..5084c5c 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -36,6 +36,8 @@
/**
* Count the number of available (regular) codecs.
*
+ * @deprecated Use {@link #getCodecInfos} instead.
+ *
* @see #REGULAR_CODECS
*/
public static final int getCodecCount() {
@@ -49,6 +51,8 @@
* Return the {@link MediaCodecInfo} object for the codec at
* the given {@code index} in the regular list.
*
+ * @deprecated Use {@link #getCodecInfos} instead.
+ *
* @see #REGULAR_CODECS
*/
public static final MediaCodecInfo getCodecInfoAt(int index) {
@@ -116,13 +120,22 @@
/**
* Use in {@link #MediaCodecList} to enumerate only codecs that are suitable
- * for normal playback and recording.
+ * for regular (buffer-to-buffer) decoding or encoding.
+ *
+ * <em>NOTE:</em> These are the codecs that are returned prior to API 21,
+ * using the now deprecated static methods.
*/
public static final int REGULAR_CODECS = 0;
/**
* Use in {@link #MediaCodecList} to enumerate all codecs, even ones that are
- * not suitable for normal playback or recording.
+ * not suitable for regular (buffer-to-buffer) decoding or encoding. These
+ * include codecs, for example, that only work with special input or output
+ * surfaces, such as secure-only or tunneled-only codecs.
+ *
+ * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+ * @see MediaCodecInfo.CodecCapabilities#FEATURE_SecurePlayback
+ * @see MediaCodecInfo.CodecCapabilities#FEATURE_TunneledPlayback
*/
public static final int ALL_CODECS = 1;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index fd79495..2036533 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -120,19 +120,6 @@
private Map<String, Object> mMap;
/**
- * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities}
- * feature name describing a required or optional feature for a codec capabilities
- * query.
- * The associated value is an integer, where non-0 value means the feature is
- * requested to be present, while 0 value means the feature is requested to be not
- * present.
- * @see MediaCodecList#findDecoderForFormat
- * @see MediaCodecList#findEncoderForFormat
- * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
- */
- public static final String KEY_FEATURE_ = "feature-";
-
- /**
* A key describing the mime type of the MediaFormat.
* The associated value is a string.
*/
@@ -422,6 +409,8 @@
* codec specific, but lower values generally result in more efficient
* (smaller-sized) encoding.
*
+ * @hide
+ *
* @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getQualityRange
*/
public static final String KEY_QUALITY = "quality";
@@ -510,6 +499,21 @@
}
/**
+ * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities}
+ * feature name describing a required or optional feature for a codec capabilities
+ * query.
+ * The associated value is an integer, where non-0 value means the feature is
+ * requested to be present, while 0 value means the feature is requested to be not
+ * present.
+ * @see MediaCodecList#findDecoderForFormat
+ * @see MediaCodecList#findEncoderForFormat
+ * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+ *
+ * @hide
+ */
+ public static final String KEY_FEATURE_ = "feature-";
+
+ /**
* Returns the value of an integer key.
*/
public final int getInteger(String name) {
@@ -559,6 +563,23 @@
}
/**
+ * Returns whether a feature is to be enabled ({@code true}) or disabled
+ * ({@code false}).
+ *
+ * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature.
+ *
+ * @throws IllegalArgumentException if the feature was neither set to be enabled
+ * nor to be disabled.
+ */
+ public boolean getFeatureEnabled(String feature) {
+ Integer enabled = (Integer)mMap.get(KEY_FEATURE_ + feature);
+ if (enabled == null) {
+ throw new IllegalArgumentException("feature is not specified");
+ }
+ return enabled != 0;
+ }
+
+ /**
* Sets the value of an integer key.
*/
public final void setInteger(String name, int value) {
@@ -594,6 +615,23 @@
}
/**
+ * Sets whether a feature is to be enabled ({@code true}) or disabled
+ * ({@code false}).
+ *
+ * If {@code enabled} is {@code true}, the feature is requested to be present.
+ * Otherwise, the feature is requested to be not present.
+ *
+ * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature.
+ *
+ * @see MediaCodecList#findDecoderForFormat
+ * @see MediaCodecList#findEncoderForFormat
+ * @see MediaCodecInfo.CodecCapabilities#isFormatSupported
+ */
+ public void setFeatureEnabled(String feature, boolean enabled) {
+ setInteger(KEY_FEATURE_ + feature, enabled ? 1 : 0);
+ }
+
+ /**
* Creates a minimal audio format.
* @param mime The mime type of the content.
* @param sampleRate The sampling rate of the content.
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 3949d7d..14bf10e 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -88,14 +88,4 @@
android:orientation="vertical"
android:paddingBottom="@dimen/qs_panel_padding" />
- <TextView
- android:id="@+id/zen_alarm_warning"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/qs_panel_padding"
- android:paddingLeft="@dimen/qs_panel_padding"
- android:paddingRight="@dimen/qs_panel_padding"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/TextAppearance.QS.Subhead" />
</com.android.systemui.volume.ZenModePanel>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 726a54f..0e18979 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -200,5 +200,8 @@
<!-- Tiles with feature timeouts: number of days to show after feature is used. -->
<integer name="days_to_show_timeout_tiles">30</integer>
+
+ <!-- Number of times to show the strong alarm warning text in the volume dialog -->
+ <integer name="zen_mode_alarm_warning_threshold">5</integer>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 189c052..b000a48 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -697,7 +697,7 @@
<string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
<!-- Zen mode: No interruptions title, with a warning about alarms. [CHAR LIMIT=60] -->
- <string name="zen_no_interruptions_with_warning">No interruptions, including alarms</string>
+ <string name="zen_no_interruptions_with_warning">No interruptions. Not even alarms.</string>
<!-- Zen mode: No interruptions. [CHAR LIMIT=40] -->
<string name="zen_no_interruptions">No interruptions</string>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index c99e1fd..ac7fc25 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -31,10 +31,9 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
-import android.text.format.DateFormat;
-import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AnimationUtils;
@@ -49,10 +48,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
-import java.text.SimpleDateFormat;
import java.util.Arrays;
-import java.util.Date;
-import java.util.Locale;
import java.util.Objects;
public class ZenModePanel extends LinearLayout {
@@ -79,10 +75,10 @@
private final Context mContext;
private final LayoutInflater mInflater;
private final H mHandler = new H();
- private final Favorites mFavorites;
+ private final Prefs mPrefs;
private final Interpolator mFastOutSlowInInterpolator;
- private final int mHardWarningColor;
- private final int mSoftWarningColor;
+ private final int mSubheadWarningColor;
+ private final int mSubheadColor;
private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
@@ -92,7 +88,6 @@
private TextView mZenSubheadExpanded;
private View mMoreSettings;
private LinearLayout mZenConditions;
- private TextView mAlarmWarning;
private Callback mCallback;
private ZenModeController mController;
@@ -103,21 +98,21 @@
private boolean mExpanded;
private boolean mHidden = false;
private int mSessionZen;
+ private int mAttachedZen;
private Condition mSessionExitCondition;
- private long mNextAlarm;
private Condition[] mConditions;
private Condition mTimeCondition;
public ZenModePanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
- mFavorites = new Favorites();
+ mPrefs = new Prefs();
mInflater = LayoutInflater.from(mContext.getApplicationContext());
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
final Resources res = mContext.getResources();
- mHardWarningColor = res.getColor(R.color.system_warning_color);
- mSoftWarningColor = res.getColor(R.color.qs_subhead);
+ mSubheadWarningColor = res.getColor(R.color.system_warning_color);
+ mSubheadColor = res.getColor(R.color.qs_subhead);
if (DEBUG) Log.d(mTag, "new ZenModePanel");
}
@@ -155,17 +150,16 @@
});
mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
- mAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (DEBUG) Log.d(mTag, "onAttachedToWindow");
- mSessionZen = getSelectedZen(-1);
+ mAttachedZen = getSelectedZen(-1);
+ mSessionZen = mAttachedZen;
mSessionExitCondition = copy(mExitCondition);
refreshExitConditionText();
- refreshNextAlarm();
updateWidgets();
}
@@ -173,6 +167,8 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ checkForAttachedZenChange();
+ mAttachedZen = -1;
mSessionZen = -1;
mSessionExitCondition = null;
setExpanded(false);
@@ -184,6 +180,17 @@
updateWidgets();
}
+ private void checkForAttachedZenChange() {
+ final int selectedZen = getSelectedZen(-1);
+ if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen);
+ if (selectedZen != mAttachedZen) {
+ if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen);
+ if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ mPrefs.trackNoneSelected();
+ }
+ }
+ }
+
private void setExpanded(boolean expanded) {
if (expanded == mExpanded) return;
mExpanded = expanded;
@@ -234,10 +241,6 @@
updateWidgets();
}
- private Uri getExitConditionId() {
- return getConditionId(mExitCondition);
- }
-
private static Uri getConditionId(Condition condition) {
return condition != null ? condition.id : null;
}
@@ -301,9 +304,7 @@
final boolean zenOff = zen == Global.ZEN_MODE_OFF;
final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
- final boolean hasNextAlarm = mNextAlarm != 0;
final boolean expanded = !mHidden && mExpanded;
- final boolean showAlarmWarning = zenNone && expanded && hasNextAlarm;
mZenButtons.setVisibility(mHidden ? GONE : VISIBLE);
mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE);
@@ -311,26 +312,6 @@
mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE);
mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE);
mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE);
- mAlarmWarning.setVisibility(zenNone && expanded && hasNextAlarm ? VISIBLE : GONE);
- if (showAlarmWarning) {
- final long exitTime = ZenModeConfig.tryParseCountdownConditionId(getExitConditionId());
- final long now = System.currentTimeMillis();
- final boolean alarmToday = time(mNextAlarm).yearDay == time(now).yearDay;
- final String skeleton = (alarmToday ? "" : "E")
- + (DateFormat.is24HourFormat(mContext) ? "Hm" : "hma");
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- final String alarm = new SimpleDateFormat(pattern).format(new Date(mNextAlarm));
- final boolean isWarning = exitTime > 0 && mNextAlarm > now && mNextAlarm < exitTime;
- if (isWarning) {
- mAlarmWarning.setText(mContext.getString(R.string.zen_alarm_warning, alarm));
- mAlarmWarning.setTextColor(mHardWarningColor);
- } else {
- mAlarmWarning.setText(mContext.getString(alarmToday
- ? R.string.zen_alarm_information_time
- : R.string.zen_alarm_information_day_time, alarm));
- mAlarmWarning.setTextColor(mSoftWarningColor);
- }
- }
if (zenNone) {
mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning);
@@ -339,12 +320,8 @@
mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
mZenSubheadCollapsed.setText(mExitConditionText);
}
- }
-
- private static Time time(long millis) {
- final Time t = new Time();
- t.set(millis);
- return t;
+ mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous()
+ ? mSubheadWarningColor : mSubheadColor);
}
private Condition parseExistingTimeCondition(Condition condition) {
@@ -373,15 +350,6 @@
Condition.FLAG_RELEVANT_NOW);
}
- private void refreshNextAlarm() {
- mNextAlarm = mController.getNextAlarm();
- }
-
- private void handleNextAlarmChanged() {
- refreshNextAlarm();
- updateWidgets();
- }
-
private void handleUpdateConditions(Condition[] conditions) {
mConditions = conditions;
handleUpdateConditions();
@@ -429,7 +397,7 @@
}
}
if (DEBUG) Log.d(mTag, "Selecting a default");
- final int favoriteIndex = mFavorites.getMinuteIndex();
+ final int favoriteIndex = mPrefs.getMinuteIndex();
if (favoriteIndex == -1) {
getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
} else {
@@ -579,9 +547,9 @@
}
setExitCondition(condition);
if (condition == null) {
- mFavorites.setMinuteIndex(-1);
+ mPrefs.setMinuteIndex(-1);
} else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) {
- mFavorites.setMinuteIndex(mBucketIndex);
+ mPrefs.setMinuteIndex(mBucketIndex);
}
mSessionExitCondition = copy(condition);
}
@@ -618,18 +586,12 @@
public void onExitConditionChanged(Condition exitCondition) {
mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
}
-
- @Override
- public void onNextAlarmChanged() {
- mHandler.sendEmptyMessage(H.NEXT_ALARM_CHANGED);
- }
};
private final class H extends Handler {
private static final int UPDATE_CONDITIONS = 1;
private static final int EXIT_CONDITION_CHANGED = 2;
private static final int UPDATE_ZEN = 3;
- private static final int NEXT_ALARM_CHANGED = 4;
private H() {
super(Looper.getMainLooper());
@@ -643,8 +605,6 @@
handleExitConditionChanged((Condition) msg.obj);
} else if (msg.what == UPDATE_ZEN) {
handleUpdateZen(msg.arg1);
- } else if (msg.what == NEXT_ALARM_CHANGED) {
- handleNextAlarmChanged();
}
}
}
@@ -661,14 +621,32 @@
Condition condition;
}
- private final class Favorites implements OnSharedPreferenceChangeListener {
+ private final class Prefs implements OnSharedPreferenceChangeListener {
private static final String KEY_MINUTE_INDEX = "minuteIndex";
+ private static final String KEY_NONE_SELECTED = "noneSelected";
+
+ private final int mNoneDangerousThreshold;
private int mMinuteIndex;
+ private int mNoneSelected;
- private Favorites() {
+ private Prefs() {
+ mNoneDangerousThreshold = mContext.getResources()
+ .getInteger(R.integer.zen_mode_alarm_warning_threshold);
prefs().registerOnSharedPreferenceChangeListener(this);
updateMinuteIndex();
+ updateNoneSelected();
+ }
+
+ public boolean isNoneDangerous() {
+ return mNoneSelected < mNoneDangerousThreshold;
+ }
+
+ public void trackNoneSelected() {
+ mNoneSelected = clampNoneSelected(mNoneSelected + 1);
+ if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold="
+ + mNoneDangerousThreshold);
+ prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply();
}
public int getMinuteIndex() {
@@ -676,9 +654,9 @@
}
public void setMinuteIndex(int minuteIndex) {
- minuteIndex = clamp(minuteIndex);
+ minuteIndex = clampIndex(minuteIndex);
if (minuteIndex == mMinuteIndex) return;
- mMinuteIndex = clamp(minuteIndex);
+ mMinuteIndex = clampIndex(minuteIndex);
if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
}
@@ -686,6 +664,7 @@
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
updateMinuteIndex();
+ updateNoneSelected();
}
private SharedPreferences prefs() {
@@ -693,12 +672,21 @@
}
private void updateMinuteIndex() {
- mMinuteIndex = clamp(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
+ mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
}
- private int clamp(int index) {
- return Math.max(-1, Math.min(MINUTE_BUCKETS.length - 1, index));
+ private int clampIndex(int index) {
+ return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1);
+ }
+
+ private void updateNoneSelected() {
+ mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0));
+ if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected);
+ }
+
+ private int clampNoneSelected(int noneSelected) {
+ return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE);
}
}