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