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