Home | History | Annotate | Download | only in httplive
      1 /*
      2  * Copyright (C) 2010 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 "M3UParser"
     19 #include <utils/Log.h>
     20 
     21 #include "M3UParser.h"
     22 #include <binder/Parcel.h>
     23 #include <cutils/properties.h>
     24 #include <media/stagefright/foundation/ADebug.h>
     25 #include <media/stagefright/foundation/AMessage.h>
     26 #include <media/stagefright/foundation/ByteUtils.h>
     27 #include <media/stagefright/MediaDefs.h>
     28 #include <media/stagefright/MediaErrors.h>
     29 #include <media/stagefright/Utils.h>
     30 #include <media/mediaplayer.h>
     31 
     32 namespace android {
     33 
     34 struct M3UParser::MediaGroup : public RefBase {
     35     enum Type {
     36         TYPE_AUDIO,
     37         TYPE_VIDEO,
     38         TYPE_SUBS,
     39         TYPE_CC,
     40     };
     41 
     42     enum FlagBits {
     43         FLAG_AUTOSELECT         = 1,
     44         FLAG_DEFAULT            = 2,
     45         FLAG_FORCED             = 4,
     46         FLAG_HAS_LANGUAGE       = 8,
     47         FLAG_HAS_URI            = 16,
     48     };
     49 
     50     explicit MediaGroup(Type type);
     51 
     52     Type type() const;
     53 
     54     status_t addMedia(
     55             const char *name,
     56             const char *uri,
     57             const char *language,
     58             uint32_t flags);
     59 
     60     bool getActiveURI(AString *uri, const char *baseURL) const;
     61 
     62     void pickRandomMediaItems();
     63     status_t selectTrack(size_t index, bool select);
     64     size_t countTracks() const;
     65     sp<AMessage> getTrackInfo(size_t index) const;
     66 
     67 protected:
     68     virtual ~MediaGroup();
     69 
     70 private:
     71 
     72     friend struct M3UParser;
     73 
     74     struct Media {
     75         AString mName;
     76         AString mURI;
     77         AString mLanguage;
     78         uint32_t mFlags;
     79         AString makeURL(const char *baseURL) const;
     80     };
     81 
     82     Type mType;
     83     Vector<Media> mMediaItems;
     84 
     85     ssize_t mSelectedIndex;
     86 
     87     DISALLOW_EVIL_CONSTRUCTORS(MediaGroup);
     88 };
     89 
     90 M3UParser::MediaGroup::MediaGroup(Type type)
     91     : mType(type),
     92       mSelectedIndex(-1) {
     93 }
     94 
     95 M3UParser::MediaGroup::~MediaGroup() {
     96 }
     97 
     98 M3UParser::MediaGroup::Type M3UParser::MediaGroup::type() const {
     99     return mType;
    100 }
    101 
    102 status_t M3UParser::MediaGroup::addMedia(
    103         const char *name,
    104         const char *uri,
    105         const char *language,
    106         uint32_t flags) {
    107     mMediaItems.push();
    108     Media &item = mMediaItems.editItemAt(mMediaItems.size() - 1);
    109 
    110     item.mName = name;
    111 
    112     if (uri) {
    113         item.mURI = uri;
    114     }
    115 
    116     if (language) {
    117         item.mLanguage = language;
    118     }
    119 
    120     item.mFlags = flags;
    121 
    122     return OK;
    123 }
    124 
    125 void M3UParser::MediaGroup::pickRandomMediaItems() {
    126 #if 1
    127     switch (mType) {
    128         case TYPE_AUDIO:
    129         {
    130             char value[PROPERTY_VALUE_MAX];
    131             if (property_get("media.httplive.audio-index", value, NULL)) {
    132                 char *end;
    133                 mSelectedIndex = strtoul(value, &end, 10);
    134                 CHECK(end > value && *end == '\0');
    135 
    136                 if (mSelectedIndex >= (ssize_t)mMediaItems.size()) {
    137                     mSelectedIndex = mMediaItems.size() - 1;
    138                 }
    139             } else {
    140                 mSelectedIndex = 0;
    141             }
    142             break;
    143         }
    144 
    145         case TYPE_VIDEO:
    146         {
    147             mSelectedIndex = 0;
    148             break;
    149         }
    150 
    151         case TYPE_SUBS:
    152         {
    153             mSelectedIndex = -1;
    154             break;
    155         }
    156 
    157         default:
    158             TRESPASS();
    159     }
    160 #else
    161     mSelectedIndex = (rand() * mMediaItems.size()) / RAND_MAX;
    162 #endif
    163 }
    164 
    165 status_t M3UParser::MediaGroup::selectTrack(size_t index, bool select) {
    166     if (mType != TYPE_SUBS && mType != TYPE_AUDIO) {
    167         ALOGE("only select subtitile/audio tracks for now!");
    168         return INVALID_OPERATION;
    169     }
    170 
    171     if (select) {
    172         if (index >= mMediaItems.size()) {
    173             ALOGE("track %zu does not exist", index);
    174             return INVALID_OPERATION;
    175         }
    176         if (mSelectedIndex == (ssize_t)index) {
    177             ALOGE("track %zu already selected", index);
    178             return BAD_VALUE;
    179         }
    180         ALOGV("selected track %zu", index);
    181         mSelectedIndex = index;
    182     } else {
    183         if (mSelectedIndex != (ssize_t)index) {
    184             ALOGE("track %zu is not selected", index);
    185             return BAD_VALUE;
    186         }
    187         ALOGV("unselected track %zu", index);
    188         mSelectedIndex = -1;
    189     }
    190 
    191     return OK;
    192 }
    193 
    194 size_t M3UParser::MediaGroup::countTracks() const {
    195     return mMediaItems.size();
    196 }
    197 
    198 sp<AMessage> M3UParser::MediaGroup::getTrackInfo(size_t index) const {
    199     if (index >= mMediaItems.size()) {
    200         return NULL;
    201     }
    202 
    203     sp<AMessage> format = new AMessage();
    204 
    205     int32_t trackType;
    206     if (mType == TYPE_AUDIO) {
    207         trackType = MEDIA_TRACK_TYPE_AUDIO;
    208     } else if (mType == TYPE_VIDEO) {
    209         trackType = MEDIA_TRACK_TYPE_VIDEO;
    210     } else if (mType == TYPE_SUBS) {
    211         trackType = MEDIA_TRACK_TYPE_SUBTITLE;
    212     } else {
    213         trackType = MEDIA_TRACK_TYPE_UNKNOWN;
    214     }
    215     format->setInt32("type", trackType);
    216 
    217     const Media &item = mMediaItems.itemAt(index);
    218     const char *lang = item.mLanguage.empty() ? "und" : item.mLanguage.c_str();
    219     format->setString("language", lang);
    220 
    221     if (mType == TYPE_SUBS) {
    222         // TO-DO: pass in a MediaFormat instead
    223         format->setString("mime", MEDIA_MIMETYPE_TEXT_VTT);
    224         format->setInt32("auto", !!(item.mFlags & MediaGroup::FLAG_AUTOSELECT));
    225         format->setInt32("default", !!(item.mFlags & MediaGroup::FLAG_DEFAULT));
    226         format->setInt32("forced", !!(item.mFlags & MediaGroup::FLAG_FORCED));
    227     }
    228 
    229     return format;
    230 }
    231 
    232 bool M3UParser::MediaGroup::getActiveURI(AString *uri, const char *baseURL) const {
    233     for (size_t i = 0; i < mMediaItems.size(); ++i) {
    234         if (mSelectedIndex >= 0 && i == (size_t)mSelectedIndex) {
    235             const Media &item = mMediaItems.itemAt(i);
    236 
    237             if (item.mURI.empty()) {
    238                 *uri = "";
    239             } else {
    240                 *uri = item.makeURL(baseURL);
    241             }
    242             return true;
    243         }
    244     }
    245 
    246     return false;
    247 }
    248 
    249 ////////////////////////////////////////////////////////////////////////////////
    250 
    251 M3UParser::M3UParser(
    252         const char *baseURI, const void *data, size_t size)
    253     : mInitCheck(NO_INIT),
    254       mBaseURI(baseURI),
    255       mIsExtM3U(false),
    256       mIsVariantPlaylist(false),
    257       mIsComplete(false),
    258       mIsEvent(false),
    259       mFirstSeqNumber(-1),
    260       mLastSeqNumber(-1),
    261       mTargetDurationUs(-1LL),
    262       mDiscontinuitySeq(0),
    263       mDiscontinuityCount(0),
    264       mSelectedIndex(-1) {
    265     mInitCheck = parse(data, size);
    266 }
    267 
    268 M3UParser::~M3UParser() {
    269 }
    270 
    271 status_t M3UParser::initCheck() const {
    272     return mInitCheck;
    273 }
    274 
    275 bool M3UParser::isExtM3U() const {
    276     return mIsExtM3U;
    277 }
    278 
    279 bool M3UParser::isVariantPlaylist() const {
    280     return mIsVariantPlaylist;
    281 }
    282 
    283 bool M3UParser::isComplete() const {
    284     return mIsComplete;
    285 }
    286 
    287 bool M3UParser::isEvent() const {
    288     return mIsEvent;
    289 }
    290 
    291 size_t M3UParser::getDiscontinuitySeq() const {
    292     return mDiscontinuitySeq;
    293 }
    294 
    295 int64_t M3UParser::getTargetDuration() const {
    296     return mTargetDurationUs;
    297 }
    298 
    299 int32_t M3UParser::getFirstSeqNumber() const {
    300     return mFirstSeqNumber;
    301 }
    302 
    303 void M3UParser::getSeqNumberRange(int32_t *firstSeq, int32_t *lastSeq) const {
    304     *firstSeq = mFirstSeqNumber;
    305     *lastSeq = mLastSeqNumber;
    306 }
    307 
    308 sp<AMessage> M3UParser::meta() {
    309     return mMeta;
    310 }
    311 
    312 size_t M3UParser::size() {
    313     return mItems.size();
    314 }
    315 
    316 bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) {
    317     if (uri) {
    318         uri->clear();
    319     }
    320 
    321     if (meta) {
    322         *meta = NULL;
    323     }
    324 
    325     if (index >= mItems.size()) {
    326         return false;
    327     }
    328 
    329     if (uri) {
    330         *uri = mItems.itemAt(index).makeURL(mBaseURI.c_str());
    331     }
    332 
    333     if (meta) {
    334         *meta = mItems.itemAt(index).mMeta;
    335     }
    336 
    337     return true;
    338 }
    339 
    340 void M3UParser::pickRandomMediaItems() {
    341     for (size_t i = 0; i < mMediaGroups.size(); ++i) {
    342         mMediaGroups.valueAt(i)->pickRandomMediaItems();
    343     }
    344 }
    345 
    346 status_t M3UParser::selectTrack(size_t index, bool select) {
    347     for (size_t i = 0, ii = index; i < mMediaGroups.size(); ++i) {
    348         sp<MediaGroup> group = mMediaGroups.valueAt(i);
    349         size_t tracks = group->countTracks();
    350         if (ii < tracks) {
    351             status_t err = group->selectTrack(ii, select);
    352             if (err == OK) {
    353                 mSelectedIndex = select ? index : -1;
    354             }
    355             return err;
    356         }
    357         ii -= tracks;
    358     }
    359     return INVALID_OPERATION;
    360 }
    361 
    362 size_t M3UParser::getTrackCount() const {
    363     size_t trackCount = 0;
    364     for (size_t i = 0; i < mMediaGroups.size(); ++i) {
    365         trackCount += mMediaGroups.valueAt(i)->countTracks();
    366     }
    367     return trackCount;
    368 }
    369 
    370 sp<AMessage> M3UParser::getTrackInfo(size_t index) const {
    371     for (size_t i = 0, ii = index; i < mMediaGroups.size(); ++i) {
    372         sp<MediaGroup> group = mMediaGroups.valueAt(i);
    373         size_t tracks = group->countTracks();
    374         if (ii < tracks) {
    375             return group->getTrackInfo(ii);
    376         }
    377         ii -= tracks;
    378     }
    379     return NULL;
    380 }
    381 
    382 ssize_t M3UParser::getSelectedIndex() const {
    383     return mSelectedIndex;
    384 }
    385 
    386 ssize_t M3UParser::getSelectedTrack(media_track_type type) const {
    387     MediaGroup::Type groupType;
    388     switch (type) {
    389         case MEDIA_TRACK_TYPE_VIDEO:
    390             groupType = MediaGroup::TYPE_VIDEO;
    391             break;
    392 
    393         case MEDIA_TRACK_TYPE_AUDIO:
    394             groupType = MediaGroup::TYPE_AUDIO;
    395             break;
    396 
    397         case MEDIA_TRACK_TYPE_SUBTITLE:
    398             groupType = MediaGroup::TYPE_SUBS;
    399             break;
    400 
    401         default:
    402             return -1;
    403     }
    404 
    405     for (size_t i = 0, ii = 0; i < mMediaGroups.size(); ++i) {
    406         sp<MediaGroup> group = mMediaGroups.valueAt(i);
    407         size_t tracks = group->countTracks();
    408         if (groupType != group->mType) {
    409             ii += tracks;
    410         } else if (group->mSelectedIndex >= 0) {
    411             return ii + group->mSelectedIndex;
    412         }
    413     }
    414 
    415     return -1;
    416 }
    417 
    418 bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
    419     if (!mIsVariantPlaylist) {
    420         if (uri != NULL) {
    421             *uri = mBaseURI;
    422         }
    423 
    424         // Assume media without any more specific attribute contains
    425         // audio and video, but no subtitles.
    426         return !strcmp("audio", key) || !strcmp("video", key);
    427     }
    428 
    429     CHECK_LT(index, mItems.size());
    430 
    431     sp<AMessage> meta = mItems.itemAt(index).mMeta;
    432 
    433     AString groupID;
    434     if (!meta->findString(key, &groupID)) {
    435         if (uri != NULL) {
    436             *uri = mItems.itemAt(index).makeURL(mBaseURI.c_str());
    437         }
    438 
    439         AString codecs;
    440         if (!meta->findString("codecs", &codecs)) {
    441             // Assume media without any more specific attribute contains
    442             // audio and video, but no subtitles.
    443             return !strcmp("audio", key) || !strcmp("video", key);
    444         } else {
    445             // Split the comma separated list of codecs.
    446             size_t offset = 0;
    447             ssize_t commaPos = -1;
    448             codecs.append(',');
    449             while ((commaPos = codecs.find(",", offset)) >= 0) {
    450                 AString codec(codecs, offset, commaPos - offset);
    451                 codec.trim();
    452                 // return true only if a codec of type `key` ("audio"/"video")
    453                 // is found.
    454                 if (codecIsType(codec, key)) {
    455                     return true;
    456                 }
    457                 offset = commaPos + 1;
    458             }
    459             return false;
    460         }
    461     }
    462 
    463     // if uri == NULL, we're only checking if the type is present,
    464     // don't care about the active URI (or if there is an active one)
    465     if (uri != NULL) {
    466         sp<MediaGroup> group = mMediaGroups.valueFor(groupID);
    467         if (!group->getActiveURI(uri, mBaseURI.c_str())) {
    468             return false;
    469         }
    470 
    471         if ((*uri).empty()) {
    472             *uri = mItems.itemAt(index).makeURL(mBaseURI.c_str());
    473         }
    474     }
    475 
    476     return true;
    477 }
    478 
    479 bool M3UParser::hasType(size_t index, const char *key) const {
    480     return getTypeURI(index, key, NULL /* uri */);
    481 }
    482 
    483 static bool MakeURL(const char *baseURL, const char *url, AString *out) {
    484     out->clear();
    485 
    486     if (strncasecmp("http://", baseURL, 7)
    487             && strncasecmp("https://", baseURL, 8)
    488             && strncasecmp("file://", baseURL, 7)) {
    489         // Base URL must be absolute
    490         return false;
    491     }
    492     if (!strncasecmp("data:", url, 5)) {
    493         return false;
    494     }
    495     const size_t schemeEnd = (strstr(baseURL, "//") - baseURL) + 2;
    496     CHECK(schemeEnd == 7 || schemeEnd == 8);
    497 
    498     if (!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) {
    499         // "url" is already an absolute URL, ignore base URL.
    500         out->setTo(url);
    501 
    502         ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str());
    503 
    504         return true;
    505     }
    506 
    507     if (url[0] == '/') {
    508         // URL is an absolute path.
    509 
    510         const char *protocolEnd = strstr(baseURL, "//") + 2;
    511         const char *pathStart = strchr(protocolEnd, '/');
    512 
    513         if (pathStart != NULL) {
    514             out->setTo(baseURL, pathStart - baseURL);
    515         } else {
    516             out->setTo(baseURL);
    517         }
    518 
    519         out->append(url);
    520     } else {
    521         // URL is a relative path
    522 
    523         // Check for a possible query string
    524         const char *qsPos = strchr(baseURL, '?');
    525         size_t end;
    526         if (qsPos != NULL) {
    527             end = qsPos - baseURL;
    528         } else {
    529             end = strlen(baseURL);
    530         }
    531         // Check for the last slash before a potential query string
    532         for (ssize_t pos = end - 1; pos >= 0; pos--) {
    533             if (baseURL[pos] == '/') {
    534                 end = pos;
    535                 break;
    536             }
    537         }
    538 
    539         // Check whether the found slash actually is part of the path
    540         // and not part of the "http://".
    541         if (end >= schemeEnd) {
    542             out->setTo(baseURL, end);
    543         } else {
    544             out->setTo(baseURL);
    545         }
    546 
    547         out->append("/");
    548         out->append(url);
    549     }
    550 
    551     ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str());
    552 
    553     return true;
    554 }
    555 
    556 AString M3UParser::Item::makeURL(const char *baseURL) const {
    557     AString out;
    558     CHECK(MakeURL(baseURL, mURI.c_str(), &out));
    559     return out;
    560 }
    561 
    562 AString M3UParser::MediaGroup::Media::makeURL(const char *baseURL) const {
    563     AString out;
    564     CHECK(MakeURL(baseURL, mURI.c_str(), &out));
    565     return out;
    566 }
    567 
    568 status_t M3UParser::parse(const void *_data, size_t size) {
    569     int32_t lineNo = 0;
    570 
    571     sp<AMessage> itemMeta;
    572 
    573     const char *data = (const char *)_data;
    574     size_t offset = 0;
    575     uint64_t segmentRangeOffset = 0;
    576     while (offset < size) {
    577         size_t offsetLF = offset;
    578         while (offsetLF < size && data[offsetLF] != '\n') {
    579             ++offsetLF;
    580         }
    581 
    582         AString line;
    583         if (offsetLF > offset && data[offsetLF - 1] == '\r') {
    584             line.setTo(&data[offset], offsetLF - offset - 1);
    585         } else {
    586             line.setTo(&data[offset], offsetLF - offset);
    587         }
    588 
    589         // ALOGI("#%s#", line.c_str());
    590 
    591         if (line.empty()) {
    592             offset = offsetLF + 1;
    593             continue;
    594         }
    595 
    596         if (lineNo == 0 && line == "#EXTM3U") {
    597             mIsExtM3U = true;
    598         }
    599 
    600         if (mIsExtM3U) {
    601             status_t err = OK;
    602 
    603             if (line.startsWith("#EXT-X-TARGETDURATION")) {
    604                 if (mIsVariantPlaylist) {
    605                     return ERROR_MALFORMED;
    606                 }
    607                 err = parseMetaData(line, &mMeta, "target-duration");
    608             } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) {
    609                 if (mIsVariantPlaylist) {
    610                     return ERROR_MALFORMED;
    611                 }
    612                 err = parseMetaData(line, &mMeta, "media-sequence");
    613             } else if (line.startsWith("#EXT-X-KEY")) {
    614                 if (mIsVariantPlaylist) {
    615                     return ERROR_MALFORMED;
    616                 }
    617                 err = parseCipherInfo(line, &itemMeta, mBaseURI);
    618             } else if (line.startsWith("#EXT-X-ENDLIST")) {
    619                 mIsComplete = true;
    620             } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) {
    621                 mIsEvent = true;
    622             } else if (line.startsWith("#EXTINF")) {
    623                 if (mIsVariantPlaylist) {
    624                     return ERROR_MALFORMED;
    625                 }
    626                 err = parseMetaDataDuration(line, &itemMeta, "durationUs");
    627             } else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) {
    628                 if (mIsVariantPlaylist) {
    629                     return ERROR_MALFORMED;
    630                 }
    631                 size_t seq;
    632                 err = parseDiscontinuitySequence(line, &seq);
    633                 if (err == OK) {
    634                     mDiscontinuitySeq = seq;
    635                     ALOGI("mDiscontinuitySeq %zu", mDiscontinuitySeq);
    636                 } else {
    637                     ALOGI("Failed to parseDiscontinuitySequence %d", err);
    638                 }
    639             } else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
    640                 if (mIsVariantPlaylist) {
    641                     return ERROR_MALFORMED;
    642                 }
    643                 if (itemMeta == NULL) {
    644                     itemMeta = new AMessage;
    645                 }
    646                 itemMeta->setInt32("discontinuity", true);
    647                 ++mDiscontinuityCount;
    648             } else if (line.startsWith("#EXT-X-STREAM-INF")) {
    649                 if (mMeta != NULL) {
    650                     return ERROR_MALFORMED;
    651                 }
    652                 mIsVariantPlaylist = true;
    653                 err = parseStreamInf(line, &itemMeta);
    654             } else if (line.startsWith("#EXT-X-BYTERANGE")) {
    655                 if (mIsVariantPlaylist) {
    656                     return ERROR_MALFORMED;
    657                 }
    658 
    659                 uint64_t length, offset;
    660                 err = parseByteRange(line, segmentRangeOffset, &length, &offset);
    661 
    662                 if (err == OK) {
    663                     if (itemMeta == NULL) {
    664                         itemMeta = new AMessage;
    665                     }
    666 
    667                     itemMeta->setInt64("range-offset", offset);
    668                     itemMeta->setInt64("range-length", length);
    669 
    670                     segmentRangeOffset = offset + length;
    671                 }
    672             } else if (line.startsWith("#EXT-X-MEDIA")) {
    673                 err = parseMedia(line);
    674             }
    675 
    676             if (err != OK) {
    677                 return err;
    678             }
    679         }
    680 
    681         if (!line.startsWith("#")) {
    682             if (itemMeta == NULL) {
    683                 ALOGV("itemMeta == NULL");
    684                 return ERROR_MALFORMED;
    685             }
    686             if (!mIsVariantPlaylist) {
    687                 int64_t durationUs;
    688                 if (!itemMeta->findInt64("durationUs", &durationUs)) {
    689                     return ERROR_MALFORMED;
    690                 }
    691                 itemMeta->setInt32("discontinuity-sequence",
    692                         mDiscontinuitySeq + mDiscontinuityCount);
    693             }
    694 
    695             mItems.push();
    696             Item *item = &mItems.editItemAt(mItems.size() - 1);
    697 
    698             item->mURI = line;
    699 
    700             item->mMeta = itemMeta;
    701 
    702             itemMeta.clear();
    703         }
    704 
    705         offset = offsetLF + 1;
    706         ++lineNo;
    707     }
    708 
    709     // playlist has no item, would cause exception
    710     if (mItems.size() == 0) {
    711         ALOGE("playlist has no item");
    712         return ERROR_MALFORMED;
    713     }
    714 
    715     // error checking of all fields that's required to appear once
    716     // (currently only checking "target-duration"), and
    717     // initialization of playlist properties (eg. mTargetDurationUs)
    718     if (!mIsVariantPlaylist) {
    719         int32_t targetDurationSecs;
    720         if (mMeta == NULL || !mMeta->findInt32(
    721                 "target-duration", &targetDurationSecs)) {
    722             ALOGE("Media playlist missing #EXT-X-TARGETDURATION");
    723             return ERROR_MALFORMED;
    724         }
    725         mTargetDurationUs = targetDurationSecs * 1000000LL;
    726 
    727         mFirstSeqNumber = 0;
    728         if (mMeta != NULL) {
    729             mMeta->findInt32("media-sequence", &mFirstSeqNumber);
    730         }
    731         mLastSeqNumber = mFirstSeqNumber + mItems.size() - 1;
    732     }
    733 
    734     for (size_t i = 0; i < mItems.size(); ++i) {
    735         sp<AMessage> meta = mItems.itemAt(i).mMeta;
    736         const char *keys[] = {"audio", "video", "subtitles"};
    737         for (size_t j = 0; j < sizeof(keys) / sizeof(const char *); ++j) {
    738             AString groupID;
    739             if (meta->findString(keys[j], &groupID)) {
    740                 ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
    741                 if (groupIndex < 0) {
    742                     ALOGE("Undefined media group '%s' referenced in stream info.",
    743                           groupID.c_str());
    744                     return ERROR_MALFORMED;
    745                 }
    746             }
    747         }
    748     }
    749 
    750     return OK;
    751 }
    752 
    753 // static
    754 status_t M3UParser::parseMetaData(
    755         const AString &line, sp<AMessage> *meta, const char *key) {
    756     ssize_t colonPos = line.find(":");
    757 
    758     if (colonPos < 0) {
    759         return ERROR_MALFORMED;
    760     }
    761 
    762     int32_t x;
    763     status_t err = ParseInt32(line.c_str() + colonPos + 1, &x);
    764 
    765     if (err != OK) {
    766         return err;
    767     }
    768 
    769     if (meta->get() == NULL) {
    770         *meta = new AMessage;
    771     }
    772     (*meta)->setInt32(key, x);
    773 
    774     return OK;
    775 }
    776 
    777 // static
    778 status_t M3UParser::parseMetaDataDuration(
    779         const AString &line, sp<AMessage> *meta, const char *key) {
    780     ssize_t colonPos = line.find(":");
    781 
    782     if (colonPos < 0) {
    783         return ERROR_MALFORMED;
    784     }
    785 
    786     double x;
    787     status_t err = ParseDouble(line.c_str() + colonPos + 1, &x);
    788 
    789     if (err != OK) {
    790         return err;
    791     }
    792 
    793     if (meta->get() == NULL) {
    794         *meta = new AMessage;
    795     }
    796     (*meta)->setInt64(key, (int64_t)(x * 1E6));
    797 
    798     return OK;
    799 }
    800 
    801 // Find the next occurence of the character "what" at or after "offset",
    802 // but ignore occurences between quotation marks.
    803 // Return the index of the occurrence or -1 if not found.
    804 static ssize_t FindNextUnquoted(
    805         const AString &line, char what, size_t offset) {
    806     CHECK_NE((int)what, (int)'"');
    807 
    808     bool quoted = false;
    809     while (offset < line.size()) {
    810         char c = line.c_str()[offset];
    811 
    812         if (c == '"') {
    813             quoted = !quoted;
    814         } else if (c == what && !quoted) {
    815             return offset;
    816         }
    817 
    818         ++offset;
    819     }
    820 
    821     return -1;
    822 }
    823 
    824 status_t M3UParser::parseStreamInf(
    825         const AString &line, sp<AMessage> *meta) const {
    826     ssize_t colonPos = line.find(":");
    827 
    828     if (colonPos < 0) {
    829         return ERROR_MALFORMED;
    830     }
    831 
    832     size_t offset = colonPos + 1;
    833 
    834     while (offset < line.size()) {
    835         ssize_t end = FindNextUnquoted(line, ',', offset);
    836         if (end < 0) {
    837             end = line.size();
    838         }
    839 
    840         AString attr(line, offset, end - offset);
    841         attr.trim();
    842 
    843         offset = end + 1;
    844 
    845         ssize_t equalPos = attr.find("=");
    846         if (equalPos < 0) {
    847             continue;
    848         }
    849 
    850         AString key(attr, 0, equalPos);
    851         key.trim();
    852 
    853         AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
    854         val.trim();
    855 
    856         ALOGV("key=%s value=%s", key.c_str(), val.c_str());
    857 
    858         if (!strcasecmp("bandwidth", key.c_str())) {
    859             const char *s = val.c_str();
    860             char *end;
    861             unsigned long x = strtoul(s, &end, 10);
    862 
    863             if (end == s || *end != '\0') {
    864                 // malformed
    865                 continue;
    866             }
    867 
    868             if (meta->get() == NULL) {
    869                 *meta = new AMessage;
    870             }
    871             (*meta)->setInt32("bandwidth", x);
    872         } else if (!strcasecmp("codecs", key.c_str())) {
    873             if (!isQuotedString(val)) {
    874                 ALOGE("Expected quoted string for %s attribute, "
    875                       "got '%s' instead.",
    876                       key.c_str(), val.c_str());;
    877 
    878                 return ERROR_MALFORMED;
    879             }
    880 
    881             key.tolower();
    882             const AString &codecs = unquoteString(val);
    883             if (meta->get() == NULL) {
    884                 *meta = new AMessage;
    885             }
    886             (*meta)->setString(key.c_str(), codecs.c_str());
    887         } else if (!strcasecmp("resolution", key.c_str())) {
    888             const char *s = val.c_str();
    889             char *end;
    890             unsigned long width = strtoul(s, &end, 10);
    891 
    892             if (end == s || *end != 'x') {
    893                 // malformed
    894                 continue;
    895             }
    896 
    897             s = end + 1;
    898             unsigned long height = strtoul(s, &end, 10);
    899 
    900             if (end == s || *end != '\0') {
    901                 // malformed
    902                 continue;
    903             }
    904 
    905             if (meta->get() == NULL) {
    906                 *meta = new AMessage;
    907             }
    908             (*meta)->setInt32("width", width);
    909             (*meta)->setInt32("height", height);
    910         } else if (!strcasecmp("audio", key.c_str())
    911                 || !strcasecmp("video", key.c_str())
    912                 || !strcasecmp("subtitles", key.c_str())) {
    913             if (!isQuotedString(val)) {
    914                 ALOGE("Expected quoted string for %s attribute, "
    915                       "got '%s' instead.",
    916                       key.c_str(), val.c_str());
    917 
    918                 return ERROR_MALFORMED;
    919             }
    920 
    921             const AString &groupID = unquoteString(val);
    922             key.tolower();
    923             if (meta->get() == NULL) {
    924                 *meta = new AMessage;
    925             }
    926             (*meta)->setString(key.c_str(), groupID.c_str());
    927         }
    928     }
    929 
    930     if (meta->get() == NULL) {
    931         return ERROR_MALFORMED;
    932     }
    933     return OK;
    934 }
    935 
    936 // static
    937 status_t M3UParser::parseCipherInfo(
    938         const AString &line, sp<AMessage> *meta, const AString &baseURI) {
    939     ssize_t colonPos = line.find(":");
    940 
    941     if (colonPos < 0) {
    942         return ERROR_MALFORMED;
    943     }
    944 
    945     size_t offset = colonPos + 1;
    946 
    947     while (offset < line.size()) {
    948         ssize_t end = FindNextUnquoted(line, ',', offset);
    949         if (end < 0) {
    950             end = line.size();
    951         }
    952 
    953         AString attr(line, offset, end - offset);
    954         attr.trim();
    955 
    956         offset = end + 1;
    957 
    958         ssize_t equalPos = attr.find("=");
    959         if (equalPos < 0) {
    960             continue;
    961         }
    962 
    963         AString key(attr, 0, equalPos);
    964         key.trim();
    965 
    966         AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
    967         val.trim();
    968 
    969         ALOGV("key=%s value=%s", key.c_str(), val.c_str());
    970 
    971         key.tolower();
    972 
    973         if (key == "method" || key == "uri" || key == "iv") {
    974             if (meta->get() == NULL) {
    975                 *meta = new AMessage;
    976             }
    977 
    978             if (key == "uri") {
    979                 if (val.size() >= 2
    980                         && val.c_str()[0] == '"'
    981                         && val.c_str()[val.size() - 1] == '"') {
    982                     // Remove surrounding quotes.
    983                     AString tmp(val, 1, val.size() - 2);
    984                     val = tmp;
    985                 }
    986 
    987                 AString absURI;
    988                 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) {
    989                     val = absURI;
    990                 } else {
    991                     ALOGE("failed to make absolute url for %s.",
    992                             uriDebugString(baseURI).c_str());
    993                 }
    994             }
    995 
    996             key.insert(AString("cipher-"), 0);
    997 
    998             (*meta)->setString(key.c_str(), val.c_str(), val.size());
    999         }
   1000     }
   1001 
   1002     return OK;
   1003 }
   1004 
   1005 // static
   1006 status_t M3UParser::parseByteRange(
   1007         const AString &line, uint64_t curOffset,
   1008         uint64_t *length, uint64_t *offset) {
   1009     ssize_t colonPos = line.find(":");
   1010 
   1011     if (colonPos < 0) {
   1012         return ERROR_MALFORMED;
   1013     }
   1014 
   1015     ssize_t atPos = line.find("@", colonPos + 1);
   1016 
   1017     AString lenStr;
   1018     if (atPos < 0) {
   1019         lenStr = AString(line, colonPos + 1, line.size() - colonPos - 1);
   1020     } else {
   1021         lenStr = AString(line, colonPos + 1, atPos - colonPos - 1);
   1022     }
   1023 
   1024     lenStr.trim();
   1025 
   1026     const char *s = lenStr.c_str();
   1027     char *end;
   1028     *length = strtoull(s, &end, 10);
   1029 
   1030     if (s == end || *end != '\0') {
   1031         return ERROR_MALFORMED;
   1032     }
   1033 
   1034     if (atPos >= 0) {
   1035         AString offStr = AString(line, atPos + 1, line.size() - atPos - 1);
   1036         offStr.trim();
   1037 
   1038         const char *s = offStr.c_str();
   1039         *offset = strtoull(s, &end, 10);
   1040 
   1041         if (s == end || *end != '\0') {
   1042             return ERROR_MALFORMED;
   1043         }
   1044     } else {
   1045         *offset = curOffset;
   1046     }
   1047 
   1048     return OK;
   1049 }
   1050 
   1051 status_t M3UParser::parseMedia(const AString &line) {
   1052     ssize_t colonPos = line.find(":");
   1053 
   1054     if (colonPos < 0) {
   1055         return ERROR_MALFORMED;
   1056     }
   1057 
   1058     bool haveGroupType = false;
   1059     MediaGroup::Type groupType = MediaGroup::TYPE_AUDIO;
   1060 
   1061     bool haveGroupID = false;
   1062     AString groupID;
   1063 
   1064     bool haveGroupLanguage = false;
   1065     AString groupLanguage;
   1066 
   1067     bool haveGroupName = false;
   1068     AString groupName;
   1069 
   1070     bool haveGroupAutoselect = false;
   1071     bool groupAutoselect = false;
   1072 
   1073     bool haveGroupDefault = false;
   1074     bool groupDefault = false;
   1075 
   1076     bool haveGroupForced = false;
   1077     bool groupForced = false;
   1078 
   1079     bool haveGroupURI = false;
   1080     AString groupURI;
   1081 
   1082     size_t offset = colonPos + 1;
   1083 
   1084     while (offset < line.size()) {
   1085         ssize_t end = FindNextUnquoted(line, ',', offset);
   1086         if (end < 0) {
   1087             end = line.size();
   1088         }
   1089 
   1090         AString attr(line, offset, end - offset);
   1091         attr.trim();
   1092 
   1093         offset = end + 1;
   1094 
   1095         ssize_t equalPos = attr.find("=");
   1096         if (equalPos < 0) {
   1097             continue;
   1098         }
   1099 
   1100         AString key(attr, 0, equalPos);
   1101         key.trim();
   1102 
   1103         AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
   1104         val.trim();
   1105 
   1106         ALOGV("key=%s value=%s", key.c_str(), val.c_str());
   1107 
   1108         if (!strcasecmp("type", key.c_str())) {
   1109             if (!strcasecmp("subtitles", val.c_str())) {
   1110                 groupType = MediaGroup::TYPE_SUBS;
   1111             } else if (!strcasecmp("audio", val.c_str())) {
   1112                 groupType = MediaGroup::TYPE_AUDIO;
   1113             } else if (!strcasecmp("video", val.c_str())) {
   1114                 groupType = MediaGroup::TYPE_VIDEO;
   1115             } else if (!strcasecmp("closed-captions", val.c_str())){
   1116                 groupType = MediaGroup::TYPE_CC;
   1117             } else {
   1118                 ALOGE("Invalid media group type '%s'", val.c_str());
   1119                 return ERROR_MALFORMED;
   1120             }
   1121 
   1122             haveGroupType = true;
   1123         } else if (!strcasecmp("group-id", key.c_str())) {
   1124             if (val.size() < 2
   1125                     || val.c_str()[0] != '"'
   1126                     || val.c_str()[val.size() - 1] != '"') {
   1127                 ALOGE("Expected quoted string for GROUP-ID, got '%s' instead.",
   1128                       val.c_str());
   1129 
   1130                 return ERROR_MALFORMED;
   1131             }
   1132 
   1133             groupID.setTo(val, 1, val.size() - 2);
   1134             haveGroupID = true;
   1135         } else if (!strcasecmp("language", key.c_str())) {
   1136             if (val.size() < 2
   1137                     || val.c_str()[0] != '"'
   1138                     || val.c_str()[val.size() - 1] != '"') {
   1139                 ALOGE("Expected quoted string for LANGUAGE, got '%s' instead.",
   1140                       val.c_str());
   1141 
   1142                 return ERROR_MALFORMED;
   1143             }
   1144 
   1145             groupLanguage.setTo(val, 1, val.size() - 2);
   1146             haveGroupLanguage = true;
   1147         } else if (!strcasecmp("name", key.c_str())) {
   1148             if (val.size() < 2
   1149                     || val.c_str()[0] != '"'
   1150                     || val.c_str()[val.size() - 1] != '"') {
   1151                 ALOGE("Expected quoted string for NAME, got '%s' instead.",
   1152                       val.c_str());
   1153 
   1154                 return ERROR_MALFORMED;
   1155             }
   1156 
   1157             groupName.setTo(val, 1, val.size() - 2);
   1158             haveGroupName = true;
   1159         } else if (!strcasecmp("autoselect", key.c_str())) {
   1160             groupAutoselect = false;
   1161             if (!strcasecmp("YES", val.c_str())) {
   1162                 groupAutoselect = true;
   1163             } else if (!strcasecmp("NO", val.c_str())) {
   1164                 groupAutoselect = false;
   1165             } else {
   1166                 ALOGE("Expected YES or NO for AUTOSELECT attribute, "
   1167                       "got '%s' instead.",
   1168                       val.c_str());
   1169 
   1170                 return ERROR_MALFORMED;
   1171             }
   1172 
   1173             haveGroupAutoselect = true;
   1174         } else if (!strcasecmp("default", key.c_str())) {
   1175             groupDefault = false;
   1176             if (!strcasecmp("YES", val.c_str())) {
   1177                 groupDefault = true;
   1178             } else if (!strcasecmp("NO", val.c_str())) {
   1179                 groupDefault = false;
   1180             } else {
   1181                 ALOGE("Expected YES or NO for DEFAULT attribute, "
   1182                       "got '%s' instead.",
   1183                       val.c_str());
   1184 
   1185                 return ERROR_MALFORMED;
   1186             }
   1187 
   1188             haveGroupDefault = true;
   1189         } else if (!strcasecmp("forced", key.c_str())) {
   1190             groupForced = false;
   1191             if (!strcasecmp("YES", val.c_str())) {
   1192                 groupForced = true;
   1193             } else if (!strcasecmp("NO", val.c_str())) {
   1194                 groupForced = false;
   1195             } else {
   1196                 ALOGE("Expected YES or NO for FORCED attribute, "
   1197                       "got '%s' instead.",
   1198                       val.c_str());
   1199 
   1200                 return ERROR_MALFORMED;
   1201             }
   1202 
   1203             haveGroupForced = true;
   1204         } else if (!strcasecmp("uri", key.c_str())) {
   1205             if (val.size() < 2
   1206                     || val.c_str()[0] != '"'
   1207                     || val.c_str()[val.size() - 1] != '"') {
   1208                 ALOGE("Expected quoted string for URI.");
   1209 
   1210                 return ERROR_MALFORMED;
   1211             }
   1212 
   1213             AString tmp(val, 1, val.size() - 2);
   1214 
   1215             groupURI = tmp;
   1216 
   1217             haveGroupURI = true;
   1218         }
   1219     }
   1220 
   1221     if (!haveGroupType || !haveGroupID || !haveGroupName) {
   1222         ALOGE("Incomplete EXT-X-MEDIA element.");
   1223         return ERROR_MALFORMED;
   1224     }
   1225 
   1226     if (groupType == MediaGroup::TYPE_CC) {
   1227         // TODO: ignore this for now.
   1228         // CC track will be detected by CCDecoder. But we still need to
   1229         // pass the CC track flags (lang, auto) to the app in the future.
   1230         return OK;
   1231     }
   1232 
   1233     uint32_t flags = 0;
   1234     if (haveGroupAutoselect && groupAutoselect) {
   1235         flags |= MediaGroup::FLAG_AUTOSELECT;
   1236     }
   1237     if (haveGroupDefault && groupDefault) {
   1238         flags |= MediaGroup::FLAG_DEFAULT;
   1239     }
   1240     if (haveGroupForced) {
   1241         if (groupType != MediaGroup::TYPE_SUBS) {
   1242             ALOGE("The FORCED attribute MUST not be present on anything "
   1243                   "but SUBS media.");
   1244 
   1245             return ERROR_MALFORMED;
   1246         }
   1247 
   1248         if (groupForced) {
   1249             flags |= MediaGroup::FLAG_FORCED;
   1250         }
   1251     }
   1252     if (haveGroupLanguage) {
   1253         flags |= MediaGroup::FLAG_HAS_LANGUAGE;
   1254     }
   1255     if (haveGroupURI) {
   1256         flags |= MediaGroup::FLAG_HAS_URI;
   1257     }
   1258 
   1259     ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
   1260     sp<MediaGroup> group;
   1261 
   1262     if (groupIndex < 0) {
   1263         group = new MediaGroup(groupType);
   1264         mMediaGroups.add(groupID, group);
   1265     } else {
   1266         group = mMediaGroups.valueAt(groupIndex);
   1267 
   1268         if (group->type() != groupType) {
   1269             ALOGE("Attempt to put media item under group of different type "
   1270                   "(groupType = %d, item type = %d",
   1271                   group->type(),
   1272                   groupType);
   1273 
   1274             return ERROR_MALFORMED;
   1275         }
   1276     }
   1277 
   1278     return group->addMedia(
   1279             groupName.c_str(),
   1280             haveGroupURI ? groupURI.c_str() : NULL,
   1281             haveGroupLanguage ? groupLanguage.c_str() : NULL,
   1282             flags);
   1283 }
   1284 
   1285 // static
   1286 status_t M3UParser::parseDiscontinuitySequence(const AString &line, size_t *seq) {
   1287     ssize_t colonPos = line.find(":");
   1288 
   1289     if (colonPos < 0) {
   1290         return ERROR_MALFORMED;
   1291     }
   1292 
   1293     int32_t x;
   1294     status_t err = ParseInt32(line.c_str() + colonPos + 1, &x);
   1295     if (err != OK) {
   1296         return err;
   1297     }
   1298 
   1299     if (x < 0) {
   1300         return ERROR_MALFORMED;
   1301     }
   1302 
   1303     if (seq) {
   1304         *seq = x;
   1305     }
   1306     return OK;
   1307 }
   1308 
   1309 // static
   1310 status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
   1311     char *end;
   1312     long lval = strtol(s, &end, 10);
   1313 
   1314     if (end == s || (*end != '\0' && *end != ',')) {
   1315         return ERROR_MALFORMED;
   1316     }
   1317 
   1318     *x = (int32_t)lval;
   1319 
   1320     return OK;
   1321 }
   1322 
   1323 // static
   1324 status_t M3UParser::ParseDouble(const char *s, double *x) {
   1325     char *end;
   1326     double dval = strtod(s, &end);
   1327 
   1328     if (end == s || (*end != '\0' && *end != ',')) {
   1329         return ERROR_MALFORMED;
   1330     }
   1331 
   1332     *x = dval;
   1333 
   1334     return OK;
   1335 }
   1336 
   1337 // static
   1338 bool M3UParser::isQuotedString(const AString &str) {
   1339     if (str.size() < 2
   1340             || str.c_str()[0] != '"'
   1341             || str.c_str()[str.size() - 1] != '"') {
   1342         return false;
   1343     }
   1344     return true;
   1345 }
   1346 
   1347 // static
   1348 AString M3UParser::unquoteString(const AString &str) {
   1349      if (!isQuotedString(str)) {
   1350          return str;
   1351      }
   1352      return AString(str, 1, str.size() - 2);
   1353 }
   1354 
   1355 // static
   1356 bool M3UParser::codecIsType(const AString &codec, const char *type) {
   1357     if (codec.size() < 4) {
   1358         return false;
   1359     }
   1360     const char *c = codec.c_str();
   1361     switch (FOURCC(c[0], c[1], c[2], c[3])) {
   1362         // List extracted from http://www.mp4ra.org/codecs.html
   1363         case 'ac-3':
   1364         case 'alac':
   1365         case 'dra1':
   1366         case 'dtsc':
   1367         case 'dtse':
   1368         case 'dtsh':
   1369         case 'dtsl':
   1370         case 'ec-3':
   1371         case 'enca':
   1372         case 'g719':
   1373         case 'g726':
   1374         case 'm4ae':
   1375         case 'mlpa':
   1376         case 'mp4a':
   1377         case 'raw ':
   1378         case 'samr':
   1379         case 'sawb':
   1380         case 'sawp':
   1381         case 'sevc':
   1382         case 'sqcp':
   1383         case 'ssmv':
   1384         case 'twos':
   1385         case 'agsm':
   1386         case 'alaw':
   1387         case 'dvi ':
   1388         case 'fl32':
   1389         case 'fl64':
   1390         case 'ima4':
   1391         case 'in24':
   1392         case 'in32':
   1393         case 'lpcm':
   1394         case 'Qclp':
   1395         case 'QDM2':
   1396         case 'QDMC':
   1397         case 'ulaw':
   1398         case 'vdva':
   1399         case 'ac-4':
   1400         case 'Opus':
   1401         case 'a3ds':
   1402         case 'dts+':
   1403         case 'dts-':
   1404         case 'dtsx':
   1405         case 'dtsy':
   1406         case 'ec+3':
   1407         case 'mha1':
   1408         case 'mha2':
   1409         case 'mhm1':
   1410         case 'mhm2':
   1411         case 'sevs':
   1412             return !strcmp("audio", type);
   1413 
   1414         case 'avc1':
   1415         case 'avc2':
   1416         case 'avcp':
   1417         case 'drac':
   1418         case 'encv':
   1419         case 'mjp2':
   1420         case 'mp4v':
   1421         case 'mvc1':
   1422         case 'mvc2':
   1423         case 'resv':
   1424         case 's263':
   1425         case 'svc1':
   1426         case 'vc-1':
   1427         case 'CFHD':
   1428         case 'civd':
   1429         case 'DV10':
   1430         case 'dvh5':
   1431         case 'dvh6':
   1432         case 'dvhp':
   1433         case 'DVOO':
   1434         case 'DVOR':
   1435         case 'DVTV':
   1436         case 'DVVT':
   1437         case 'flic':
   1438         case 'gif ':
   1439         case 'h261':
   1440         case 'h263':
   1441         case 'HD10':
   1442         case 'jpeg':
   1443         case 'M105':
   1444         case 'mjpa':
   1445         case 'mjpb':
   1446         case 'png ':
   1447         case 'PNTG':
   1448         case 'rle ':
   1449         case 'rpza':
   1450         case 'Shr0':
   1451         case 'Shr1':
   1452         case 'Shr2':
   1453         case 'Shr3':
   1454         case 'Shr4':
   1455         case 'SVQ1':
   1456         case 'SVQ3':
   1457         case 'tga ':
   1458         case 'tiff':
   1459         case 'WRLE':
   1460         case 'a3d1':
   1461         case 'a3d2':
   1462         case 'a3d3':
   1463         case 'a3d4':
   1464         case 'avc3':
   1465         case 'avc4':
   1466         case 'dva1':
   1467         case 'dvav':
   1468         case 'dvh1':
   1469         case 'dvhe':
   1470         case 'hev1':
   1471         case 'hev2':
   1472         case 'hvc1':
   1473         case 'hvc2':
   1474         case 'hvt1':
   1475         case 'lhe1':
   1476         case 'lht1':
   1477         case 'lhv1':
   1478         case 'mjpg':
   1479         case 'mvc3':
   1480         case 'mvc4':
   1481         case 'mvd1':
   1482         case 'mvd2':
   1483         case 'mvd3':
   1484         case 'mvd4':
   1485         case 'rv60':
   1486         case 'svc2':
   1487         case 'vp08':
   1488         case 'vp09':
   1489             return !strcmp("video", type);
   1490 
   1491         default:
   1492             return false;
   1493     }
   1494 }
   1495 
   1496 }  // namespace android
   1497