| /* |
| * Copyright 2024, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "VideoCapabilities" |
| |
| #include <android-base/strings.h> |
| |
| #include <media/CodecCapabilities.h> |
| #include <media/VideoCapabilities.h> |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/MediaCodecConstants.h> |
| |
| #include <utils/Errors.h> |
| |
| namespace android { |
| |
| static const Range<int64_t> POSITIVE_INT64 = Range((int64_t)1, INT64_MAX); |
| static const Range<int32_t> BITRATE_RANGE = Range<int32_t>(0, 500000000); |
| static const Range<int32_t> FRAME_RATE_RANGE = Range<int32_t>(0, 960); |
| static const Range<Rational> POSITIVE_RATIONALS = |
| Range<Rational>(Rational((int32_t)1, INT32_MAX), Rational(INT32_MAX, (int32_t)1)); |
| |
| const Range<int32_t>& VideoCapabilities::getBitrateRange() const { |
| return mBitrateRange; |
| } |
| |
| const Range<int32_t>& VideoCapabilities::getSupportedWidths() const { |
| return mWidthRange; |
| } |
| |
| const Range<int32_t>& VideoCapabilities::getSupportedHeights() const { |
| return mHeightRange; |
| } |
| |
| int32_t VideoCapabilities::getWidthAlignment() const { |
| return mWidthAlignment; |
| } |
| |
| int32_t VideoCapabilities::getHeightAlignment() const { |
| return mHeightAlignment; |
| } |
| |
| int32_t VideoCapabilities::getSmallerDimensionUpperLimit() const { |
| return mSmallerDimensionUpperLimit; |
| } |
| |
| const Range<int32_t>& VideoCapabilities::getSupportedFrameRates() const { |
| return mFrameRateRange; |
| } |
| |
| std::optional<Range<int32_t>> VideoCapabilities::getSupportedWidthsFor(int32_t height) const { |
| Range<int32_t> range = mWidthRange; |
| if (!mHeightRange.contains(height) |
| || (height % mHeightAlignment) != 0) { |
| ALOGE("unsupported height"); |
| return std::nullopt; |
| } |
| const int32_t heightInBlocks = divUp(height, mBlockHeight); |
| |
| // constrain by block count and by block aspect ratio |
| const int32_t minWidthInBlocks = std::max( |
| divUp(mBlockCountRange.lower(), heightInBlocks), |
| (int32_t)std::ceil(mBlockAspectRatioRange.lower().asDouble() |
| * heightInBlocks)); |
| const int32_t maxWidthInBlocks = std::min( |
| mBlockCountRange.upper() / heightInBlocks, |
| (int32_t)(mBlockAspectRatioRange.upper().asDouble() |
| * 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( |
| (int32_t)std::ceil(mAspectRatioRange.lower().asDouble() |
| * height), |
| (int32_t)(mAspectRatioRange.upper().asDouble() * height)); |
| return range; |
| } |
| |
| std::optional<Range<int32_t>> VideoCapabilities::getSupportedHeightsFor(int32_t width) const { |
| Range<int32_t> range = mHeightRange; |
| if (!mWidthRange.contains(width) |
| || (width % mWidthAlignment) != 0) { |
| ALOGE("unsupported width"); |
| return std::nullopt; |
| } |
| const int32_t widthInBlocks = divUp(width, mBlockWidth); |
| |
| // constrain by block count and by block aspect ratio |
| const int32_t minHeightInBlocks = std::max( |
| divUp(mBlockCountRange.lower(), widthInBlocks), |
| (int32_t)std::ceil(widthInBlocks / |
| mBlockAspectRatioRange.upper().asDouble())); |
| const int32_t maxHeightInBlocks = std::min( |
| mBlockCountRange.upper() / widthInBlocks, |
| (int32_t)(widthInBlocks / |
| mBlockAspectRatioRange.lower().asDouble())); |
| 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( |
| (int32_t)std::ceil(width / |
| mAspectRatioRange.upper().asDouble()), |
| (int32_t)(width / mAspectRatioRange.lower().asDouble())); |
| return range; |
| } |
| |
| std::optional<Range<double>> VideoCapabilities::getSupportedFrameRatesFor( |
| int32_t width, int32_t height) const { |
| if (!supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| std::nullopt /* rate */)) { |
| ALOGE("Unsupported size. width: %d, height: %d", width, height); |
| return std::nullopt; |
| } |
| |
| const int32_t blockCount = |
| divUp(width, mBlockWidth) * divUp(height, mBlockHeight); |
| |
| return std::make_optional(Range( |
| std::max(mBlocksPerSecondRange.lower() / (double) blockCount, |
| (double) mFrameRateRange.lower()), |
| std::min(mBlocksPerSecondRange.upper() / (double) blockCount, |
| (double) mFrameRateRange.upper()))); |
| } |
| |
| int32_t VideoCapabilities::getBlockCount(int32_t width, int32_t height) const { |
| return divUp(width, mBlockWidth) * divUp(height, mBlockHeight); |
| } |
| |
| std::optional<VideoSize> VideoCapabilities::findClosestSize( |
| int32_t width, int32_t height) const { |
| int32_t targetBlockCount = getBlockCount(width, height); |
| std::optional<VideoSize> closestSize; |
| int32_t minDiff = INT32_MAX; |
| for (const auto &[size, range] : mMeasuredFrameRates) { |
| int32_t diff = std::abs(targetBlockCount - |
| getBlockCount(size.getWidth(), size.getHeight())); |
| if (diff < minDiff) { |
| minDiff = diff; |
| closestSize = size; |
| } |
| } |
| return closestSize; |
| } |
| |
| std::optional<Range<double>> VideoCapabilities::estimateFrameRatesFor( |
| int32_t width, int32_t height) const { |
| std::optional<VideoSize> size = findClosestSize(width, height); |
| if (!size) { |
| return std::nullopt; |
| } |
| auto rangeItr = mMeasuredFrameRates.find(size.value()); |
| if (rangeItr == mMeasuredFrameRates.end()) { |
| return std::nullopt; |
| } |
| Range<int64_t> range = rangeItr->second; |
| double ratio = getBlockCount(size.value().getWidth(), size.value().getHeight()) |
| / (double)std::max(getBlockCount(width, height), 1); |
| return std::make_optional(Range(range.lower() * ratio, range.upper() * ratio)); |
| } |
| |
| std::optional<Range<double>> VideoCapabilities::getAchievableFrameRatesFor( |
| int32_t width, int32_t height) const { |
| if (!supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| std::nullopt /* rate */)) { |
| ALOGE("Unsupported size. width: %d, height: %d", width, height); |
| return std::nullopt; |
| } |
| |
| if (mMeasuredFrameRates.empty()) { |
| ALOGW("Codec did not publish any measurement data."); |
| return std::nullopt; |
| } |
| |
| return estimateFrameRatesFor(width, height); |
| } |
| |
| // VideoCapabilities::PerformancePoint |
| |
| int32_t VideoCapabilities::PerformancePoint::getMaxMacroBlocks() const { |
| return saturateInt64ToInt32(mWidth * (int64_t)mHeight); |
| } |
| |
| int32_t VideoCapabilities::PerformancePoint::getWidth() const { |
| return mWidth; |
| } |
| |
| int32_t VideoCapabilities::PerformancePoint::getHeight() const { |
| return mHeight; |
| } |
| |
| int32_t VideoCapabilities::PerformancePoint::getMaxFrameRate() const { |
| return mMaxFrameRate; |
| } |
| |
| int64_t VideoCapabilities::PerformancePoint::getMaxMacroBlockRate() const { |
| return mMaxMacroBlockRate; |
| } |
| |
| VideoSize VideoCapabilities::PerformancePoint::getBlockSize() const { |
| return mBlockSize; |
| } |
| |
| std::string VideoCapabilities::PerformancePoint::toString() const { |
| int64_t blockWidth = 16 * (int64_t)mBlockSize.getWidth(); |
| int64_t blockHeight = 16 * (int64_t)mBlockSize.getHeight(); |
| int32_t origRate = (int32_t)divUp(mMaxMacroBlockRate, (int64_t)getMaxMacroBlocks()); |
| std::string info = std::to_string(mWidth * (int64_t)16) + "x" |
| + std::to_string(mHeight * (int64_t)16) + "@" + std::to_string(origRate); |
| if (origRate < mMaxFrameRate) { |
| info += ", max " + std::to_string(mMaxFrameRate) + "fps"; |
| } |
| if (blockWidth > 16 || blockHeight > 16) { |
| info += ", " + std::to_string(blockWidth) + "x" |
| + std::to_string(blockHeight) + " blocks"; |
| } |
| return "PerformancePoint(" + info + ")"; |
| } |
| |
| void VideoCapabilities::PerformancePoint::init(int32_t width, int32_t height, |
| int32_t frameRate, int32_t maxFrameRate, VideoSize blockSize) { |
| mBlockSize = VideoSize(divUp(blockSize.getWidth(), (int32_t)16), |
| divUp(blockSize.getHeight(), (int32_t)16)); |
| // Use IsPowerOfTwoStrict as we do not want width and height to be 0; |
| if (!IsPowerOfTwoStrict(blockSize.getWidth()) || !IsPowerOfTwoStrict(blockSize.getHeight())) { |
| ALOGE("The width and height of a PerformancePoint must be the power of two and not zero." |
| " width: %d, height: %d", blockSize.getWidth(), blockSize.getHeight()); |
| } |
| |
| // these are guaranteed not to overflow as we decimate by 16 |
| mWidth = (int32_t)(divUp(std::max(width, 1), |
| std::max(blockSize.getWidth(), 16)) |
| * mBlockSize.getWidth()); |
| mHeight = (int32_t)(divUp(std::max(height, 1), |
| std::max(blockSize.getHeight(), 16)) |
| * mBlockSize.getHeight()); |
| mMaxFrameRate = std::max(std::max(frameRate, maxFrameRate), 1); |
| mMaxMacroBlockRate = std::max(frameRate, 1) * (int64_t)getMaxMacroBlocks(); |
| } |
| |
| VideoCapabilities::PerformancePoint::PerformancePoint(int32_t width, int32_t height, |
| int32_t frameRate, int32_t maxFrameRate, VideoSize blockSize) { |
| init(width, height, frameRate, maxFrameRate, blockSize); |
| } |
| |
| VideoCapabilities::PerformancePoint::PerformancePoint(VideoSize blockSize, int32_t width, |
| int32_t height, int32_t maxFrameRate, int64_t maxMacroBlockRate) : |
| mBlockSize(blockSize), mWidth(width), mHeight(height), mMaxFrameRate(maxFrameRate), |
| mMaxMacroBlockRate(maxMacroBlockRate) {} |
| |
| VideoCapabilities::PerformancePoint::PerformancePoint( |
| const PerformancePoint &pp, VideoSize newBlockSize) { |
| init(16 * pp.mWidth, 16 * pp.mHeight, |
| // guaranteed not to overflow as these were multiplied at construction |
| (int32_t)divUp(pp.mMaxMacroBlockRate, (int64_t)pp.getMaxMacroBlocks()), |
| pp.mMaxFrameRate, |
| VideoSize(std::max(newBlockSize.getWidth(), 16 * pp.mBlockSize.getWidth()), |
| std::max(newBlockSize.getHeight(), 16 * pp.mBlockSize.getHeight()))); |
| } |
| |
| VideoCapabilities::PerformancePoint::PerformancePoint( |
| int32_t width, int32_t height, int32_t frameRate) { |
| init(width, height, frameRate, frameRate /* maxFrameRate */, VideoSize(16, 16)); |
| } |
| |
| int32_t VideoCapabilities::PerformancePoint::saturateInt64ToInt32(int64_t value) const { |
| if (value < INT32_MIN) { |
| return INT32_MIN; |
| } else if (value > INT32_MAX) { |
| return INT32_MAX; |
| } else { |
| return (int32_t)value; |
| } |
| } |
| |
| /* This method may overflow */ |
| int32_t VideoCapabilities::PerformancePoint::align( |
| int32_t value, int32_t alignment) const { |
| return divUp(value, alignment) * alignment; |
| } |
| |
| bool VideoCapabilities::PerformancePoint::covers( |
| const sp<AMessage> &format) const { |
| int32_t width, height; |
| format->findInt32(KEY_WIDTH, &width); |
| format->findInt32(KEY_HEIGHT, &height); |
| double frameRate; |
| format->findDouble(KEY_FRAME_RATE, &frameRate); |
| PerformancePoint other = PerformancePoint( |
| width, height, |
| // safely convert ceil(double) to int through float cast and std::round |
| std::round((float)(std::ceil(frameRate))) |
| ); |
| return covers(other); |
| } |
| |
| bool VideoCapabilities::PerformancePoint::covers( |
| const PerformancePoint &other) const { |
| // convert performance points to common block size |
| VideoSize commonSize = getCommonBlockSize(other); |
| PerformancePoint aligned = PerformancePoint(*this, commonSize); |
| PerformancePoint otherAligned = PerformancePoint(other, commonSize); |
| |
| return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks() |
| && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate |
| && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate); |
| } |
| |
| VideoSize VideoCapabilities::PerformancePoint::getCommonBlockSize( |
| const PerformancePoint &other) const { |
| return VideoSize( |
| 16 * std::max(mBlockSize.getWidth(), other.mBlockSize.getWidth()), |
| 16 * std::max(mBlockSize.getHeight(), other.mBlockSize.getHeight())); |
| } |
| |
| bool VideoCapabilities::PerformancePoint::equals( |
| const PerformancePoint &other) const { |
| // convert performance points to common block size |
| VideoSize commonSize = getCommonBlockSize(other); |
| PerformancePoint aligned = PerformancePoint(*this, commonSize); |
| PerformancePoint otherAligned = PerformancePoint(other, commonSize); |
| |
| return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks() |
| && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate |
| && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate); |
| } |
| |
| // VideoCapabilities |
| |
| const std::vector<VideoCapabilities::PerformancePoint>& |
| VideoCapabilities::getSupportedPerformancePoints() const { |
| return mPerformancePoints; |
| } |
| |
| bool VideoCapabilities::areSizeAndRateSupported( |
| int32_t width, int32_t height, double frameRate) const { |
| return supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| std::make_optional<double>(frameRate)); |
| } |
| |
| bool VideoCapabilities::isSizeSupported(int32_t width, int32_t height) const { |
| return supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| std::nullopt /* rate */); |
| } |
| |
| bool VideoCapabilities::supports(std::optional<int32_t> width, std::optional<int32_t> height, |
| std::optional<double> rate) const { |
| bool ok = true; |
| |
| if (width) { |
| ok &= mWidthRange.contains(width.value()) |
| && (width.value() % mWidthAlignment == 0); |
| } |
| if (height) { |
| ok &= mHeightRange.contains(height.value()) |
| && (height.value() % mHeightAlignment == 0); |
| } |
| if (rate) { |
| ok &= mFrameRateRange.contains(Range<int32_t>::RangeFor(rate.value())); |
| } |
| if (height && width) { |
| ok &= std::min(height.value(), width.value()) <= mSmallerDimensionUpperLimit; |
| |
| const int32_t widthInBlocks = divUp(width.value(), mBlockWidth); |
| const int32_t heightInBlocks = divUp(height.value(), mBlockHeight); |
| const int32_t blockCount = widthInBlocks * heightInBlocks; |
| ok &= mBlockCountRange.contains(blockCount) |
| && mBlockAspectRatioRange.contains( |
| Rational(widthInBlocks, heightInBlocks)) |
| && mAspectRatioRange.contains(Rational(width.value(), height.value())); |
| if (rate) { |
| double blocksPerSec = blockCount * rate.value(); |
| ok &= mBlocksPerSecondRange.contains( |
| Range<int64_t>::RangeFor(blocksPerSec)); |
| } |
| } |
| return ok; |
| } |
| |
| bool VideoCapabilities::supportsFormat(const sp<AMessage> &format) const { |
| int32_t widthVal, heightVal; |
| std::optional<int32_t> width = format->findInt32(KEY_WIDTH, &widthVal) |
| ? std::make_optional<int32_t>(widthVal) : std::nullopt; |
| std::optional<int32_t> height = format->findInt32(KEY_HEIGHT, &heightVal) |
| ? std::make_optional<int32_t>(heightVal) : std::nullopt; |
| double rateVal; |
| std::optional<double> rate = format->findDouble(KEY_FRAME_RATE, &rateVal) |
| ? std::make_optional<double>(rateVal) : std::nullopt; |
| |
| if (!supports(width, height, rate)) { |
| return false; |
| } |
| |
| if (!CodecCapabilities::SupportsBitrate(mBitrateRange, format)) { |
| return false; |
| } |
| |
| // we ignore color-format for now as it is not reliably reported by codec |
| return true; |
| } |
| |
| // static |
| std::shared_ptr<VideoCapabilities> VideoCapabilities::Create(std::string mediaType, |
| std::vector<ProfileLevel> profLevs, const sp<AMessage> &format) { |
| std::shared_ptr<VideoCapabilities> caps(new VideoCapabilities()); |
| caps->init(mediaType, profLevs, format); |
| return caps; |
| } |
| |
| void VideoCapabilities::init(std::string mediaType, std::vector<ProfileLevel> profLevs, |
| const sp<AMessage> &format) { |
| mMediaType = mediaType; |
| mProfileLevels = profLevs; |
| mError = 0; |
| |
| initWithPlatformLimits(); |
| applyLevelLimits(); |
| parseFromInfo(format); |
| updateLimits(); |
| } |
| |
| VideoSize VideoCapabilities::getBlockSize() const { |
| return VideoSize(mBlockWidth, mBlockHeight); |
| } |
| |
| const Range<int32_t>& VideoCapabilities::getBlockCountRange() const { |
| return mBlockCountRange; |
| } |
| |
| const Range<int64_t>& VideoCapabilities::getBlocksPerSecondRange() const { |
| return mBlocksPerSecondRange; |
| } |
| |
| Range<Rational> VideoCapabilities::getAspectRatioRange(bool blocks) const { |
| return blocks ? mBlockAspectRatioRange : mAspectRatioRange; |
| } |
| |
| void VideoCapabilities::initWithPlatformLimits() { |
| mBitrateRange = BITRATE_RANGE; |
| |
| mWidthRange = VideoSize::GetAllowedDimensionRange(); |
| mHeightRange = VideoSize::GetAllowedDimensionRange(); |
| mFrameRateRange = FRAME_RATE_RANGE; |
| |
| mHorizontalBlockRange = VideoSize::GetAllowedDimensionRange(); |
| mVerticalBlockRange = VideoSize::GetAllowedDimensionRange(); |
| |
| // full positive ranges are supported as these get calculated |
| mBlockCountRange = POSITIVE_INT32; |
| mBlocksPerSecondRange = POSITIVE_INT64; |
| |
| mBlockAspectRatioRange = POSITIVE_RATIONALS; |
| mAspectRatioRange = POSITIVE_RATIONALS; |
| |
| // YUV 4:2:0 requires 2:2 alignment |
| mWidthAlignment = 2; |
| mHeightAlignment = 2; |
| mBlockWidth = 2; |
| mBlockHeight = 2; |
| mSmallerDimensionUpperLimit = VideoSize::GetAllowedDimensionRange().upper(); |
| } |
| |
| std::vector<VideoCapabilities::PerformancePoint> |
| VideoCapabilities::getPerformancePoints( |
| const sp<AMessage> &format) const { |
| std::vector<PerformancePoint> ret; |
| AMessage::Type type; |
| for (int i = 0; i < format->countEntries(); i++) { |
| const char *name = format->getEntryNameAt(i, &type); |
| AString rangeStr; |
| if (!format->findString(name, &rangeStr)) { |
| continue; |
| } |
| |
| const std::string key = std::string(name); |
| // looking for: performance-point-WIDTHxHEIGHT-range |
| |
| // check none performance point |
| if (key == "performance-point-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 ret; |
| } |
| |
| // parse size from key |
| std::regex sizeRegex("performance-point-(.+)-range"); |
| std::smatch sizeMatch; |
| if (!std::regex_match(key, sizeMatch, sizeRegex)) { |
| continue; |
| } |
| std::optional<VideoSize> size = VideoSize::ParseSize(sizeMatch[1].str()); |
| if (!size || size.value().getWidth() * size.value().getHeight() <= 0) { |
| continue; |
| } |
| |
| // parse range from value |
| std::optional<Range<int64_t>> range = Range<int64_t>::Parse(std::string(rangeStr.c_str())); |
| if (!range || range.value().lower() < 0 || range.value().upper() < 0) { |
| continue; |
| } |
| |
| PerformancePoint given = PerformancePoint( |
| size.value().getWidth(), size.value().getHeight(), (int32_t)range.value().lower(), |
| (int32_t)range.value().upper(), VideoSize(mBlockWidth, mBlockHeight)); |
| PerformancePoint rotated = PerformancePoint( |
| size.value().getHeight(), size.value().getWidth(), (int32_t)range.value().lower(), |
| (int32_t)range.value().upper(), VideoSize(mBlockWidth, mBlockHeight)); |
| ret.push_back(given); |
| if (!given.covers(rotated)) { |
| ret.push_back(rotated); |
| } |
| } |
| |
| // check if the component specified no performance point indication |
| if (ret.size() == 0) { |
| return ret; |
| } |
| |
| // sort reversed by area first, then by frame rate |
| std::sort(ret.begin(), ret.end(), [](const PerformancePoint &a, const PerformancePoint &b) { |
| return -((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 ret; |
| } |
| |
| std::map<VideoSize, Range<int64_t>, VideoSizeCompare> VideoCapabilities |
| ::getMeasuredFrameRates(const sp<AMessage> &format) const { |
| std::map<VideoSize, Range<int64_t>, VideoSizeCompare> ret; |
| AMessage::Type type; |
| for (int i = 0; i < format->countEntries(); i++) { |
| const char *name = format->getEntryNameAt(i, &type); |
| AString rangeStr; |
| if (!format->findString(name, &rangeStr)) { |
| continue; |
| } |
| |
| const std::string key = std::string(name); |
| // looking for: measured-frame-rate-WIDTHxHEIGHT-range |
| |
| std::regex sizeRegex("measured-frame-rate-(.+)-range"); |
| std::smatch sizeMatch; |
| if (!std::regex_match(key, sizeMatch, sizeRegex)) { |
| continue; |
| } |
| |
| std::optional<VideoSize> size = VideoSize::ParseSize(sizeMatch[1].str()); |
| if (!size || size.value().getWidth() * size.value().getHeight() <= 0) { |
| continue; |
| } |
| |
| std::optional<Range<int64_t>> range = Range<int64_t>::Parse(std::string(rangeStr.c_str())); |
| if (!range || range.value().lower() < 0 || range.value().upper() < 0) { |
| continue; |
| } |
| |
| ret.emplace(size.value(), range.value()); |
| } |
| return ret; |
| } |
| |
| // static |
| std::optional<std::pair<Range<int32_t>, Range<int32_t>>> VideoCapabilities |
| ::ParseWidthHeightRanges(const std::string &str) { |
| std::optional<std::pair<VideoSize, VideoSize>> range = VideoSize::ParseSizeRange(str); |
| if (!range) { |
| ALOGW("could not parse size range: %s", str.c_str()); |
| return std::nullopt; |
| } |
| |
| return std::make_optional(std::pair( |
| Range(range.value().first.getWidth(), range.value().second.getWidth()), |
| Range(range.value().first.getHeight(), range.value().second.getHeight()))); |
| } |
| |
| // static |
| int32_t VideoCapabilities::EquivalentVP9Level(const sp<AMessage> &format) { |
| int32_t blockSizeWidth = 8; |
| int32_t blockSizeHeight = 8; |
| // VideoSize *blockSizePtr = &VideoSize(8, 8); |
| AString blockSizeStr; |
| if (format->findString("block-size", &blockSizeStr)) { |
| std::optional<VideoSize> parsedBlockSize |
| = VideoSize::ParseSize(std::string(blockSizeStr.c_str())); |
| if (parsedBlockSize) { |
| // blockSize = parsedBlockSize.value(); |
| blockSizeWidth = parsedBlockSize.value().getWidth(); |
| blockSizeHeight = parsedBlockSize.value().getHeight(); |
| } |
| } |
| int32_t BS = blockSizeWidth * blockSizeHeight; |
| |
| int32_t FS = 0; |
| AString blockCountRangeStr; |
| if (format->findString("block-count-range", &blockCountRangeStr)) { |
| std::optional<Range<int>> counts = Range<int32_t>::Parse( |
| std::string(blockCountRangeStr.c_str())); |
| if (counts) { |
| FS = BS * counts.value().upper(); |
| } |
| } |
| |
| int64_t SR = 0; |
| AString blockRatesStr; |
| if (format->findString("blocks-per-second-range", &blockRatesStr)) { |
| std::optional<Range<int64_t>> blockRates |
| = Range<int64_t>::Parse(std::string(blockRatesStr.c_str())); |
| if (blockRates) { |
| // ToDo: Catch the potential overflow issue. |
| SR = BS * blockRates.value().upper(); |
| } |
| } |
| |
| int32_t D = 0; |
| AString dimensionRangesStr; |
| if (format->findString("size-range", &dimensionRangesStr)) { |
| std::optional<std::pair<Range<int>, Range<int>>> dimensionRanges = |
| ParseWidthHeightRanges(std::string(dimensionRangesStr.c_str())); |
| if (dimensionRanges) { |
| D = std::max(dimensionRanges.value().first.upper(), |
| dimensionRanges.value().second.upper()); |
| } |
| } |
| |
| int32_t BR = 0; |
| AString bitrateRangeStr; |
| if (format->findString("bitrate-range", &bitrateRangeStr)) { |
| std::optional<Range<int>> bitRates = Range<int32_t>::Parse( |
| std::string(bitrateRangeStr.c_str())); |
| if (bitRates) { |
| BR = divUp(bitRates.value().upper(), 1000); |
| } |
| } |
| |
| if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512) |
| return VP9Level1; |
| if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768) |
| return VP9Level11; |
| if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960) |
| return VP9Level2; |
| if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344) |
| return VP9Level21; |
| if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048) |
| return VP9Level3; |
| if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752) |
| return VP9Level31; |
| if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160) |
| return VP9Level4; |
| if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160) |
| return VP9Level41; |
| if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384) |
| return VP9Level5; |
| if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384) |
| return VP9Level51; |
| if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384) |
| return VP9Level52; |
| if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832) |
| return VP9Level6; |
| if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832) |
| return VP9Level61; |
| if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832) |
| return VP9Level62; |
| // returning largest level |
| return VP9Level62; |
| } |
| |
| void VideoCapabilities::parseFromInfo(const sp<AMessage> &format) { |
| VideoSize blockSize = VideoSize(mBlockWidth, mBlockHeight); |
| VideoSize alignment = VideoSize(mWidthAlignment, mHeightAlignment); |
| std::optional<Range<int32_t>> counts, widths, heights; |
| std::optional<Range<int32_t>> frameRates, bitRates; |
| std::optional<Range<int64_t>> blockRates; |
| std::optional<Range<Rational>> ratios, blockRatios; |
| |
| AString blockSizeStr; |
| if (format->findString("block-size", &blockSizeStr)) { |
| std::optional<VideoSize> parsedBlockSize |
| = VideoSize::ParseSize(std::string(blockSizeStr.c_str())); |
| blockSize = parsedBlockSize.value_or(blockSize); |
| } |
| AString alignmentStr; |
| if (format->findString("alignment", &alignmentStr)) { |
| std::optional<VideoSize> parsedAlignment |
| = VideoSize::ParseSize(std::string(alignmentStr.c_str())); |
| alignment = parsedAlignment.value_or(alignment); |
| } |
| AString blockCountRangeStr; |
| if (format->findString("block-count-range", &blockCountRangeStr)) { |
| std::optional<Range<int>> parsedBlockCountRange = |
| Range<int32_t>::Parse(std::string(blockCountRangeStr.c_str())); |
| if (parsedBlockCountRange) { |
| counts = parsedBlockCountRange.value(); |
| } |
| } |
| AString blockRatesStr; |
| if (format->findString("blocks-per-second-range", &blockRatesStr)) { |
| blockRates = Range<int64_t>::Parse(std::string(blockRatesStr.c_str())); |
| } |
| mMeasuredFrameRates = getMeasuredFrameRates(format); |
| mPerformancePoints = getPerformancePoints(format); |
| AString sizeRangesStr; |
| if (format->findString("size-range", &sizeRangesStr)) { |
| std::optional<std::pair<Range<int>, Range<int>>> sizeRanges = |
| ParseWidthHeightRanges(std::string(sizeRangesStr.c_str())); |
| if (sizeRanges) { |
| widths = sizeRanges.value().first; |
| heights = sizeRanges.value().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 (format->contains("feature-can-swap-width-height")) { |
| if (widths && heights) { |
| mSmallerDimensionUpperLimit = |
| std::min(widths.value().upper(), heights.value().upper()); |
| widths = heights = widths.value().extend(heights.value()); |
| } else { |
| ALOGW("feature can-swap-width-height is best used with size-range"); |
| mSmallerDimensionUpperLimit = |
| std::min(mWidthRange.upper(), mHeightRange.upper()); |
| mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); |
| } |
| } |
| |
| AString ratioStr; |
| if (format->findString("block-aspect-ratio-range", &ratioStr)) { |
| ratios = Rational::ParseRange(std::string(ratioStr.c_str())); |
| } |
| AString blockRatiosStr; |
| if (format->findString("pixel-aspect-ratio-range", &blockRatiosStr)) { |
| blockRatios = Rational::ParseRange(std::string(blockRatiosStr.c_str())); |
| } |
| AString frameRatesStr; |
| if (format->findString("frame-rate-range", &frameRatesStr)) { |
| frameRates = Range<int32_t>::Parse(std::string(frameRatesStr.c_str())); |
| if (frameRates) { |
| frameRates = frameRates.value().intersect(FRAME_RATE_RANGE); |
| if (frameRates.value().empty()) { |
| ALOGW("frame rate range is out of limits"); |
| frameRates = std::nullopt; |
| } |
| } |
| } |
| AString bitRatesStr; |
| if (format->findString("bitrate-range", &bitRatesStr)) { |
| bitRates = Range<int32_t>::Parse(std::string(bitRatesStr.c_str())); |
| if (bitRates) { |
| bitRates = bitRates.value().intersect(BITRATE_RANGE); |
| if (bitRates.value().empty()) { |
| ALOGW("bitrate range is out of limits"); |
| bitRates = std::nullopt; |
| } |
| } |
| } |
| |
| if (!IsPowerOfTwo(blockSize.getWidth()) || !IsPowerOfTwo(blockSize.getHeight()) |
| || !IsPowerOfTwo(alignment.getWidth()) || !IsPowerOfTwo(alignment.getHeight())) { |
| ALOGE("The widths and heights of blockSizes and alignments must be the power of two." |
| " blockSize width: %d; blockSize height: %d;" |
| " alignment width: %d; alignment height: %d.", |
| blockSize.getWidth(), blockSize.getHeight(), |
| alignment.getWidth(), alignment.getHeight()); |
| mError |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| return; |
| } |
| |
| // update block-size and alignment |
| applyMacroBlockLimits( |
| INT32_MAX, INT32_MAX, INT32_MAX, INT64_MAX, |
| blockSize.getWidth(), blockSize.getHeight(), |
| alignment.getWidth(), alignment.getHeight()); |
| |
| if ((mError & ERROR_CAPABILITIES_UNSUPPORTED) != 0 || mAllowMbOverride) { |
| // codec supports profiles that we don't know. |
| // Use supplied values clipped to platform limits |
| if (widths) { |
| mWidthRange = VideoSize::GetAllowedDimensionRange().intersect(widths.value()); |
| } |
| if (heights) { |
| mHeightRange = VideoSize::GetAllowedDimensionRange().intersect(heights.value()); |
| } |
| if (counts) { |
| mBlockCountRange = POSITIVE_INT32.intersect( |
| counts.value().factor(mBlockWidth * mBlockHeight |
| / blockSize.getWidth() / blockSize.getHeight())); |
| } |
| if (blockRates) { |
| mBlocksPerSecondRange = POSITIVE_INT64.intersect( |
| blockRates.value().factor(mBlockWidth * mBlockHeight |
| / blockSize.getWidth() / blockSize.getHeight())); |
| } |
| if (blockRatios) { |
| mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( |
| Rational::ScaleRange(blockRatios.value(), |
| mBlockHeight / blockSize.getHeight(), |
| mBlockWidth / blockSize.getWidth())); |
| } |
| if (ratios) { |
| mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios.value()); |
| } |
| if (frameRates) { |
| mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates.value()); |
| } |
| if (bitRates) { |
| // only allow bitrate override if unsupported profiles were encountered |
| if ((mError & ERROR_CAPABILITIES_UNSUPPORTED) != 0) { |
| mBitrateRange = BITRATE_RANGE.intersect(bitRates.value()); |
| } else { |
| mBitrateRange = mBitrateRange.intersect(bitRates.value()); |
| } |
| } |
| } else { |
| // no unsupported profile/levels, so restrict values to known limits |
| if (widths) { |
| mWidthRange = mWidthRange.intersect(widths.value()); |
| } |
| if (heights) { |
| mHeightRange = mHeightRange.intersect(heights.value()); |
| } |
| if (counts) { |
| mBlockCountRange = mBlockCountRange.intersect( |
| counts.value().factor(mBlockWidth * mBlockHeight |
| / blockSize.getWidth() / blockSize.getHeight())); |
| } |
| if (blockRates) { |
| mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( |
| blockRates.value().factor(mBlockWidth * mBlockHeight |
| / blockSize.getWidth() / blockSize.getHeight())); |
| } |
| if (blockRatios) { |
| mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( |
| Rational::ScaleRange(blockRatios.value(), |
| mBlockHeight / blockSize.getHeight(), |
| mBlockWidth / blockSize.getWidth())); |
| } |
| if (ratios) { |
| mAspectRatioRange = mAspectRatioRange.intersect(ratios.value()); |
| } |
| if (frameRates) { |
| mFrameRateRange = mFrameRateRange.intersect(frameRates.value()); |
| } |
| if (bitRates) { |
| mBitrateRange = mBitrateRange.intersect(bitRates.value()); |
| } |
| } |
| updateLimits(); |
| } |
| |
| void VideoCapabilities::applyBlockLimits( |
| int32_t blockWidth, int32_t blockHeight, |
| Range<int32_t> counts, Range<int64_t> rates, Range<Rational> ratios) { |
| |
| if (!IsPowerOfTwo(blockWidth) || !IsPowerOfTwo(blockHeight)) { |
| ALOGE("blockWidth and blockHeight must be the power of two." |
| " blockWidth: %d; blockHeight: %d", blockWidth, blockHeight); |
| mError |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| return; |
| } |
| |
| const int32_t newBlockWidth = std::max(blockWidth, mBlockWidth); |
| const int32_t newBlockHeight = std::max(blockHeight, mBlockHeight); |
| |
| // factor will always be a power-of-2 |
| int32_t factor = |
| newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; |
| if (factor != 1) { |
| mBlockCountRange = mBlockCountRange.factor(factor); |
| mBlocksPerSecondRange = mBlocksPerSecondRange.factor(factor); |
| mBlockAspectRatioRange = Rational::ScaleRange( |
| mBlockAspectRatioRange, |
| newBlockHeight / mBlockHeight, |
| newBlockWidth / mBlockWidth); |
| mHorizontalBlockRange = mHorizontalBlockRange.factor(newBlockWidth / mBlockWidth); |
| mVerticalBlockRange = mVerticalBlockRange.factor(newBlockHeight / mBlockHeight); |
| } |
| factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; |
| if (factor != 1) { |
| counts = counts.factor(factor); |
| rates = rates.factor((int64_t)factor); |
| ratios = Rational::ScaleRange( |
| ratios, newBlockHeight / blockHeight, |
| newBlockWidth / blockWidth); |
| } |
| mBlockCountRange = mBlockCountRange.intersect(counts); |
| mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); |
| mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); |
| mBlockWidth = newBlockWidth; |
| mBlockHeight = newBlockHeight; |
| } |
| |
| void VideoCapabilities::applyAlignment( |
| int32_t widthAlignment, int32_t heightAlignment) { |
| if (!IsPowerOfTwo(widthAlignment) || !IsPowerOfTwo(heightAlignment)) { |
| ALOGE("width and height alignments must be the power of two." |
| " widthAlignment: %d; heightAlignment: %d", widthAlignment, heightAlignment); |
| mError |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| return; |
| } |
| |
| if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) { |
| // maintain assumption that 0 < alignment <= block-size |
| applyBlockLimits( |
| std::max(widthAlignment, mBlockWidth), |
| std::max(heightAlignment, mBlockHeight), |
| POSITIVE_INT32, POSITIVE_INT64, POSITIVE_RATIONALS); |
| } |
| |
| mWidthAlignment = std::max(widthAlignment, mWidthAlignment); |
| mHeightAlignment = std::max(heightAlignment, mHeightAlignment); |
| |
| mWidthRange = mWidthRange.align(mWidthAlignment); |
| mHeightRange = mHeightRange.align(mHeightAlignment); |
| } |
| |
| void VideoCapabilities::updateLimits() { |
| // pixels -> blocks <- counts |
| mHorizontalBlockRange = mHorizontalBlockRange.intersect( |
| mWidthRange.factor(mBlockWidth)); |
| mHorizontalBlockRange = mHorizontalBlockRange.intersect( |
| Range( mBlockCountRange.lower() / mVerticalBlockRange.upper(), |
| mBlockCountRange.upper() / mVerticalBlockRange.lower())); |
| mVerticalBlockRange = mVerticalBlockRange.intersect( |
| mHeightRange.factor(mBlockHeight)); |
| mVerticalBlockRange = mVerticalBlockRange.intersect( |
| Range( mBlockCountRange.lower() / mHorizontalBlockRange.upper(), |
| mBlockCountRange.upper() / mHorizontalBlockRange.lower())); |
| mBlockCountRange = mBlockCountRange.intersect( |
| Range( mHorizontalBlockRange.lower() |
| * mVerticalBlockRange.lower(), |
| mHorizontalBlockRange.upper() |
| * mVerticalBlockRange.upper())); |
| mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( |
| Rational(mHorizontalBlockRange.lower(), mVerticalBlockRange.upper()), |
| Rational(mHorizontalBlockRange.upper(), mVerticalBlockRange.lower())); |
| |
| // blocks -> pixels |
| mWidthRange = mWidthRange.intersect( |
| (mHorizontalBlockRange.lower() - 1) * mBlockWidth + mWidthAlignment, |
| mHorizontalBlockRange.upper() * mBlockWidth); |
| mHeightRange = mHeightRange.intersect( |
| (mVerticalBlockRange.lower() - 1) * mBlockHeight + mHeightAlignment, |
| mVerticalBlockRange.upper() * mBlockHeight); |
| mAspectRatioRange = mAspectRatioRange.intersect( |
| Rational(mWidthRange.lower(), mHeightRange.upper()), |
| Rational(mWidthRange.upper(), mHeightRange.lower())); |
| |
| mSmallerDimensionUpperLimit = std::min( |
| mSmallerDimensionUpperLimit, |
| std::min(mWidthRange.upper(), mHeightRange.upper())); |
| |
| // blocks -> rate |
| mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( |
| mBlockCountRange.lower() * (int64_t)mFrameRateRange.lower(), |
| mBlockCountRange.upper() * (int64_t)mFrameRateRange.upper()); |
| mFrameRateRange = mFrameRateRange.intersect( |
| (int32_t)(mBlocksPerSecondRange.lower() |
| / mBlockCountRange.upper()), |
| (int32_t)(mBlocksPerSecondRange.upper() |
| / (double)mBlockCountRange.lower())); |
| } |
| |
| void VideoCapabilities::applyMacroBlockLimits( |
| int32_t maxHorizontalBlocks, int32_t maxVerticalBlocks, |
| int32_t maxBlocks, int64_t maxBlocksPerSecond, |
| int32_t blockWidth, int32_t blockHeight, |
| int32_t widthAlignment, int32_t heightAlignment) { |
| applyMacroBlockLimits( |
| 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */, |
| maxHorizontalBlocks, maxVerticalBlocks, |
| maxBlocks, maxBlocksPerSecond, |
| blockWidth, blockHeight, widthAlignment, heightAlignment); |
| } |
| |
| void VideoCapabilities::applyMacroBlockLimits( |
| int32_t minHorizontalBlocks, int32_t minVerticalBlocks, |
| int32_t maxHorizontalBlocks, int32_t maxVerticalBlocks, |
| int32_t maxBlocks, int64_t maxBlocksPerSecond, |
| int32_t blockWidth, int32_t blockHeight, |
| int32_t widthAlignment, int32_t heightAlignment) { |
| applyAlignment(widthAlignment, heightAlignment); |
| applyBlockLimits( |
| blockWidth, blockHeight, Range((int32_t)1, maxBlocks), |
| Range((int64_t)1, maxBlocksPerSecond), |
| Range(Rational(1, maxVerticalBlocks), Rational(maxHorizontalBlocks, 1))); |
| mHorizontalBlockRange = |
| mHorizontalBlockRange.intersect( |
| divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)), |
| maxHorizontalBlocks / (mBlockWidth / blockWidth)); |
| mVerticalBlockRange = |
| mVerticalBlockRange.intersect( |
| divUp(minVerticalBlocks, (mBlockHeight / blockHeight)), |
| maxVerticalBlocks / (mBlockHeight / blockHeight)); |
| } |
| |
| void VideoCapabilities::applyLevelLimits() { |
| int64_t maxBlocksPerSecond = 0; |
| int32_t maxBlocks = 0; |
| int32_t maxBps = 0; |
| int32_t maxDPBBlocks = 0; |
| |
| int errors = ERROR_CAPABILITIES_NONE_SUPPORTED; |
| const char *mediaType = mMediaType.c_str(); |
| if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_AVC)) { |
| maxBlocks = 99; |
| maxBlocksPerSecond = 1485; |
| maxBps = 64000; |
| maxDPBBlocks = 396; |
| for (ProfileLevel profileLevel: mProfileLevels) { |
| int32_t MBPS = 0, FS = 0, BR = 0, DPB = 0; |
| bool supported = true; |
| switch (profileLevel.mLevel) { |
| case AVCLevel1: |
| MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; |
| case AVCLevel1b: |
| MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; |
| case AVCLevel11: |
| MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; |
| case AVCLevel12: |
| MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; |
| case AVCLevel13: |
| MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; |
| case AVCLevel2: |
| MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; |
| case AVCLevel21: |
| MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; |
| case AVCLevel22: |
| MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; |
| case AVCLevel3: |
| MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; |
| case AVCLevel31: |
| MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; |
| case AVCLevel32: |
| MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; |
| case AVCLevel4: |
| MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; |
| case AVCLevel41: |
| MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; |
| case AVCLevel42: |
| MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; |
| case AVCLevel5: |
| MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; |
| case AVCLevel51: |
| MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; |
| case AVCLevel52: |
| MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; |
| case AVCLevel6: |
| MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break; |
| case AVCLevel61: |
| MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break; |
| case AVCLevel62: |
| MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break; |
| default: |
| ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| switch (profileLevel.mProfile) { |
| case AVCProfileConstrainedHigh: |
| case AVCProfileHigh: |
| BR *= 1250; break; |
| case AVCProfileHigh10: |
| BR *= 3000; break; |
| case AVCProfileExtended: |
| case AVCProfileHigh422: |
| case AVCProfileHigh444: |
| ALOGW("Unsupported profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| supported = false; |
| FALLTHROUGH_INTENDED; |
| // fall through - treat as base profile |
| case AVCProfileConstrainedBaseline: |
| FALLTHROUGH_INTENDED; |
| case AVCProfileBaseline: |
| FALLTHROUGH_INTENDED; |
| case AVCProfileMain: |
| BR *= 1000; break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| BR *= 1000; |
| } |
| if (supported) { |
| errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| } |
| maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| maxBlocks = std::max(FS, maxBlocks); |
| maxBps = std::max(BR, maxBps); |
| maxDPBBlocks = std::max(maxDPBBlocks, DPB); |
| } |
| |
| int32_t maxLengthInBlocks = (int32_t)(std::sqrt(8 * maxBlocks)); |
| applyMacroBlockLimits( |
| maxLengthInBlocks, maxLengthInBlocks, |
| maxBlocks, maxBlocksPerSecond, |
| 16 /* blockWidth */, 16 /* blockHeight */, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_MPEG2)) { |
| int32_t maxWidth = 11, maxHeight = 9, maxRate = 15; |
| maxBlocks = 99; |
| maxBlocksPerSecond = 1485; |
| maxBps = 64000; |
| for (ProfileLevel profileLevel: mProfileLevels) { |
| int32_t MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; |
| bool supported = true; |
| switch (profileLevel.mProfile) { |
| case MPEG2ProfileSimple: |
| switch (profileLevel.mLevel) { |
| case MPEG2LevelML: |
| FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; |
| default: |
| ALOGW("Unrecognized profile/level %d/%d for %s", |
| profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| break; |
| case MPEG2ProfileMain: |
| switch (profileLevel.mLevel) { |
| case MPEG2LevelLL: |
| FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; |
| case MPEG2LevelML: |
| FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; |
| case MPEG2LevelH14: |
| FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break; |
| case MPEG2LevelHL: |
| FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break; |
| case MPEG2LevelHP: |
| FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; |
| default: |
| ALOGW("Unrecognized profile/level %d / %d for %s", |
| profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| break; |
| case MPEG2Profile422: |
| case MPEG2ProfileSNR: |
| case MPEG2ProfileSpatial: |
| case MPEG2ProfileHigh: |
| ALOGW("Unsupported profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNSUPPORTED; |
| supported = false; |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| if (supported) { |
| errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| } |
| maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| maxBlocks = std::max(FS, maxBlocks); |
| maxBps = std::max(BR * 1000, maxBps); |
| maxWidth = std::max(W, maxWidth); |
| maxHeight = std::max(H, maxHeight); |
| maxRate = std::max(FR, maxRate); |
| } |
| applyMacroBlockLimits(maxWidth, maxHeight, |
| maxBlocks, maxBlocksPerSecond, |
| 16 /* blockWidth */, 16 /* blockHeight */, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| mFrameRateRange = mFrameRateRange.intersect(12, maxRate); |
| } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_MPEG4)) { |
| int32_t maxWidth = 11, maxHeight = 9, maxRate = 15; |
| maxBlocks = 99; |
| maxBlocksPerSecond = 1485; |
| maxBps = 64000; |
| for (ProfileLevel profileLevel: mProfileLevels) { |
| int32_t MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; |
| bool strict = false; // true: W, H and FR are individual max limits |
| bool supported = true; |
| switch (profileLevel.mProfile) { |
| case MPEG4ProfileSimple: |
| switch (profileLevel.mLevel) { |
| case MPEG4Level0: |
| strict = true; |
| FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; |
| case MPEG4Level1: |
| FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; |
| case MPEG4Level0b: |
| strict = true; |
| FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; |
| case MPEG4Level2: |
| FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; |
| case MPEG4Level3: |
| FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; |
| case MPEG4Level4a: |
| FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break; |
| case MPEG4Level5: |
| FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break; |
| case MPEG4Level6: |
| FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break; |
| default: |
| ALOGW("Unrecognized profile/level %d/%d for %s", |
| profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| break; |
| case MPEG4ProfileAdvancedSimple: |
| switch (profileLevel.mLevel) { |
| case MPEG4Level0: |
| case MPEG4Level1: |
| FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; |
| case MPEG4Level2: |
| FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; |
| case MPEG4Level3: |
| FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; |
| case MPEG4Level3b: |
| FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break; |
| case MPEG4Level4: |
| FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; |
| case MPEG4Level5: |
| FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; |
| default: |
| ALOGW("Unrecognized profile/level %d/%d for %s", |
| profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| break; |
| case MPEG4ProfileMain: // 2-4 |
| case MPEG4ProfileNbit: // 2 |
| case MPEG4ProfileAdvancedRealTime: // 1-4 |
| case MPEG4ProfileCoreScalable: // 1-3 |
| case MPEG4ProfileAdvancedCoding: // 1-4 |
| case MPEG4ProfileCore: // 1-2 |
| case MPEG4ProfileAdvancedCore: // 1-4 |
| case MPEG4ProfileSimpleScalable: // 0-2 |
| case 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 MPEG4ProfileBasicAnimated: // 1-2 |
| case MPEG4ProfileScalableTexture: // 1 |
| case MPEG4ProfileSimpleFace: // 1-2 |
| case MPEG4ProfileAdvancedScalable: // 1-3 |
| case MPEG4ProfileSimpleFBA: // 1-2 |
| ALOGV("Unsupported profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNSUPPORTED; |
| supported = false; |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| if (supported) { |
| errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| } |
| maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| maxBlocks = std::max(FS, maxBlocks); |
| maxBps = std::max(BR * 1000, maxBps); |
| if (strict) { |
| maxWidth = std::max(W, maxWidth); |
| maxHeight = std::max(H, maxHeight); |
| maxRate = std::max(FR, maxRate); |
| } else { |
| // assuming max 60 fps frame rate and 1:2 aspect ratio |
| int32_t maxDim = (int32_t)std::sqrt(2 * FS); |
| maxWidth = std::max(maxDim, maxWidth); |
| maxHeight = std::max(maxDim, maxHeight); |
| maxRate = std::max(std::max(FR, 60), maxRate); |
| } |
| } |
| applyMacroBlockLimits(maxWidth, maxHeight, |
| maxBlocks, maxBlocksPerSecond, |
| 16 /* blockWidth */, 16 /* blockHeight */, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| mFrameRateRange = mFrameRateRange.intersect(12, maxRate); |
| } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_H263)) { |
| int32_t maxWidth = 11, maxHeight = 9, maxRate = 15; |
| int32_t minWidth = maxWidth, minHeight = maxHeight; |
| int32_t minAlignment = 16; |
| maxBlocks = 99; |
| maxBlocksPerSecond = 1485; |
| maxBps = 64000; |
| for (ProfileLevel profileLevel: mProfileLevels) { |
| int32_t MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight; |
| bool strict = false; // true: support only sQCIF, QCIF (maybe CIF) |
| switch (profileLevel.mLevel) { |
| case H263Level10: |
| strict = true; // only supports sQCIF & QCIF |
| FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; |
| case H263Level20: |
| strict = true; // only supports sQCIF, QCIF & CIF |
| FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break; |
| case H263Level30: |
| strict = true; // only supports sQCIF, QCIF & CIF |
| FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; |
| case H263Level40: |
| strict = true; // only supports sQCIF, QCIF & CIF |
| FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; |
| case H263Level45: |
| // only implies level 10 support |
| strict = profileLevel.mProfile == H263ProfileBaseline |
| || profileLevel.mProfile == |
| H263ProfileBackwardCompatible; |
| if (!strict) { |
| minW = 1; minH = 1; minAlignment = 4; |
| } |
| FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; |
| case 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 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 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: |
| ALOGW("Unrecognized profile/level %d/%d for %s", |
| profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| switch (profileLevel.mProfile) { |
| case H263ProfileBackwardCompatible: |
| case H263ProfileBaseline: |
| case H263ProfileH320Coding: |
| case H263ProfileHighCompression: |
| case H263ProfileHighLatency: |
| case H263ProfileInterlace: |
| case H263ProfileInternet: |
| case H263ProfileISWV2: |
| case H263ProfileISWV3: |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_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_CAPABILITIES_NONE_SUPPORTED; |
| maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| maxBlocks = std::max(W * H, maxBlocks); |
| maxBps = std::max(BR * 64000, maxBps); |
| maxWidth = std::max(W, maxWidth); |
| maxHeight = std::max(H, maxHeight); |
| maxRate = std::max(FR, maxRate); |
| minWidth = std::min(minW, minWidth); |
| minHeight = std::min(minH, minHeight); |
| } |
| // unless we encountered custom frame size support, limit size to QCIF and CIF |
| // using aspect ratio. |
| if (!mAllowMbOverride) { |
| mBlockAspectRatioRange = |
| Range(Rational(11, 9), Rational(11, 9)); |
| } |
| applyMacroBlockLimits( |
| minWidth, minHeight, |
| maxWidth, maxHeight, |
| maxBlocks, maxBlocksPerSecond, |
| 16 /* blockWidth */, 16 /* blockHeight */, |
| minAlignment /* widthAlignment */, minAlignment /* heightAlignment */); |
| mFrameRateRange = Range(1, maxRate); |
| } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_VP8)) { |
| maxBlocks = INT_MAX; |
| maxBlocksPerSecond = INT_MAX; |
| |
| // 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 (ProfileLevel profileLevel: mProfileLevels) { |
| switch (profileLevel.mLevel) { |
| case VP8Level_Version0: |
| case VP8Level_Version1: |
| case VP8Level_Version2: |
| case VP8Level_Version3: |
| break; |
| default: |
| ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| switch (profileLevel.mProfile) { |
| case VP8ProfileMain: |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| } |
| |
| const int32_t blockSize = 16; |
| applyMacroBlockLimits(SHRT_MAX, SHRT_MAX, |
| maxBlocks, maxBlocksPerSecond, blockSize, blockSize, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_VP9)) { |
| maxBlocksPerSecond = 829440; |
| maxBlocks = 36864; |
| maxBps = 200000; |
| int32_t maxDim = 512; |
| |
| for (ProfileLevel profileLevel: mProfileLevels) { |
| int64_t SR = 0; // luma sample rate |
| int32_t FS = 0; // luma picture size |
| int32_t BR = 0; // bit rate kbps |
| int32_t D = 0; // luma dimension |
| switch (profileLevel.mLevel) { |
| case VP9Level1: |
| SR = 829440; FS = 36864; BR = 200; D = 512; break; |
| case VP9Level11: |
| SR = 2764800; FS = 73728; BR = 800; D = 768; break; |
| case VP9Level2: |
| SR = 4608000; FS = 122880; BR = 1800; D = 960; break; |
| case VP9Level21: |
| SR = 9216000; FS = 245760; BR = 3600; D = 1344; break; |
| case VP9Level3: |
| SR = 20736000; FS = 552960; BR = 7200; D = 2048; break; |
| case VP9Level31: |
| SR = 36864000; FS = 983040; BR = 12000; D = 2752; break; |
| case VP9Level4: |
| SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break; |
| case VP9Level41: |
| SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break; |
| case VP9Level5: |
| SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break; |
| case VP9Level51: |
| SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break; |
| case VP9Level52: |
| SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break; |
| case VP9Level6: |
| SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break; |
| case VP9Level61: |
| SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break; |
| case VP9Level62: |
| SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break; |
| default: |
| ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| switch (profileLevel.mProfile) { |
| case VP9Profile0: |
| case VP9Profile1: |
| case VP9Profile2: |
| case VP9Profile3: |
| case VP9Profile2HDR: |
| case VP9Profile3HDR: |
| case VP9Profile2HDR10Plus: |
| case VP9Profile3HDR10Plus: |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| maxBlocksPerSecond = std::max(SR, maxBlocksPerSecond); |
| maxBlocks = std::max(FS, maxBlocks); |
| maxBps = std::max(BR * 1000, maxBps); |
| maxDim = std::max(D, maxDim); |
| } |
| |
| const int32_t blockSize = 8; |
| int32_t maxLengthInBlocks = divUp(maxDim, blockSize); |
| maxBlocks = divUp(maxBlocks, blockSize * blockSize); |
| maxBlocksPerSecond = divUp(maxBlocksPerSecond, blockSize * (int64_t)blockSize); |
| |
| applyMacroBlockLimits( |
| maxLengthInBlocks, maxLengthInBlocks, |
| maxBlocks, maxBlocksPerSecond, |
| blockSize, blockSize, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| } else if (base::EqualsIgnoreCase(mMediaType, 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 (ProfileLevel profileLevel: mProfileLevels) { |
| double FR = 0; |
| int32_t FS = 0, BR = 0; |
| switch (profileLevel.mLevel) { |
| /* 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 HEVCMainTierLevel1: |
| case HEVCHighTierLevel1: |
| FR = 15; FS = 36864; BR = 128; break; |
| case HEVCMainTierLevel2: |
| case HEVCHighTierLevel2: |
| FR = 30; FS = 122880; BR = 1500; break; |
| case HEVCMainTierLevel21: |
| case HEVCHighTierLevel21: |
| FR = 30; FS = 245760; BR = 3000; break; |
| case HEVCMainTierLevel3: |
| case HEVCHighTierLevel3: |
| FR = 30; FS = 552960; BR = 6000; break; |
| case HEVCMainTierLevel31: |
| case HEVCHighTierLevel31: |
| FR = 33.75; FS = 983040; BR = 10000; break; |
| case HEVCMainTierLevel4: |
| FR = 30; FS = 2228224; BR = 12000; break; |
| case HEVCHighTierLevel4: |
| FR = 30; FS = 2228224; BR = 30000; break; |
| case HEVCMainTierLevel41: |
| FR = 60; FS = 2228224; BR = 20000; break; |
| case HEVCHighTierLevel41: |
| FR = 60; FS = 2228224; BR = 50000; break; |
| case HEVCMainTierLevel5: |
| FR = 30; FS = 8912896; BR = 25000; break; |
| case HEVCHighTierLevel5: |
| FR = 30; FS = 8912896; BR = 100000; break; |
| case HEVCMainTierLevel51: |
| FR = 60; FS = 8912896; BR = 40000; break; |
| case HEVCHighTierLevel51: |
| FR = 60; FS = 8912896; BR = 160000; break; |
| case HEVCMainTierLevel52: |
| FR = 120; FS = 8912896; BR = 60000; break; |
| case HEVCHighTierLevel52: |
| FR = 120; FS = 8912896; BR = 240000; break; |
| case HEVCMainTierLevel6: |
| FR = 30; FS = 35651584; BR = 60000; break; |
| case HEVCHighTierLevel6: |
| FR = 30; FS = 35651584; BR = 240000; break; |
| case HEVCMainTierLevel61: |
| FR = 60; FS = 35651584; BR = 120000; break; |
| case HEVCHighTierLevel61: |
| FR = 60; FS = 35651584; BR = 480000; break; |
| case HEVCMainTierLevel62: |
| FR = 120; FS = 35651584; BR = 240000; break; |
| case HEVCHighTierLevel62: |
| FR = 120; FS = 35651584; BR = 800000; break; |
| default: |
| ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| switch (profileLevel.mProfile) { |
| case HEVCProfileMain: |
| case HEVCProfileMain10: |
| case HEVCProfileMainStill: |
| case HEVCProfileMain10HDR10: |
| case HEVCProfileMain10HDR10Plus: |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_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_CAPABILITIES_NONE_SUPPORTED; |
| maxBlocksPerSecond = std::max((int64_t)(FR * FS), maxBlocksPerSecond); |
| maxBlocks = std::max(FS, maxBlocks); |
| maxBps = std::max(1000 * BR, maxBps); |
| } |
| |
| int32_t maxLengthInBlocks = (int32_t)(std::sqrt(8 * maxBlocks)); |
| applyMacroBlockLimits( |
| maxLengthInBlocks, maxLengthInBlocks, |
| maxBlocks, maxBlocksPerSecond, |
| 8 /* blockWidth */, 8 /* blockHeight */, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_AV1)) { |
| maxBlocksPerSecond = 829440; |
| maxBlocks = 36864; |
| maxBps = 200000; |
| int32_t 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 (ProfileLevel profileLevel: mProfileLevels) { |
| int64_t SR = 0; // luma sample rate |
| int32_t FS = 0; // luma picture size |
| int32_t BR = 0; // bit rate kbps |
| int32_t D = 0; // luma D |
| switch (profileLevel.mLevel) { |
| case AV1Level2: |
| SR = 5529600; FS = 147456; BR = 1500; D = 2048; break; |
| case AV1Level21: |
| case AV1Level22: |
| case AV1Level23: |
| SR = 10454400; FS = 278784; BR = 3000; D = 2816; break; |
| |
| case AV1Level3: |
| SR = 24969600; FS = 665856; BR = 6000; D = 4352; break; |
| case AV1Level31: |
| case AV1Level32: |
| case AV1Level33: |
| SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break; |
| |
| case AV1Level4: |
| SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break; |
| case AV1Level41: |
| case AV1Level42: |
| case AV1Level43: |
| SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break; |
| |
| case AV1Level5: |
| SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break; |
| case AV1Level51: |
| SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break; |
| case AV1Level52: |
| SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break; |
| case AV1Level53: |
| SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break; |
| |
| case AV1Level6: |
| SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break; |
| case AV1Level61: |
| SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break; |
| case AV1Level62: |
| SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break; |
| case AV1Level63: |
| SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break; |
| |
| default: |
| ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| switch (profileLevel.mProfile) { |
| case AV1ProfileMain8: |
| case AV1ProfileMain10: |
| case AV1ProfileMain10HDR10: |
| case AV1ProfileMain10HDR10Plus: |
| break; |
| default: |
| ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| } |
| errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| maxBlocksPerSecond = std::max(SR, maxBlocksPerSecond); |
| maxBlocks = std::max(FS, maxBlocks); |
| maxBps = std::max(BR * 1000, maxBps); |
| maxDim = std::max(D, maxDim); |
| } |
| |
| const int32_t blockSize = 8; |
| int32_t maxLengthInBlocks = divUp(maxDim, blockSize); |
| maxBlocks = divUp(maxBlocks, blockSize * blockSize); |
| maxBlocksPerSecond = divUp(maxBlocksPerSecond, blockSize * (int64_t)blockSize); |
| applyMacroBlockLimits( |
| maxLengthInBlocks, maxLengthInBlocks, |
| maxBlocks, maxBlocksPerSecond, |
| blockSize, blockSize, |
| 1 /* widthAlignment */, 1 /* heightAlignment */); |
| } else { |
| ALOGW("Unsupported mime %s", mediaType); |
| // using minimal bitrate here. should be overridden by |
| // info from media_codecs.xml |
| maxBps = 64000; |
| errors |= ERROR_CAPABILITIES_UNSUPPORTED; |
| } |
| mBitrateRange = Range(1, maxBps); |
| mError |= errors; |
| } |
| |
| } // namespace android |