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