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