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
65sp<MediaCodecList::BinderDeathObserver> MediaCodecList::sBinderDeathObserver;
66
67void MediaCodecList::BinderDeathObserver::binderDied(const wp<IBinder> &who __unused) {
68    Mutex::Autolock _l(sRemoteInitMutex);
69    sRemoteList.clear();
70    sBinderDeathObserver.clear();
71}
72
73// static
74sp<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
96MediaCodecList::MediaCodecList()
97    : mInitCheck(NO_INIT) {
98    parseTopLevelXMLFile("/etc/media_codecs.xml");
99}
100
101void 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
197MediaCodecList::~MediaCodecList() {
198}
199
200status_t MediaCodecList::initCheck() const {
201    return mInitCheck;
202}
203
204void 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
255void MediaCodecList::StartElementHandlerWrapper(
256        void *me, const char *name, const char **attrs) {
257    static_cast<MediaCodecList *>(me)->startElementHandler(name, attrs);
258}
259
260// static
261void MediaCodecList::EndElementHandlerWrapper(void *me, const char *name) {
262    static_cast<MediaCodecList *>(me)->endElementHandler(name);
263}
264
265status_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
306void 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
394void 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
465status_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
506status_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
528status_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
554status_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
590ssize_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
628static 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
634static status_t limitError(AString name, const char *msg) {
635    ALOGE("limit '%s' %s", name.c_str(), msg);
636    return -EINVAL;
637}
638
639static 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
645status_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
783static 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
792status_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
847ssize_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
859size_t MediaCodecList::countCodecs() const {
860    return mCodecInfos.size();
861}
862
863}  // namespace android
864