Pawin Vongmasa | 6ed07dc | 2017-04-05 06:26:56 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2017, 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 "MediaCodecsXmlParser" |
| 19 | #include <utils/Log.h> |
| 20 | |
| 21 | #include <media/vndk/xmlparser/1.0/MediaCodecsXmlParser.h> |
| 22 | |
| 23 | #include <media/MediaCodecInfo.h> |
| 24 | |
| 25 | #include <media/stagefright/foundation/ADebug.h> |
| 26 | #include <media/stagefright/foundation/AMessage.h> |
| 27 | #include <media/stagefright/foundation/AUtils.h> |
| 28 | #include <media/stagefright/MediaErrors.h> |
| 29 | |
| 30 | #include <sys/stat.h> |
| 31 | |
| 32 | #include <expat.h> |
| 33 | #include <string> |
| 34 | |
| 35 | #define MEDIA_CODECS_CONFIG_FILE_PATH_MAX_LENGTH 256 |
| 36 | |
| 37 | namespace android { |
| 38 | |
| 39 | namespace { // Local variables and functions |
| 40 | |
| 41 | const char *kProfilingResults = |
| 42 | "/data/misc/media/media_codecs_profiling_results.xml"; |
| 43 | |
| 44 | // Treblized media codec list will be located in /odm/etc or /vendor/etc. |
| 45 | const char *kConfigLocationList[] = |
| 46 | {"/odm/etc", "/vendor/etc", "/etc"}; |
| 47 | constexpr int kConfigLocationListSize = |
| 48 | (sizeof(kConfigLocationList) / sizeof(kConfigLocationList[0])); |
| 49 | |
| 50 | bool findMediaCodecListFileFullPath( |
| 51 | const char *file_name, std::string *out_path) { |
| 52 | for (int i = 0; i < kConfigLocationListSize; i++) { |
| 53 | *out_path = std::string(kConfigLocationList[i]) + "/" + file_name; |
| 54 | struct stat file_stat; |
| 55 | if (stat(out_path->c_str(), &file_stat) == 0 && |
| 56 | S_ISREG(file_stat.st_mode)) { |
| 57 | return true; |
| 58 | } |
| 59 | } |
| 60 | return false; |
| 61 | } |
| 62 | |
| 63 | // Find TypeInfo by name. |
| 64 | std::vector<TypeInfo>::iterator findTypeInfo( |
| 65 | CodecInfo &codecInfo, const AString &typeName) { |
| 66 | return std::find_if( |
| 67 | codecInfo.mTypes.begin(), codecInfo.mTypes.end(), |
| 68 | [typeName](const auto &typeInfo) { |
| 69 | return typeInfo.mName == typeName; |
| 70 | }); |
| 71 | } |
| 72 | |
| 73 | // Convert a string into a boolean value. |
| 74 | bool ParseBoolean(const char *s) { |
| 75 | if (!strcasecmp(s, "true") || !strcasecmp(s, "yes") || !strcasecmp(s, "y")) { |
| 76 | return true; |
| 77 | } |
| 78 | char *end; |
| 79 | unsigned long res = strtoul(s, &end, 10); |
| 80 | return *s != '\0' && *end == '\0' && res > 0; |
| 81 | } |
| 82 | |
| 83 | } // unnamed namespace |
| 84 | |
| 85 | MediaCodecsXmlParser::MediaCodecsXmlParser() : |
| 86 | mInitCheck(NO_INIT), |
| 87 | mUpdate(false) { |
| 88 | std::string config_file_path; |
| 89 | if (findMediaCodecListFileFullPath( |
| 90 | "media_codecs.xml", &config_file_path)) { |
| 91 | parseTopLevelXMLFile(config_file_path.c_str(), false); |
| 92 | } else { |
| 93 | mInitCheck = NAME_NOT_FOUND; |
| 94 | } |
| 95 | if (findMediaCodecListFileFullPath( |
| 96 | "media_codecs_performance.xml", &config_file_path)) { |
| 97 | parseTopLevelXMLFile(config_file_path.c_str(), true); |
| 98 | } |
| 99 | parseTopLevelXMLFile(kProfilingResults, true); |
| 100 | } |
| 101 | |
| 102 | void MediaCodecsXmlParser::parseTopLevelXMLFile( |
| 103 | const char *codecs_xml, bool ignore_errors) { |
| 104 | // get href_base |
| 105 | const char *href_base_end = strrchr(codecs_xml, '/'); |
| 106 | if (href_base_end != NULL) { |
| 107 | mHrefBase = AString(codecs_xml, href_base_end - codecs_xml + 1); |
| 108 | } |
| 109 | |
| 110 | mInitCheck = OK; // keeping this here for safety |
| 111 | mCurrentSection = SECTION_TOPLEVEL; |
| 112 | mDepth = 0; |
| 113 | |
| 114 | parseXMLFile(codecs_xml); |
| 115 | |
| 116 | if (mInitCheck != OK) { |
| 117 | if (ignore_errors) { |
| 118 | mInitCheck = OK; |
| 119 | return; |
| 120 | } |
| 121 | mCodecInfos.clear(); |
| 122 | return; |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | MediaCodecsXmlParser::~MediaCodecsXmlParser() { |
| 127 | } |
| 128 | |
| 129 | status_t MediaCodecsXmlParser::initCheck() const { |
| 130 | return mInitCheck; |
| 131 | } |
| 132 | |
| 133 | void MediaCodecsXmlParser::parseXMLFile(const char *path) { |
| 134 | FILE *file = fopen(path, "r"); |
| 135 | |
| 136 | if (file == NULL) { |
| 137 | ALOGW("unable to open media codecs configuration xml file: %s", path); |
| 138 | mInitCheck = NAME_NOT_FOUND; |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | ALOGV("Start parsing %s", path); |
| 143 | XML_Parser parser = ::XML_ParserCreate(NULL); |
| 144 | CHECK(parser != NULL); |
| 145 | |
| 146 | ::XML_SetUserData(parser, this); |
| 147 | ::XML_SetElementHandler( |
| 148 | parser, StartElementHandlerWrapper, EndElementHandlerWrapper); |
| 149 | |
| 150 | const int BUFF_SIZE = 512; |
| 151 | while (mInitCheck == OK) { |
| 152 | void *buff = ::XML_GetBuffer(parser, BUFF_SIZE); |
| 153 | if (buff == NULL) { |
| 154 | ALOGE("failed in call to XML_GetBuffer()"); |
| 155 | mInitCheck = UNKNOWN_ERROR; |
| 156 | break; |
| 157 | } |
| 158 | |
| 159 | int bytes_read = ::fread(buff, 1, BUFF_SIZE, file); |
| 160 | if (bytes_read < 0) { |
| 161 | ALOGE("failed in call to read"); |
| 162 | mInitCheck = ERROR_IO; |
| 163 | break; |
| 164 | } |
| 165 | |
| 166 | XML_Status status = ::XML_ParseBuffer(parser, bytes_read, bytes_read == 0); |
| 167 | if (status != XML_STATUS_OK) { |
| 168 | ALOGE("malformed (%s)", ::XML_ErrorString(::XML_GetErrorCode(parser))); |
| 169 | mInitCheck = ERROR_MALFORMED; |
| 170 | break; |
| 171 | } |
| 172 | |
| 173 | if (bytes_read == 0) { |
| 174 | break; |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | ::XML_ParserFree(parser); |
| 179 | |
| 180 | fclose(file); |
| 181 | file = NULL; |
| 182 | } |
| 183 | |
| 184 | // static |
| 185 | void MediaCodecsXmlParser::StartElementHandlerWrapper( |
| 186 | void *me, const char *name, const char **attrs) { |
| 187 | static_cast<MediaCodecsXmlParser *>(me)->startElementHandler(name, attrs); |
| 188 | } |
| 189 | |
| 190 | // static |
| 191 | void MediaCodecsXmlParser::EndElementHandlerWrapper(void *me, const char *name) { |
| 192 | static_cast<MediaCodecsXmlParser *>(me)->endElementHandler(name); |
| 193 | } |
| 194 | |
| 195 | status_t MediaCodecsXmlParser::includeXMLFile(const char **attrs) { |
| 196 | const char *href = NULL; |
| 197 | size_t i = 0; |
| 198 | while (attrs[i] != NULL) { |
| 199 | if (!strcmp(attrs[i], "href")) { |
| 200 | if (attrs[i + 1] == NULL) { |
| 201 | return -EINVAL; |
| 202 | } |
| 203 | href = attrs[i + 1]; |
| 204 | ++i; |
| 205 | } else { |
| 206 | ALOGE("includeXMLFile: unrecognized attribute: %s", attrs[i]); |
| 207 | return -EINVAL; |
| 208 | } |
| 209 | ++i; |
| 210 | } |
| 211 | |
| 212 | // For security reasons and for simplicity, file names can only contain |
| 213 | // [a-zA-Z0-9_.] and must start with media_codecs_ and end with .xml |
| 214 | for (i = 0; href[i] != '\0'; i++) { |
| 215 | if (href[i] == '.' || href[i] == '_' || |
| 216 | (href[i] >= '0' && href[i] <= '9') || |
| 217 | (href[i] >= 'A' && href[i] <= 'Z') || |
| 218 | (href[i] >= 'a' && href[i] <= 'z')) { |
| 219 | continue; |
| 220 | } |
| 221 | ALOGE("invalid include file name: %s", href); |
| 222 | return -EINVAL; |
| 223 | } |
| 224 | |
| 225 | AString filename = href; |
| 226 | if (!filename.startsWith("media_codecs_") || |
| 227 | !filename.endsWith(".xml")) { |
| 228 | ALOGE("invalid include file name: %s", href); |
| 229 | return -EINVAL; |
| 230 | } |
| 231 | filename.insert(mHrefBase, 0); |
| 232 | |
| 233 | parseXMLFile(filename.c_str()); |
| 234 | return mInitCheck; |
| 235 | } |
| 236 | |
| 237 | void MediaCodecsXmlParser::startElementHandler( |
| 238 | const char *name, const char **attrs) { |
| 239 | if (mInitCheck != OK) { |
| 240 | return; |
| 241 | } |
| 242 | |
| 243 | bool inType = true; |
| 244 | |
| 245 | if (!strcmp(name, "Include")) { |
| 246 | mInitCheck = includeXMLFile(attrs); |
| 247 | if (mInitCheck == OK) { |
| 248 | mPastSections.push(mCurrentSection); |
| 249 | mCurrentSection = SECTION_INCLUDE; |
| 250 | } |
| 251 | ++mDepth; |
| 252 | return; |
| 253 | } |
| 254 | |
| 255 | switch (mCurrentSection) { |
| 256 | case SECTION_TOPLEVEL: |
| 257 | { |
| 258 | if (!strcmp(name, "Decoders")) { |
| 259 | mCurrentSection = SECTION_DECODERS; |
| 260 | } else if (!strcmp(name, "Encoders")) { |
| 261 | mCurrentSection = SECTION_ENCODERS; |
| 262 | } else if (!strcmp(name, "Settings")) { |
| 263 | mCurrentSection = SECTION_SETTINGS; |
| 264 | } |
| 265 | break; |
| 266 | } |
| 267 | |
| 268 | case SECTION_SETTINGS: |
| 269 | { |
| 270 | if (!strcmp(name, "Setting")) { |
| 271 | mInitCheck = addSettingFromAttributes(attrs); |
| 272 | } |
| 273 | break; |
| 274 | } |
| 275 | |
| 276 | case SECTION_DECODERS: |
| 277 | { |
| 278 | if (!strcmp(name, "MediaCodec")) { |
| 279 | mInitCheck = |
| 280 | addMediaCodecFromAttributes(false /* encoder */, attrs); |
| 281 | |
| 282 | mCurrentSection = SECTION_DECODER; |
| 283 | } |
| 284 | break; |
| 285 | } |
| 286 | |
| 287 | case SECTION_ENCODERS: |
| 288 | { |
| 289 | if (!strcmp(name, "MediaCodec")) { |
| 290 | mInitCheck = |
| 291 | addMediaCodecFromAttributes(true /* encoder */, attrs); |
| 292 | |
| 293 | mCurrentSection = SECTION_ENCODER; |
| 294 | } |
| 295 | break; |
| 296 | } |
| 297 | |
| 298 | case SECTION_DECODER: |
| 299 | case SECTION_ENCODER: |
| 300 | { |
| 301 | if (!strcmp(name, "Quirk")) { |
| 302 | mInitCheck = addQuirk(attrs); |
| 303 | } else if (!strcmp(name, "Type")) { |
| 304 | mInitCheck = addTypeFromAttributes(attrs, (mCurrentSection == SECTION_ENCODER)); |
| 305 | mCurrentSection = |
| 306 | (mCurrentSection == SECTION_DECODER |
| 307 | ? SECTION_DECODER_TYPE : SECTION_ENCODER_TYPE); |
| 308 | } |
| 309 | } |
| 310 | inType = false; |
| 311 | // fall through |
| 312 | |
| 313 | case SECTION_DECODER_TYPE: |
| 314 | case SECTION_ENCODER_TYPE: |
| 315 | { |
| 316 | // ignore limits and features specified outside of type |
| 317 | bool outside = !inType && mCurrentType == mCodecInfos[mCurrentName].mTypes.end(); |
| 318 | if (outside && (!strcmp(name, "Limit") || !strcmp(name, "Feature"))) { |
| 319 | ALOGW("ignoring %s specified outside of a Type", name); |
| 320 | } else if (!strcmp(name, "Limit")) { |
| 321 | mInitCheck = addLimit(attrs); |
| 322 | } else if (!strcmp(name, "Feature")) { |
| 323 | mInitCheck = addFeature(attrs); |
| 324 | } |
| 325 | break; |
| 326 | } |
| 327 | |
| 328 | default: |
| 329 | break; |
| 330 | } |
| 331 | |
| 332 | ++mDepth; |
| 333 | } |
| 334 | |
| 335 | void MediaCodecsXmlParser::endElementHandler(const char *name) { |
| 336 | if (mInitCheck != OK) { |
| 337 | return; |
| 338 | } |
| 339 | |
| 340 | switch (mCurrentSection) { |
| 341 | case SECTION_SETTINGS: |
| 342 | { |
| 343 | if (!strcmp(name, "Settings")) { |
| 344 | mCurrentSection = SECTION_TOPLEVEL; |
| 345 | } |
| 346 | break; |
| 347 | } |
| 348 | |
| 349 | case SECTION_DECODERS: |
| 350 | { |
| 351 | if (!strcmp(name, "Decoders")) { |
| 352 | mCurrentSection = SECTION_TOPLEVEL; |
| 353 | } |
| 354 | break; |
| 355 | } |
| 356 | |
| 357 | case SECTION_ENCODERS: |
| 358 | { |
| 359 | if (!strcmp(name, "Encoders")) { |
| 360 | mCurrentSection = SECTION_TOPLEVEL; |
| 361 | } |
| 362 | break; |
| 363 | } |
| 364 | |
| 365 | case SECTION_DECODER_TYPE: |
| 366 | case SECTION_ENCODER_TYPE: |
| 367 | { |
| 368 | if (!strcmp(name, "Type")) { |
| 369 | mCurrentSection = |
| 370 | (mCurrentSection == SECTION_DECODER_TYPE |
| 371 | ? SECTION_DECODER : SECTION_ENCODER); |
| 372 | |
| 373 | mCurrentType = mCodecInfos[mCurrentName].mTypes.end(); |
| 374 | } |
| 375 | break; |
| 376 | } |
| 377 | |
| 378 | case SECTION_DECODER: |
| 379 | { |
| 380 | if (!strcmp(name, "MediaCodec")) { |
| 381 | mCurrentSection = SECTION_DECODERS; |
| 382 | mCurrentName.clear(); |
| 383 | } |
| 384 | break; |
| 385 | } |
| 386 | |
| 387 | case SECTION_ENCODER: |
| 388 | { |
| 389 | if (!strcmp(name, "MediaCodec")) { |
| 390 | mCurrentSection = SECTION_ENCODERS; |
| 391 | mCurrentName.clear(); |
| 392 | } |
| 393 | break; |
| 394 | } |
| 395 | |
| 396 | case SECTION_INCLUDE: |
| 397 | { |
| 398 | if (!strcmp(name, "Include") && mPastSections.size() > 0) { |
| 399 | mCurrentSection = mPastSections.top(); |
| 400 | mPastSections.pop(); |
| 401 | } |
| 402 | break; |
| 403 | } |
| 404 | |
| 405 | default: |
| 406 | break; |
| 407 | } |
| 408 | |
| 409 | --mDepth; |
| 410 | } |
| 411 | |
| 412 | status_t MediaCodecsXmlParser::addSettingFromAttributes(const char **attrs) { |
| 413 | const char *name = NULL; |
| 414 | const char *value = NULL; |
| 415 | const char *update = NULL; |
| 416 | |
| 417 | size_t i = 0; |
| 418 | while (attrs[i] != NULL) { |
| 419 | if (!strcmp(attrs[i], "name")) { |
| 420 | if (attrs[i + 1] == NULL) { |
| 421 | ALOGE("addSettingFromAttributes: name is null"); |
| 422 | return -EINVAL; |
| 423 | } |
| 424 | name = attrs[i + 1]; |
| 425 | ++i; |
| 426 | } else if (!strcmp(attrs[i], "value")) { |
| 427 | if (attrs[i + 1] == NULL) { |
| 428 | ALOGE("addSettingFromAttributes: value is null"); |
| 429 | return -EINVAL; |
| 430 | } |
| 431 | value = attrs[i + 1]; |
| 432 | ++i; |
| 433 | } else if (!strcmp(attrs[i], "update")) { |
| 434 | if (attrs[i + 1] == NULL) { |
| 435 | ALOGE("addSettingFromAttributes: update is null"); |
| 436 | return -EINVAL; |
| 437 | } |
| 438 | update = attrs[i + 1]; |
| 439 | ++i; |
| 440 | } else { |
| 441 | ALOGE("addSettingFromAttributes: unrecognized attribute: %s", attrs[i]); |
| 442 | return -EINVAL; |
| 443 | } |
| 444 | |
| 445 | ++i; |
| 446 | } |
| 447 | |
| 448 | if (name == NULL || value == NULL) { |
| 449 | ALOGE("addSettingFromAttributes: name or value unspecified"); |
| 450 | return -EINVAL; |
| 451 | } |
| 452 | |
| 453 | mUpdate = (update != NULL) && ParseBoolean(update); |
| 454 | if (mUpdate != (mGlobalSettings.count(name) > 0)) { |
| 455 | ALOGE("addSettingFromAttributes: updating non-existing setting"); |
| 456 | return -EINVAL; |
| 457 | } |
| 458 | mGlobalSettings[name] = value; |
| 459 | |
| 460 | return OK; |
| 461 | } |
| 462 | |
| 463 | status_t MediaCodecsXmlParser::addMediaCodecFromAttributes( |
| 464 | bool encoder, const char **attrs) { |
| 465 | const char *name = NULL; |
| 466 | const char *type = NULL; |
| 467 | const char *update = NULL; |
| 468 | |
| 469 | size_t i = 0; |
| 470 | while (attrs[i] != NULL) { |
| 471 | if (!strcmp(attrs[i], "name")) { |
| 472 | if (attrs[i + 1] == NULL) { |
| 473 | ALOGE("addMediaCodecFromAttributes: name is null"); |
| 474 | return -EINVAL; |
| 475 | } |
| 476 | name = attrs[i + 1]; |
| 477 | ++i; |
| 478 | } else if (!strcmp(attrs[i], "type")) { |
| 479 | if (attrs[i + 1] == NULL) { |
| 480 | ALOGE("addMediaCodecFromAttributes: type is null"); |
| 481 | return -EINVAL; |
| 482 | } |
| 483 | type = attrs[i + 1]; |
| 484 | ++i; |
| 485 | } else if (!strcmp(attrs[i], "update")) { |
| 486 | if (attrs[i + 1] == NULL) { |
| 487 | ALOGE("addMediaCodecFromAttributes: update is null"); |
| 488 | return -EINVAL; |
| 489 | } |
| 490 | update = attrs[i + 1]; |
| 491 | ++i; |
| 492 | } else { |
| 493 | ALOGE("addMediaCodecFromAttributes: unrecognized attribute: %s", attrs[i]); |
| 494 | return -EINVAL; |
| 495 | } |
| 496 | |
| 497 | ++i; |
| 498 | } |
| 499 | |
| 500 | if (name == NULL) { |
| 501 | ALOGE("addMediaCodecFromAttributes: name not found"); |
| 502 | return -EINVAL; |
| 503 | } |
| 504 | |
| 505 | mUpdate = (update != NULL) && ParseBoolean(update); |
| 506 | if (mUpdate != (mCodecInfos.count(name) > 0)) { |
| 507 | ALOGE("addMediaCodecFromAttributes: updating non-existing codec or vice versa"); |
| 508 | return -EINVAL; |
| 509 | } |
| 510 | |
| 511 | CodecInfo *info = &mCodecInfos[name]; |
| 512 | if (mUpdate) { |
| 513 | // existing codec |
| 514 | mCurrentName = name; |
| 515 | mCurrentType = info->mTypes.begin(); |
| 516 | if (type != NULL) { |
| 517 | // existing type |
| 518 | mCurrentType = findTypeInfo(*info, type); |
| 519 | if (mCurrentType == info->mTypes.end()) { |
| 520 | ALOGE("addMediaCodecFromAttributes: updating non-existing type"); |
| 521 | return -EINVAL; |
| 522 | } |
| 523 | } |
| 524 | } else { |
| 525 | // new codec |
| 526 | mCurrentName = name; |
| 527 | mQuirks[name].clear(); |
| 528 | info->mTypes.clear(); |
| 529 | info->mTypes.emplace_back(); |
| 530 | mCurrentType = --info->mTypes.end(); |
| 531 | mCurrentType->mName = type; |
| 532 | info->mIsEncoder = encoder; |
| 533 | } |
| 534 | |
| 535 | return OK; |
| 536 | } |
| 537 | |
| 538 | status_t MediaCodecsXmlParser::addQuirk(const char **attrs) { |
| 539 | const char *name = NULL; |
| 540 | |
| 541 | size_t i = 0; |
| 542 | while (attrs[i] != NULL) { |
| 543 | if (!strcmp(attrs[i], "name")) { |
| 544 | if (attrs[i + 1] == NULL) { |
| 545 | ALOGE("addQuirk: name is null"); |
| 546 | return -EINVAL; |
| 547 | } |
| 548 | name = attrs[i + 1]; |
| 549 | ++i; |
| 550 | } else { |
| 551 | ALOGE("addQuirk: unrecognized attribute: %s", attrs[i]); |
| 552 | return -EINVAL; |
| 553 | } |
| 554 | |
| 555 | ++i; |
| 556 | } |
| 557 | |
| 558 | if (name == NULL) { |
| 559 | ALOGE("addQuirk: name not found"); |
| 560 | return -EINVAL; |
| 561 | } |
| 562 | |
| 563 | mQuirks[mCurrentName].emplace_back(name); |
| 564 | return OK; |
| 565 | } |
| 566 | |
| 567 | status_t MediaCodecsXmlParser::addTypeFromAttributes(const char **attrs, bool encoder) { |
| 568 | const char *name = NULL; |
| 569 | const char *update = NULL; |
| 570 | |
| 571 | size_t i = 0; |
| 572 | while (attrs[i] != NULL) { |
| 573 | if (!strcmp(attrs[i], "name")) { |
| 574 | if (attrs[i + 1] == NULL) { |
| 575 | ALOGE("addTypeFromAttributes: name is null"); |
| 576 | return -EINVAL; |
| 577 | } |
| 578 | name = attrs[i + 1]; |
| 579 | ++i; |
| 580 | } else if (!strcmp(attrs[i], "update")) { |
| 581 | if (attrs[i + 1] == NULL) { |
| 582 | ALOGE("addTypeFromAttributes: update is null"); |
| 583 | return -EINVAL; |
| 584 | } |
| 585 | update = attrs[i + 1]; |
| 586 | ++i; |
| 587 | } else { |
| 588 | ALOGE("addTypeFromAttributes: unrecognized attribute: %s", attrs[i]); |
| 589 | return -EINVAL; |
| 590 | } |
| 591 | |
| 592 | ++i; |
| 593 | } |
| 594 | |
| 595 | if (name == NULL) { |
| 596 | return -EINVAL; |
| 597 | } |
| 598 | |
| 599 | CodecInfo *info = &mCodecInfos[mCurrentName]; |
| 600 | info->mIsEncoder = encoder; |
| 601 | mCurrentType = findTypeInfo(*info, name); |
| 602 | if (!mUpdate) { |
| 603 | if (mCurrentType != info->mTypes.end()) { |
| 604 | ALOGE("addTypeFromAttributes: re-defining existing type without update"); |
| 605 | return -EINVAL; |
| 606 | } |
| 607 | info->mTypes.emplace_back(); |
| 608 | mCurrentType = --info->mTypes.end(); |
| 609 | } else if (mCurrentType == info->mTypes.end()) { |
| 610 | ALOGE("addTypeFromAttributes: updating non-existing type"); |
| 611 | return -EINVAL; |
| 612 | } |
| 613 | |
| 614 | return OK; |
| 615 | } |
| 616 | |
| 617 | static status_t limitFoundMissingAttr(const AString &name, const char *attr, bool found = true) { |
| 618 | ALOGE("limit '%s' with %s'%s' attribute", name.c_str(), |
| 619 | (found ? "" : "no "), attr); |
| 620 | return -EINVAL; |
| 621 | } |
| 622 | |
| 623 | static status_t limitError(const AString &name, const char *msg) { |
| 624 | ALOGE("limit '%s' %s", name.c_str(), msg); |
| 625 | return -EINVAL; |
| 626 | } |
| 627 | |
| 628 | static status_t limitInvalidAttr(const AString &name, const char *attr, const AString &value) { |
| 629 | ALOGE("limit '%s' with invalid '%s' attribute (%s)", name.c_str(), |
| 630 | attr, value.c_str()); |
| 631 | return -EINVAL; |
| 632 | } |
| 633 | |
| 634 | status_t MediaCodecsXmlParser::addLimit(const char **attrs) { |
| 635 | sp<AMessage> msg = new AMessage(); |
| 636 | |
| 637 | size_t i = 0; |
| 638 | while (attrs[i] != NULL) { |
| 639 | if (attrs[i + 1] == NULL) { |
| 640 | ALOGE("addLimit: limit is not given"); |
| 641 | return -EINVAL; |
| 642 | } |
| 643 | |
| 644 | // attributes with values |
| 645 | if (!strcmp(attrs[i], "name") |
| 646 | || !strcmp(attrs[i], "default") |
| 647 | || !strcmp(attrs[i], "in") |
| 648 | || !strcmp(attrs[i], "max") |
| 649 | || !strcmp(attrs[i], "min") |
| 650 | || !strcmp(attrs[i], "range") |
| 651 | || !strcmp(attrs[i], "ranges") |
| 652 | || !strcmp(attrs[i], "scale") |
| 653 | || !strcmp(attrs[i], "value")) { |
| 654 | msg->setString(attrs[i], attrs[i + 1]); |
| 655 | ++i; |
| 656 | } else { |
| 657 | ALOGE("addLimit: unrecognized limit: %s", attrs[i]); |
| 658 | return -EINVAL; |
| 659 | } |
| 660 | ++i; |
| 661 | } |
| 662 | |
| 663 | AString name; |
| 664 | if (!msg->findString("name", &name)) { |
| 665 | ALOGE("limit with no 'name' attribute"); |
| 666 | return -EINVAL; |
| 667 | } |
| 668 | |
| 669 | // size, blocks, bitrate, frame-rate, blocks-per-second, aspect-ratio, |
| 670 | // measured-frame-rate, measured-blocks-per-second: range |
| 671 | // quality: range + default + [scale] |
| 672 | // complexity: range + default |
| 673 | bool found; |
| 674 | if (mCurrentType == mCodecInfos[mCurrentName].mTypes.end()) { |
| 675 | ALOGW("ignoring null type"); |
| 676 | return OK; |
| 677 | } |
| 678 | |
| 679 | if (name == "aspect-ratio" || name == "bitrate" || name == "block-count" |
| 680 | || name == "blocks-per-second" || name == "complexity" |
| 681 | || name == "frame-rate" || name == "quality" || name == "size" |
| 682 | || name == "measured-blocks-per-second" || name.startsWith("measured-frame-rate-")) { |
| 683 | AString min, max; |
| 684 | if (msg->findString("min", &min) && msg->findString("max", &max)) { |
| 685 | min.append("-"); |
| 686 | min.append(max); |
| 687 | if (msg->contains("range") || msg->contains("value")) { |
| 688 | return limitError(name, "has 'min' and 'max' as well as 'range' or " |
| 689 | "'value' attributes"); |
| 690 | } |
| 691 | msg->setString("range", min); |
| 692 | } else if (msg->contains("min") || msg->contains("max")) { |
| 693 | return limitError(name, "has only 'min' or 'max' attribute"); |
| 694 | } else if (msg->findString("value", &max)) { |
| 695 | min = max; |
| 696 | min.append("-"); |
| 697 | min.append(max); |
| 698 | if (msg->contains("range")) { |
| 699 | return limitError(name, "has both 'range' and 'value' attributes"); |
| 700 | } |
| 701 | msg->setString("range", min); |
| 702 | } |
| 703 | |
| 704 | AString range, scale = "linear", def, in_; |
| 705 | if (!msg->findString("range", &range)) { |
| 706 | return limitError(name, "with no 'range', 'value' or 'min'/'max' attributes"); |
| 707 | } |
| 708 | |
| 709 | if ((name == "quality" || name == "complexity") ^ |
| 710 | (found = msg->findString("default", &def))) { |
| 711 | return limitFoundMissingAttr(name, "default", found); |
| 712 | } |
| 713 | if (name != "quality" && msg->findString("scale", &scale)) { |
| 714 | return limitFoundMissingAttr(name, "scale"); |
| 715 | } |
| 716 | if ((name == "aspect-ratio") ^ (found = msg->findString("in", &in_))) { |
| 717 | return limitFoundMissingAttr(name, "in", found); |
| 718 | } |
| 719 | |
| 720 | if (name == "aspect-ratio") { |
| 721 | if (!(in_ == "pixels") && !(in_ == "blocks")) { |
| 722 | return limitInvalidAttr(name, "in", in_); |
| 723 | } |
| 724 | in_.erase(5, 1); // (pixel|block)-aspect-ratio |
| 725 | in_.append("-"); |
| 726 | in_.append(name); |
| 727 | name = in_; |
| 728 | } |
| 729 | if (name == "quality") { |
| 730 | mCurrentType->mDetails["quality-scale"] = scale; |
| 731 | } |
| 732 | if (name == "quality" || name == "complexity") { |
| 733 | AString tag = name; |
| 734 | tag.append("-default"); |
| 735 | mCurrentType->mDetails[tag] = def; |
| 736 | } |
| 737 | AString tag = name; |
| 738 | tag.append("-range"); |
| 739 | mCurrentType->mDetails[tag] = range; |
| 740 | } else { |
| 741 | AString max, value, ranges; |
| 742 | if (msg->contains("default")) { |
| 743 | return limitFoundMissingAttr(name, "default"); |
| 744 | } else if (msg->contains("in")) { |
| 745 | return limitFoundMissingAttr(name, "in"); |
| 746 | } else if ((name == "channel-count" || name == "concurrent-instances") ^ |
| 747 | (found = msg->findString("max", &max))) { |
| 748 | return limitFoundMissingAttr(name, "max", found); |
| 749 | } else if (msg->contains("min")) { |
| 750 | return limitFoundMissingAttr(name, "min"); |
| 751 | } else if (msg->contains("range")) { |
| 752 | return limitFoundMissingAttr(name, "range"); |
| 753 | } else if ((name == "sample-rate") ^ |
| 754 | (found = msg->findString("ranges", &ranges))) { |
| 755 | return limitFoundMissingAttr(name, "ranges", found); |
| 756 | } else if (msg->contains("scale")) { |
| 757 | return limitFoundMissingAttr(name, "scale"); |
| 758 | } else if ((name == "alignment" || name == "block-size") ^ |
| 759 | (found = msg->findString("value", &value))) { |
| 760 | return limitFoundMissingAttr(name, "value", found); |
| 761 | } |
| 762 | |
| 763 | if (max.size()) { |
| 764 | AString tag = "max-"; |
| 765 | tag.append(name); |
| 766 | mCurrentType->mDetails[tag] = max; |
| 767 | } else if (value.size()) { |
| 768 | mCurrentType->mDetails[name] = value; |
| 769 | } else if (ranges.size()) { |
| 770 | AString tag = name; |
| 771 | tag.append("-ranges"); |
| 772 | mCurrentType->mDetails[tag] = ranges; |
| 773 | } else { |
| 774 | ALOGW("Ignoring unrecognized limit '%s'", name.c_str()); |
| 775 | } |
| 776 | } |
| 777 | |
| 778 | return OK; |
| 779 | } |
| 780 | |
| 781 | status_t MediaCodecsXmlParser::addFeature(const char **attrs) { |
| 782 | size_t i = 0; |
| 783 | const char *name = NULL; |
| 784 | int32_t optional = -1; |
| 785 | int32_t required = -1; |
| 786 | const char *value = NULL; |
| 787 | |
| 788 | while (attrs[i] != NULL) { |
| 789 | if (attrs[i + 1] == NULL) { |
| 790 | ALOGE("addFeature: feature is not given"); |
| 791 | return -EINVAL; |
| 792 | } |
| 793 | |
| 794 | // attributes with values |
| 795 | if (!strcmp(attrs[i], "name")) { |
| 796 | name = attrs[i + 1]; |
| 797 | ++i; |
| 798 | } else if (!strcmp(attrs[i], "optional") || !strcmp(attrs[i], "required")) { |
| 799 | int value = (int)ParseBoolean(attrs[i + 1]); |
| 800 | if (!strcmp(attrs[i], "optional")) { |
| 801 | optional = value; |
| 802 | } else { |
| 803 | required = value; |
| 804 | } |
| 805 | ++i; |
| 806 | } else if (!strcmp(attrs[i], "value")) { |
| 807 | value = attrs[i + 1]; |
| 808 | ++i; |
| 809 | } else { |
| 810 | ALOGE("addFeature: unrecognized attribute: %s", attrs[i]); |
| 811 | return -EINVAL; |
| 812 | } |
| 813 | ++i; |
| 814 | } |
| 815 | if (name == NULL) { |
| 816 | ALOGE("feature with no 'name' attribute"); |
| 817 | return -EINVAL; |
| 818 | } |
| 819 | |
| 820 | if (optional == required && optional != -1) { |
| 821 | ALOGE("feature '%s' is both/neither optional and required", name); |
| 822 | return -EINVAL; |
| 823 | } |
| 824 | |
| 825 | if (mCurrentType == mCodecInfos[mCurrentName].mTypes.end()) { |
| 826 | ALOGW("ignoring null type"); |
| 827 | return OK; |
| 828 | } |
| 829 | if (value != NULL) { |
| 830 | mCurrentType->mStringFeatures[name] = value; |
| 831 | } else { |
| 832 | mCurrentType->mBoolFeatures[name] = (required == 1) || (optional == 0); |
| 833 | } |
| 834 | return OK; |
| 835 | } |
| 836 | |
| 837 | void MediaCodecsXmlParser::getGlobalSettings( |
| 838 | std::map<AString, AString> *settings) const { |
| 839 | settings->clear(); |
| 840 | settings->insert(mGlobalSettings.begin(), mGlobalSettings.end()); |
| 841 | } |
| 842 | |
| 843 | status_t MediaCodecsXmlParser::getCodecInfo(const char *name, CodecInfo *info) const { |
| 844 | if (mCodecInfos.count(name) == 0) { |
| 845 | ALOGE("Codec not found with name '%s'", name); |
| 846 | return NAME_NOT_FOUND; |
| 847 | } |
| 848 | *info = mCodecInfos.at(name); |
| 849 | return OK; |
| 850 | } |
| 851 | |
| 852 | status_t MediaCodecsXmlParser::getQuirks(const char *name, std::vector<AString> *quirks) const { |
| 853 | if (mQuirks.count(name) == 0) { |
| 854 | ALOGE("Codec not found with name '%s'", name); |
| 855 | return NAME_NOT_FOUND; |
| 856 | } |
| 857 | quirks->clear(); |
| 858 | quirks->insert(quirks->end(), mQuirks.at(name).begin(), mQuirks.at(name).end()); |
| 859 | return OK; |
| 860 | } |
| 861 | |
| 862 | } // namespace android |