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