diff options
| author | 2024-06-24 20:27:24 +0000 | |
|---|---|---|
| committer | 2025-01-03 00:58:44 +0000 | |
| commit | 51a8cbbcf30398ac427ff95424890e5b1253a17c (patch) | |
| tree | fcda47c6d382f361befc99fb18f000c44e4e3795 | |
| parent | 46b4dfd33602f1b1ecd76dc19b46a24fd74b35e0 (diff) | |
Java and JNI support for native VideoCapabilities.
Bug: b/306023029
Test: MediaCodecCapabilitiesTest
Change-Id: I97d71394599fd6c2c884f457914121871dec8211
| -rw-r--r-- | media/java/android/media/MediaCodecInfo.java | 3269 | ||||
| -rw-r--r-- | media/jni/android_media_CodecCapabilities.cpp | 165 |
2 files changed, 1922 insertions, 1512 deletions
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index c6ceb071e23a..5f79f4409fc7 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -1105,7 +1105,7 @@ public final class MediaCodecInfo { // a level check. Map<String, Object> levelCriticalFormatMap = new HashMap<>(map); final Set<String> criticalKeys = isVideo() - ? VideoCapabilities.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS + ? VideoCapabilities.VideoCapsLegacyImpl.VIDEO_LEVEL_CRITICAL_FORMAT_KEYS : isAudio() ? AudioCapabilities.AudioCapsLegacyImpl.AUDIO_LEVEL_CRITICAL_FORMAT_KEYS : null; @@ -1362,7 +1362,7 @@ public final class MediaCodecInfo { if (profLevs.length == 0 && mMime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { CodecProfileLevel profLev = new CodecProfileLevel(); profLev.profile = CodecProfileLevel.VP9Profile0; - profLev.level = VideoCapabilities.equivalentVP9Level(info); + profLev.level = VideoCapabilities.VideoCapsLegacyImpl.equivalentVP9Level(info); profLevs = new CodecProfileLevel[] { profLev }; } profileLevels = profLevs; @@ -2061,304 +2061,6 @@ public final class MediaCodecInfo { */ public static final class VideoCapabilities { private static final String TAG = "VideoCapabilities"; - private CodecCapabilities mParent; - private Range<Integer> mBitrateRange; - - private Range<Integer> mHeightRange; - private Range<Integer> mWidthRange; - private Range<Integer> mBlockCountRange; - private Range<Integer> mHorizontalBlockRange; - private Range<Integer> mVerticalBlockRange; - private Range<Rational> mAspectRatioRange; - private Range<Rational> mBlockAspectRatioRange; - private Range<Long> mBlocksPerSecondRange; - private Map<Size, Range<Long>> mMeasuredFrameRates; - private List<PerformancePoint> mPerformancePoints; - private Range<Integer> mFrameRateRange; - - private int mBlockWidth; - private int mBlockHeight; - private int mWidthAlignment; - private int mHeightAlignment; - private int mSmallerDimensionUpperLimit; - - private boolean mAllowMbOverride; // allow XML to override calculated limits - - /** - * Returns the range of supported bitrates in bits/second. - */ - public Range<Integer> getBitrateRange() { - return mBitrateRange; - } - - /** - * Returns the range of supported video widths. - * <p class=note> - * 32-bit processes will not support resolutions larger than 4096x4096 due to - * the limited address space. - */ - public Range<Integer> getSupportedWidths() { - return mWidthRange; - } - - /** - * Returns the range of supported video heights. - * <p class=note> - * 32-bit processes will not support resolutions larger than 4096x4096 due to - * the limited address space. - */ - public Range<Integer> getSupportedHeights() { - return mHeightRange; - } - - /** - * Returns the alignment requirement for video width (in pixels). - * - * This is a power-of-2 value that video width must be a - * multiple of. - */ - public int getWidthAlignment() { - return mWidthAlignment; - } - - /** - * Returns the alignment requirement for video height (in pixels). - * - * This is a power-of-2 value that video height must be a - * multiple of. - */ - public int getHeightAlignment() { - return mHeightAlignment; - } - - /** - * Return the upper limit on the smaller dimension of width or height. - * <p></p> - * Some codecs have a limit on the smaller dimension, whether it be - * the width or the height. E.g. a codec may only be able to handle - * up to 1920x1080 both in landscape and portrait mode (1080x1920). - * In this case the maximum width and height are both 1920, but the - * smaller dimension limit will be 1080. For other codecs, this is - * {@code Math.min(getSupportedWidths().getUpper(), - * getSupportedHeights().getUpper())}. - * - * @hide - */ - public int getSmallerDimensionUpperLimit() { - return mSmallerDimensionUpperLimit; - } - - /** - * Returns the range of supported frame rates. - * <p> - * This is not a performance indicator. Rather, it expresses the - * limits specified in the coding standard, based on the complexities - * of encoding material for later playback at a certain frame rate, - * or the decoding of such material in non-realtime. - */ - public Range<Integer> getSupportedFrameRates() { - return mFrameRateRange; - } - - /** - * Returns the range of supported video widths for a video height. - * @param height the height of the video - */ - public Range<Integer> getSupportedWidthsFor(int height) { - try { - Range<Integer> range = mWidthRange; - if (!mHeightRange.contains(height) - || (height % mHeightAlignment) != 0) { - throw new IllegalArgumentException("unsupported height"); - } - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - - // constrain by block count and by block aspect ratio - final int minWidthInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), - (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() - * heightInBlocks)); - final int maxWidthInBlocks = Math.min( - mBlockCountRange.getUpper() / heightInBlocks, - (int)(mBlockAspectRatioRange.getUpper().doubleValue() - * heightInBlocks)); - range = range.intersect( - (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, - maxWidthInBlocks * mBlockWidth); - - // constrain by smaller dimension limit - if (height > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } - - // constrain by aspect ratio - range = range.intersect( - (int)Math.ceil(mAspectRatioRange.getLower().doubleValue() - * height), - (int)(mAspectRatioRange.getUpper().doubleValue() * height)); - return range; - } catch (IllegalArgumentException e) { - // height is not supported because there are no suitable widths - Log.v(TAG, "could not get supported widths for " + height); - throw new IllegalArgumentException("unsupported height"); - } - } - - /** - * Returns the range of supported video heights for a video width - * @param width the width of the video - */ - public Range<Integer> getSupportedHeightsFor(int width) { - try { - Range<Integer> range = mHeightRange; - if (!mWidthRange.contains(width) - || (width % mWidthAlignment) != 0) { - throw new IllegalArgumentException("unsupported width"); - } - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - - // constrain by block count and by block aspect ratio - final int minHeightInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), - (int)Math.ceil(widthInBlocks / - mBlockAspectRatioRange.getUpper().doubleValue())); - final int maxHeightInBlocks = Math.min( - mBlockCountRange.getUpper() / widthInBlocks, - (int)(widthInBlocks / - mBlockAspectRatioRange.getLower().doubleValue())); - range = range.intersect( - (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, - maxHeightInBlocks * mBlockHeight); - - // constrain by smaller dimension limit - if (width > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } - - // constrain by aspect ratio - range = range.intersect( - (int)Math.ceil(width / - mAspectRatioRange.getUpper().doubleValue()), - (int)(width / mAspectRatioRange.getLower().doubleValue())); - return range; - } catch (IllegalArgumentException e) { - // width is not supported because there are no suitable heights - Log.v(TAG, "could not get supported heights for " + width); - throw new IllegalArgumentException("unsupported width"); - } - } - - /** - * Returns the range of supported video frame rates for a video size. - * <p> - * This is not a performance indicator. Rather, it expresses the limits specified in - * the coding standard, based on the complexities of encoding material of a given - * size for later playback at a certain frame rate, or the decoding of such material - * in non-realtime. - - * @param width the width of the video - * @param height the height of the video - */ - public Range<Double> getSupportedFrameRatesFor(int width, int height) { - Range<Integer> range = mHeightRange; - if (!supports(width, height, null)) { - throw new IllegalArgumentException("unsupported size"); - } - final int blockCount = - Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); - - return Range.create( - Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, - (double) mFrameRateRange.getLower()), - Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, - (double) mFrameRateRange.getUpper())); - } - - private int getBlockCount(int width, int height) { - return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); - } - - @NonNull - private Size findClosestSize(int width, int height) { - int targetBlockCount = getBlockCount(width, height); - Size closestSize = null; - int minDiff = Integer.MAX_VALUE; - for (Size size : mMeasuredFrameRates.keySet()) { - int diff = Math.abs(targetBlockCount - - getBlockCount(size.getWidth(), size.getHeight())); - if (diff < minDiff) { - minDiff = diff; - closestSize = size; - } - } - return closestSize; - } - - private Range<Double> estimateFrameRatesFor(int width, int height) { - Size size = findClosestSize(width, height); - Range<Long> range = mMeasuredFrameRates.get(size); - Double ratio = getBlockCount(size.getWidth(), size.getHeight()) - / (double)Math.max(getBlockCount(width, height), 1); - return Range.create(range.getLower() * ratio, range.getUpper() * ratio); - } - - /** - * Returns the range of achievable video frame rates for a video size. - * May return {@code null}, if the codec did not publish any measurement - * data. - * <p> - * This is a performance estimate provided by the device manufacturer based on statistical - * sampling of full-speed decoding and encoding measurements in various configurations - * of common video sizes supported by the codec. As such it should only be used to - * compare individual codecs on the device. The value is not suitable for comparing - * different devices or even different android releases for the same device. - * <p> - * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range - * corresponds to the fastest frame rates achieved in the tested configurations. As - * such, it should not be used to gauge guaranteed or even average codec performance - * on the device. - * <p> - * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range - * corresponds closer to sustained performance <em>in tested configurations</em>. - * One can expect to achieve sustained performance higher than the lower limit more than - * 50% of the time, and higher than half of the lower limit at least 90% of the time - * <em>in tested configurations</em>. - * Conversely, one can expect performance lower than twice the upper limit at least - * 90% of the time. - * <p class=note> - * Tested configurations use a single active codec. For use cases where multiple - * codecs are active, applications can expect lower and in most cases significantly lower - * performance. - * <p class=note> - * The returned range value is interpolated from the nearest frame size(s) tested. - * Codec performance is severely impacted by other activity on the device as well - * as environmental factors (such as battery level, temperature or power source), and can - * vary significantly even in a steady environment. - * <p class=note> - * Use this method in cases where only codec performance matters, e.g. to evaluate if - * a codec has any chance of meeting a performance target. Codecs are listed - * in {@link MediaCodecList} in the preferred order as defined by the device - * manufacturer. As such, applications should use the first suitable codec in the - * list to achieve the best balance between power use and performance. - * - * @param width the width of the video - * @param height the height of the video - * - * @throws IllegalArgumentException if the video size is not supported. - */ - @Nullable - public Range<Double> getAchievableFrameRatesFor(int width, int height) { - if (!supports(width, height, null)) { - throw new IllegalArgumentException("unsupported size"); - } - - if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) { - Log.w(TAG, "Codec did not publish any measurement data."); - return null; - } - - return estimateFrameRatesFor(width, height); - } /** * Video performance points are a set of standard performance points defined by number of @@ -2748,1322 +2450,1861 @@ public final class MediaCodecInfo { public static final PerformancePoint UHD_240 = new PerformancePoint(3840, 2160, 240); } - /** - * Returns the supported performance points. May return {@code null} if the codec did not - * publish any performance point information (e.g. the vendor codecs have not been updated - * to the latest android release). May return an empty list if the codec published that - * if does not guarantee any performance points. - * <p> - * This is a performance guarantee provided by the device manufacturer for hardware codecs - * based on hardware capabilities of the device. - * <p> - * The returned list is sorted first by decreasing number of pixels, then by decreasing - * width, and finally by decreasing frame rate. - * Performance points assume a single active codec. For use cases where multiple - * codecs are active, should use that highest pixel count, and add the frame rates of - * each individual codec. - * <p class=note> - * 32-bit processes will not support resolutions larger than 4096x4096 due to - * the limited address space, but performance points will be presented as is. - * In other words, even though a component publishes a performance point for - * a resolution higher than 4096x4096, it does not mean that the resolution is supported - * for 32-bit processes. - */ - @Nullable - public List<PerformancePoint> getSupportedPerformancePoints() { - return mPerformancePoints; - } + /* package private */ interface VideoCapsIntf { + public Range<Integer> getBitrateRange(); - /** - * 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); - } + public Range<Integer> getSupportedWidths(); - /** - * 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); - } + public Range<Integer> getSupportedHeights(); - private boolean supports(Integer width, Integer height, Number rate) { - boolean ok = true; + public int getWidthAlignment(); - if (ok && width != null) { - ok = mWidthRange.contains(width) - && (width % mWidthAlignment == 0); - } - if (ok && height != null) { - ok = mHeightRange.contains(height) - && (height % mHeightAlignment == 0); - } - if (ok && rate != null) { - ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue())); - } - if (ok && height != null && width != null) { - ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; + public int getHeightAlignment(); - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - final int blockCount = widthInBlocks * heightInBlocks; - ok = ok && mBlockCountRange.contains(blockCount) - && mBlockAspectRatioRange.contains( - new Rational(widthInBlocks, heightInBlocks)) - && mAspectRatioRange.contains(new Rational(width, height)); - if (ok && rate != null) { - double blocksPerSec = blockCount * rate.doubleValue(); - ok = mBlocksPerSecondRange.contains( - Utils.longRangeFor(blocksPerSec)); - } - } - return ok; - } + public int getSmallerDimensionUpperLimit(); - /* package private */ - // must not contain KEY_PROFILE - static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( - MediaFormat.KEY_WIDTH, - MediaFormat.KEY_HEIGHT, - MediaFormat.KEY_FRAME_RATE, - MediaFormat.KEY_BIT_RATE, - MediaFormat.KEY_MIME); + public Range<Integer> getSupportedFrameRates(); - /** - * @hide - * @throws java.lang.ClassCastException */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); - Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); - Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE); + public Range<Integer> getSupportedWidthsFor(int height); - if (!supports(width, height, rate)) { - return false; - } + public Range<Integer> getSupportedHeightsFor(int width); - if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) { - return false; - } + public Range<Double> getSupportedFrameRatesFor(int width, int height); - // we ignore color-format for now as it is not reliably reported by codec - return true; - } + public Range<Double> getAchievableFrameRatesFor(int width, int height); - /* no public constructor */ - private VideoCapabilities() { } + public boolean areSizeAndRateSupported(int width, int height, double frameRate); - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static VideoCapabilities create( - MediaFormat info, CodecCapabilities parent) { - VideoCapabilities caps = new VideoCapabilities(); - caps.init(info, parent); - return caps; - } + public boolean isSizeSupported(int width, int height); - private void init(MediaFormat info, CodecCapabilities parent) { - mParent = parent; - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); - updateLimits(); - } + public boolean supportsFormat(MediaFormat format); - /** @hide */ - public Size getBlockSize() { - return new Size(mBlockWidth, mBlockHeight); + public List<PerformancePoint> getSupportedPerformancePoints(); } - /** @hide */ - public Range<Integer> getBlockCountRange() { - return mBlockCountRange; - } + /* package private */ static final class VideoCapsLegacyImpl implements VideoCapsIntf { + /* package private */ + // must not contain KEY_PROFILE + static final Set<String> VIDEO_LEVEL_CRITICAL_FORMAT_KEYS = Set.of( + MediaFormat.KEY_WIDTH, + MediaFormat.KEY_HEIGHT, + MediaFormat.KEY_FRAME_RATE, + MediaFormat.KEY_BIT_RATE, + MediaFormat.KEY_MIME); - /** @hide */ - public Range<Long> getBlocksPerSecondRange() { - return mBlocksPerSecondRange; - } + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; - /** @hide */ - public Range<Rational> getAspectRatioRange(boolean blocks) { - return blocks ? mBlockAspectRatioRange : mAspectRatioRange; - } + private Range<Integer> mHeightRange; + private Range<Integer> mWidthRange; + private Range<Integer> mBlockCountRange; + private Range<Integer> mHorizontalBlockRange; + private Range<Integer> mVerticalBlockRange; + private Range<Rational> mAspectRatioRange; + private Range<Rational> mBlockAspectRatioRange; + private Range<Long> mBlocksPerSecondRange; + private Map<Size, Range<Long>> mMeasuredFrameRates; + private List<PerformancePoint> mPerformancePoints; + private Range<Integer> mFrameRateRange; + + private int mBlockWidth; + private int mBlockHeight; + private int mWidthAlignment; + private int mHeightAlignment; + private int mSmallerDimensionUpperLimit; + + private boolean mAllowMbOverride; // allow XML to override calculated limits - private void initWithPlatformLimits() { - mBitrateRange = BITRATE_RANGE; + /* no public constructor */ + private VideoCapsLegacyImpl() { } - mWidthRange = getSizeRange(); - mHeightRange = getSizeRange(); - mFrameRateRange = FRAME_RATE_RANGE; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + public static VideoCapsLegacyImpl create( + MediaFormat info, CodecCapabilities parent) { + if (GetFlag(() -> android.media.codec.Flags.nativeCapabilites())) { + Log.d(TAG, "Legacy implementation is called while native flag is on."); + } - mHorizontalBlockRange = getSizeRange(); - mVerticalBlockRange = getSizeRange(); + VideoCapsLegacyImpl caps = new VideoCapsLegacyImpl(); + caps.init(info, parent); + return caps; + } - // full positive ranges are supported as these get calculated - mBlockCountRange = POSITIVE_INTEGERS; - mBlocksPerSecondRange = POSITIVE_LONGS; + private void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + updateLimits(); + } - mBlockAspectRatioRange = POSITIVE_RATIONALS; - mAspectRatioRange = POSITIVE_RATIONALS; + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } - mWidthAlignment = 1; - mHeightAlignment = 1; - mBlockWidth = 1; - mBlockHeight = 1; - mSmallerDimensionUpperLimit = getSizeRange().getUpper(); - } + public Range<Integer> getSupportedWidths() { + return mWidthRange; + } - private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) { - Vector<PerformancePoint> ret = new Vector<>(); - final String prefix = "performance-point-"; - Set<String> keys = map.keySet(); - for (String key : keys) { - // looking for: performance-point-WIDTHxHEIGHT-range - if (!key.startsWith(prefix)) { - continue; - } - String subKey = key.substring(prefix.length()); - if (subKey.equals("none") && ret.size() == 0) { - // This means that component knowingly did not publish performance points. - // This is different from when the component forgot to publish performance - // points. - return Collections.unmodifiableList(ret); - } - String[] temp = key.split("-"); - if (temp.length != 4) { - continue; - } - String sizeStr = temp[2]; - Size size = Utils.parseSize(sizeStr, null); - if (size == null || size.getWidth() * size.getHeight() <= 0) { - continue; - } - Range<Long> range = Utils.parseLongRange(map.get(key), null); - if (range == null || range.getLower() < 0 || range.getUpper() < 0) { - continue; - } - PerformancePoint given = new PerformancePoint( - size.getWidth(), size.getHeight(), range.getLower().intValue(), - range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); - PerformancePoint rotated = new PerformancePoint( - size.getHeight(), size.getWidth(), range.getLower().intValue(), - range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); - ret.add(given); - if (!given.covers(rotated)) { - ret.add(rotated); - } + public Range<Integer> getSupportedHeights() { + return mHeightRange; } - // check if the component specified no performance point indication - if (ret.size() == 0) { - return null; + public int getWidthAlignment() { + return mWidthAlignment; } - // sort reversed by area first, then by frame rate - ret.sort((a, b) -> - -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ? - (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) : - (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ? - (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) : - (a.getMaxFrameRate() != b.getMaxFrameRate()) ? - (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0)); + public int getHeightAlignment() { + return mHeightAlignment; + } - return Collections.unmodifiableList(ret); - } + /** @hide */ + public int getSmallerDimensionUpperLimit() { + return mSmallerDimensionUpperLimit; + } - private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) { - Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>(); - final String prefix = "measured-frame-rate-"; - Set<String> keys = map.keySet(); - for (String key : keys) { - // looking for: measured-frame-rate-WIDTHxHEIGHT-range - if (!key.startsWith(prefix)) { - continue; - } - String subKey = key.substring(prefix.length()); - String[] temp = key.split("-"); - if (temp.length != 5) { - continue; - } - String sizeStr = temp[3]; - Size size = Utils.parseSize(sizeStr, null); - if (size == null || size.getWidth() * size.getHeight() <= 0) { - continue; - } - Range<Long> range = Utils.parseLongRange(map.get(key), null); - if (range == null || range.getLower() < 0 || range.getUpper() < 0) { - continue; - } - ret.put(size, range); + public Range<Integer> getSupportedFrameRates() { + return mFrameRateRange; } - return ret; - } - private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) { - Pair<Size, Size> range = Utils.parseSizeRange(o); - if (range != null) { + public Range<Integer> getSupportedWidthsFor(int height) { try { - return Pair.create( - Range.create(range.first.getWidth(), range.second.getWidth()), - Range.create(range.first.getHeight(), range.second.getHeight())); + 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) { - Log.w(TAG, "could not parse size range '" + o + "'"); + // height is not supported because there are no suitable widths + Log.v(TAG, "could not get supported widths for " + height); + throw new IllegalArgumentException("unsupported height"); } } - return null; - } - /** @hide */ - public static int equivalentVP9Level(MediaFormat info) { - final Map<String, Object> map = info.getMap(); + 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); + } - Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8)); - int BS = blockSize.getWidth() * blockSize.getHeight(); - - Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null); - int FS = counts == null ? 0 : BS * counts.getUpper(); - - Range<Long> blockRates = - Utils.parseLongRange(map.get("blocks-per-second-range"), null); - long SR = blockRates == null ? 0 : BS * blockRates.getUpper(); - - Pair<Range<Integer>, Range<Integer>> dimensionRanges = - parseWidthHeightRanges(map.get("size-range")); - int D = dimensionRanges == null ? 0 : Math.max( - dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper()); - - Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); - int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000); - - if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512) - return CodecProfileLevel.VP9Level1; - if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768) - return CodecProfileLevel.VP9Level11; - if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960) - return CodecProfileLevel.VP9Level2; - if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344) - return CodecProfileLevel.VP9Level21; - if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048) - return CodecProfileLevel.VP9Level3; - if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752) - return CodecProfileLevel.VP9Level31; - if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160) - return CodecProfileLevel.VP9Level4; - if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160) - return CodecProfileLevel.VP9Level41; - if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384) - return CodecProfileLevel.VP9Level5; - if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384) - return CodecProfileLevel.VP9Level51; - if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384) - return CodecProfileLevel.VP9Level52; - if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832) - return CodecProfileLevel.VP9Level6; - if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832) - return CodecProfileLevel.VP9Level61; - if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832) - return CodecProfileLevel.VP9Level62; - // returning largest level - return CodecProfileLevel.VP9Level62; - } + // constrain by aspect ratio + range = range.intersect( + (int) Math.ceil(width + / mAspectRatioRange.getUpper().doubleValue()), + (int) (width / mAspectRatioRange.getLower().doubleValue())); + return range; + } catch (IllegalArgumentException e) { + // width is not supported because there are no suitable heights + Log.v(TAG, "could not get supported heights for " + width); + throw new IllegalArgumentException("unsupported width"); + } + } - private void parseFromInfo(MediaFormat info) { - final Map<String, Object> map = info.getMap(); - Size blockSize = new Size(mBlockWidth, mBlockHeight); - Size alignment = new Size(mWidthAlignment, mHeightAlignment); - Range<Integer> counts = null, widths = null, heights = null; - Range<Integer> frameRates = null, bitRates = null; - Range<Long> blockRates = null; - Range<Rational> ratios = null, blockRatios = null; - - blockSize = Utils.parseSize(map.get("block-size"), blockSize); - alignment = Utils.parseSize(map.get("alignment"), alignment); - counts = Utils.parseIntRange(map.get("block-count-range"), null); - blockRates = - Utils.parseLongRange(map.get("blocks-per-second-range"), null); - mMeasuredFrameRates = getMeasuredFrameRates(map); - mPerformancePoints = getPerformancePoints(map); - Pair<Range<Integer>, Range<Integer>> sizeRanges = - parseWidthHeightRanges(map.get("size-range")); - if (sizeRanges != null) { - widths = sizeRanges.first; - heights = sizeRanges.second; - } - // for now this just means using the smaller max size as 2nd - // upper limit. - // for now we are keeping the profile specific "width/height - // in macroblocks" limits. - if (map.containsKey("feature-can-swap-width-height")) { - if (widths != null) { - mSmallerDimensionUpperLimit = - Math.min(widths.getUpper(), heights.getUpper()); - widths = heights = widths.extend(heights); - } else { - Log.w(TAG, "feature can-swap-width-height is best used with size-range"); - mSmallerDimensionUpperLimit = - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); - mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); + 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())); } - 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; + private int getBlockCount(int width, int height) { + return Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); + } + + @NonNull + private Size findClosestSize(int width, int height) { + int targetBlockCount = getBlockCount(width, height); + Size closestSize = null; + int minDiff = Integer.MAX_VALUE; + for (Size size : mMeasuredFrameRates.keySet()) { + int diff = Math.abs(targetBlockCount + - getBlockCount(size.getWidth(), size.getHeight())); + if (diff < minDiff) { + minDiff = diff; + closestSize = size; + } } + return closestSize; } - bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); - if (bitRates != null) { - try { - bitRates = bitRates.intersect(BITRATE_RANGE); - } catch (IllegalArgumentException e) { - Log.w(TAG, "bitrate range (" + bitRates - + ") is out of limits: " + BITRATE_RANGE); - bitRates = null; + + private Range<Double> estimateFrameRatesFor(int width, int height) { + Size size = findClosestSize(width, height); + Range<Long> range = mMeasuredFrameRates.get(size); + Double ratio = getBlockCount(size.getWidth(), size.getHeight()) + / (double)Math.max(getBlockCount(width, height), 1); + return Range.create(range.getLower() * ratio, range.getUpper() * ratio); + } + + /** @throws IllegalArgumentException if the video size is not supported. */ + @Nullable + public Range<Double> getAchievableFrameRatesFor(int width, int height) { + if (!supports(width, height, null)) { + throw new IllegalArgumentException("unsupported size"); } + + if (mMeasuredFrameRates == null || mMeasuredFrameRates.size() <= 0) { + Log.w(TAG, "Codec did not publish any measurement data."); + return null; + } + + return estimateFrameRatesFor(width, height); } - checkPowerOfTwo( - blockSize.getWidth(), "block-size width must be power of two"); - checkPowerOfTwo( - blockSize.getHeight(), "block-size height must be power of two"); + @Nullable + public List<PerformancePoint> getSupportedPerformancePoints() { + return mPerformancePoints; + } - checkPowerOfTwo( - alignment.getWidth(), "alignment width must be power of two"); - checkPowerOfTwo( - alignment.getHeight(), "alignment height must be power of two"); + public boolean areSizeAndRateSupported( + int width, int height, double frameRate) { + return supports(width, height, frameRate); + } - // 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()); + public boolean isSizeSupported(int width, int height) { + return supports(width, height, null); + } - if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) { - // codec supports profiles that we don't know. - // Use supplied values clipped to platform limits - if (widths != null) { - mWidthRange = getSizeRange().intersect(widths); - } - if (heights != null) { - mHeightRange = getSizeRange().intersect(heights); - } - if (counts != null) { - mBlockCountRange = POSITIVE_INTEGERS.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = POSITIVE_LONGS.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); + private boolean supports(Integer width, Integer height, Number rate) { + boolean ok = true; + + if (ok && width != null) { + ok = mWidthRange.contains(width) + && (width % mWidthAlignment == 0); } - if (ratios != null) { - mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); + if (ok && height != null) { + ok = mHeightRange.contains(height) + && (height % mHeightAlignment == 0); } - if (frameRates != null) { - mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); + if (ok && rate != null) { + ok = mFrameRateRange.contains(Utils.intRangeFor(rate.doubleValue())); } - if (bitRates != null) { - // only allow bitrate override if unsupported profiles were encountered - if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { - mBitrateRange = BITRATE_RANGE.intersect(bitRates); - } else { - mBitrateRange = mBitrateRange.intersect(bitRates); + if (ok && height != null && width != null) { + ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; + + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + final int blockCount = widthInBlocks * heightInBlocks; + ok = ok && mBlockCountRange.contains(blockCount) + && mBlockAspectRatioRange.contains( + new Rational(widthInBlocks, heightInBlocks)) + && mAspectRatioRange.contains(new Rational(width, height)); + if (ok && rate != null) { + double blocksPerSec = blockCount * rate.doubleValue(); + ok = mBlocksPerSecondRange.contains( + Utils.longRangeFor(blocksPerSec)); } } - } 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); + return ok; + } + + /** + * @hide + * @throws java.lang.ClassCastException */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); + Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); + Number rate = (Number)map.get(MediaFormat.KEY_FRAME_RATE); + + if (!supports(width, height, rate)) { + return false; } - if (bitRates != null) { - mBitrateRange = mBitrateRange.intersect(bitRates); + + if (!CodecCapabilities.supportsBitrate(mBitrateRange, format)) { + return false; } + + // we ignore color-format for now as it is not reliably reported by codec + return true; } - 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; - } + /** @hide */ + public Size getBlockSize() { + return new Size(mBlockWidth, mBlockHeight); + } - 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"); + /** @hide */ + public Range<Integer> getBlockCountRange() { + return mBlockCountRange; + } - 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); + /** @hide */ + public Range<Long> getBlocksPerSecondRange() { + return mBlocksPerSecondRange; } - mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); - mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); + /** @hide */ + public Range<Rational> getAspectRatioRange(boolean blocks) { + return blocks ? mBlockAspectRatioRange : mAspectRatioRange; + } - mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); - mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); - } + private void initWithPlatformLimits() { + mBitrateRange = BITRATE_RANGE; - 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())); - } + mWidthRange = getSizeRange(); + mHeightRange = getSizeRange(); + mFrameRateRange = FRAME_RATE_RANGE; - private void applyMacroBlockLimits( - int maxHorizontalBlocks, int maxVerticalBlocks, - int maxBlocks, long maxBlocksPerSecond, - int blockWidth, int blockHeight, - int widthAlignment, int heightAlignment) { - applyMacroBlockLimits( - 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */, - maxHorizontalBlocks, maxVerticalBlocks, - maxBlocks, maxBlocksPerSecond, - blockWidth, blockHeight, widthAlignment, heightAlignment); - } + mHorizontalBlockRange = getSizeRange(); + mVerticalBlockRange = getSizeRange(); - private void applyMacroBlockLimits( - int minHorizontalBlocks, int minVerticalBlocks, - int maxHorizontalBlocks, int maxVerticalBlocks, - int maxBlocks, long maxBlocksPerSecond, - int blockWidth, int blockHeight, - int widthAlignment, int heightAlignment) { - applyAlignment(widthAlignment, heightAlignment); - applyBlockLimits( - blockWidth, blockHeight, Range.create(1, maxBlocks), - Range.create(1L, maxBlocksPerSecond), - Range.create( - new Rational(1, maxVerticalBlocks), - new Rational(maxHorizontalBlocks, 1))); - mHorizontalBlockRange = - mHorizontalBlockRange.intersect( - Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)), - maxHorizontalBlocks / (mBlockWidth / blockWidth)); - mVerticalBlockRange = - mVerticalBlockRange.intersect( - Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)), - maxVerticalBlocks / (mBlockHeight / blockHeight)); - } + // full positive ranges are supported as these get calculated + mBlockCountRange = POSITIVE_INTEGERS; + mBlocksPerSecondRange = POSITIVE_LONGS; - private void applyLevelLimits() { - long maxBlocksPerSecond = 0; - int maxBlocks = 0; - int maxBps = 0; - int maxDPBBlocks = 0; + mBlockAspectRatioRange = POSITIVE_RATIONALS; + mAspectRatioRange = POSITIVE_RATIONALS; - int errors = ERROR_NONE_SUPPORTED; - CodecProfileLevel[] profileLevels = mParent.profileLevels; - String mime = mParent.getMimeType(); + mWidthAlignment = 1; + mHeightAlignment = 1; + mBlockWidth = 1; + mBlockHeight = 1; + mSmallerDimensionUpperLimit = getSizeRange().getUpper(); + } - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - maxDPBBlocks = 396; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, DPB = 0; - boolean supported = true; - switch (profileLevel.level) { - case CodecProfileLevel.AVCLevel1: - MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; - case CodecProfileLevel.AVCLevel1b: - MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; - case CodecProfileLevel.AVCLevel11: - MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; - case CodecProfileLevel.AVCLevel12: - MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; - case CodecProfileLevel.AVCLevel13: - MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; - case CodecProfileLevel.AVCLevel2: - MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; - case CodecProfileLevel.AVCLevel21: - MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; - case CodecProfileLevel.AVCLevel22: - MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel3: - MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel31: - MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; - case CodecProfileLevel.AVCLevel32: - MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; - case CodecProfileLevel.AVCLevel4: - MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel41: - MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel42: - MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; - case CodecProfileLevel.AVCLevel5: - MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; - case CodecProfileLevel.AVCLevel51: - MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; - case CodecProfileLevel.AVCLevel52: - MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; - case CodecProfileLevel.AVCLevel6: - MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break; - case CodecProfileLevel.AVCLevel61: - MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break; - case CodecProfileLevel.AVCLevel62: - MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + private @Nullable List<PerformancePoint> getPerformancePoints(Map<String, Object> map) { + Vector<PerformancePoint> ret = new Vector<>(); + final String prefix = "performance-point-"; + Set<String> keys = map.keySet(); + for (String key : keys) { + // looking for: performance-point-WIDTHxHEIGHT-range + if (!key.startsWith(prefix)) { + continue; } - switch (profileLevel.profile) { - case CodecProfileLevel.AVCProfileConstrainedHigh: - case CodecProfileLevel.AVCProfileHigh: - BR *= 1250; break; - case CodecProfileLevel.AVCProfileHigh10: - BR *= 3000; break; - case CodecProfileLevel.AVCProfileExtended: - case CodecProfileLevel.AVCProfileHigh422: - case CodecProfileLevel.AVCProfileHigh444: - Log.w(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - // fall through - treat as base profile - case CodecProfileLevel.AVCProfileConstrainedBaseline: - case CodecProfileLevel.AVCProfileBaseline: - case CodecProfileLevel.AVCProfileMain: - BR *= 1000; break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - BR *= 1000; + String subKey = key.substring(prefix.length()); + if (subKey.equals("none") && ret.size() == 0) { + // This means that component knowingly did not publish performance points. + // This is different from when the component forgot to publish performance + // points. + return Collections.unmodifiableList(ret); } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; + String[] temp = key.split("-"); + if (temp.length != 4) { + continue; } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR, maxBps); - maxDPBBlocks = Math.max(maxDPBBlocks, DPB); - } - - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; - boolean supported = true; - switch (profileLevel.profile) { - case CodecProfileLevel.MPEG2ProfileSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG2LevelML: - FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG2ProfileMain: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG2LevelLL: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; - case CodecProfileLevel.MPEG2LevelML: - FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; - case CodecProfileLevel.MPEG2LevelH14: - FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break; - case CodecProfileLevel.MPEG2LevelHL: - FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break; - case CodecProfileLevel.MPEG2LevelHP: - FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG2Profile422: - case CodecProfileLevel.MPEG2ProfileSNR: - case CodecProfileLevel.MPEG2ProfileSpatial: - case CodecProfileLevel.MPEG2ProfileHigh: - Log.i(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + String sizeStr = temp[2]; + Size size = Utils.parseSize(sizeStr, null); + if (size == null || size.getWidth() * size.getHeight() <= 0) { + continue; } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; + Range<Long> range = Utils.parseLongRange(map.get(key), null); + if (range == null || range.getLower() < 0 || range.getUpper() < 0) { + continue; } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); - } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = mFrameRateRange.intersect(12, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; - boolean strict = false; // true: W, H and FR are individual max limits - boolean supported = true; - switch (profileLevel.profile) { - case CodecProfileLevel.MPEG4ProfileSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - strict = true; - FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level0b: - strict = true; - FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level4a: - FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break; - case CodecProfileLevel.MPEG4Level5: - FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break; - case CodecProfileLevel.MPEG4Level6: - FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileAdvancedSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; - case CodecProfileLevel.MPEG4Level3b: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break; - case CodecProfileLevel.MPEG4Level4: - FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; - case CodecProfileLevel.MPEG4Level5: - FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileMain: // 2-4 - case CodecProfileLevel.MPEG4ProfileNbit: // 2 - case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 - case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 - case CodecProfileLevel.MPEG4ProfileCore: // 1-2 - case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 - case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 - case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 - - // Studio profiles are not supported by our codecs. - - // Only profiles that can decode simple object types are considered. - // The following profiles are not able to. - case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 - case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 - case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 - case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 - Log.i(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + PerformancePoint given = new PerformancePoint( + size.getWidth(), size.getHeight(), range.getLower().intValue(), + range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); + PerformancePoint rotated = new PerformancePoint( + size.getHeight(), size.getWidth(), range.getLower().intValue(), + range.getUpper().intValue(), new Size(mBlockWidth, mBlockHeight)); + ret.add(given); + if (!given.covers(rotated)) { + ret.add(rotated); } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; + } + + // check if the component specified no performance point indication + if (ret.size() == 0) { + return null; + } + + // sort reversed by area first, then by frame rate + ret.sort((a, b) -> + -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) + ? (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) : + (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) + ? (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) : + (a.getMaxFrameRate() != b.getMaxFrameRate()) + ? (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0)); + + return Collections.unmodifiableList(ret); + } + + private Map<Size, Range<Long>> getMeasuredFrameRates(Map<String, Object> map) { + Map<Size, Range<Long>> ret = new HashMap<Size, Range<Long>>(); + final String prefix = "measured-frame-rate-"; + Set<String> keys = map.keySet(); + for (String key : keys) { + // looking for: measured-frame-rate-WIDTHxHEIGHT-range + if (!key.startsWith(prefix)) { + continue; } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - if (strict) { - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); - } else { - // assuming max 60 fps frame rate and 1:2 aspect ratio - int maxDim = (int)Math.sqrt(FS * 2); - maxWidth = Math.max(maxDim, maxWidth); - maxHeight = Math.max(maxDim, maxHeight); - maxRate = Math.max(Math.max(FR, 60), maxRate); + String subKey = key.substring(prefix.length()); + String[] temp = key.split("-"); + if (temp.length != 5) { + continue; } - } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = mFrameRateRange.intersect(12, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - int minWidth = maxWidth, minHeight = maxHeight; - int minAlignment = 16; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight; - boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF) - switch (profileLevel.level) { - case CodecProfileLevel.H263Level10: - strict = true; // only supports sQCIF & QCIF - FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level20: - strict = true; // only supports sQCIF, QCIF & CIF - FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break; - case CodecProfileLevel.H263Level30: - strict = true; // only supports sQCIF, QCIF & CIF - FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level40: - strict = true; // only supports sQCIF, QCIF & CIF - FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level45: - // only implies level 10 support - strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline - || profileLevel.profile == - CodecProfileLevel.H263ProfileBackwardCompatible; - if (!strict) { - minW = 1; minH = 1; minAlignment = 4; - } - FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level50: - // only supports 50fps for H > 15 - minW = 1; minH = 1; minAlignment = 4; - FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level60: - // only supports 50fps for H > 15 - minW = 1; minH = 1; minAlignment = 4; - FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level70: - // only supports 50fps for H > 30 - minW = 1; minH = 1; minAlignment = 4; - FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; - default: - Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile - + "/" + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + String sizeStr = temp[3]; + Size size = Utils.parseSize(sizeStr, null); + if (size == null || size.getWidth() * size.getHeight() <= 0) { + continue; } - 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; + Range<Long> range = Utils.parseLongRange(map.get(key), null); + if (range == null || range.getLower() < 0 || range.getUpper() < 0) { + continue; } - if (strict) { - // Strict levels define sub-QCIF min size and enumerated sizes. We cannot - // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities - // but we can express "only QCIF (& CIF)", so set minimume size at QCIF. - // minW = 8; minH = 6; - minW = 11; minH = 9; - } else { - // any support for non-strict levels (including unrecognized profiles or - // levels) allow custom frame size support beyond supported limits - // (other than bitrate) - mAllowMbOverride = true; + ret.put(size, range); + } + return ret; + } + + private static Pair<Range<Integer>, Range<Integer>> parseWidthHeightRanges(Object o) { + Pair<Size, Size> range = Utils.parseSizeRange(o); + if (range != null) { + try { + return Pair.create( + Range.create(range.first.getWidth(), range.second.getWidth()), + Range.create(range.first.getHeight(), range.second.getHeight())); + } catch (IllegalArgumentException e) { + Log.w(TAG, "could not parse size range '" + o + "'"); } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(W * H, maxBlocks); - maxBps = Math.max(BR * 64000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); - minWidth = Math.min(minW, minWidth); - minHeight = Math.min(minH, minHeight); } - // unless we encountered custom frame size support, limit size to QCIF and CIF - // using aspect ratio. - if (!mAllowMbOverride) { - mBlockAspectRatioRange = - Range.create(new Rational(11, 9), new Rational(11, 9)); + return null; + } + + /** @hide */ + public static int equivalentVP9Level(MediaFormat info) { + final Map<String, Object> map = info.getMap(); + + Size blockSize = Utils.parseSize(map.get("block-size"), new Size(8, 8)); + int BS = blockSize.getWidth() * blockSize.getHeight(); + + Range<Integer> counts = Utils.parseIntRange(map.get("block-count-range"), null); + int FS = counts == null ? 0 : BS * counts.getUpper(); + + Range<Long> blockRates = + Utils.parseLongRange(map.get("blocks-per-second-range"), null); + long SR = blockRates == null ? 0 : BS * blockRates.getUpper(); + + Pair<Range<Integer>, Range<Integer>> dimensionRanges = + parseWidthHeightRanges(map.get("size-range")); + int D = dimensionRanges == null ? 0 : Math.max( + dimensionRanges.first.getUpper(), dimensionRanges.second.getUpper()); + + Range<Integer> bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); + int BR = bitRates == null ? 0 : Utils.divUp(bitRates.getUpper(), 1000); + + if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512) + return CodecProfileLevel.VP9Level1; + if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768) + return CodecProfileLevel.VP9Level11; + if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960) + return CodecProfileLevel.VP9Level2; + if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344) + return CodecProfileLevel.VP9Level21; + if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048) + return CodecProfileLevel.VP9Level3; + if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752) + return CodecProfileLevel.VP9Level31; + if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160) + return CodecProfileLevel.VP9Level4; + if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160) + return CodecProfileLevel.VP9Level41; + if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384) + return CodecProfileLevel.VP9Level5; + if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384) + return CodecProfileLevel.VP9Level51; + if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384) + return CodecProfileLevel.VP9Level52; + if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832) + return CodecProfileLevel.VP9Level6; + if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832) + return CodecProfileLevel.VP9Level61; + if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832) + return CodecProfileLevel.VP9Level62; + // returning largest level + return CodecProfileLevel.VP9Level62; + } + + private void parseFromInfo(MediaFormat info) { + final Map<String, Object> map = info.getMap(); + Size blockSize = new Size(mBlockWidth, mBlockHeight); + Size alignment = new Size(mWidthAlignment, mHeightAlignment); + Range<Integer> counts = null, widths = null, heights = null; + Range<Integer> frameRates = null, bitRates = null; + Range<Long> blockRates = null; + Range<Rational> ratios = null, blockRatios = null; + + blockSize = Utils.parseSize(map.get("block-size"), blockSize); + alignment = Utils.parseSize(map.get("alignment"), alignment); + counts = Utils.parseIntRange(map.get("block-count-range"), null); + blockRates = + Utils.parseLongRange(map.get("blocks-per-second-range"), null); + mMeasuredFrameRates = getMeasuredFrameRates(map); + mPerformancePoints = getPerformancePoints(map); + Pair<Range<Integer>, Range<Integer>> sizeRanges = + parseWidthHeightRanges(map.get("size-range")); + if (sizeRanges != null) { + widths = sizeRanges.first; + heights = sizeRanges.second; } - applyMacroBlockLimits( - minWidth, minHeight, - maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - minAlignment /* widthAlignment */, minAlignment /* heightAlignment */); - mFrameRateRange = Range.create(1, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) { - maxBlocks = Integer.MAX_VALUE; - maxBlocksPerSecond = Integer.MAX_VALUE; - - // TODO: set to 100Mbps for now, need a number for VP8 - maxBps = 100000000; - - // profile levels are not indicative for VPx, but verify - // them nonetheless - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.level) { - case CodecProfileLevel.VP8Level_Version0: - case CodecProfileLevel.VP8Level_Version1: - case CodecProfileLevel.VP8Level_Version2: - case CodecProfileLevel.VP8Level_Version3: - break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.VP8ProfileMain: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + // for now this just means using the smaller max size as 2nd + // upper limit. + // for now we are keeping the profile specific "width/height + // in macroblocks" limits. + if (map.containsKey("feature-can-swap-width-height")) { + if (widths != null) { + mSmallerDimensionUpperLimit = + Math.min(widths.getUpper(), heights.getUpper()); + widths = heights = widths.extend(heights); + } else { + Log.w(TAG, "feature can-swap-width-height is best used with size-range"); + mSmallerDimensionUpperLimit = + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); + mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); } - errors &= ~ERROR_NONE_SUPPORTED; } - final int blockSize = 16; - applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, - maxBlocks, maxBlocksPerSecond, blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { - maxBlocksPerSecond = 829440; - maxBlocks = 36864; - maxBps = 200000; - int maxDim = 512; - - for (CodecProfileLevel profileLevel: profileLevels) { - long SR = 0; // luma sample rate - int FS = 0; // luma picture size - int BR = 0; // bit rate kbps - int D = 0; // luma dimension - switch (profileLevel.level) { - case CodecProfileLevel.VP9Level1: - SR = 829440; FS = 36864; BR = 200; D = 512; break; - case CodecProfileLevel.VP9Level11: - SR = 2764800; FS = 73728; BR = 800; D = 768; break; - case CodecProfileLevel.VP9Level2: - SR = 4608000; FS = 122880; BR = 1800; D = 960; break; - case CodecProfileLevel.VP9Level21: - SR = 9216000; FS = 245760; BR = 3600; D = 1344; break; - case CodecProfileLevel.VP9Level3: - SR = 20736000; FS = 552960; BR = 7200; D = 2048; break; - case CodecProfileLevel.VP9Level31: - SR = 36864000; FS = 983040; BR = 12000; D = 2752; break; - case CodecProfileLevel.VP9Level4: - SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break; - case CodecProfileLevel.VP9Level41: - SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break; - case CodecProfileLevel.VP9Level5: - SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break; - case CodecProfileLevel.VP9Level51: - SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break; - case CodecProfileLevel.VP9Level52: - SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break; - case CodecProfileLevel.VP9Level6: - SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break; - case CodecProfileLevel.VP9Level61: - SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break; - case CodecProfileLevel.VP9Level62: - SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + 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; } - switch (profileLevel.profile) { - case CodecProfileLevel.VP9Profile0: - case CodecProfileLevel.VP9Profile1: - case CodecProfileLevel.VP9Profile2: - case CodecProfileLevel.VP9Profile3: - case CodecProfileLevel.VP9Profile2HDR: - case CodecProfileLevel.VP9Profile3HDR: - case CodecProfileLevel.VP9Profile2HDR10Plus: - case CodecProfileLevel.VP9Profile3HDR10Plus: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + } + bitRates = Utils.parseIntRange(map.get("bitrate-range"), null); + if (bitRates != null) { + try { + bitRates = bitRates.intersect(BITRATE_RANGE); + } catch (IllegalArgumentException e) { + Log.w(TAG, "bitrate range (" + bitRates + + ") is out of limits: " + BITRATE_RANGE); + bitRates = null; } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxDim = Math.max(D, maxDim); } - final int blockSize = 8; - int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); - maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); - maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); + 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( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { - // CTBs are at least 8x8 so use 8x8 block size - maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks - maxBlocksPerSecond = maxBlocks * 15; - maxBps = 128000; - for (CodecProfileLevel profileLevel: profileLevels) { - double FR = 0; - int FS = 0; - int BR = 0; - switch (profileLevel.level) { - /* The HEVC spec talks only in a very convoluted manner about the - existence of levels 1-3.1 for High tier, which could also be - understood as 'decoders and encoders should treat these levels - as if they were Main tier', so we do that. */ - case CodecProfileLevel.HEVCMainTierLevel1: - case CodecProfileLevel.HEVCHighTierLevel1: - FR = 15; FS = 36864; BR = 128; break; - case CodecProfileLevel.HEVCMainTierLevel2: - case CodecProfileLevel.HEVCHighTierLevel2: - FR = 30; FS = 122880; BR = 1500; break; - case CodecProfileLevel.HEVCMainTierLevel21: - case CodecProfileLevel.HEVCHighTierLevel21: - FR = 30; FS = 245760; BR = 3000; break; - case CodecProfileLevel.HEVCMainTierLevel3: - case CodecProfileLevel.HEVCHighTierLevel3: - FR = 30; FS = 552960; BR = 6000; break; - case CodecProfileLevel.HEVCMainTierLevel31: - case CodecProfileLevel.HEVCHighTierLevel31: - FR = 33.75; FS = 983040; BR = 10000; break; - case CodecProfileLevel.HEVCMainTierLevel4: - FR = 30; FS = 2228224; BR = 12000; break; - case CodecProfileLevel.HEVCHighTierLevel4: - FR = 30; FS = 2228224; BR = 30000; break; - case CodecProfileLevel.HEVCMainTierLevel41: - FR = 60; FS = 2228224; BR = 20000; break; - case CodecProfileLevel.HEVCHighTierLevel41: - FR = 60; FS = 2228224; BR = 50000; break; - case CodecProfileLevel.HEVCMainTierLevel5: - FR = 30; FS = 8912896; BR = 25000; break; - case CodecProfileLevel.HEVCHighTierLevel5: - FR = 30; FS = 8912896; BR = 100000; break; - case CodecProfileLevel.HEVCMainTierLevel51: - FR = 60; FS = 8912896; BR = 40000; break; - case CodecProfileLevel.HEVCHighTierLevel51: - FR = 60; FS = 8912896; BR = 160000; break; - case CodecProfileLevel.HEVCMainTierLevel52: - FR = 120; FS = 8912896; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel52: - FR = 120; FS = 8912896; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel6: - FR = 30; FS = 35651584; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel6: - FR = 30; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel61: - FR = 60; FS = 35651584; BR = 120000; break; - case CodecProfileLevel.HEVCHighTierLevel61: - FR = 60; FS = 35651584; BR = 480000; break; - case CodecProfileLevel.HEVCMainTierLevel62: - FR = 120; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCHighTierLevel62: - FR = 120; FS = 35651584; BR = 800000; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, + Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(), + alignment.getWidth(), alignment.getHeight()); + + if ((mParent.mError & ERROR_UNSUPPORTED) != 0 || mAllowMbOverride) { + // codec supports profiles that we don't know. + // Use supplied values clipped to platform limits + if (widths != null) { + mWidthRange = getSizeRange().intersect(widths); + } + if (heights != null) { + mHeightRange = getSizeRange().intersect(heights); + } + if (counts != null) { + mBlockCountRange = POSITIVE_INTEGERS.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRates != null) { + mBlocksPerSecondRange = POSITIVE_LONGS.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRatios != null) { + mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); + } + if (ratios != null) { + mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); + } + if (frameRates != null) { + mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); + } + if (bitRates != null) { + // only allow bitrate override if unsupported profiles were encountered + if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { + mBitrateRange = BITRATE_RANGE.intersect(bitRates); + } else { + mBitrateRange = mBitrateRange.intersect(bitRates); + } + } + } else { + // no unsupported profile/levels, so restrict values to known limits + if (widths != null) { + mWidthRange = mWidthRange.intersect(widths); + } + if (heights != null) { + mHeightRange = mHeightRange.intersect(heights); + } + if (counts != null) { + mBlockCountRange = mBlockCountRange.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRates != null) { + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRatios != null) { + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); + } + if (ratios != null) { + mAspectRatioRange = mAspectRatioRange.intersect(ratios); } - switch (profileLevel.profile) { - case CodecProfileLevel.HEVCProfileMain: - case CodecProfileLevel.HEVCProfileMain10: - case CodecProfileLevel.HEVCProfileMainStill: - case CodecProfileLevel.HEVCProfileMain10HDR10: - case CodecProfileLevel.HEVCProfileMain10HDR10Plus: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + if (frameRates != null) { + mFrameRateRange = mFrameRateRange.intersect(frameRates); } + if (bitRates != null) { + mBitrateRange = mBitrateRange.intersect(bitRates); + } + } + updateLimits(); + } + + private void applyBlockLimits( + int blockWidth, int blockHeight, + Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) { + checkPowerOfTwo(blockWidth, "blockWidth must be a power of two"); + checkPowerOfTwo(blockHeight, "blockHeight must be a power of two"); + + final int newBlockWidth = Math.max(blockWidth, mBlockWidth); + final int newBlockHeight = Math.max(blockHeight, mBlockHeight); + + // factor will always be a power-of-2 + int factor = + newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; + if (factor != 1) { + mBlockCountRange = Utils.factorRange(mBlockCountRange, factor); + mBlocksPerSecondRange = Utils.factorRange( + mBlocksPerSecondRange, factor); + mBlockAspectRatioRange = Utils.scaleRange( + mBlockAspectRatioRange, + newBlockHeight / mBlockHeight, + newBlockWidth / mBlockWidth); + mHorizontalBlockRange = Utils.factorRange( + mHorizontalBlockRange, newBlockWidth / mBlockWidth); + mVerticalBlockRange = Utils.factorRange( + mVerticalBlockRange, newBlockHeight / mBlockHeight); + } + factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; + if (factor != 1) { + counts = Utils.factorRange(counts, factor); + rates = Utils.factorRange(rates, factor); + ratios = Utils.scaleRange( + ratios, newBlockHeight / blockHeight, + newBlockWidth / blockWidth); + } + mBlockCountRange = mBlockCountRange.intersect(counts); + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); + mBlockWidth = newBlockWidth; + mBlockHeight = newBlockHeight; + } + + private void applyAlignment(int widthAlignment, int heightAlignment) { + checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two"); + checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two"); - /* DPB logic: - if (width * height <= FS / 4) DPB = 16; - else if (width * height <= FS / 2) DPB = 12; - else if (width * height <= FS * 0.75) DPB = 8; - else DPB = 6; - */ - - FS >>= 6; // convert pixels to blocks - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); + 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); } - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); + mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); + + mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); + mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); + } + + private void updateLimits() { + // pixels -> blocks <- counts + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Utils.factorRange(mWidthRange, mBlockWidth)); + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Utils.factorRange(mHeightRange, mBlockHeight)); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); + mBlockCountRange = mBlockCountRange.intersect( + Range.create( + mHorizontalBlockRange.getLower() + * mVerticalBlockRange.getLower(), + mHorizontalBlockRange.getUpper() + * mVerticalBlockRange.getUpper())); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + new Rational( + mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), + new Rational( + mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); + + // blocks -> pixels + mWidthRange = mWidthRange.intersect( + (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, + mHorizontalBlockRange.getUpper() * mBlockWidth); + mHeightRange = mHeightRange.intersect( + (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, + mVerticalBlockRange.getUpper() * mBlockHeight); + mAspectRatioRange = mAspectRatioRange.intersect( + new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), + new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); + + mSmallerDimensionUpperLimit = Math.min( + mSmallerDimensionUpperLimit, + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); + + // blocks -> rate + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), + mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); + mFrameRateRange = mFrameRateRange.intersect( + (int)(mBlocksPerSecondRange.getLower() + / mBlockCountRange.getUpper()), + (int)(mBlocksPerSecondRange.getUpper() + / (double)mBlockCountRange.getLower())); + } + + private void applyMacroBlockLimits( + int maxHorizontalBlocks, int maxVerticalBlocks, + int maxBlocks, long maxBlocksPerSecond, + int blockWidth, int blockHeight, + int widthAlignment, int heightAlignment) { applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, + 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */, + maxHorizontalBlocks, maxVerticalBlocks, maxBlocks, maxBlocksPerSecond, - 8 /* blockWidth */, 8 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) { - maxBlocksPerSecond = 829440; - maxBlocks = 36864; - maxBps = 200000; - int maxDim = 512; - - // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec, - // corresponding to the definitions in - // "AV1 Bitstream & Decoding Process Specification", Annex A - // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/ - for (CodecProfileLevel profileLevel: profileLevels) { - long SR = 0; // luma sample rate - int FS = 0; // luma picture size - int BR = 0; // bit rate kbps - int D = 0; // luma D - switch (profileLevel.level) { - case CodecProfileLevel.AV1Level2: - SR = 5529600; FS = 147456; BR = 1500; D = 2048; break; - case CodecProfileLevel.AV1Level21: - case CodecProfileLevel.AV1Level22: - case CodecProfileLevel.AV1Level23: - SR = 10454400; FS = 278784; BR = 3000; D = 2816; break; - - case CodecProfileLevel.AV1Level3: - SR = 24969600; FS = 665856; BR = 6000; D = 4352; break; - case CodecProfileLevel.AV1Level31: - case CodecProfileLevel.AV1Level32: - case CodecProfileLevel.AV1Level33: - SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break; - - case CodecProfileLevel.AV1Level4: - SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break; - case CodecProfileLevel.AV1Level41: - case CodecProfileLevel.AV1Level42: - case CodecProfileLevel.AV1Level43: - SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break; - - case CodecProfileLevel.AV1Level5: - SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break; - case CodecProfileLevel.AV1Level51: - SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break; - case CodecProfileLevel.AV1Level52: - SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break; - case CodecProfileLevel.AV1Level53: - SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break; - - case CodecProfileLevel.AV1Level6: - SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break; - case CodecProfileLevel.AV1Level61: - SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break; - case CodecProfileLevel.AV1Level62: - SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break; - case CodecProfileLevel.AV1Level63: - SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break; - - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + blockWidth, blockHeight, widthAlignment, heightAlignment); + } + + private void applyMacroBlockLimits( + int minHorizontalBlocks, int minVerticalBlocks, + int maxHorizontalBlocks, int maxVerticalBlocks, + int maxBlocks, long maxBlocksPerSecond, + int blockWidth, int blockHeight, + int widthAlignment, int heightAlignment) { + applyAlignment(widthAlignment, heightAlignment); + applyBlockLimits( + blockWidth, blockHeight, Range.create(1, maxBlocks), + Range.create(1L, maxBlocksPerSecond), + Range.create( + new Rational(1, maxVerticalBlocks), + new Rational(maxHorizontalBlocks, 1))); + mHorizontalBlockRange = + mHorizontalBlockRange.intersect( + Utils.divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)), + maxHorizontalBlocks / (mBlockWidth / blockWidth)); + mVerticalBlockRange = + mVerticalBlockRange.intersect( + Utils.divUp(minVerticalBlocks, (mBlockHeight / blockHeight)), + maxVerticalBlocks / (mBlockHeight / blockHeight)); + } + + private void applyLevelLimits() { + long maxBlocksPerSecond = 0; + int maxBlocks = 0; + int maxBps = 0; + int maxDPBBlocks = 0; + + int errors = ERROR_NONE_SUPPORTED; + CodecProfileLevel[] profileLevels = mParent.profileLevels; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + maxDPBBlocks = 396; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, DPB = 0; + boolean supported = true; + switch (profileLevel.level) { + case CodecProfileLevel.AVCLevel1: + MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; + case CodecProfileLevel.AVCLevel1b: + MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; + case CodecProfileLevel.AVCLevel11: + MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; + case CodecProfileLevel.AVCLevel12: + MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; + case CodecProfileLevel.AVCLevel13: + MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; + case CodecProfileLevel.AVCLevel2: + MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; + case CodecProfileLevel.AVCLevel21: + MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; + case CodecProfileLevel.AVCLevel22: + MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel3: + MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel31: + MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; + case CodecProfileLevel.AVCLevel32: + MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; + case CodecProfileLevel.AVCLevel4: + MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel41: + MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel42: + MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; + case CodecProfileLevel.AVCLevel5: + MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; + case CodecProfileLevel.AVCLevel51: + MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; + case CodecProfileLevel.AVCLevel52: + MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; + case CodecProfileLevel.AVCLevel6: + MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break; + case CodecProfileLevel.AVCLevel61: + MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break; + case CodecProfileLevel.AVCLevel62: + MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.AVCProfileConstrainedHigh: + case CodecProfileLevel.AVCProfileHigh: + BR *= 1250; break; + case CodecProfileLevel.AVCProfileHigh10: + BR *= 3000; break; + case CodecProfileLevel.AVCProfileExtended: + case CodecProfileLevel.AVCProfileHigh422: + case CodecProfileLevel.AVCProfileHigh444: + Log.w(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + // fall through - treat as base profile + case CodecProfileLevel.AVCProfileConstrainedBaseline: + case CodecProfileLevel.AVCProfileBaseline: + case CodecProfileLevel.AVCProfileMain: + BR *= 1000; break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + BR *= 1000; + } + if (supported) { + errors &= ~ERROR_NONE_SUPPORTED; + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR, maxBps); + maxDPBBlocks = Math.max(maxDPBBlocks, DPB); } - switch (profileLevel.profile) { - case CodecProfileLevel.AV1ProfileMain8: - case CodecProfileLevel.AV1ProfileMain10: - case CodecProfileLevel.AV1ProfileMain10HDR10: - case CodecProfileLevel.AV1ProfileMain10HDR10Plus: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; + + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; + boolean supported = true; + switch (profileLevel.profile) { + case CodecProfileLevel.MPEG2ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG2LevelML: + FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG2ProfileMain: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG2LevelLL: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; + case CodecProfileLevel.MPEG2LevelML: + FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; + case CodecProfileLevel.MPEG2LevelH14: + FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break; + case CodecProfileLevel.MPEG2LevelHL: + FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break; + case CodecProfileLevel.MPEG2LevelHP: + FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG2Profile422: + case CodecProfileLevel.MPEG2ProfileSNR: + case CodecProfileLevel.MPEG2ProfileSpatial: + case CodecProfileLevel.MPEG2ProfileHigh: + Log.i(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + if (supported) { + errors &= ~ERROR_NONE_SUPPORTED; + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = mFrameRateRange.intersect(12, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; + boolean strict = false; // true: W, H and FR are individual max limits + boolean supported = true; + switch (profileLevel.profile) { + case CodecProfileLevel.MPEG4ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + strict = true; + FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level0b: + strict = true; + FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level4a: + FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break; + case CodecProfileLevel.MPEG4Level5: + FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break; + case CodecProfileLevel.MPEG4Level6: + FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileAdvancedSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; + case CodecProfileLevel.MPEG4Level3b: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break; + case CodecProfileLevel.MPEG4Level4: + FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; + case CodecProfileLevel.MPEG4Level5: + FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileMain: // 2-4 + case CodecProfileLevel.MPEG4ProfileNbit: // 2 + case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 + case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 + case CodecProfileLevel.MPEG4ProfileCore: // 1-2 + case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 + case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 + case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 + + // Studio profiles are not supported by our codecs. + + // Only profiles that can decode simple object types are considered. + // The following profiles are not able to. + case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 + case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 + case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 + case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 + Log.i(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + if (supported) { + errors &= ~ERROR_NONE_SUPPORTED; + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + if (strict) { + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } else { + // assuming max 60 fps frame rate and 1:2 aspect ratio + int maxDim = (int)Math.sqrt(FS * 2); + maxWidth = Math.max(maxDim, maxWidth); + maxHeight = Math.max(maxDim, maxHeight); + maxRate = Math.max(Math.max(FR, 60), maxRate); + } + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = mFrameRateRange.intersect(12, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + int minWidth = maxWidth, minHeight = maxHeight; + int minAlignment = 16; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight; + boolean strict = false; // true: support only sQCIF, QCIF (maybe CIF) + switch (profileLevel.level) { + case CodecProfileLevel.H263Level10: + strict = true; // only supports sQCIF & QCIF + FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level20: + strict = true; // only supports sQCIF, QCIF & CIF + FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break; + case CodecProfileLevel.H263Level30: + strict = true; // only supports sQCIF, QCIF & CIF + FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level40: + strict = true; // only supports sQCIF, QCIF & CIF + FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level45: + // only implies level 10 support + strict = profileLevel.profile == CodecProfileLevel.H263ProfileBaseline + || profileLevel.profile + == CodecProfileLevel.H263ProfileBackwardCompatible; + if (!strict) { + minW = 1; minH = 1; minAlignment = 4; + } + FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level50: + // only supports 50fps for H > 15 + minW = 1; minH = 1; minAlignment = 4; + FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level60: + // only supports 50fps for H > 15 + minW = 1; minH = 1; minAlignment = 4; + FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level70: + // only supports 50fps for H > 30 + minW = 1; minH = 1; minAlignment = 4; + FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; + default: + Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile + + "/" + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.H263ProfileBackwardCompatible: + case CodecProfileLevel.H263ProfileBaseline: + case CodecProfileLevel.H263ProfileH320Coding: + case CodecProfileLevel.H263ProfileHighCompression: + case CodecProfileLevel.H263ProfileHighLatency: + case CodecProfileLevel.H263ProfileInterlace: + case CodecProfileLevel.H263ProfileInternet: + case CodecProfileLevel.H263ProfileISWV2: + case CodecProfileLevel.H263ProfileISWV3: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + if (strict) { + // Strict levels define sub-QCIF min size and enumerated sizes. We + // cannot express support for "only sQCIF & QCIF (& CIF)" using + // VideoCapabilities but we can express "only QCIF (& CIF)", so set + // minimume size at QCIF.minW = 8; minH = 6; + minW = 11; minH = 9; + } else { + // any support for non-strict levels (including unrecognized profiles or + // levels) allow custom frame size support beyond supported limits + // (other than bitrate) + mAllowMbOverride = true; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(W * H, maxBlocks); + maxBps = Math.max(BR * 64000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + minWidth = Math.min(minW, minWidth); + minHeight = Math.min(minH, minHeight); } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxDim = Math.max(D, maxDim); + // unless we encountered custom frame size support, limit size to QCIF and CIF + // using aspect ratio. + if (!mAllowMbOverride) { + mBlockAspectRatioRange = + Range.create(new Rational(11, 9), new Rational(11, 9)); + } + applyMacroBlockLimits( + minWidth, minHeight, + maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + minAlignment /* widthAlignment */, minAlignment /* heightAlignment */); + mFrameRateRange = Range.create(1, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8)) { + maxBlocks = Integer.MAX_VALUE; + maxBlocksPerSecond = Integer.MAX_VALUE; + + // TODO: set to 100Mbps for now, need a number for VP8 + maxBps = 100000000; + + // profile levels are not indicative for VPx, but verify + // them nonetheless + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.level) { + case CodecProfileLevel.VP8Level_Version0: + case CodecProfileLevel.VP8Level_Version1: + case CodecProfileLevel.VP8Level_Version2: + case CodecProfileLevel.VP8Level_Version3: + break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.VP8ProfileMain: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + } + + final int blockSize = 16; + applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, + maxBlocks, maxBlocksPerSecond, blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { + maxBlocksPerSecond = 829440; + maxBlocks = 36864; + maxBps = 200000; + int maxDim = 512; + + for (CodecProfileLevel profileLevel: profileLevels) { + long SR = 0; // luma sample rate + int FS = 0; // luma picture size + int BR = 0; // bit rate kbps + int D = 0; // luma dimension + switch (profileLevel.level) { + case CodecProfileLevel.VP9Level1: + SR = 829440; FS = 36864; BR = 200; D = 512; break; + case CodecProfileLevel.VP9Level11: + SR = 2764800; FS = 73728; BR = 800; D = 768; break; + case CodecProfileLevel.VP9Level2: + SR = 4608000; FS = 122880; BR = 1800; D = 960; break; + case CodecProfileLevel.VP9Level21: + SR = 9216000; FS = 245760; BR = 3600; D = 1344; break; + case CodecProfileLevel.VP9Level3: + SR = 20736000; FS = 552960; BR = 7200; D = 2048; break; + case CodecProfileLevel.VP9Level31: + SR = 36864000; FS = 983040; BR = 12000; D = 2752; break; + case CodecProfileLevel.VP9Level4: + SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break; + case CodecProfileLevel.VP9Level41: + SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break; + case CodecProfileLevel.VP9Level5: + SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break; + case CodecProfileLevel.VP9Level51: + SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break; + case CodecProfileLevel.VP9Level52: + SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break; + case CodecProfileLevel.VP9Level6: + SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break; + case CodecProfileLevel.VP9Level61: + SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break; + case CodecProfileLevel.VP9Level62: + SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.VP9Profile0: + case CodecProfileLevel.VP9Profile1: + case CodecProfileLevel.VP9Profile2: + case CodecProfileLevel.VP9Profile3: + case CodecProfileLevel.VP9Profile2HDR: + case CodecProfileLevel.VP9Profile3HDR: + case CodecProfileLevel.VP9Profile2HDR10Plus: + case CodecProfileLevel.VP9Profile3HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxDim = Math.max(D, maxDim); + } + + final int blockSize = 8; + int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); + maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); + + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { + // CTBs are at least 8x8 so use 8x8 block size + maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks + maxBlocksPerSecond = maxBlocks * 15; + maxBps = 128000; + for (CodecProfileLevel profileLevel: profileLevels) { + double FR = 0; + int FS = 0; + int BR = 0; + switch (profileLevel.level) { + /* The HEVC spec talks only in a very convoluted manner about the + existence of levels 1-3.1 for High tier, which could also be + understood as 'decoders and encoders should treat these levels + as if they were Main tier', so we do that. */ + case CodecProfileLevel.HEVCMainTierLevel1: + case CodecProfileLevel.HEVCHighTierLevel1: + FR = 15; FS = 36864; BR = 128; break; + case CodecProfileLevel.HEVCMainTierLevel2: + case CodecProfileLevel.HEVCHighTierLevel2: + FR = 30; FS = 122880; BR = 1500; break; + case CodecProfileLevel.HEVCMainTierLevel21: + case CodecProfileLevel.HEVCHighTierLevel21: + FR = 30; FS = 245760; BR = 3000; break; + case CodecProfileLevel.HEVCMainTierLevel3: + case CodecProfileLevel.HEVCHighTierLevel3: + FR = 30; FS = 552960; BR = 6000; break; + case CodecProfileLevel.HEVCMainTierLevel31: + case CodecProfileLevel.HEVCHighTierLevel31: + FR = 33.75; FS = 983040; BR = 10000; break; + case CodecProfileLevel.HEVCMainTierLevel4: + FR = 30; FS = 2228224; BR = 12000; break; + case CodecProfileLevel.HEVCHighTierLevel4: + FR = 30; FS = 2228224; BR = 30000; break; + case CodecProfileLevel.HEVCMainTierLevel41: + FR = 60; FS = 2228224; BR = 20000; break; + case CodecProfileLevel.HEVCHighTierLevel41: + FR = 60; FS = 2228224; BR = 50000; break; + case CodecProfileLevel.HEVCMainTierLevel5: + FR = 30; FS = 8912896; BR = 25000; break; + case CodecProfileLevel.HEVCHighTierLevel5: + FR = 30; FS = 8912896; BR = 100000; break; + case CodecProfileLevel.HEVCMainTierLevel51: + FR = 60; FS = 8912896; BR = 40000; break; + case CodecProfileLevel.HEVCHighTierLevel51: + FR = 60; FS = 8912896; BR = 160000; break; + case CodecProfileLevel.HEVCMainTierLevel52: + FR = 120; FS = 8912896; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel52: + FR = 120; FS = 8912896; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel6: + FR = 30; FS = 35651584; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel6: + FR = 30; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel61: + FR = 60; FS = 35651584; BR = 120000; break; + case CodecProfileLevel.HEVCHighTierLevel61: + FR = 60; FS = 35651584; BR = 480000; break; + case CodecProfileLevel.HEVCMainTierLevel62: + FR = 120; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCHighTierLevel62: + FR = 120; FS = 35651584; BR = 800000; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.HEVCProfileMain: + case CodecProfileLevel.HEVCProfileMain10: + case CodecProfileLevel.HEVCProfileMainStill: + case CodecProfileLevel.HEVCProfileMain10HDR10: + case CodecProfileLevel.HEVCProfileMain10HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + + /* DPB logic: + if (width * height <= FS / 4) DPB = 16; + else if (width * height <= FS / 2) DPB = 12; + else if (width * height <= FS * 0.75) DPB = 8; + else DPB = 6; + */ + + FS >>= 6; // convert pixels to blocks + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + } + + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 8 /* blockWidth */, 8 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1)) { + maxBlocksPerSecond = 829440; + maxBlocks = 36864; + maxBps = 200000; + int maxDim = 512; + + // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec, + // corresponding to the definitions in + // "AV1 Bitstream & Decoding Process Specification", Annex A + // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/ + for (CodecProfileLevel profileLevel: profileLevels) { + long SR = 0; // luma sample rate + int FS = 0; // luma picture size + int BR = 0; // bit rate kbps + int D = 0; // luma D + switch (profileLevel.level) { + case CodecProfileLevel.AV1Level2: + SR = 5529600; FS = 147456; BR = 1500; D = 2048; break; + case CodecProfileLevel.AV1Level21: + case CodecProfileLevel.AV1Level22: + case CodecProfileLevel.AV1Level23: + SR = 10454400; FS = 278784; BR = 3000; D = 2816; break; + + case CodecProfileLevel.AV1Level3: + SR = 24969600; FS = 665856; BR = 6000; D = 4352; break; + case CodecProfileLevel.AV1Level31: + case CodecProfileLevel.AV1Level32: + case CodecProfileLevel.AV1Level33: + SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break; + + case CodecProfileLevel.AV1Level4: + SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break; + case CodecProfileLevel.AV1Level41: + case CodecProfileLevel.AV1Level42: + case CodecProfileLevel.AV1Level43: + SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break; + + case CodecProfileLevel.AV1Level5: + SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break; + case CodecProfileLevel.AV1Level51: + SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break; + case CodecProfileLevel.AV1Level52: + SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break; + case CodecProfileLevel.AV1Level53: + SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break; + + case CodecProfileLevel.AV1Level6: + SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break; + case CodecProfileLevel.AV1Level61: + SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break; + case CodecProfileLevel.AV1Level62: + SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break; + case CodecProfileLevel.AV1Level63: + SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break; + + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.AV1ProfileMain8: + case CodecProfileLevel.AV1ProfileMain10: + case CodecProfileLevel.AV1ProfileMain10HDR10: + case CodecProfileLevel.AV1ProfileMain10HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxDim = Math.max(D, maxDim); + } + + final int blockSize = 8; + int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); + maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else { + Log.w(TAG, "Unsupported mime " + mime); + // using minimal bitrate here. should be overridden by + // info from media_codecs.xml + maxBps = 64000; + errors |= ERROR_UNSUPPORTED; } + mBitrateRange = Range.create(1, maxBps); + mParent.mError |= errors; + } + } - final int blockSize = 8; - int maxLengthInBlocks = Utils.divUp(maxDim, blockSize); - maxBlocks = Utils.divUp(maxBlocks, blockSize * blockSize); - maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else { - Log.w(TAG, "Unsupported mime " + mime); - // using minimal bitrate here. should be overriden by - // info from media_codecs.xml - maxBps = 64000; - errors |= ERROR_UNSUPPORTED; - } - mBitrateRange = Range.create(1, maxBps); - mParent.mError |= errors; + /* package private */ static final class VideoCapsNativeImpl implements VideoCapsIntf { + private long mNativeContext; // accessed by native methods + + private Range<Integer> mBitrateRange; + private Range<Integer> mHeightRange; + private Range<Integer> mWidthRange; + private Range<Integer> mFrameRateRange; + private List<PerformancePoint> mPerformancePoints; + + private int mWidthAlignment; + private int mHeightAlignment; + + // Used by JNI to construct Java VideoCapsNativeImpl + /** package private */ VideoCapsNativeImpl(Range<Integer> bitrateRange, + Range<Integer> widthRange, Range<Integer> heightRange, + Range<Integer> frameRateRange, List<PerformancePoint> performancePoints, + int widthAlignment, int heightAlignment) { + mBitrateRange = new Range<Integer>(bitrateRange.getLower(), + bitrateRange.getUpper()); + mWidthRange = new Range<Integer>(widthRange.getLower(), widthRange.getUpper()); + mHeightRange = new Range<Integer>(heightRange.getLower(), heightRange.getUpper()); + mFrameRateRange = new Range<Integer>(frameRateRange.getLower(), + frameRateRange.getUpper()); + mPerformancePoints = new ArrayList<PerformancePoint>(); + for (PerformancePoint pp : performancePoints) { + mPerformancePoints.add(new PerformancePoint(pp)); + } + mWidthAlignment = widthAlignment; + mHeightAlignment = heightAlignment; + } + + /* no public constructor */ + private VideoCapsNativeImpl() { } + + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } + + public Range<Integer> getSupportedWidths() { + return mWidthRange; + } + + public Range<Integer> getSupportedHeights() { + return mHeightRange; + } + + public int getWidthAlignment() { + return mWidthAlignment; + } + + public int getHeightAlignment() { + return mHeightAlignment; + } + + /** @hide */ + public int getSmallerDimensionUpperLimit() { + return native_getSmallerDimensionUpperLimit(); + } + + public Range<Integer> getSupportedFrameRates() { + return mFrameRateRange; + } + + @Nullable + public List<PerformancePoint> getSupportedPerformancePoints() { + return mPerformancePoints; + } + + public Range<Integer> getSupportedWidthsFor(int height) { + return native_getSupportedWidthsFor(height); + } + + public Range<Integer> getSupportedHeightsFor(int width) { + return native_getSupportedHeightsFor(width); + } + + public Range<Double> getSupportedFrameRatesFor(int width, int height) { + return native_getSupportedFrameRatesFor(width, height); + } + + /** @throws IllegalArgumentException if the video size is not supported. */ + @Nullable + public Range<Double> getAchievableFrameRatesFor(int width, int height) { + return native_getAchievableFrameRatesFor(width, height); + } + + public boolean areSizeAndRateSupported(int width, int height, double frameRate) { + return native_areSizeAndRateSupported(width, height, frameRate); + } + + public boolean isSizeSupported(int width, int height) { + return native_isSizeSupported(width, height); + } + + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + throw new UnsupportedOperationException( + "Java Implementation should not call native implemenatation"); + } + + private native Range<Integer> native_getSupportedWidthsFor(int height); + private native Range<Integer> native_getSupportedHeightsFor(int width); + private native Range<Double> native_getSupportedFrameRatesFor(int width, int height); + private native Range<Double> native_getAchievableFrameRatesFor(int width, int height); + private native boolean native_areSizeAndRateSupported( + int width, int height, double frameRate); + private native boolean native_isSizeSupported(int width, int height); + private native int native_getSmallerDimensionUpperLimit(); + + private static native void native_init(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } + } + + private VideoCapsIntf mImpl; + + /** @hide */ + public static VideoCapabilities create( + MediaFormat info, CodecCapabilities parent) { + VideoCapsLegacyImpl impl = VideoCapsLegacyImpl.create(info, parent); + VideoCapabilities caps = new VideoCapabilities(impl); + return caps; + } + + /* package private */ VideoCapabilities(VideoCapsIntf impl) { + mImpl = impl; + } + + /* no public constructor */ + private VideoCapabilities() { } + + /** + * Returns the range of supported bitrates in bits/second. + */ + public Range<Integer> getBitrateRange() { + return mImpl.getBitrateRange(); + } + + /** + * Returns the range of supported video widths. + * <p class=note> + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space. + */ + public Range<Integer> getSupportedWidths() { + return mImpl.getSupportedWidths(); + } + + /** + * Returns the range of supported video heights. + * <p class=note> + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space. + */ + public Range<Integer> getSupportedHeights() { + return mImpl.getSupportedHeights(); + } + + /** + * Returns the alignment requirement for video width (in pixels). + * + * This is a power-of-2 value that video width must be a + * multiple of. + */ + public int getWidthAlignment() { + return mImpl.getWidthAlignment(); + } + + /** + * Returns the alignment requirement for video height (in pixels). + * + * This is a power-of-2 value that video height must be a + * multiple of. + */ + public int getHeightAlignment() { + return mImpl.getWidthAlignment(); + } + + /** + * Return the upper limit on the smaller dimension of width or height. + * <p></p> + * Some codecs have a limit on the smaller dimension, whether it be + * the width or the height. E.g. a codec may only be able to handle + * up to 1920x1080 both in landscape and portrait mode (1080x1920). + * In this case the maximum width and height are both 1920, but the + * smaller dimension limit will be 1080. For other codecs, this is + * {@code Math.min(getSupportedWidths().getUpper(), + * getSupportedHeights().getUpper())}. + * + * @hide + */ + public int getSmallerDimensionUpperLimit() { + return mImpl.getSmallerDimensionUpperLimit(); + } + + /** + * Returns the range of supported frame rates. + * <p> + * This is not a performance indicator. Rather, it expresses the + * limits specified in the coding standard, based on the complexities + * of encoding material for later playback at a certain frame rate, + * or the decoding of such material in non-realtime. + */ + public Range<Integer> getSupportedFrameRates() { + return mImpl.getSupportedFrameRates(); + } + + /** + * Returns the range of supported video widths for a video height. + * @param height the height of the video + */ + public Range<Integer> getSupportedWidthsFor(int height) { + return mImpl.getSupportedWidthsFor(height); + } + + /** + * Returns the range of supported video heights for a video width + * @param width the width of the video + */ + public Range<Integer> getSupportedHeightsFor(int width) { + return mImpl.getSupportedHeightsFor(width); + } + + /** + * Returns the range of supported video frame rates for a video size. + * <p> + * This is not a performance indicator. Rather, it expresses the limits specified in + * the coding standard, based on the complexities of encoding material of a given + * size for later playback at a certain frame rate, or the decoding of such material + * in non-realtime. + + * @param width the width of the video + * @param height the height of the video + */ + public Range<Double> getSupportedFrameRatesFor(int width, int height) { + return mImpl.getSupportedFrameRatesFor(width, height); + } + + /** + * Returns the range of achievable video frame rates for a video size. + * May return {@code null}, if the codec did not publish any measurement + * data. + * <p> + * This is a performance estimate provided by the device manufacturer based on statistical + * sampling of full-speed decoding and encoding measurements in various configurations + * of common video sizes supported by the codec. As such it should only be used to + * compare individual codecs on the device. The value is not suitable for comparing + * different devices or even different android releases for the same device. + * <p> + * <em>On {@link android.os.Build.VERSION_CODES#M} release</em> the returned range + * corresponds to the fastest frame rates achieved in the tested configurations. As + * such, it should not be used to gauge guaranteed or even average codec performance + * on the device. + * <p> + * <em>On {@link android.os.Build.VERSION_CODES#N} release</em> the returned range + * corresponds closer to sustained performance <em>in tested configurations</em>. + * One can expect to achieve sustained performance higher than the lower limit more than + * 50% of the time, and higher than half of the lower limit at least 90% of the time + * <em>in tested configurations</em>. + * Conversely, one can expect performance lower than twice the upper limit at least + * 90% of the time. + * <p class=note> + * Tested configurations use a single active codec. For use cases where multiple + * codecs are active, applications can expect lower and in most cases significantly lower + * performance. + * <p class=note> + * The returned range value is interpolated from the nearest frame size(s) tested. + * Codec performance is severely impacted by other activity on the device as well + * as environmental factors (such as battery level, temperature or power source), and can + * vary significantly even in a steady environment. + * <p class=note> + * Use this method in cases where only codec performance matters, e.g. to evaluate if + * a codec has any chance of meeting a performance target. Codecs are listed + * in {@link MediaCodecList} in the preferred order as defined by the device + * manufacturer. As such, applications should use the first suitable codec in the + * list to achieve the best balance between power use and performance. + * + * @param width the width of the video + * @param height the height of the video + * + * @throws IllegalArgumentException if the video size is not supported. + */ + @Nullable + public Range<Double> getAchievableFrameRatesFor(int width, int height) { + return mImpl.getAchievableFrameRatesFor(width, height); + } + + /** + * Returns the supported performance points. May return {@code null} if the codec did not + * publish any performance point information (e.g. the vendor codecs have not been updated + * to the latest android release). May return an empty list if the codec published that + * if does not guarantee any performance points. + * <p> + * This is a performance guarantee provided by the device manufacturer for hardware codecs + * based on hardware capabilities of the device. + * <p> + * The returned list is sorted first by decreasing number of pixels, then by decreasing + * width, and finally by decreasing frame rate. + * Performance points assume a single active codec. For use cases where multiple + * codecs are active, should use that highest pixel count, and add the frame rates of + * each individual codec. + * <p class=note> + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space, but performance points will be presented as is. + * In other words, even though a component publishes a performance point for + * a resolution higher than 4096x4096, it does not mean that the resolution is supported + * for 32-bit processes. + */ + @Nullable + public List<PerformancePoint> getSupportedPerformancePoints() { + return mImpl.getSupportedPerformancePoints(); + } + + /** + * Returns whether a given video size ({@code width} and + * {@code height}) and {@code frameRate} combination is supported. + */ + public boolean areSizeAndRateSupported(int width, int height, double frameRate) { + return mImpl.areSizeAndRateSupported(width, height, frameRate); + } + + /** + * Returns whether a given video size ({@code width} and + * {@code height}) is supported. + */ + public boolean isSizeSupported(int width, int height) { + return mImpl.isSizeSupported(width, height); + } + + /** + * @hide + * @throws java.lang.ClassCastException + * @throws java.lang.UnsupportedOperationException + */ + public boolean supportsFormat(MediaFormat format) { + return mImpl.supportsFormat(format); } } @@ -5061,6 +5302,10 @@ public final class MediaCodecInfo { return Range.create(Integer.valueOf(lower), Integer.valueOf(upper)); } + private static Range<Double> constructDoubleRange(double lower, double upper) { + return Range.create(Double.valueOf(lower), Double.valueOf(upper)); + } + private static List<VideoCapabilities.PerformancePoint> constructPerformancePointList(VideoCapabilities.PerformancePoint[] array) { return Arrays.asList(array); diff --git a/media/jni/android_media_CodecCapabilities.cpp b/media/jni/android_media_CodecCapabilities.cpp index 94dcc1ea8626..ccaa140845dc 100644 --- a/media/jni/android_media_CodecCapabilities.cpp +++ b/media/jni/android_media_CodecCapabilities.cpp @@ -29,6 +29,7 @@ namespace android { struct fields_t { jfieldID audioCapsContext; + jfieldID videoCapsContext; }; static fields_t fields; @@ -40,6 +41,33 @@ static AudioCapabilities* getAudioCapabilities(JNIEnv *env, jobject thiz) { return p; } +static VideoCapabilities* getVideoCapabilities(JNIEnv *env, jobject thiz) { + VideoCapabilities* const p = (VideoCapabilities*)env->GetLongField( + thiz, fields.videoCapsContext); + return p; +} + +// Utils + +static jobject convertToJavaIntRange(JNIEnv *env, const Range<int32_t>& range) { + jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper"); + jmethodID constructIntegerRangeID = env->GetStaticMethodID(helperClazz, "constructIntegerRange", + "(II)Landroid/util/Range;"); + jobject jRange = env->CallStaticObjectMethod(helperClazz, constructIntegerRangeID, + range.lower(), range.upper()); + + return jRange; +} + +static jobject convertToJavaDoubleRange(JNIEnv *env, const Range<double>& range) { + jclass helperClazz = env->FindClass("android/media/MediaCodecInfo$GenericHelper"); + jmethodID constructDoubleRangeID = env->GetStaticMethodID(helperClazz, "constructDoubleRange", + "(DD)Landroid/util/Range;"); + jobject jRange = env->CallStaticObjectMethod(helperClazz, constructDoubleRangeID, + range.lower(), range.upper()); + return jRange; +} + // Converters between Java objects and native instances static VideoCapabilities::PerformancePoint convertToNativePerformancePoint( @@ -158,6 +186,125 @@ static jboolean android_media_VideoCapabilities_PerformancePoint_equals(JNIEnv * return res; } +// VideoCapabilities + +static void android_media_VideoCapabilities_native_init(JNIEnv *env, jobject /* thiz */) { + jclass clazz + = env->FindClass("android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl"); + if (clazz == NULL) { + return; + } + + fields.videoCapsContext = env->GetFieldID(clazz, "mNativeContext", "J"); + if (fields.videoCapsContext == NULL) { + return; + } + + env->DeleteLocalRef(clazz); +} + +static jboolean android_media_VideoCapabilities_areSizeAndRateSupported(JNIEnv *env, jobject thiz, + int32_t width, int32_t height, double frameRate) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + bool res = videoCaps->areSizeAndRateSupported(width, height, frameRate); + return res; +} + +static jboolean android_media_VideoCapabilities_isSizeSupported(JNIEnv *env, jobject thiz, + int32_t width, int32_t height) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + bool res = videoCaps->isSizeSupported(width, height); + return res; +} + +static jobject android_media_VideoCapabilities_getAchievableFrameRatesFor(JNIEnv *env, jobject thiz, + int32_t width, int32_t height) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + std::optional<Range<double>> frameRates = videoCaps->getAchievableFrameRatesFor(width, height); + if (!frameRates) { + return NULL; + } + jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value()); + return jFrameRates; +} + +static jobject android_media_VideoCapabilities_getSupportedFrameRatesFor(JNIEnv *env, jobject thiz, + int32_t width, int32_t height) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + std::optional<Range<double>> frameRates = videoCaps->getSupportedFrameRatesFor(width, height); + if (!frameRates) { + return NULL; + } + jobject jFrameRates = convertToJavaDoubleRange(env, frameRates.value()); + return jFrameRates; +} + +static jobject android_media_VideoCapabilities_getSupportedWidthsFor(JNIEnv *env, jobject thiz, + int32_t height) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + std::optional<Range<int32_t>> supportedWidths = videoCaps->getSupportedWidthsFor(height); + if (!supportedWidths) { + return NULL; + } + jobject jSupportedWidths = convertToJavaIntRange(env, supportedWidths.value()); + + return jSupportedWidths; +} + +static jobject android_media_VideoCapabilities_getSupportedHeightsFor(JNIEnv *env, jobject thiz, + int32_t width) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + std::optional<Range<int32_t>> supportedHeights = videoCaps->getSupportedHeightsFor(width); + if (!supportedHeights) { + return NULL; + } + jobject jSupportedHeights = convertToJavaIntRange(env, supportedHeights.value()); + + return jSupportedHeights; +} + +static jint android_media_VideoCapabilities_getSmallerDimensionUpperLimit(JNIEnv *env, + jobject thiz) { + VideoCapabilities* const videoCaps = getVideoCapabilities(env, thiz); + if (videoCaps == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return 0; + } + + int smallerDimensionUpperLimit = videoCaps->getSmallerDimensionUpperLimit(); + return smallerDimensionUpperLimit; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gAudioCapsMethods[] = { @@ -172,6 +319,17 @@ static const JNINativeMethod gPerformancePointMethods[] = { {"native_equals", "(Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;)Z", (void *)android_media_VideoCapabilities_PerformancePoint_equals}, }; +static const JNINativeMethod gVideoCapsMethods[] = { + {"native_init", "()V", (void *)android_media_VideoCapabilities_native_init}, + {"native_areSizeAndRateSupported", "(IID)Z", (void *)android_media_VideoCapabilities_areSizeAndRateSupported}, + {"native_isSizeSupported", "(II)Z", (void *)android_media_VideoCapabilities_isSizeSupported}, + {"native_getAchievableFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getAchievableFrameRatesFor}, + {"native_getSupportedFrameRatesFor", "(II)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedFrameRatesFor}, + {"native_getSupportedWidthsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedWidthsFor}, + {"native_getSupportedHeightsFor", "(I)Landroid/util/Range;", (void *)android_media_VideoCapabilities_getSupportedHeightsFor}, + {"native_getSmallerDimensionUpperLimit", "()I", (void *)android_media_VideoCapabilities_getSmallerDimensionUpperLimit} +}; + int register_android_media_CodecCapabilities(JNIEnv *env) { int result = AndroidRuntime::registerNativeMethods(env, "android/media/MediaCodecInfo$AudioCapabilities$AudioCapsNativeImpl", @@ -187,5 +345,12 @@ int register_android_media_CodecCapabilities(JNIEnv *env) { return result; } + result = AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodecInfo$VideoCapabilities$VideoCapsNativeImpl", + gVideoCapsMethods, NELEM(gVideoCapsMethods)); + if (result != JNI_OK) { + return result; + } + return result; }
\ No newline at end of file |