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