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