Songyue Han | 4ed0abd | 2024-06-29 00:26:54 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2024, The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //#define LOG_NDEBUG 0 |
| 18 | #define LOG_TAG "VideoCapabilities" |
| 19 | |
| 20 | #include <android-base/strings.h> |
| 21 | |
| 22 | #include <media/CodecCapabilities.h> |
| 23 | #include <media/VideoCapabilities.h> |
| 24 | #include <media/stagefright/foundation/ADebug.h> |
| 25 | #include <media/stagefright/MediaCodecConstants.h> |
| 26 | |
| 27 | #include <utils/Errors.h> |
| 28 | |
| 29 | namespace android { |
| 30 | |
| 31 | static const Range<int64_t> POSITIVE_INT64 = Range((int64_t)1, INT64_MAX); |
| 32 | static const Range<int32_t> BITRATE_RANGE = Range<int32_t>(0, 500000000); |
| 33 | static const Range<int32_t> FRAME_RATE_RANGE = Range<int32_t>(0, 960); |
| 34 | static const Range<Rational> POSITIVE_RATIONALS = |
| 35 | Range<Rational>(Rational((int32_t)1, INT32_MAX), Rational(INT32_MAX, (int32_t)1)); |
| 36 | |
| 37 | const Range<int32_t>& VideoCapabilities::getBitrateRange() const { |
| 38 | return mBitrateRange; |
| 39 | } |
| 40 | |
| 41 | const Range<int32_t>& VideoCapabilities::getSupportedWidths() const { |
| 42 | return mWidthRange; |
| 43 | } |
| 44 | |
| 45 | const Range<int32_t>& VideoCapabilities::getSupportedHeights() const { |
| 46 | return mHeightRange; |
| 47 | } |
| 48 | |
| 49 | int32_t VideoCapabilities::getWidthAlignment() const { |
| 50 | return mWidthAlignment; |
| 51 | } |
| 52 | |
| 53 | int32_t VideoCapabilities::getHeightAlignment() const { |
| 54 | return mHeightAlignment; |
| 55 | } |
| 56 | |
| 57 | int32_t VideoCapabilities::getSmallerDimensionUpperLimit() const { |
| 58 | return mSmallerDimensionUpperLimit; |
| 59 | } |
| 60 | |
| 61 | const Range<int32_t>& VideoCapabilities::getSupportedFrameRates() const { |
| 62 | return mFrameRateRange; |
| 63 | } |
| 64 | |
| 65 | std::optional<Range<int32_t>> VideoCapabilities::getSupportedWidthsFor(int32_t height) const { |
| 66 | Range<int32_t> range = mWidthRange; |
| 67 | if (!mHeightRange.contains(height) |
| 68 | || (height % mHeightAlignment) != 0) { |
| 69 | ALOGE("unsupported height"); |
| 70 | return std::nullopt; |
| 71 | } |
| 72 | const int32_t heightInBlocks = divUp(height, mBlockHeight); |
| 73 | |
| 74 | // constrain by block count and by block aspect ratio |
| 75 | const int32_t minWidthInBlocks = std::max( |
| 76 | divUp(mBlockCountRange.lower(), heightInBlocks), |
| 77 | (int32_t)std::ceil(mBlockAspectRatioRange.lower().asDouble() |
| 78 | * heightInBlocks)); |
| 79 | const int32_t maxWidthInBlocks = std::min( |
| 80 | mBlockCountRange.upper() / heightInBlocks, |
| 81 | (int32_t)(mBlockAspectRatioRange.upper().asDouble() |
| 82 | * heightInBlocks)); |
| 83 | range = range.intersect( |
| 84 | (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, |
| 85 | maxWidthInBlocks * mBlockWidth); |
| 86 | |
| 87 | // constrain by smaller dimension limit |
| 88 | if (height > mSmallerDimensionUpperLimit) { |
| 89 | range = range.intersect(1, mSmallerDimensionUpperLimit); |
| 90 | } |
| 91 | |
| 92 | // constrain by aspect ratio |
| 93 | range = range.intersect( |
| 94 | (int32_t)std::ceil(mAspectRatioRange.lower().asDouble() |
| 95 | * height), |
| 96 | (int32_t)(mAspectRatioRange.upper().asDouble() * height)); |
| 97 | return range; |
| 98 | } |
| 99 | |
| 100 | std::optional<Range<int32_t>> VideoCapabilities::getSupportedHeightsFor(int32_t width) const { |
| 101 | Range<int32_t> range = mHeightRange; |
| 102 | if (!mWidthRange.contains(width) |
| 103 | || (width % mWidthAlignment) != 0) { |
| 104 | ALOGE("unsupported width"); |
| 105 | return std::nullopt; |
| 106 | } |
| 107 | const int32_t widthInBlocks = divUp(width, mBlockWidth); |
| 108 | |
| 109 | // constrain by block count and by block aspect ratio |
| 110 | const int32_t minHeightInBlocks = std::max( |
| 111 | divUp(mBlockCountRange.lower(), widthInBlocks), |
| 112 | (int32_t)std::ceil(widthInBlocks / |
| 113 | mBlockAspectRatioRange.upper().asDouble())); |
| 114 | const int32_t maxHeightInBlocks = std::min( |
| 115 | mBlockCountRange.upper() / widthInBlocks, |
| 116 | (int32_t)(widthInBlocks / |
| 117 | mBlockAspectRatioRange.lower().asDouble())); |
| 118 | range = range.intersect( |
| 119 | (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, |
| 120 | maxHeightInBlocks * mBlockHeight); |
| 121 | |
| 122 | // constrain by smaller dimension limit |
| 123 | if (width > mSmallerDimensionUpperLimit) { |
| 124 | range = range.intersect(1, mSmallerDimensionUpperLimit); |
| 125 | } |
| 126 | |
| 127 | // constrain by aspect ratio |
| 128 | range = range.intersect( |
| 129 | (int32_t)std::ceil(width / |
| 130 | mAspectRatioRange.upper().asDouble()), |
| 131 | (int32_t)(width / mAspectRatioRange.lower().asDouble())); |
| 132 | return range; |
| 133 | } |
| 134 | |
| 135 | std::optional<Range<double>> VideoCapabilities::getSupportedFrameRatesFor( |
| 136 | int32_t width, int32_t height) const { |
| 137 | if (!supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| 138 | std::nullopt /* rate */)) { |
| 139 | ALOGE("Unsupported size. width: %d, height: %d", width, height); |
| 140 | return std::nullopt; |
| 141 | } |
| 142 | |
| 143 | const int32_t blockCount = |
| 144 | divUp(width, mBlockWidth) * divUp(height, mBlockHeight); |
| 145 | |
| 146 | return std::make_optional(Range( |
| 147 | std::max(mBlocksPerSecondRange.lower() / (double) blockCount, |
| 148 | (double) mFrameRateRange.lower()), |
| 149 | std::min(mBlocksPerSecondRange.upper() / (double) blockCount, |
| 150 | (double) mFrameRateRange.upper()))); |
| 151 | } |
| 152 | |
| 153 | int32_t VideoCapabilities::getBlockCount(int32_t width, int32_t height) const { |
| 154 | return divUp(width, mBlockWidth) * divUp(height, mBlockHeight); |
| 155 | } |
| 156 | |
| 157 | std::optional<VideoSize> VideoCapabilities::findClosestSize( |
| 158 | int32_t width, int32_t height) const { |
| 159 | int32_t targetBlockCount = getBlockCount(width, height); |
| 160 | std::optional<VideoSize> closestSize; |
| 161 | int32_t minDiff = INT32_MAX; |
| 162 | for (const auto &[size, range] : mMeasuredFrameRates) { |
| 163 | int32_t diff = std::abs(targetBlockCount - |
| 164 | getBlockCount(size.getWidth(), size.getHeight())); |
| 165 | if (diff < minDiff) { |
| 166 | minDiff = diff; |
| 167 | closestSize = size; |
| 168 | } |
| 169 | } |
| 170 | return closestSize; |
| 171 | } |
| 172 | |
| 173 | std::optional<Range<double>> VideoCapabilities::estimateFrameRatesFor( |
| 174 | int32_t width, int32_t height) const { |
| 175 | std::optional<VideoSize> size = findClosestSize(width, height); |
| 176 | if (!size) { |
| 177 | return std::nullopt; |
| 178 | } |
| 179 | auto rangeItr = mMeasuredFrameRates.find(size.value()); |
| 180 | if (rangeItr == mMeasuredFrameRates.end()) { |
| 181 | return std::nullopt; |
| 182 | } |
| 183 | Range<int64_t> range = rangeItr->second; |
| 184 | double ratio = getBlockCount(size.value().getWidth(), size.value().getHeight()) |
| 185 | / (double)std::max(getBlockCount(width, height), 1); |
| 186 | return std::make_optional(Range(range.lower() * ratio, range.upper() * ratio)); |
| 187 | } |
| 188 | |
| 189 | std::optional<Range<double>> VideoCapabilities::getAchievableFrameRatesFor( |
| 190 | int32_t width, int32_t height) const { |
| 191 | if (!supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| 192 | std::nullopt /* rate */)) { |
| 193 | ALOGE("Unsupported size. width: %d, height: %d", width, height); |
| 194 | return std::nullopt; |
| 195 | } |
| 196 | |
| 197 | if (mMeasuredFrameRates.empty()) { |
| 198 | ALOGW("Codec did not publish any measurement data."); |
| 199 | return std::nullopt; |
| 200 | } |
| 201 | |
| 202 | return estimateFrameRatesFor(width, height); |
| 203 | } |
| 204 | |
| 205 | // VideoCapabilities::PerformancePoint |
| 206 | |
| 207 | int32_t VideoCapabilities::PerformancePoint::getMaxMacroBlocks() const { |
| 208 | return saturateInt64ToInt32(mWidth * (int64_t)mHeight); |
| 209 | } |
| 210 | |
| 211 | int32_t VideoCapabilities::PerformancePoint::getWidth() const { |
| 212 | return mWidth; |
| 213 | } |
| 214 | |
| 215 | int32_t VideoCapabilities::PerformancePoint::getHeight() const { |
| 216 | return mHeight; |
| 217 | } |
| 218 | |
| 219 | int32_t VideoCapabilities::PerformancePoint::getMaxFrameRate() const { |
| 220 | return mMaxFrameRate; |
| 221 | } |
| 222 | |
| 223 | int64_t VideoCapabilities::PerformancePoint::getMaxMacroBlockRate() const { |
| 224 | return mMaxMacroBlockRate; |
| 225 | } |
| 226 | |
| 227 | VideoSize VideoCapabilities::PerformancePoint::getBlockSize() const { |
| 228 | return mBlockSize; |
| 229 | } |
| 230 | |
| 231 | std::string VideoCapabilities::PerformancePoint::toString() const { |
| 232 | int64_t blockWidth = 16 * (int64_t)mBlockSize.getWidth(); |
| 233 | int64_t blockHeight = 16 * (int64_t)mBlockSize.getHeight(); |
| 234 | int32_t origRate = (int32_t)divUp(mMaxMacroBlockRate, (int64_t)getMaxMacroBlocks()); |
| 235 | std::string info = std::to_string(mWidth * (int64_t)16) + "x" |
| 236 | + std::to_string(mHeight * (int64_t)16) + "@" + std::to_string(origRate); |
| 237 | if (origRate < mMaxFrameRate) { |
| 238 | info += ", max " + std::to_string(mMaxFrameRate) + "fps"; |
| 239 | } |
| 240 | if (blockWidth > 16 || blockHeight > 16) { |
| 241 | info += ", " + std::to_string(blockWidth) + "x" |
| 242 | + std::to_string(blockHeight) + " blocks"; |
| 243 | } |
| 244 | return "PerformancePoint(" + info + ")"; |
| 245 | } |
| 246 | |
| 247 | void VideoCapabilities::PerformancePoint::init(int32_t width, int32_t height, |
| 248 | int32_t frameRate, int32_t maxFrameRate, VideoSize blockSize) { |
| 249 | mBlockSize = VideoSize(divUp(blockSize.getWidth(), (int32_t)16), |
| 250 | divUp(blockSize.getHeight(), (int32_t)16)); |
| 251 | // Use IsPowerOfTwoStrict as we do not want width and height to be 0; |
| 252 | if (!IsPowerOfTwoStrict(blockSize.getWidth()) || !IsPowerOfTwoStrict(blockSize.getHeight())) { |
| 253 | ALOGE("The width and height of a PerformancePoint must be the power of two and not zero." |
| 254 | " width: %d, height: %d", blockSize.getWidth(), blockSize.getHeight()); |
| 255 | } |
| 256 | |
| 257 | // these are guaranteed not to overflow as we decimate by 16 |
| 258 | mWidth = (int32_t)(divUp(std::max(width, 1), |
| 259 | std::max(blockSize.getWidth(), 16)) |
| 260 | * mBlockSize.getWidth()); |
| 261 | mHeight = (int32_t)(divUp(std::max(height, 1), |
| 262 | std::max(blockSize.getHeight(), 16)) |
| 263 | * mBlockSize.getHeight()); |
| 264 | mMaxFrameRate = std::max(std::max(frameRate, maxFrameRate), 1); |
| 265 | mMaxMacroBlockRate = std::max(frameRate, 1) * (int64_t)getMaxMacroBlocks(); |
| 266 | } |
| 267 | |
| 268 | VideoCapabilities::PerformancePoint::PerformancePoint(int32_t width, int32_t height, |
| 269 | int32_t frameRate, int32_t maxFrameRate, VideoSize blockSize) { |
| 270 | init(width, height, frameRate, maxFrameRate, blockSize); |
| 271 | } |
| 272 | |
| 273 | VideoCapabilities::PerformancePoint::PerformancePoint(VideoSize blockSize, int32_t width, |
| 274 | int32_t height, int32_t maxFrameRate, int64_t maxMacroBlockRate) : |
| 275 | mBlockSize(blockSize), mWidth(width), mHeight(height), mMaxFrameRate(maxFrameRate), |
| 276 | mMaxMacroBlockRate(maxMacroBlockRate) {} |
| 277 | |
| 278 | VideoCapabilities::PerformancePoint::PerformancePoint( |
| 279 | const PerformancePoint &pp, VideoSize newBlockSize) { |
| 280 | init(16 * pp.mWidth, 16 * pp.mHeight, |
| 281 | // guaranteed not to overflow as these were multiplied at construction |
| 282 | (int32_t)divUp(pp.mMaxMacroBlockRate, (int64_t)pp.getMaxMacroBlocks()), |
| 283 | pp.mMaxFrameRate, |
| 284 | VideoSize(std::max(newBlockSize.getWidth(), 16 * pp.mBlockSize.getWidth()), |
| 285 | std::max(newBlockSize.getHeight(), 16 * pp.mBlockSize.getHeight()))); |
| 286 | } |
| 287 | |
| 288 | VideoCapabilities::PerformancePoint::PerformancePoint( |
| 289 | int32_t width, int32_t height, int32_t frameRate) { |
| 290 | init(width, height, frameRate, frameRate /* maxFrameRate */, VideoSize(16, 16)); |
| 291 | } |
| 292 | |
| 293 | int32_t VideoCapabilities::PerformancePoint::saturateInt64ToInt32(int64_t value) const { |
| 294 | if (value < INT32_MIN) { |
| 295 | return INT32_MIN; |
| 296 | } else if (value > INT32_MAX) { |
| 297 | return INT32_MAX; |
| 298 | } else { |
| 299 | return (int32_t)value; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | /* This method may overflow */ |
| 304 | int32_t VideoCapabilities::PerformancePoint::align( |
| 305 | int32_t value, int32_t alignment) const { |
| 306 | return divUp(value, alignment) * alignment; |
| 307 | } |
| 308 | |
| 309 | bool VideoCapabilities::PerformancePoint::covers( |
| 310 | const sp<AMessage> &format) const { |
| 311 | int32_t width, height; |
| 312 | format->findInt32(KEY_WIDTH, &width); |
| 313 | format->findInt32(KEY_HEIGHT, &height); |
| 314 | double frameRate; |
| 315 | format->findDouble(KEY_FRAME_RATE, &frameRate); |
| 316 | PerformancePoint other = PerformancePoint( |
| 317 | width, height, |
| 318 | // safely convert ceil(double) to int through float cast and std::round |
| 319 | std::round((float)(std::ceil(frameRate))) |
| 320 | ); |
| 321 | return covers(other); |
| 322 | } |
| 323 | |
| 324 | bool VideoCapabilities::PerformancePoint::covers( |
| 325 | const PerformancePoint &other) const { |
| 326 | // convert performance points to common block size |
| 327 | VideoSize commonSize = getCommonBlockSize(other); |
| 328 | PerformancePoint aligned = PerformancePoint(*this, commonSize); |
| 329 | PerformancePoint otherAligned = PerformancePoint(other, commonSize); |
| 330 | |
| 331 | return (aligned.getMaxMacroBlocks() >= otherAligned.getMaxMacroBlocks() |
| 332 | && aligned.mMaxFrameRate >= otherAligned.mMaxFrameRate |
| 333 | && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate); |
| 334 | } |
| 335 | |
| 336 | VideoSize VideoCapabilities::PerformancePoint::getCommonBlockSize( |
| 337 | const PerformancePoint &other) const { |
| 338 | return VideoSize( |
| 339 | 16 * std::max(mBlockSize.getWidth(), other.mBlockSize.getWidth()), |
| 340 | 16 * std::max(mBlockSize.getHeight(), other.mBlockSize.getHeight())); |
| 341 | } |
| 342 | |
| 343 | bool VideoCapabilities::PerformancePoint::equals( |
| 344 | const PerformancePoint &other) const { |
| 345 | // convert performance points to common block size |
| 346 | VideoSize commonSize = getCommonBlockSize(other); |
| 347 | PerformancePoint aligned = PerformancePoint(*this, commonSize); |
| 348 | PerformancePoint otherAligned = PerformancePoint(other, commonSize); |
| 349 | |
| 350 | return (aligned.getMaxMacroBlocks() == otherAligned.getMaxMacroBlocks() |
| 351 | && aligned.mMaxFrameRate == otherAligned.mMaxFrameRate |
| 352 | && aligned.mMaxMacroBlockRate == otherAligned.mMaxMacroBlockRate); |
| 353 | } |
| 354 | |
| 355 | // VideoCapabilities |
| 356 | |
| 357 | const std::vector<VideoCapabilities::PerformancePoint>& |
| 358 | VideoCapabilities::getSupportedPerformancePoints() const { |
| 359 | return mPerformancePoints; |
| 360 | } |
| 361 | |
| 362 | bool VideoCapabilities::areSizeAndRateSupported( |
| 363 | int32_t width, int32_t height, double frameRate) const { |
| 364 | return supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| 365 | std::make_optional<double>(frameRate)); |
| 366 | } |
| 367 | |
| 368 | bool VideoCapabilities::isSizeSupported(int32_t width, int32_t height) const { |
| 369 | return supports(std::make_optional<int32_t>(width), std::make_optional<int32_t>(height), |
| 370 | std::nullopt /* rate */); |
| 371 | } |
| 372 | |
| 373 | bool VideoCapabilities::supports(std::optional<int32_t> width, std::optional<int32_t> height, |
| 374 | std::optional<double> rate) const { |
| 375 | bool ok = true; |
| 376 | |
| 377 | if (width) { |
| 378 | ok &= mWidthRange.contains(width.value()) |
| 379 | && (width.value() % mWidthAlignment == 0); |
| 380 | } |
| 381 | if (height) { |
| 382 | ok &= mHeightRange.contains(height.value()) |
| 383 | && (height.value() % mHeightAlignment == 0); |
| 384 | } |
| 385 | if (rate) { |
| 386 | ok &= mFrameRateRange.contains(Range<int32_t>::RangeFor(rate.value())); |
| 387 | } |
| 388 | if (height && width) { |
| 389 | ok &= std::min(height.value(), width.value()) <= mSmallerDimensionUpperLimit; |
| 390 | |
| 391 | const int32_t widthInBlocks = divUp(width.value(), mBlockWidth); |
| 392 | const int32_t heightInBlocks = divUp(height.value(), mBlockHeight); |
| 393 | const int32_t blockCount = widthInBlocks * heightInBlocks; |
| 394 | ok &= mBlockCountRange.contains(blockCount) |
| 395 | && mBlockAspectRatioRange.contains( |
| 396 | Rational(widthInBlocks, heightInBlocks)) |
| 397 | && mAspectRatioRange.contains(Rational(width.value(), height.value())); |
| 398 | if (rate) { |
| 399 | double blocksPerSec = blockCount * rate.value(); |
| 400 | ok &= mBlocksPerSecondRange.contains( |
| 401 | Range<int64_t>::RangeFor(blocksPerSec)); |
| 402 | } |
| 403 | } |
| 404 | return ok; |
| 405 | } |
| 406 | |
| 407 | bool VideoCapabilities::supportsFormat(const sp<AMessage> &format) const { |
| 408 | int32_t widthVal, heightVal; |
| 409 | std::optional<int32_t> width = format->findInt32(KEY_WIDTH, &widthVal) |
| 410 | ? std::make_optional<int32_t>(widthVal) : std::nullopt; |
| 411 | std::optional<int32_t> height = format->findInt32(KEY_HEIGHT, &heightVal) |
| 412 | ? std::make_optional<int32_t>(heightVal) : std::nullopt; |
| 413 | double rateVal; |
| 414 | std::optional<double> rate = format->findDouble(KEY_FRAME_RATE, &rateVal) |
| 415 | ? std::make_optional<double>(rateVal) : std::nullopt; |
| 416 | |
| 417 | if (!supports(width, height, rate)) { |
| 418 | return false; |
| 419 | } |
| 420 | |
| 421 | if (!CodecCapabilities::SupportsBitrate(mBitrateRange, format)) { |
| 422 | return false; |
| 423 | } |
| 424 | |
| 425 | // we ignore color-format for now as it is not reliably reported by codec |
| 426 | return true; |
| 427 | } |
| 428 | |
| 429 | // static |
| 430 | std::shared_ptr<VideoCapabilities> VideoCapabilities::Create(std::string mediaType, |
| 431 | std::vector<ProfileLevel> profLevs, const sp<AMessage> &format) { |
| 432 | std::shared_ptr<VideoCapabilities> caps(new VideoCapabilities()); |
| 433 | caps->init(mediaType, profLevs, format); |
| 434 | return caps; |
| 435 | } |
| 436 | |
| 437 | void VideoCapabilities::init(std::string mediaType, std::vector<ProfileLevel> profLevs, |
| 438 | const sp<AMessage> &format) { |
| 439 | mMediaType = mediaType; |
| 440 | mProfileLevels = profLevs; |
| 441 | mError = 0; |
| 442 | |
| 443 | initWithPlatformLimits(); |
| 444 | applyLevelLimits(); |
| 445 | parseFromInfo(format); |
| 446 | updateLimits(); |
| 447 | } |
| 448 | |
| 449 | VideoSize VideoCapabilities::getBlockSize() const { |
| 450 | return VideoSize(mBlockWidth, mBlockHeight); |
| 451 | } |
| 452 | |
| 453 | const Range<int32_t>& VideoCapabilities::getBlockCountRange() const { |
| 454 | return mBlockCountRange; |
| 455 | } |
| 456 | |
| 457 | const Range<int64_t>& VideoCapabilities::getBlocksPerSecondRange() const { |
| 458 | return mBlocksPerSecondRange; |
| 459 | } |
| 460 | |
| 461 | Range<Rational> VideoCapabilities::getAspectRatioRange(bool blocks) const { |
| 462 | return blocks ? mBlockAspectRatioRange : mAspectRatioRange; |
| 463 | } |
| 464 | |
| 465 | void VideoCapabilities::initWithPlatformLimits() { |
| 466 | mBitrateRange = BITRATE_RANGE; |
| 467 | |
| 468 | mWidthRange = VideoSize::GetAllowedDimensionRange(); |
| 469 | mHeightRange = VideoSize::GetAllowedDimensionRange(); |
| 470 | mFrameRateRange = FRAME_RATE_RANGE; |
| 471 | |
| 472 | mHorizontalBlockRange = VideoSize::GetAllowedDimensionRange(); |
| 473 | mVerticalBlockRange = VideoSize::GetAllowedDimensionRange(); |
| 474 | |
| 475 | // full positive ranges are supported as these get calculated |
| 476 | mBlockCountRange = POSITIVE_INT32; |
| 477 | mBlocksPerSecondRange = POSITIVE_INT64; |
| 478 | |
| 479 | mBlockAspectRatioRange = POSITIVE_RATIONALS; |
| 480 | mAspectRatioRange = POSITIVE_RATIONALS; |
| 481 | |
| 482 | // YUV 4:2:0 requires 2:2 alignment |
| 483 | mWidthAlignment = 2; |
| 484 | mHeightAlignment = 2; |
| 485 | mBlockWidth = 2; |
| 486 | mBlockHeight = 2; |
| 487 | mSmallerDimensionUpperLimit = VideoSize::GetAllowedDimensionRange().upper(); |
| 488 | } |
| 489 | |
| 490 | std::vector<VideoCapabilities::PerformancePoint> |
| 491 | VideoCapabilities::getPerformancePoints( |
| 492 | const sp<AMessage> &format) const { |
| 493 | std::vector<PerformancePoint> ret; |
| 494 | AMessage::Type type; |
| 495 | for (int i = 0; i < format->countEntries(); i++) { |
| 496 | const char *name = format->getEntryNameAt(i, &type); |
| 497 | AString rangeStr; |
| 498 | if (!format->findString(name, &rangeStr)) { |
| 499 | continue; |
| 500 | } |
| 501 | |
| 502 | const std::string key = std::string(name); |
| 503 | // looking for: performance-point-WIDTHxHEIGHT-range |
| 504 | |
| 505 | // check none performance point |
| 506 | if (key == "performance-point-none" && ret.size() == 0) { |
| 507 | // This means that component knowingly did not publish performance points. |
| 508 | // This is different from when the component forgot to publish performance |
| 509 | // points. |
| 510 | return ret; |
| 511 | } |
| 512 | |
| 513 | // parse size from key |
| 514 | std::regex sizeRegex("performance-point-(.+)-range"); |
| 515 | std::smatch sizeMatch; |
| 516 | if (!std::regex_match(key, sizeMatch, sizeRegex)) { |
| 517 | continue; |
| 518 | } |
| 519 | std::optional<VideoSize> size = VideoSize::ParseSize(sizeMatch[1].str()); |
| 520 | if (!size || size.value().getWidth() * size.value().getHeight() <= 0) { |
| 521 | continue; |
| 522 | } |
| 523 | |
| 524 | // parse range from value |
| 525 | std::optional<Range<int64_t>> range = Range<int64_t>::Parse(std::string(rangeStr.c_str())); |
| 526 | if (!range || range.value().lower() < 0 || range.value().upper() < 0) { |
| 527 | continue; |
| 528 | } |
| 529 | |
| 530 | PerformancePoint given = PerformancePoint( |
| 531 | size.value().getWidth(), size.value().getHeight(), (int32_t)range.value().lower(), |
| 532 | (int32_t)range.value().upper(), VideoSize(mBlockWidth, mBlockHeight)); |
| 533 | PerformancePoint rotated = PerformancePoint( |
| 534 | size.value().getHeight(), size.value().getWidth(), (int32_t)range.value().lower(), |
| 535 | (int32_t)range.value().upper(), VideoSize(mBlockWidth, mBlockHeight)); |
| 536 | ret.push_back(given); |
| 537 | if (!given.covers(rotated)) { |
| 538 | ret.push_back(rotated); |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | // check if the component specified no performance point indication |
| 543 | if (ret.size() == 0) { |
| 544 | return ret; |
| 545 | } |
| 546 | |
| 547 | // sort reversed by area first, then by frame rate |
| 548 | std::sort(ret.begin(), ret.end(), [](const PerformancePoint &a, const PerformancePoint &b) { |
| 549 | return -((a.getMaxMacroBlocks() != b.getMaxMacroBlocks()) ? |
| 550 | (a.getMaxMacroBlocks() < b.getMaxMacroBlocks() ? -1 : 1) : |
| 551 | (a.getMaxMacroBlockRate() != b.getMaxMacroBlockRate()) ? |
| 552 | (a.getMaxMacroBlockRate() < b.getMaxMacroBlockRate() ? -1 : 1) : |
| 553 | (a.getMaxFrameRate() != b.getMaxFrameRate()) ? |
| 554 | (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0); |
| 555 | }); |
| 556 | |
| 557 | return ret; |
| 558 | } |
| 559 | |
| 560 | std::map<VideoSize, Range<int64_t>, VideoSizeCompare> VideoCapabilities |
| 561 | ::getMeasuredFrameRates(const sp<AMessage> &format) const { |
| 562 | std::map<VideoSize, Range<int64_t>, VideoSizeCompare> ret; |
| 563 | AMessage::Type type; |
| 564 | for (int i = 0; i < format->countEntries(); i++) { |
| 565 | const char *name = format->getEntryNameAt(i, &type); |
| 566 | AString rangeStr; |
| 567 | if (!format->findString(name, &rangeStr)) { |
| 568 | continue; |
| 569 | } |
| 570 | |
| 571 | const std::string key = std::string(name); |
| 572 | // looking for: measured-frame-rate-WIDTHxHEIGHT-range |
| 573 | |
| 574 | std::regex sizeRegex("measured-frame-rate-(.+)-range"); |
| 575 | std::smatch sizeMatch; |
| 576 | if (!std::regex_match(key, sizeMatch, sizeRegex)) { |
| 577 | continue; |
| 578 | } |
| 579 | |
| 580 | std::optional<VideoSize> size = VideoSize::ParseSize(sizeMatch[1].str()); |
| 581 | if (!size || size.value().getWidth() * size.value().getHeight() <= 0) { |
| 582 | continue; |
| 583 | } |
| 584 | |
| 585 | std::optional<Range<int64_t>> range = Range<int64_t>::Parse(std::string(rangeStr.c_str())); |
| 586 | if (!range || range.value().lower() < 0 || range.value().upper() < 0) { |
| 587 | continue; |
| 588 | } |
| 589 | |
| 590 | ret.emplace(size.value(), range.value()); |
| 591 | } |
| 592 | return ret; |
| 593 | } |
| 594 | |
| 595 | // static |
| 596 | std::optional<std::pair<Range<int32_t>, Range<int32_t>>> VideoCapabilities |
| 597 | ::ParseWidthHeightRanges(const std::string &str) { |
| 598 | std::optional<std::pair<VideoSize, VideoSize>> range = VideoSize::ParseSizeRange(str); |
| 599 | if (!range) { |
| 600 | ALOGW("could not parse size range: %s", str.c_str()); |
| 601 | return std::nullopt; |
| 602 | } |
| 603 | |
| 604 | return std::make_optional(std::pair( |
| 605 | Range(range.value().first.getWidth(), range.value().second.getWidth()), |
| 606 | Range(range.value().first.getHeight(), range.value().second.getHeight()))); |
| 607 | } |
| 608 | |
| 609 | // static |
| 610 | int32_t VideoCapabilities::EquivalentVP9Level(const sp<AMessage> &format) { |
| 611 | int32_t blockSizeWidth = 8; |
| 612 | int32_t blockSizeHeight = 8; |
| 613 | // VideoSize *blockSizePtr = &VideoSize(8, 8); |
| 614 | AString blockSizeStr; |
| 615 | if (format->findString("block-size", &blockSizeStr)) { |
| 616 | std::optional<VideoSize> parsedBlockSize |
| 617 | = VideoSize::ParseSize(std::string(blockSizeStr.c_str())); |
| 618 | if (parsedBlockSize) { |
| 619 | // blockSize = parsedBlockSize.value(); |
| 620 | blockSizeWidth = parsedBlockSize.value().getWidth(); |
| 621 | blockSizeHeight = parsedBlockSize.value().getHeight(); |
| 622 | } |
| 623 | } |
| 624 | int32_t BS = blockSizeWidth * blockSizeHeight; |
| 625 | |
| 626 | int32_t FS = 0; |
| 627 | AString blockCountRangeStr; |
| 628 | if (format->findString("block-count-range", &blockCountRangeStr)) { |
| 629 | std::optional<Range<int>> counts = Range<int32_t>::Parse( |
| 630 | std::string(blockCountRangeStr.c_str())); |
| 631 | if (counts) { |
| 632 | FS = BS * counts.value().upper(); |
| 633 | } |
| 634 | } |
| 635 | |
| 636 | int64_t SR = 0; |
| 637 | AString blockRatesStr; |
| 638 | if (format->findString("blocks-per-second-range", &blockRatesStr)) { |
| 639 | std::optional<Range<int64_t>> blockRates |
| 640 | = Range<int64_t>::Parse(std::string(blockRatesStr.c_str())); |
| 641 | if (blockRates) { |
| 642 | // ToDo: Catch the potential overflow issue. |
| 643 | SR = BS * blockRates.value().upper(); |
| 644 | } |
| 645 | } |
| 646 | |
| 647 | int32_t D = 0; |
| 648 | AString dimensionRangesStr; |
| 649 | if (format->findString("size-range", &dimensionRangesStr)) { |
| 650 | std::optional<std::pair<Range<int>, Range<int>>> dimensionRanges = |
| 651 | ParseWidthHeightRanges(std::string(dimensionRangesStr.c_str())); |
| 652 | if (dimensionRanges) { |
| 653 | D = std::max(dimensionRanges.value().first.upper(), |
| 654 | dimensionRanges.value().second.upper()); |
| 655 | } |
| 656 | } |
| 657 | |
| 658 | int32_t BR = 0; |
| 659 | AString bitrateRangeStr; |
| 660 | if (format->findString("bitrate-range", &bitrateRangeStr)) { |
| 661 | std::optional<Range<int>> bitRates = Range<int32_t>::Parse( |
| 662 | std::string(bitrateRangeStr.c_str())); |
| 663 | if (bitRates) { |
| 664 | BR = divUp(bitRates.value().upper(), 1000); |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | if (SR <= 829440 && FS <= 36864 && BR <= 200 && D <= 512) |
| 669 | return VP9Level1; |
| 670 | if (SR <= 2764800 && FS <= 73728 && BR <= 800 && D <= 768) |
| 671 | return VP9Level11; |
| 672 | if (SR <= 4608000 && FS <= 122880 && BR <= 1800 && D <= 960) |
| 673 | return VP9Level2; |
| 674 | if (SR <= 9216000 && FS <= 245760 && BR <= 3600 && D <= 1344) |
| 675 | return VP9Level21; |
| 676 | if (SR <= 20736000 && FS <= 552960 && BR <= 7200 && D <= 2048) |
| 677 | return VP9Level3; |
| 678 | if (SR <= 36864000 && FS <= 983040 && BR <= 12000 && D <= 2752) |
| 679 | return VP9Level31; |
| 680 | if (SR <= 83558400 && FS <= 2228224 && BR <= 18000 && D <= 4160) |
| 681 | return VP9Level4; |
| 682 | if (SR <= 160432128 && FS <= 2228224 && BR <= 30000 && D <= 4160) |
| 683 | return VP9Level41; |
| 684 | if (SR <= 311951360 && FS <= 8912896 && BR <= 60000 && D <= 8384) |
| 685 | return VP9Level5; |
| 686 | if (SR <= 588251136 && FS <= 8912896 && BR <= 120000 && D <= 8384) |
| 687 | return VP9Level51; |
| 688 | if (SR <= 1176502272 && FS <= 8912896 && BR <= 180000 && D <= 8384) |
| 689 | return VP9Level52; |
| 690 | if (SR <= 1176502272 && FS <= 35651584 && BR <= 180000 && D <= 16832) |
| 691 | return VP9Level6; |
| 692 | if (SR <= 2353004544L && FS <= 35651584 && BR <= 240000 && D <= 16832) |
| 693 | return VP9Level61; |
| 694 | if (SR <= 4706009088L && FS <= 35651584 && BR <= 480000 && D <= 16832) |
| 695 | return VP9Level62; |
| 696 | // returning largest level |
| 697 | return VP9Level62; |
| 698 | } |
| 699 | |
| 700 | void VideoCapabilities::parseFromInfo(const sp<AMessage> &format) { |
| 701 | VideoSize blockSize = VideoSize(mBlockWidth, mBlockHeight); |
| 702 | VideoSize alignment = VideoSize(mWidthAlignment, mHeightAlignment); |
| 703 | std::optional<Range<int32_t>> counts, widths, heights; |
| 704 | std::optional<Range<int32_t>> frameRates, bitRates; |
| 705 | std::optional<Range<int64_t>> blockRates; |
| 706 | std::optional<Range<Rational>> ratios, blockRatios; |
| 707 | |
| 708 | AString blockSizeStr; |
| 709 | if (format->findString("block-size", &blockSizeStr)) { |
| 710 | std::optional<VideoSize> parsedBlockSize |
| 711 | = VideoSize::ParseSize(std::string(blockSizeStr.c_str())); |
| 712 | blockSize = parsedBlockSize.value_or(blockSize); |
| 713 | } |
| 714 | AString alignmentStr; |
| 715 | if (format->findString("alignment", &alignmentStr)) { |
| 716 | std::optional<VideoSize> parsedAlignment |
| 717 | = VideoSize::ParseSize(std::string(alignmentStr.c_str())); |
| 718 | alignment = parsedAlignment.value_or(alignment); |
| 719 | } |
| 720 | AString blockCountRangeStr; |
| 721 | if (format->findString("block-count-range", &blockCountRangeStr)) { |
| 722 | std::optional<Range<int>> parsedBlockCountRange = |
| 723 | Range<int32_t>::Parse(std::string(blockCountRangeStr.c_str())); |
| 724 | if (parsedBlockCountRange) { |
| 725 | counts = parsedBlockCountRange.value(); |
| 726 | } |
| 727 | } |
| 728 | AString blockRatesStr; |
| 729 | if (format->findString("blocks-per-second-range", &blockRatesStr)) { |
| 730 | blockRates = Range<int64_t>::Parse(std::string(blockRatesStr.c_str())); |
| 731 | } |
| 732 | mMeasuredFrameRates = getMeasuredFrameRates(format); |
| 733 | mPerformancePoints = getPerformancePoints(format); |
| 734 | AString sizeRangesStr; |
| 735 | if (format->findString("size-range", &sizeRangesStr)) { |
| 736 | std::optional<std::pair<Range<int>, Range<int>>> sizeRanges = |
| 737 | ParseWidthHeightRanges(std::string(sizeRangesStr.c_str())); |
| 738 | if (sizeRanges) { |
| 739 | widths = sizeRanges.value().first; |
| 740 | heights = sizeRanges.value().second; |
| 741 | } |
| 742 | } |
| 743 | // for now this just means using the smaller max size as 2nd |
| 744 | // upper limit. |
| 745 | // for now we are keeping the profile specific "width/height |
| 746 | // in macroblocks" limits. |
| 747 | if (format->contains("feature-can-swap-width-height")) { |
| 748 | if (widths && heights) { |
| 749 | mSmallerDimensionUpperLimit = |
| 750 | std::min(widths.value().upper(), heights.value().upper()); |
| 751 | widths = heights = widths.value().extend(heights.value()); |
| 752 | } else { |
| 753 | ALOGW("feature can-swap-width-height is best used with size-range"); |
| 754 | mSmallerDimensionUpperLimit = |
| 755 | std::min(mWidthRange.upper(), mHeightRange.upper()); |
| 756 | mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); |
| 757 | } |
| 758 | } |
| 759 | |
| 760 | AString ratioStr; |
| 761 | if (format->findString("block-aspect-ratio-range", &ratioStr)) { |
| 762 | ratios = Rational::ParseRange(std::string(ratioStr.c_str())); |
| 763 | } |
| 764 | AString blockRatiosStr; |
| 765 | if (format->findString("pixel-aspect-ratio-range", &blockRatiosStr)) { |
| 766 | blockRatios = Rational::ParseRange(std::string(blockRatiosStr.c_str())); |
| 767 | } |
| 768 | AString frameRatesStr; |
| 769 | if (format->findString("frame-rate-range", &frameRatesStr)) { |
| 770 | frameRates = Range<int32_t>::Parse(std::string(frameRatesStr.c_str())); |
| 771 | if (frameRates) { |
| 772 | frameRates = frameRates.value().intersect(FRAME_RATE_RANGE); |
| 773 | if (frameRates.value().empty()) { |
| 774 | ALOGW("frame rate range is out of limits"); |
| 775 | frameRates = std::nullopt; |
| 776 | } |
| 777 | } |
| 778 | } |
| 779 | AString bitRatesStr; |
| 780 | if (format->findString("bitrate-range", &bitRatesStr)) { |
| 781 | bitRates = Range<int32_t>::Parse(std::string(bitRatesStr.c_str())); |
| 782 | if (bitRates) { |
| 783 | bitRates = bitRates.value().intersect(BITRATE_RANGE); |
| 784 | if (bitRates.value().empty()) { |
| 785 | ALOGW("bitrate range is out of limits"); |
| 786 | bitRates = std::nullopt; |
| 787 | } |
| 788 | } |
| 789 | } |
| 790 | |
| 791 | if (!IsPowerOfTwo(blockSize.getWidth()) || !IsPowerOfTwo(blockSize.getHeight()) |
| 792 | || !IsPowerOfTwo(alignment.getWidth()) || !IsPowerOfTwo(alignment.getHeight())) { |
| 793 | ALOGE("The widths and heights of blockSizes and alignments must be the power of two." |
| 794 | " blockSize width: %d; blockSize height: %d;" |
| 795 | " alignment width: %d; alignment height: %d.", |
| 796 | blockSize.getWidth(), blockSize.getHeight(), |
| 797 | alignment.getWidth(), alignment.getHeight()); |
| 798 | mError |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 799 | return; |
| 800 | } |
| 801 | |
| 802 | // update block-size and alignment |
| 803 | applyMacroBlockLimits( |
| 804 | INT32_MAX, INT32_MAX, INT32_MAX, INT64_MAX, |
| 805 | blockSize.getWidth(), blockSize.getHeight(), |
| 806 | alignment.getWidth(), alignment.getHeight()); |
| 807 | |
| 808 | if ((mError & ERROR_CAPABILITIES_UNSUPPORTED) != 0 || mAllowMbOverride) { |
| 809 | // codec supports profiles that we don't know. |
| 810 | // Use supplied values clipped to platform limits |
| 811 | if (widths) { |
| 812 | mWidthRange = VideoSize::GetAllowedDimensionRange().intersect(widths.value()); |
| 813 | } |
| 814 | if (heights) { |
| 815 | mHeightRange = VideoSize::GetAllowedDimensionRange().intersect(heights.value()); |
| 816 | } |
| 817 | if (counts) { |
| 818 | mBlockCountRange = POSITIVE_INT32.intersect( |
| 819 | counts.value().factor(mBlockWidth * mBlockHeight |
| 820 | / blockSize.getWidth() / blockSize.getHeight())); |
| 821 | } |
| 822 | if (blockRates) { |
| 823 | mBlocksPerSecondRange = POSITIVE_INT64.intersect( |
| 824 | blockRates.value().factor(mBlockWidth * mBlockHeight |
| 825 | / blockSize.getWidth() / blockSize.getHeight())); |
| 826 | } |
| 827 | if (blockRatios) { |
| 828 | mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( |
| 829 | Rational::ScaleRange(blockRatios.value(), |
| 830 | mBlockHeight / blockSize.getHeight(), |
| 831 | mBlockWidth / blockSize.getWidth())); |
| 832 | } |
| 833 | if (ratios) { |
| 834 | mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios.value()); |
| 835 | } |
| 836 | if (frameRates) { |
| 837 | mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates.value()); |
| 838 | } |
| 839 | if (bitRates) { |
| 840 | // only allow bitrate override if unsupported profiles were encountered |
| 841 | if ((mError & ERROR_CAPABILITIES_UNSUPPORTED) != 0) { |
| 842 | mBitrateRange = BITRATE_RANGE.intersect(bitRates.value()); |
| 843 | } else { |
| 844 | mBitrateRange = mBitrateRange.intersect(bitRates.value()); |
| 845 | } |
| 846 | } |
| 847 | } else { |
| 848 | // no unsupported profile/levels, so restrict values to known limits |
| 849 | if (widths) { |
| 850 | mWidthRange = mWidthRange.intersect(widths.value()); |
| 851 | } |
| 852 | if (heights) { |
| 853 | mHeightRange = mHeightRange.intersect(heights.value()); |
| 854 | } |
| 855 | if (counts) { |
| 856 | mBlockCountRange = mBlockCountRange.intersect( |
| 857 | counts.value().factor(mBlockWidth * mBlockHeight |
| 858 | / blockSize.getWidth() / blockSize.getHeight())); |
| 859 | } |
| 860 | if (blockRates) { |
| 861 | mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( |
| 862 | blockRates.value().factor(mBlockWidth * mBlockHeight |
| 863 | / blockSize.getWidth() / blockSize.getHeight())); |
| 864 | } |
| 865 | if (blockRatios) { |
| 866 | mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( |
| 867 | Rational::ScaleRange(blockRatios.value(), |
| 868 | mBlockHeight / blockSize.getHeight(), |
| 869 | mBlockWidth / blockSize.getWidth())); |
| 870 | } |
| 871 | if (ratios) { |
| 872 | mAspectRatioRange = mAspectRatioRange.intersect(ratios.value()); |
| 873 | } |
| 874 | if (frameRates) { |
| 875 | mFrameRateRange = mFrameRateRange.intersect(frameRates.value()); |
| 876 | } |
| 877 | if (bitRates) { |
| 878 | mBitrateRange = mBitrateRange.intersect(bitRates.value()); |
| 879 | } |
| 880 | } |
| 881 | updateLimits(); |
| 882 | } |
| 883 | |
| 884 | void VideoCapabilities::applyBlockLimits( |
| 885 | int32_t blockWidth, int32_t blockHeight, |
| 886 | Range<int32_t> counts, Range<int64_t> rates, Range<Rational> ratios) { |
| 887 | |
| 888 | if (!IsPowerOfTwo(blockWidth) || !IsPowerOfTwo(blockHeight)) { |
| 889 | ALOGE("blockWidth and blockHeight must be the power of two." |
| 890 | " blockWidth: %d; blockHeight: %d", blockWidth, blockHeight); |
| 891 | mError |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 892 | return; |
| 893 | } |
| 894 | |
| 895 | const int32_t newBlockWidth = std::max(blockWidth, mBlockWidth); |
| 896 | const int32_t newBlockHeight = std::max(blockHeight, mBlockHeight); |
| 897 | |
| 898 | // factor will always be a power-of-2 |
| 899 | int32_t factor = |
| 900 | newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; |
| 901 | if (factor != 1) { |
| 902 | mBlockCountRange = mBlockCountRange.factor(factor); |
| 903 | mBlocksPerSecondRange = mBlocksPerSecondRange.factor(factor); |
| 904 | mBlockAspectRatioRange = Rational::ScaleRange( |
| 905 | mBlockAspectRatioRange, |
| 906 | newBlockHeight / mBlockHeight, |
| 907 | newBlockWidth / mBlockWidth); |
| 908 | mHorizontalBlockRange = mHorizontalBlockRange.factor(newBlockWidth / mBlockWidth); |
| 909 | mVerticalBlockRange = mVerticalBlockRange.factor(newBlockHeight / mBlockHeight); |
| 910 | } |
| 911 | factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; |
| 912 | if (factor != 1) { |
| 913 | counts = counts.factor(factor); |
| 914 | rates = rates.factor((int64_t)factor); |
| 915 | ratios = Rational::ScaleRange( |
| 916 | ratios, newBlockHeight / blockHeight, |
| 917 | newBlockWidth / blockWidth); |
| 918 | } |
| 919 | mBlockCountRange = mBlockCountRange.intersect(counts); |
| 920 | mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); |
| 921 | mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); |
| 922 | mBlockWidth = newBlockWidth; |
| 923 | mBlockHeight = newBlockHeight; |
| 924 | } |
| 925 | |
| 926 | void VideoCapabilities::applyAlignment( |
| 927 | int32_t widthAlignment, int32_t heightAlignment) { |
| 928 | if (!IsPowerOfTwo(widthAlignment) || !IsPowerOfTwo(heightAlignment)) { |
| 929 | ALOGE("width and height alignments must be the power of two." |
| 930 | " widthAlignment: %d; heightAlignment: %d", widthAlignment, heightAlignment); |
| 931 | mError |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 932 | return; |
| 933 | } |
| 934 | |
| 935 | if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) { |
| 936 | // maintain assumption that 0 < alignment <= block-size |
| 937 | applyBlockLimits( |
| 938 | std::max(widthAlignment, mBlockWidth), |
| 939 | std::max(heightAlignment, mBlockHeight), |
| 940 | POSITIVE_INT32, POSITIVE_INT64, POSITIVE_RATIONALS); |
| 941 | } |
| 942 | |
| 943 | mWidthAlignment = std::max(widthAlignment, mWidthAlignment); |
| 944 | mHeightAlignment = std::max(heightAlignment, mHeightAlignment); |
| 945 | |
| 946 | mWidthRange = mWidthRange.align(mWidthAlignment); |
| 947 | mHeightRange = mHeightRange.align(mHeightAlignment); |
| 948 | } |
| 949 | |
| 950 | void VideoCapabilities::updateLimits() { |
| 951 | // pixels -> blocks <- counts |
| 952 | mHorizontalBlockRange = mHorizontalBlockRange.intersect( |
| 953 | mWidthRange.factor(mBlockWidth)); |
| 954 | mHorizontalBlockRange = mHorizontalBlockRange.intersect( |
| 955 | Range( mBlockCountRange.lower() / mVerticalBlockRange.upper(), |
| 956 | mBlockCountRange.upper() / mVerticalBlockRange.lower())); |
| 957 | mVerticalBlockRange = mVerticalBlockRange.intersect( |
| 958 | mHeightRange.factor(mBlockHeight)); |
| 959 | mVerticalBlockRange = mVerticalBlockRange.intersect( |
| 960 | Range( mBlockCountRange.lower() / mHorizontalBlockRange.upper(), |
| 961 | mBlockCountRange.upper() / mHorizontalBlockRange.lower())); |
| 962 | mBlockCountRange = mBlockCountRange.intersect( |
| 963 | Range( mHorizontalBlockRange.lower() |
| 964 | * mVerticalBlockRange.lower(), |
| 965 | mHorizontalBlockRange.upper() |
| 966 | * mVerticalBlockRange.upper())); |
| 967 | mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( |
| 968 | Rational(mHorizontalBlockRange.lower(), mVerticalBlockRange.upper()), |
| 969 | Rational(mHorizontalBlockRange.upper(), mVerticalBlockRange.lower())); |
| 970 | |
| 971 | // blocks -> pixels |
| 972 | mWidthRange = mWidthRange.intersect( |
| 973 | (mHorizontalBlockRange.lower() - 1) * mBlockWidth + mWidthAlignment, |
| 974 | mHorizontalBlockRange.upper() * mBlockWidth); |
| 975 | mHeightRange = mHeightRange.intersect( |
| 976 | (mVerticalBlockRange.lower() - 1) * mBlockHeight + mHeightAlignment, |
| 977 | mVerticalBlockRange.upper() * mBlockHeight); |
| 978 | mAspectRatioRange = mAspectRatioRange.intersect( |
| 979 | Rational(mWidthRange.lower(), mHeightRange.upper()), |
| 980 | Rational(mWidthRange.upper(), mHeightRange.lower())); |
| 981 | |
| 982 | mSmallerDimensionUpperLimit = std::min( |
| 983 | mSmallerDimensionUpperLimit, |
| 984 | std::min(mWidthRange.upper(), mHeightRange.upper())); |
| 985 | |
| 986 | // blocks -> rate |
| 987 | mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( |
| 988 | mBlockCountRange.lower() * (int64_t)mFrameRateRange.lower(), |
| 989 | mBlockCountRange.upper() * (int64_t)mFrameRateRange.upper()); |
| 990 | mFrameRateRange = mFrameRateRange.intersect( |
| 991 | (int32_t)(mBlocksPerSecondRange.lower() |
| 992 | / mBlockCountRange.upper()), |
| 993 | (int32_t)(mBlocksPerSecondRange.upper() |
| 994 | / (double)mBlockCountRange.lower())); |
| 995 | } |
| 996 | |
| 997 | void VideoCapabilities::applyMacroBlockLimits( |
| 998 | int32_t maxHorizontalBlocks, int32_t maxVerticalBlocks, |
| 999 | int32_t maxBlocks, int64_t maxBlocksPerSecond, |
| 1000 | int32_t blockWidth, int32_t blockHeight, |
| 1001 | int32_t widthAlignment, int32_t heightAlignment) { |
| 1002 | applyMacroBlockLimits( |
| 1003 | 1 /* minHorizontalBlocks */, 1 /* minVerticalBlocks */, |
| 1004 | maxHorizontalBlocks, maxVerticalBlocks, |
| 1005 | maxBlocks, maxBlocksPerSecond, |
| 1006 | blockWidth, blockHeight, widthAlignment, heightAlignment); |
| 1007 | } |
| 1008 | |
| 1009 | void VideoCapabilities::applyMacroBlockLimits( |
| 1010 | int32_t minHorizontalBlocks, int32_t minVerticalBlocks, |
| 1011 | int32_t maxHorizontalBlocks, int32_t maxVerticalBlocks, |
| 1012 | int32_t maxBlocks, int64_t maxBlocksPerSecond, |
| 1013 | int32_t blockWidth, int32_t blockHeight, |
| 1014 | int32_t widthAlignment, int32_t heightAlignment) { |
| 1015 | applyAlignment(widthAlignment, heightAlignment); |
| 1016 | applyBlockLimits( |
| 1017 | blockWidth, blockHeight, Range((int32_t)1, maxBlocks), |
| 1018 | Range((int64_t)1, maxBlocksPerSecond), |
| 1019 | Range(Rational(1, maxVerticalBlocks), Rational(maxHorizontalBlocks, 1))); |
| 1020 | mHorizontalBlockRange = |
| 1021 | mHorizontalBlockRange.intersect( |
| 1022 | divUp(minHorizontalBlocks, (mBlockWidth / blockWidth)), |
| 1023 | maxHorizontalBlocks / (mBlockWidth / blockWidth)); |
| 1024 | mVerticalBlockRange = |
| 1025 | mVerticalBlockRange.intersect( |
| 1026 | divUp(minVerticalBlocks, (mBlockHeight / blockHeight)), |
| 1027 | maxVerticalBlocks / (mBlockHeight / blockHeight)); |
| 1028 | } |
| 1029 | |
| 1030 | void VideoCapabilities::applyLevelLimits() { |
| 1031 | int64_t maxBlocksPerSecond = 0; |
| 1032 | int32_t maxBlocks = 0; |
| 1033 | int32_t maxBps = 0; |
| 1034 | int32_t maxDPBBlocks = 0; |
| 1035 | |
| 1036 | int errors = ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1037 | const char *mediaType = mMediaType.c_str(); |
| 1038 | if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_AVC)) { |
| 1039 | maxBlocks = 99; |
| 1040 | maxBlocksPerSecond = 1485; |
| 1041 | maxBps = 64000; |
| 1042 | maxDPBBlocks = 396; |
| 1043 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1044 | int32_t MBPS = 0, FS = 0, BR = 0, DPB = 0; |
| 1045 | bool supported = true; |
| 1046 | switch (profileLevel.mLevel) { |
| 1047 | case AVCLevel1: |
| 1048 | MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; |
| 1049 | case AVCLevel1b: |
| 1050 | MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; |
| 1051 | case AVCLevel11: |
| 1052 | MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; |
| 1053 | case AVCLevel12: |
| 1054 | MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; |
| 1055 | case AVCLevel13: |
| 1056 | MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; |
| 1057 | case AVCLevel2: |
| 1058 | MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; |
| 1059 | case AVCLevel21: |
| 1060 | MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; |
| 1061 | case AVCLevel22: |
| 1062 | MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; |
| 1063 | case AVCLevel3: |
| 1064 | MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; |
| 1065 | case AVCLevel31: |
| 1066 | MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; |
| 1067 | case AVCLevel32: |
| 1068 | MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; |
| 1069 | case AVCLevel4: |
| 1070 | MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; |
| 1071 | case AVCLevel41: |
| 1072 | MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; |
| 1073 | case AVCLevel42: |
| 1074 | MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; |
| 1075 | case AVCLevel5: |
| 1076 | MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; |
| 1077 | case AVCLevel51: |
| 1078 | MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; |
| 1079 | case AVCLevel52: |
| 1080 | MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; |
| 1081 | case AVCLevel6: |
| 1082 | MBPS = 4177920; FS = 139264; BR = 240000; DPB = 696320; break; |
| 1083 | case AVCLevel61: |
| 1084 | MBPS = 8355840; FS = 139264; BR = 480000; DPB = 696320; break; |
| 1085 | case AVCLevel62: |
| 1086 | MBPS = 16711680; FS = 139264; BR = 800000; DPB = 696320; break; |
| 1087 | default: |
| 1088 | ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| 1089 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1090 | } |
| 1091 | switch (profileLevel.mProfile) { |
| 1092 | case AVCProfileConstrainedHigh: |
| 1093 | case AVCProfileHigh: |
| 1094 | BR *= 1250; break; |
| 1095 | case AVCProfileHigh10: |
| 1096 | BR *= 3000; break; |
| 1097 | case AVCProfileExtended: |
| 1098 | case AVCProfileHigh422: |
| 1099 | case AVCProfileHigh444: |
| 1100 | ALOGW("Unsupported profile %d for %s", profileLevel.mProfile, mediaType); |
| 1101 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1102 | supported = false; |
| 1103 | FALLTHROUGH_INTENDED; |
| 1104 | // fall through - treat as base profile |
| 1105 | case AVCProfileConstrainedBaseline: |
| 1106 | FALLTHROUGH_INTENDED; |
| 1107 | case AVCProfileBaseline: |
| 1108 | FALLTHROUGH_INTENDED; |
| 1109 | case AVCProfileMain: |
| 1110 | BR *= 1000; break; |
| 1111 | default: |
| 1112 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1113 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1114 | BR *= 1000; |
| 1115 | } |
| 1116 | if (supported) { |
| 1117 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1118 | } |
| 1119 | maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| 1120 | maxBlocks = std::max(FS, maxBlocks); |
| 1121 | maxBps = std::max(BR, maxBps); |
| 1122 | maxDPBBlocks = std::max(maxDPBBlocks, DPB); |
| 1123 | } |
| 1124 | |
| 1125 | int32_t maxLengthInBlocks = (int32_t)(std::sqrt(8 * maxBlocks)); |
| 1126 | applyMacroBlockLimits( |
| 1127 | maxLengthInBlocks, maxLengthInBlocks, |
| 1128 | maxBlocks, maxBlocksPerSecond, |
| 1129 | 16 /* blockWidth */, 16 /* blockHeight */, |
| 1130 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1131 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_MPEG2)) { |
| 1132 | int32_t maxWidth = 11, maxHeight = 9, maxRate = 15; |
| 1133 | maxBlocks = 99; |
| 1134 | maxBlocksPerSecond = 1485; |
| 1135 | maxBps = 64000; |
| 1136 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1137 | int32_t MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; |
| 1138 | bool supported = true; |
| 1139 | switch (profileLevel.mProfile) { |
| 1140 | case MPEG2ProfileSimple: |
| 1141 | switch (profileLevel.mLevel) { |
| 1142 | case MPEG2LevelML: |
| 1143 | FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; |
| 1144 | default: |
| 1145 | ALOGW("Unrecognized profile/level %d/%d for %s", |
| 1146 | profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| 1147 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1148 | } |
| 1149 | break; |
| 1150 | case MPEG2ProfileMain: |
| 1151 | switch (profileLevel.mLevel) { |
| 1152 | case MPEG2LevelLL: |
| 1153 | FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; |
| 1154 | case MPEG2LevelML: |
| 1155 | FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 15000; break; |
| 1156 | case MPEG2LevelH14: |
| 1157 | FR = 60; W = 90; H = 68; MBPS = 183600; FS = 6120; BR = 60000; break; |
| 1158 | case MPEG2LevelHL: |
| 1159 | FR = 60; W = 120; H = 68; MBPS = 244800; FS = 8160; BR = 80000; break; |
| 1160 | case MPEG2LevelHP: |
| 1161 | FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; |
| 1162 | default: |
| 1163 | ALOGW("Unrecognized profile/level %d / %d for %s", |
| 1164 | profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| 1165 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1166 | } |
| 1167 | break; |
| 1168 | case MPEG2Profile422: |
| 1169 | case MPEG2ProfileSNR: |
| 1170 | case MPEG2ProfileSpatial: |
| 1171 | case MPEG2ProfileHigh: |
| 1172 | ALOGW("Unsupported profile %d for %s", profileLevel.mProfile, mediaType); |
| 1173 | errors |= ERROR_CAPABILITIES_UNSUPPORTED; |
| 1174 | supported = false; |
| 1175 | break; |
| 1176 | default: |
| 1177 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1178 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1179 | } |
| 1180 | if (supported) { |
| 1181 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1182 | } |
| 1183 | maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| 1184 | maxBlocks = std::max(FS, maxBlocks); |
| 1185 | maxBps = std::max(BR * 1000, maxBps); |
| 1186 | maxWidth = std::max(W, maxWidth); |
| 1187 | maxHeight = std::max(H, maxHeight); |
| 1188 | maxRate = std::max(FR, maxRate); |
| 1189 | } |
| 1190 | applyMacroBlockLimits(maxWidth, maxHeight, |
| 1191 | maxBlocks, maxBlocksPerSecond, |
| 1192 | 16 /* blockWidth */, 16 /* blockHeight */, |
| 1193 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1194 | mFrameRateRange = mFrameRateRange.intersect(12, maxRate); |
| 1195 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_MPEG4)) { |
| 1196 | int32_t maxWidth = 11, maxHeight = 9, maxRate = 15; |
| 1197 | maxBlocks = 99; |
| 1198 | maxBlocksPerSecond = 1485; |
| 1199 | maxBps = 64000; |
| 1200 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1201 | int32_t MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; |
| 1202 | bool strict = false; // true: W, H and FR are individual max limits |
| 1203 | bool supported = true; |
| 1204 | switch (profileLevel.mProfile) { |
| 1205 | case MPEG4ProfileSimple: |
| 1206 | switch (profileLevel.mLevel) { |
| 1207 | case MPEG4Level0: |
| 1208 | strict = true; |
| 1209 | FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; |
| 1210 | case MPEG4Level1: |
| 1211 | FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; |
| 1212 | case MPEG4Level0b: |
| 1213 | strict = true; |
| 1214 | FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; |
| 1215 | case MPEG4Level2: |
| 1216 | FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; |
| 1217 | case MPEG4Level3: |
| 1218 | FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; |
| 1219 | case MPEG4Level4a: |
| 1220 | FR = 30; W = 40; H = 30; MBPS = 36000; FS = 1200; BR = 4000; break; |
| 1221 | case MPEG4Level5: |
| 1222 | FR = 30; W = 45; H = 36; MBPS = 40500; FS = 1620; BR = 8000; break; |
| 1223 | case MPEG4Level6: |
| 1224 | FR = 30; W = 80; H = 45; MBPS = 108000; FS = 3600; BR = 12000; break; |
| 1225 | default: |
| 1226 | ALOGW("Unrecognized profile/level %d/%d for %s", |
| 1227 | profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| 1228 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1229 | } |
| 1230 | break; |
| 1231 | case MPEG4ProfileAdvancedSimple: |
| 1232 | switch (profileLevel.mLevel) { |
| 1233 | case MPEG4Level0: |
| 1234 | case MPEG4Level1: |
| 1235 | FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; |
| 1236 | case MPEG4Level2: |
| 1237 | FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; |
| 1238 | case MPEG4Level3: |
| 1239 | FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; |
| 1240 | case MPEG4Level3b: |
| 1241 | FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 1500; break; |
| 1242 | case MPEG4Level4: |
| 1243 | FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; |
| 1244 | case MPEG4Level5: |
| 1245 | FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; |
| 1246 | default: |
| 1247 | ALOGW("Unrecognized profile/level %d/%d for %s", |
| 1248 | profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| 1249 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1250 | } |
| 1251 | break; |
| 1252 | case MPEG4ProfileMain: // 2-4 |
| 1253 | case MPEG4ProfileNbit: // 2 |
| 1254 | case MPEG4ProfileAdvancedRealTime: // 1-4 |
| 1255 | case MPEG4ProfileCoreScalable: // 1-3 |
| 1256 | case MPEG4ProfileAdvancedCoding: // 1-4 |
| 1257 | case MPEG4ProfileCore: // 1-2 |
| 1258 | case MPEG4ProfileAdvancedCore: // 1-4 |
| 1259 | case MPEG4ProfileSimpleScalable: // 0-2 |
| 1260 | case MPEG4ProfileHybrid: // 1-2 |
| 1261 | |
| 1262 | // Studio profiles are not supported by our codecs. |
| 1263 | |
| 1264 | // Only profiles that can decode simple object types are considered. |
| 1265 | // The following profiles are not able to. |
| 1266 | case MPEG4ProfileBasicAnimated: // 1-2 |
| 1267 | case MPEG4ProfileScalableTexture: // 1 |
| 1268 | case MPEG4ProfileSimpleFace: // 1-2 |
| 1269 | case MPEG4ProfileAdvancedScalable: // 1-3 |
| 1270 | case MPEG4ProfileSimpleFBA: // 1-2 |
| 1271 | ALOGV("Unsupported profile %d for %s", profileLevel.mProfile, mediaType); |
| 1272 | errors |= ERROR_CAPABILITIES_UNSUPPORTED; |
| 1273 | supported = false; |
| 1274 | break; |
| 1275 | default: |
| 1276 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1277 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1278 | } |
| 1279 | if (supported) { |
| 1280 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1281 | } |
| 1282 | maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| 1283 | maxBlocks = std::max(FS, maxBlocks); |
| 1284 | maxBps = std::max(BR * 1000, maxBps); |
| 1285 | if (strict) { |
| 1286 | maxWidth = std::max(W, maxWidth); |
| 1287 | maxHeight = std::max(H, maxHeight); |
| 1288 | maxRate = std::max(FR, maxRate); |
| 1289 | } else { |
| 1290 | // assuming max 60 fps frame rate and 1:2 aspect ratio |
| 1291 | int32_t maxDim = (int32_t)std::sqrt(2 * FS); |
| 1292 | maxWidth = std::max(maxDim, maxWidth); |
| 1293 | maxHeight = std::max(maxDim, maxHeight); |
| 1294 | maxRate = std::max(std::max(FR, 60), maxRate); |
| 1295 | } |
| 1296 | } |
| 1297 | applyMacroBlockLimits(maxWidth, maxHeight, |
| 1298 | maxBlocks, maxBlocksPerSecond, |
| 1299 | 16 /* blockWidth */, 16 /* blockHeight */, |
| 1300 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1301 | mFrameRateRange = mFrameRateRange.intersect(12, maxRate); |
| 1302 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_H263)) { |
| 1303 | int32_t maxWidth = 11, maxHeight = 9, maxRate = 15; |
| 1304 | int32_t minWidth = maxWidth, minHeight = maxHeight; |
| 1305 | int32_t minAlignment = 16; |
| 1306 | maxBlocks = 99; |
| 1307 | maxBlocksPerSecond = 1485; |
| 1308 | maxBps = 64000; |
| 1309 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1310 | int32_t MBPS = 0, BR = 0, FR = 0, W = 0, H = 0, minW = minWidth, minH = minHeight; |
| 1311 | bool strict = false; // true: support only sQCIF, QCIF (maybe CIF) |
| 1312 | switch (profileLevel.mLevel) { |
| 1313 | case H263Level10: |
| 1314 | strict = true; // only supports sQCIF & QCIF |
| 1315 | FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; |
| 1316 | case H263Level20: |
| 1317 | strict = true; // only supports sQCIF, QCIF & CIF |
| 1318 | FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * 15; break; |
| 1319 | case H263Level30: |
| 1320 | strict = true; // only supports sQCIF, QCIF & CIF |
| 1321 | FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; |
| 1322 | case H263Level40: |
| 1323 | strict = true; // only supports sQCIF, QCIF & CIF |
| 1324 | FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; |
| 1325 | case H263Level45: |
| 1326 | // only implies level 10 support |
| 1327 | strict = profileLevel.mProfile == H263ProfileBaseline |
| 1328 | || profileLevel.mProfile == |
| 1329 | H263ProfileBackwardCompatible; |
| 1330 | if (!strict) { |
| 1331 | minW = 1; minH = 1; minAlignment = 4; |
| 1332 | } |
| 1333 | FR = 15; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; |
| 1334 | case H263Level50: |
| 1335 | // only supports 50fps for H > 15 |
| 1336 | minW = 1; minH = 1; minAlignment = 4; |
| 1337 | FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; |
| 1338 | case H263Level60: |
| 1339 | // only supports 50fps for H > 15 |
| 1340 | minW = 1; minH = 1; minAlignment = 4; |
| 1341 | FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; |
| 1342 | case H263Level70: |
| 1343 | // only supports 50fps for H > 30 |
| 1344 | minW = 1; minH = 1; minAlignment = 4; |
| 1345 | FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; |
| 1346 | default: |
| 1347 | ALOGW("Unrecognized profile/level %d/%d for %s", |
| 1348 | profileLevel.mProfile, profileLevel.mLevel, mediaType); |
| 1349 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1350 | } |
| 1351 | switch (profileLevel.mProfile) { |
| 1352 | case H263ProfileBackwardCompatible: |
| 1353 | case H263ProfileBaseline: |
| 1354 | case H263ProfileH320Coding: |
| 1355 | case H263ProfileHighCompression: |
| 1356 | case H263ProfileHighLatency: |
| 1357 | case H263ProfileInterlace: |
| 1358 | case H263ProfileInternet: |
| 1359 | case H263ProfileISWV2: |
| 1360 | case H263ProfileISWV3: |
| 1361 | break; |
| 1362 | default: |
| 1363 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1364 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1365 | } |
| 1366 | if (strict) { |
| 1367 | // Strict levels define sub-QCIF min size and enumerated sizes. We cannot |
| 1368 | // express support for "only sQCIF & QCIF (& CIF)" using VideoCapabilities |
| 1369 | // but we can express "only QCIF (& CIF)", so set minimume size at QCIF. |
| 1370 | // minW = 8; minH = 6; |
| 1371 | minW = 11; minH = 9; |
| 1372 | } else { |
| 1373 | // any support for non-strict levels (including unrecognized profiles or |
| 1374 | // levels) allow custom frame size support beyond supported limits |
| 1375 | // (other than bitrate) |
| 1376 | mAllowMbOverride = true; |
| 1377 | } |
| 1378 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1379 | maxBlocksPerSecond = std::max((int64_t)MBPS, maxBlocksPerSecond); |
| 1380 | maxBlocks = std::max(W * H, maxBlocks); |
| 1381 | maxBps = std::max(BR * 64000, maxBps); |
| 1382 | maxWidth = std::max(W, maxWidth); |
| 1383 | maxHeight = std::max(H, maxHeight); |
| 1384 | maxRate = std::max(FR, maxRate); |
| 1385 | minWidth = std::min(minW, minWidth); |
| 1386 | minHeight = std::min(minH, minHeight); |
| 1387 | } |
| 1388 | // unless we encountered custom frame size support, limit size to QCIF and CIF |
| 1389 | // using aspect ratio. |
| 1390 | if (!mAllowMbOverride) { |
| 1391 | mBlockAspectRatioRange = |
| 1392 | Range(Rational(11, 9), Rational(11, 9)); |
| 1393 | } |
| 1394 | applyMacroBlockLimits( |
| 1395 | minWidth, minHeight, |
| 1396 | maxWidth, maxHeight, |
| 1397 | maxBlocks, maxBlocksPerSecond, |
| 1398 | 16 /* blockWidth */, 16 /* blockHeight */, |
| 1399 | minAlignment /* widthAlignment */, minAlignment /* heightAlignment */); |
| 1400 | mFrameRateRange = Range(1, maxRate); |
| 1401 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_VP8)) { |
| 1402 | maxBlocks = INT_MAX; |
| 1403 | maxBlocksPerSecond = INT_MAX; |
| 1404 | |
| 1405 | // TODO: set to 100Mbps for now, need a number for VP8 |
| 1406 | maxBps = 100000000; |
| 1407 | |
| 1408 | // profile levels are not indicative for VPx, but verify |
| 1409 | // them nonetheless |
| 1410 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1411 | switch (profileLevel.mLevel) { |
| 1412 | case VP8Level_Version0: |
| 1413 | case VP8Level_Version1: |
| 1414 | case VP8Level_Version2: |
| 1415 | case VP8Level_Version3: |
| 1416 | break; |
| 1417 | default: |
| 1418 | ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| 1419 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1420 | } |
| 1421 | switch (profileLevel.mProfile) { |
| 1422 | case VP8ProfileMain: |
| 1423 | break; |
| 1424 | default: |
| 1425 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1426 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1427 | } |
| 1428 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1429 | } |
| 1430 | |
| 1431 | const int32_t blockSize = 16; |
| 1432 | applyMacroBlockLimits(SHRT_MAX, SHRT_MAX, |
| 1433 | maxBlocks, maxBlocksPerSecond, blockSize, blockSize, |
| 1434 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1435 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_VP9)) { |
| 1436 | maxBlocksPerSecond = 829440; |
| 1437 | maxBlocks = 36864; |
| 1438 | maxBps = 200000; |
| 1439 | int32_t maxDim = 512; |
| 1440 | |
| 1441 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1442 | int64_t SR = 0; // luma sample rate |
| 1443 | int32_t FS = 0; // luma picture size |
| 1444 | int32_t BR = 0; // bit rate kbps |
| 1445 | int32_t D = 0; // luma dimension |
| 1446 | switch (profileLevel.mLevel) { |
| 1447 | case VP9Level1: |
| 1448 | SR = 829440; FS = 36864; BR = 200; D = 512; break; |
| 1449 | case VP9Level11: |
| 1450 | SR = 2764800; FS = 73728; BR = 800; D = 768; break; |
| 1451 | case VP9Level2: |
| 1452 | SR = 4608000; FS = 122880; BR = 1800; D = 960; break; |
| 1453 | case VP9Level21: |
| 1454 | SR = 9216000; FS = 245760; BR = 3600; D = 1344; break; |
| 1455 | case VP9Level3: |
| 1456 | SR = 20736000; FS = 552960; BR = 7200; D = 2048; break; |
| 1457 | case VP9Level31: |
| 1458 | SR = 36864000; FS = 983040; BR = 12000; D = 2752; break; |
| 1459 | case VP9Level4: |
| 1460 | SR = 83558400; FS = 2228224; BR = 18000; D = 4160; break; |
| 1461 | case VP9Level41: |
| 1462 | SR = 160432128; FS = 2228224; BR = 30000; D = 4160; break; |
| 1463 | case VP9Level5: |
| 1464 | SR = 311951360; FS = 8912896; BR = 60000; D = 8384; break; |
| 1465 | case VP9Level51: |
| 1466 | SR = 588251136; FS = 8912896; BR = 120000; D = 8384; break; |
| 1467 | case VP9Level52: |
| 1468 | SR = 1176502272; FS = 8912896; BR = 180000; D = 8384; break; |
| 1469 | case VP9Level6: |
| 1470 | SR = 1176502272; FS = 35651584; BR = 180000; D = 16832; break; |
| 1471 | case VP9Level61: |
| 1472 | SR = 2353004544L; FS = 35651584; BR = 240000; D = 16832; break; |
| 1473 | case VP9Level62: |
| 1474 | SR = 4706009088L; FS = 35651584; BR = 480000; D = 16832; break; |
| 1475 | default: |
| 1476 | ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| 1477 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1478 | } |
| 1479 | switch (profileLevel.mProfile) { |
| 1480 | case VP9Profile0: |
| 1481 | case VP9Profile1: |
| 1482 | case VP9Profile2: |
| 1483 | case VP9Profile3: |
| 1484 | case VP9Profile2HDR: |
| 1485 | case VP9Profile3HDR: |
| 1486 | case VP9Profile2HDR10Plus: |
| 1487 | case VP9Profile3HDR10Plus: |
| 1488 | break; |
| 1489 | default: |
| 1490 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1491 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1492 | } |
| 1493 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1494 | maxBlocksPerSecond = std::max(SR, maxBlocksPerSecond); |
| 1495 | maxBlocks = std::max(FS, maxBlocks); |
| 1496 | maxBps = std::max(BR * 1000, maxBps); |
| 1497 | maxDim = std::max(D, maxDim); |
| 1498 | } |
| 1499 | |
| 1500 | const int32_t blockSize = 8; |
| 1501 | int32_t maxLengthInBlocks = divUp(maxDim, blockSize); |
| 1502 | maxBlocks = divUp(maxBlocks, blockSize * blockSize); |
| 1503 | maxBlocksPerSecond = divUp(maxBlocksPerSecond, blockSize * (int64_t)blockSize); |
| 1504 | |
| 1505 | applyMacroBlockLimits( |
| 1506 | maxLengthInBlocks, maxLengthInBlocks, |
| 1507 | maxBlocks, maxBlocksPerSecond, |
| 1508 | blockSize, blockSize, |
| 1509 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1510 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_HEVC)) { |
| 1511 | // CTBs are at least 8x8 so use 8x8 block size |
| 1512 | maxBlocks = 36864 >> 6; // 192x192 pixels == 576 8x8 blocks |
| 1513 | maxBlocksPerSecond = maxBlocks * 15; |
| 1514 | maxBps = 128000; |
| 1515 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1516 | double FR = 0; |
| 1517 | int32_t FS = 0, BR = 0; |
| 1518 | switch (profileLevel.mLevel) { |
| 1519 | /* The HEVC spec talks only in a very convoluted manner about the |
| 1520 | existence of levels 1-3.1 for High tier, which could also be |
| 1521 | understood as 'decoders and encoders should treat these levels |
| 1522 | as if they were Main tier', so we do that. */ |
| 1523 | case HEVCMainTierLevel1: |
| 1524 | case HEVCHighTierLevel1: |
| 1525 | FR = 15; FS = 36864; BR = 128; break; |
| 1526 | case HEVCMainTierLevel2: |
| 1527 | case HEVCHighTierLevel2: |
| 1528 | FR = 30; FS = 122880; BR = 1500; break; |
| 1529 | case HEVCMainTierLevel21: |
| 1530 | case HEVCHighTierLevel21: |
| 1531 | FR = 30; FS = 245760; BR = 3000; break; |
| 1532 | case HEVCMainTierLevel3: |
| 1533 | case HEVCHighTierLevel3: |
| 1534 | FR = 30; FS = 552960; BR = 6000; break; |
| 1535 | case HEVCMainTierLevel31: |
| 1536 | case HEVCHighTierLevel31: |
| 1537 | FR = 33.75; FS = 983040; BR = 10000; break; |
| 1538 | case HEVCMainTierLevel4: |
| 1539 | FR = 30; FS = 2228224; BR = 12000; break; |
| 1540 | case HEVCHighTierLevel4: |
| 1541 | FR = 30; FS = 2228224; BR = 30000; break; |
| 1542 | case HEVCMainTierLevel41: |
| 1543 | FR = 60; FS = 2228224; BR = 20000; break; |
| 1544 | case HEVCHighTierLevel41: |
| 1545 | FR = 60; FS = 2228224; BR = 50000; break; |
| 1546 | case HEVCMainTierLevel5: |
| 1547 | FR = 30; FS = 8912896; BR = 25000; break; |
| 1548 | case HEVCHighTierLevel5: |
| 1549 | FR = 30; FS = 8912896; BR = 100000; break; |
| 1550 | case HEVCMainTierLevel51: |
| 1551 | FR = 60; FS = 8912896; BR = 40000; break; |
| 1552 | case HEVCHighTierLevel51: |
| 1553 | FR = 60; FS = 8912896; BR = 160000; break; |
| 1554 | case HEVCMainTierLevel52: |
| 1555 | FR = 120; FS = 8912896; BR = 60000; break; |
| 1556 | case HEVCHighTierLevel52: |
| 1557 | FR = 120; FS = 8912896; BR = 240000; break; |
| 1558 | case HEVCMainTierLevel6: |
| 1559 | FR = 30; FS = 35651584; BR = 60000; break; |
| 1560 | case HEVCHighTierLevel6: |
| 1561 | FR = 30; FS = 35651584; BR = 240000; break; |
| 1562 | case HEVCMainTierLevel61: |
| 1563 | FR = 60; FS = 35651584; BR = 120000; break; |
| 1564 | case HEVCHighTierLevel61: |
| 1565 | FR = 60; FS = 35651584; BR = 480000; break; |
| 1566 | case HEVCMainTierLevel62: |
| 1567 | FR = 120; FS = 35651584; BR = 240000; break; |
| 1568 | case HEVCHighTierLevel62: |
| 1569 | FR = 120; FS = 35651584; BR = 800000; break; |
| 1570 | default: |
| 1571 | ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| 1572 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1573 | } |
| 1574 | switch (profileLevel.mProfile) { |
| 1575 | case HEVCProfileMain: |
| 1576 | case HEVCProfileMain10: |
| 1577 | case HEVCProfileMainStill: |
| 1578 | case HEVCProfileMain10HDR10: |
| 1579 | case HEVCProfileMain10HDR10Plus: |
| 1580 | break; |
| 1581 | default: |
| 1582 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1583 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1584 | } |
| 1585 | |
| 1586 | /* DPB logic: |
| 1587 | if (width * height <= FS / 4) DPB = 16; |
| 1588 | else if (width * height <= FS / 2) DPB = 12; |
| 1589 | else if (width * height <= FS * 0.75) DPB = 8; |
| 1590 | else DPB = 6; |
| 1591 | */ |
| 1592 | |
| 1593 | FS >>= 6; // convert pixels to blocks |
| 1594 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1595 | maxBlocksPerSecond = std::max((int64_t)(FR * FS), maxBlocksPerSecond); |
| 1596 | maxBlocks = std::max(FS, maxBlocks); |
| 1597 | maxBps = std::max(1000 * BR, maxBps); |
| 1598 | } |
| 1599 | |
| 1600 | int32_t maxLengthInBlocks = (int32_t)(std::sqrt(8 * maxBlocks)); |
| 1601 | applyMacroBlockLimits( |
| 1602 | maxLengthInBlocks, maxLengthInBlocks, |
| 1603 | maxBlocks, maxBlocksPerSecond, |
| 1604 | 8 /* blockWidth */, 8 /* blockHeight */, |
| 1605 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1606 | } else if (base::EqualsIgnoreCase(mMediaType, MIMETYPE_VIDEO_AV1)) { |
| 1607 | maxBlocksPerSecond = 829440; |
| 1608 | maxBlocks = 36864; |
| 1609 | maxBps = 200000; |
| 1610 | int32_t maxDim = 512; |
| 1611 | |
| 1612 | // Sample rate, Picture Size, Bit rate and luma dimension for AV1 Codec, |
| 1613 | // corresponding to the definitions in |
| 1614 | // "AV1 Bitstream & Decoding Process Specification", Annex A |
| 1615 | // found at https://aomedia.org/av1-bitstream-and-decoding-process-specification/ |
| 1616 | for (ProfileLevel profileLevel: mProfileLevels) { |
| 1617 | int64_t SR = 0; // luma sample rate |
| 1618 | int32_t FS = 0; // luma picture size |
| 1619 | int32_t BR = 0; // bit rate kbps |
| 1620 | int32_t D = 0; // luma D |
| 1621 | switch (profileLevel.mLevel) { |
| 1622 | case AV1Level2: |
| 1623 | SR = 5529600; FS = 147456; BR = 1500; D = 2048; break; |
| 1624 | case AV1Level21: |
| 1625 | case AV1Level22: |
| 1626 | case AV1Level23: |
| 1627 | SR = 10454400; FS = 278784; BR = 3000; D = 2816; break; |
| 1628 | |
| 1629 | case AV1Level3: |
| 1630 | SR = 24969600; FS = 665856; BR = 6000; D = 4352; break; |
| 1631 | case AV1Level31: |
| 1632 | case AV1Level32: |
| 1633 | case AV1Level33: |
| 1634 | SR = 39938400; FS = 1065024; BR = 10000; D = 5504; break; |
| 1635 | |
| 1636 | case AV1Level4: |
| 1637 | SR = 77856768; FS = 2359296; BR = 12000; D = 6144; break; |
| 1638 | case AV1Level41: |
| 1639 | case AV1Level42: |
| 1640 | case AV1Level43: |
| 1641 | SR = 155713536; FS = 2359296; BR = 20000; D = 6144; break; |
| 1642 | |
| 1643 | case AV1Level5: |
| 1644 | SR = 273715200; FS = 8912896; BR = 30000; D = 8192; break; |
| 1645 | case AV1Level51: |
| 1646 | SR = 547430400; FS = 8912896; BR = 40000; D = 8192; break; |
| 1647 | case AV1Level52: |
| 1648 | SR = 1094860800; FS = 8912896; BR = 60000; D = 8192; break; |
| 1649 | case AV1Level53: |
| 1650 | SR = 1176502272; FS = 8912896; BR = 60000; D = 8192; break; |
| 1651 | |
| 1652 | case AV1Level6: |
| 1653 | SR = 1176502272; FS = 35651584; BR = 60000; D = 16384; break; |
| 1654 | case AV1Level61: |
| 1655 | SR = 2189721600L; FS = 35651584; BR = 100000; D = 16384; break; |
| 1656 | case AV1Level62: |
| 1657 | SR = 4379443200L; FS = 35651584; BR = 160000; D = 16384; break; |
| 1658 | case AV1Level63: |
| 1659 | SR = 4706009088L; FS = 35651584; BR = 160000; D = 16384; break; |
| 1660 | |
| 1661 | default: |
| 1662 | ALOGW("Unrecognized level %d for %s", profileLevel.mLevel, mediaType); |
| 1663 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1664 | } |
| 1665 | switch (profileLevel.mProfile) { |
| 1666 | case AV1ProfileMain8: |
| 1667 | case AV1ProfileMain10: |
| 1668 | case AV1ProfileMain10HDR10: |
| 1669 | case AV1ProfileMain10HDR10Plus: |
| 1670 | break; |
| 1671 | default: |
| 1672 | ALOGW("Unrecognized profile %d for %s", profileLevel.mProfile, mediaType); |
| 1673 | errors |= ERROR_CAPABILITIES_UNRECOGNIZED; |
| 1674 | } |
| 1675 | errors &= ~ERROR_CAPABILITIES_NONE_SUPPORTED; |
| 1676 | maxBlocksPerSecond = std::max(SR, maxBlocksPerSecond); |
| 1677 | maxBlocks = std::max(FS, maxBlocks); |
| 1678 | maxBps = std::max(BR * 1000, maxBps); |
| 1679 | maxDim = std::max(D, maxDim); |
| 1680 | } |
| 1681 | |
| 1682 | const int32_t blockSize = 8; |
| 1683 | int32_t maxLengthInBlocks = divUp(maxDim, blockSize); |
| 1684 | maxBlocks = divUp(maxBlocks, blockSize * blockSize); |
| 1685 | maxBlocksPerSecond = divUp(maxBlocksPerSecond, blockSize * (int64_t)blockSize); |
| 1686 | applyMacroBlockLimits( |
| 1687 | maxLengthInBlocks, maxLengthInBlocks, |
| 1688 | maxBlocks, maxBlocksPerSecond, |
| 1689 | blockSize, blockSize, |
| 1690 | 1 /* widthAlignment */, 1 /* heightAlignment */); |
| 1691 | } else { |
| 1692 | ALOGW("Unsupported mime %s", mediaType); |
| 1693 | // using minimal bitrate here. should be overridden by |
| 1694 | // info from media_codecs.xml |
| 1695 | maxBps = 64000; |
| 1696 | errors |= ERROR_CAPABILITIES_UNSUPPORTED; |
| 1697 | } |
| 1698 | mBitrateRange = Range(1, maxBps); |
| 1699 | mError |= errors; |
| 1700 | } |
| 1701 | |
| 1702 | } // namespace android |