Home | History | Annotate | Download | only in libstagefright
      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