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