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")) {
    607                 if (mIsVariantPlaylist) {
    608                     return ERROR_MALFORMED;
    609                 }
    610                 if (itemMeta == NULL) {
    611                     itemMeta = new AMessage;
    612                 }
    613                 itemMeta->setInt32("discontinuity", true);
    614                 ++mDiscontinuityCount;
    615             } else if (line.startsWith("#EXT-X-STREAM-INF")) {
    616                 if (mMeta != NULL) {
    617                     return ERROR_MALFORMED;
    618                 }
    619                 mIsVariantPlaylist = true;
    620                 err = parseStreamInf(line, &itemMeta);
    621             } else if (line.startsWith("#EXT-X-BYTERANGE")) {
    622                 if (mIsVariantPlaylist) {
    623                     return ERROR_MALFORMED;
    624                 }
    625 
    626                 uint64_t length, offset;
    627                 err = parseByteRange(line, segmentRangeOffset, &length, &offset);
    628 
    629                 if (err == OK) {
    630                     if (itemMeta == NULL) {
    631                         itemMeta = new AMessage;
    632                     }
    633 
    634                     itemMeta->setInt64("range-offset", offset);
    635                     itemMeta->setInt64("range-length", length);
    636 
    637                     segmentRangeOffset = offset + length;
    638                 }
    639             } else if (line.startsWith("#EXT-X-MEDIA")) {
    640                 err = parseMedia(line);
    641             } else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) {
    642                 if (mIsVariantPlaylist) {
    643                     return ERROR_MALFORMED;
    644                 }
    645                 size_t seq;
    646                 err = parseDiscontinuitySequence(line, &seq);
    647                 if (err == OK) {
    648                     mDiscontinuitySeq = seq;
    649                 }
    650             }
    651 
    652             if (err != OK) {
    653                 return err;
    654             }
    655         }
    656 
    657         if (!line.startsWith("#")) {
    658             if (!mIsVariantPlaylist) {
    659                 int64_t durationUs;
    660                 if (itemMeta == NULL
    661                         || !itemMeta->findInt64("durationUs", &durationUs)) {
    662                     return ERROR_MALFORMED;
    663                 }
    664                 itemMeta->setInt32("discontinuity-sequence",
    665                         mDiscontinuitySeq + mDiscontinuityCount);
    666             }
    667 
    668             mItems.push();
    669             Item *item = &mItems.editItemAt(mItems.size() - 1);
    670 
    671             CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI));
    672 
    673             item->mMeta = itemMeta;
    674 
    675             itemMeta.clear();
    676         }
    677 
    678         offset = offsetLF + 1;
    679         ++lineNo;
    680     }
    681 
    682     // error checking of all fields that's required to appear once
    683     // (currently only checking "target-duration"), and
    684     // initialization of playlist properties (eg. mTargetDurationUs)
    685     if (!mIsVariantPlaylist) {
    686         int32_t targetDurationSecs;
    687         if (mMeta == NULL || !mMeta->findInt32(
    688                 "target-duration", &targetDurationSecs)) {
    689             ALOGE("Media playlist missing #EXT-X-TARGETDURATION");
    690             return ERROR_MALFORMED;
    691         }
    692         mTargetDurationUs = targetDurationSecs * 1000000ll;
    693 
    694         mFirstSeqNumber = 0;
    695         if (mMeta != NULL) {
    696             mMeta->findInt32("media-sequence", &mFirstSeqNumber);
    697         }
    698         mLastSeqNumber = mFirstSeqNumber + mItems.size() - 1;
    699     }
    700 
    701     return OK;
    702 }
    703 
    704 // static
    705 status_t M3UParser::parseMetaData(
    706         const AString &line, sp<AMessage> *meta, const char *key) {
    707     ssize_t colonPos = line.find(":");
    708 
    709     if (colonPos < 0) {
    710         return ERROR_MALFORMED;
    711     }
    712 
    713     int32_t x;
    714     status_t err = ParseInt32(line.c_str() + colonPos + 1, &x);
    715 
    716     if (err != OK) {
    717         return err;
    718     }
    719 
    720     if (meta->get() == NULL) {
    721         *meta = new AMessage;
    722     }
    723     (*meta)->setInt32(key, x);
    724 
    725     return OK;
    726 }
    727 
    728 // static
    729 status_t M3UParser::parseMetaDataDuration(
    730         const AString &line, sp<AMessage> *meta, const char *key) {
    731     ssize_t colonPos = line.find(":");
    732 
    733     if (colonPos < 0) {
    734         return ERROR_MALFORMED;
    735     }
    736 
    737     double x;
    738     status_t err = ParseDouble(line.c_str() + colonPos + 1, &x);
    739 
    740     if (err != OK) {
    741         return err;
    742     }
    743 
    744     if (meta->get() == NULL) {
    745         *meta = new AMessage;
    746     }
    747     (*meta)->setInt64(key, (int64_t)(x * 1E6));
    748 
    749     return OK;
    750 }
    751 
    752 // Find the next occurence of the character "what" at or after "offset",
    753 // but ignore occurences between quotation marks.
    754 // Return the index of the occurrence or -1 if not found.
    755 static ssize_t FindNextUnquoted(
    756         const AString &line, char what, size_t offset) {
    757     CHECK_NE((int)what, (int)'"');
    758 
    759     bool quoted = false;
    760     while (offset < line.size()) {
    761         char c = line.c_str()[offset];
    762 
    763         if (c == '"') {
    764             quoted = !quoted;
    765         } else if (c == what && !quoted) {
    766             return offset;
    767         }
    768 
    769         ++offset;
    770     }
    771 
    772     return -1;
    773 }
    774 
    775 status_t M3UParser::parseStreamInf(
    776         const AString &line, sp<AMessage> *meta) const {
    777     ssize_t colonPos = line.find(":");
    778 
    779     if (colonPos < 0) {
    780         return ERROR_MALFORMED;
    781     }
    782 
    783     size_t offset = colonPos + 1;
    784 
    785     while (offset < line.size()) {
    786         ssize_t end = FindNextUnquoted(line, ',', offset);
    787         if (end < 0) {
    788             end = line.size();
    789         }
    790 
    791         AString attr(line, offset, end - offset);
    792         attr.trim();
    793 
    794         offset = end + 1;
    795 
    796         ssize_t equalPos = attr.find("=");
    797         if (equalPos < 0) {
    798             continue;
    799         }
    800 
    801         AString key(attr, 0, equalPos);
    802         key.trim();
    803 
    804         AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
    805         val.trim();
    806 
    807         ALOGV("key=%s value=%s", key.c_str(), val.c_str());
    808 
    809         if (!strcasecmp("bandwidth", key.c_str())) {
    810             const char *s = val.c_str();
    811             char *end;
    812             unsigned long x = strtoul(s, &end, 10);
    813 
    814             if (end == s || *end != '\0') {
    815                 // malformed
    816                 continue;
    817             }
    818 
    819             if (meta->get() == NULL) {
    820                 *meta = new AMessage;
    821             }
    822             (*meta)->setInt32("bandwidth", x);
    823         } else if (!strcasecmp("codecs", key.c_str())) {
    824             if (!isQuotedString(val)) {
    825                 ALOGE("Expected quoted string for %s attribute, "
    826                       "got '%s' instead.",
    827                       key.c_str(), val.c_str());;
    828 
    829                 return ERROR_MALFORMED;
    830             }
    831 
    832             key.tolower();
    833             const AString &codecs = unquoteString(val);
    834             if (meta->get() == NULL) {
    835                 *meta = new AMessage;
    836             }
    837             (*meta)->setString(key.c_str(), codecs.c_str());
    838         } else if (!strcasecmp("resolution", key.c_str())) {
    839             const char *s = val.c_str();
    840             char *end;
    841             unsigned long width = strtoul(s, &end, 10);
    842 
    843             if (end == s || *end != 'x') {
    844                 // malformed
    845                 continue;
    846             }
    847 
    848             s = end + 1;
    849             unsigned long height = strtoul(s, &end, 10);
    850 
    851             if (end == s || *end != '\0') {
    852                 // malformed
    853                 continue;
    854             }
    855 
    856             if (meta->get() == NULL) {
    857                 *meta = new AMessage;
    858             }
    859             (*meta)->setInt32("width", width);
    860             (*meta)->setInt32("height", height);
    861         } else if (!strcasecmp("audio", key.c_str())
    862                 || !strcasecmp("video", key.c_str())
    863                 || !strcasecmp("subtitles", key.c_str())) {
    864             if (!isQuotedString(val)) {
    865                 ALOGE("Expected quoted string for %s attribute, "
    866                       "got '%s' instead.",
    867                       key.c_str(), val.c_str());
    868 
    869                 return ERROR_MALFORMED;
    870             }
    871 
    872             const AString &groupID = unquoteString(val);
    873             ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
    874 
    875             if (groupIndex < 0) {
    876                 ALOGE("Undefined media group '%s' referenced in stream info.",
    877                       groupID.c_str());
    878 
    879                 return ERROR_MALFORMED;
    880             }
    881 
    882             key.tolower();
    883             if (meta->get() == NULL) {
    884                 *meta = new AMessage;
    885             }
    886             (*meta)->setString(key.c_str(), groupID.c_str());
    887         }
    888     }
    889 
    890     return OK;
    891 }
    892 
    893 // static
    894 status_t M3UParser::parseCipherInfo(
    895         const AString &line, sp<AMessage> *meta, const AString &baseURI) {
    896     ssize_t colonPos = line.find(":");
    897 
    898     if (colonPos < 0) {
    899         return ERROR_MALFORMED;
    900     }
    901 
    902     size_t offset = colonPos + 1;
    903 
    904     while (offset < line.size()) {
    905         ssize_t end = FindNextUnquoted(line, ',', offset);
    906         if (end < 0) {
    907             end = line.size();
    908         }
    909 
    910         AString attr(line, offset, end - offset);
    911         attr.trim();
    912 
    913         offset = end + 1;
    914 
    915         ssize_t equalPos = attr.find("=");
    916         if (equalPos < 0) {
    917             continue;
    918         }
    919 
    920         AString key(attr, 0, equalPos);
    921         key.trim();
    922 
    923         AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
    924         val.trim();
    925 
    926         ALOGV("key=%s value=%s", key.c_str(), val.c_str());
    927 
    928         key.tolower();
    929 
    930         if (key == "method" || key == "uri" || key == "iv") {
    931             if (meta->get() == NULL) {
    932                 *meta = new AMessage;
    933             }
    934 
    935             if (key == "uri") {
    936                 if (val.size() >= 2
    937                         && val.c_str()[0] == '"'
    938                         && val.c_str()[val.size() - 1] == '"') {
    939                     // Remove surrounding quotes.
    940                     AString tmp(val, 1, val.size() - 2);
    941                     val = tmp;
    942                 }
    943 
    944                 AString absURI;
    945                 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) {
    946                     val = absURI;
    947                 } else {
    948                     ALOGE("failed to make absolute url for %s.",
    949                             uriDebugString(baseURI).c_str());
    950                 }
    951             }
    952 
    953             key.insert(AString("cipher-"), 0);
    954 
    955             (*meta)->setString(key.c_str(), val.c_str(), val.size());
    956         }
    957     }
    958 
    959     return OK;
    960 }
    961 
    962 // static
    963 status_t M3UParser::parseByteRange(
    964         const AString &line, uint64_t curOffset,
    965         uint64_t *length, uint64_t *offset) {
    966     ssize_t colonPos = line.find(":");
    967 
    968     if (colonPos < 0) {
    969         return ERROR_MALFORMED;
    970     }
    971 
    972     ssize_t atPos = line.find("@", colonPos + 1);
    973 
    974     AString lenStr;
    975     if (atPos < 0) {
    976         lenStr = AString(line, colonPos + 1, line.size() - colonPos - 1);
    977     } else {
    978         lenStr = AString(line, colonPos + 1, atPos - colonPos - 1);
    979     }
    980 
    981     lenStr.trim();
    982 
    983     const char *s = lenStr.c_str();
    984     char *end;
    985     *length = strtoull(s, &end, 10);
    986 
    987     if (s == end || *end != '\0') {
    988         return ERROR_MALFORMED;
    989     }
    990 
    991     if (atPos >= 0) {
    992         AString offStr = AString(line, atPos + 1, line.size() - atPos - 1);
    993         offStr.trim();
    994 
    995         const char *s = offStr.c_str();
    996         *offset = strtoull(s, &end, 10);
    997 
    998         if (s == end || *end != '\0') {
    999             return ERROR_MALFORMED;
   1000         }
   1001     } else {
   1002         *offset = curOffset;
   1003     }
   1004 
   1005     return OK;
   1006 }
   1007 
   1008 status_t M3UParser::parseMedia(const AString &line) {
   1009     ssize_t colonPos = line.find(":");
   1010 
   1011     if (colonPos < 0) {
   1012         return ERROR_MALFORMED;
   1013     }
   1014 
   1015     bool haveGroupType = false;
   1016     MediaGroup::Type groupType = MediaGroup::TYPE_AUDIO;
   1017 
   1018     bool haveGroupID = false;
   1019     AString groupID;
   1020 
   1021     bool haveGroupLanguage = false;
   1022     AString groupLanguage;
   1023 
   1024     bool haveGroupName = false;
   1025     AString groupName;
   1026 
   1027     bool haveGroupAutoselect = false;
   1028     bool groupAutoselect = false;
   1029 
   1030     bool haveGroupDefault = false;
   1031     bool groupDefault = false;
   1032 
   1033     bool haveGroupForced = false;
   1034     bool groupForced = false;
   1035 
   1036     bool haveGroupURI = false;
   1037     AString groupURI;
   1038 
   1039     size_t offset = colonPos + 1;
   1040 
   1041     while (offset < line.size()) {
   1042         ssize_t end = FindNextUnquoted(line, ',', offset);
   1043         if (end < 0) {
   1044             end = line.size();
   1045         }
   1046 
   1047         AString attr(line, offset, end - offset);
   1048         attr.trim();
   1049 
   1050         offset = end + 1;
   1051 
   1052         ssize_t equalPos = attr.find("=");
   1053         if (equalPos < 0) {
   1054             continue;
   1055         }
   1056 
   1057         AString key(attr, 0, equalPos);
   1058         key.trim();
   1059 
   1060         AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
   1061         val.trim();
   1062 
   1063         ALOGV("key=%s value=%s", key.c_str(), val.c_str());
   1064 
   1065         if (!strcasecmp("type", key.c_str())) {
   1066             if (!strcasecmp("subtitles", val.c_str())) {
   1067                 groupType = MediaGroup::TYPE_SUBS;
   1068             } else if (!strcasecmp("audio", val.c_str())) {
   1069                 groupType = MediaGroup::TYPE_AUDIO;
   1070             } else if (!strcasecmp("video", val.c_str())) {
   1071                 groupType = MediaGroup::TYPE_VIDEO;
   1072             } else if (!strcasecmp("closed-captions", val.c_str())){
   1073                 groupType = MediaGroup::TYPE_CC;
   1074             } else {
   1075                 ALOGE("Invalid media group type '%s'", val.c_str());
   1076                 return ERROR_MALFORMED;
   1077             }
   1078 
   1079             haveGroupType = true;
   1080         } else if (!strcasecmp("group-id", key.c_str())) {
   1081             if (val.size() < 2
   1082                     || val.c_str()[0] != '"'
   1083                     || val.c_str()[val.size() - 1] != '"') {
   1084                 ALOGE("Expected quoted string for GROUP-ID, got '%s' instead.",
   1085                       val.c_str());
   1086 
   1087                 return ERROR_MALFORMED;
   1088             }
   1089 
   1090             groupID.setTo(val, 1, val.size() - 2);
   1091             haveGroupID = true;
   1092         } else if (!strcasecmp("language", key.c_str())) {
   1093             if (val.size() < 2
   1094                     || val.c_str()[0] != '"'
   1095                     || val.c_str()[val.size() - 1] != '"') {
   1096                 ALOGE("Expected quoted string for LANGUAGE, got '%s' instead.",
   1097                       val.c_str());
   1098 
   1099                 return ERROR_MALFORMED;
   1100             }
   1101 
   1102             groupLanguage.setTo(val, 1, val.size() - 2);
   1103             haveGroupLanguage = true;
   1104         } else if (!strcasecmp("name", key.c_str())) {
   1105             if (val.size() < 2
   1106                     || val.c_str()[0] != '"'
   1107                     || val.c_str()[val.size() - 1] != '"') {
   1108                 ALOGE("Expected quoted string for NAME, got '%s' instead.",
   1109                       val.c_str());
   1110 
   1111                 return ERROR_MALFORMED;
   1112             }
   1113 
   1114             groupName.setTo(val, 1, val.size() - 2);
   1115             haveGroupName = true;
   1116         } else if (!strcasecmp("autoselect", key.c_str())) {
   1117             groupAutoselect = false;
   1118             if (!strcasecmp("YES", val.c_str())) {
   1119                 groupAutoselect = true;
   1120             } else if (!strcasecmp("NO", val.c_str())) {
   1121                 groupAutoselect = false;
   1122             } else {
   1123                 ALOGE("Expected YES or NO for AUTOSELECT attribute, "
   1124                       "got '%s' instead.",
   1125                       val.c_str());
   1126 
   1127                 return ERROR_MALFORMED;
   1128             }
   1129 
   1130             haveGroupAutoselect = true;
   1131         } else if (!strcasecmp("default", key.c_str())) {
   1132             groupDefault = false;
   1133             if (!strcasecmp("YES", val.c_str())) {
   1134                 groupDefault = true;
   1135             } else if (!strcasecmp("NO", val.c_str())) {
   1136                 groupDefault = false;
   1137             } else {
   1138                 ALOGE("Expected YES or NO for DEFAULT attribute, "
   1139                       "got '%s' instead.",
   1140                       val.c_str());
   1141 
   1142                 return ERROR_MALFORMED;
   1143             }
   1144 
   1145             haveGroupDefault = true;
   1146         } else if (!strcasecmp("forced", key.c_str())) {
   1147             groupForced = false;
   1148             if (!strcasecmp("YES", val.c_str())) {
   1149                 groupForced = true;
   1150             } else if (!strcasecmp("NO", val.c_str())) {
   1151                 groupForced = false;
   1152             } else {
   1153                 ALOGE("Expected YES or NO for FORCED attribute, "
   1154                       "got '%s' instead.",
   1155                       val.c_str());
   1156 
   1157                 return ERROR_MALFORMED;
   1158             }
   1159 
   1160             haveGroupForced = true;
   1161         } else if (!strcasecmp("uri", key.c_str())) {
   1162             if (val.size() < 2
   1163                     || val.c_str()[0] != '"'
   1164                     || val.c_str()[val.size() - 1] != '"') {
   1165                 ALOGE("Expected quoted string for URI, got '%s' instead.",
   1166                       val.c_str());
   1167 
   1168                 return ERROR_MALFORMED;
   1169             }
   1170 
   1171             AString tmp(val, 1, val.size() - 2);
   1172 
   1173             if (!MakeURL(mBaseURI.c_str(), tmp.c_str(), &groupURI)) {
   1174                 ALOGI("Failed to make absolute URI from '%s'.", tmp.c_str());
   1175             }
   1176 
   1177             haveGroupURI = true;
   1178         }
   1179     }
   1180 
   1181     if (!haveGroupType || !haveGroupID || !haveGroupName) {
   1182         ALOGE("Incomplete EXT-X-MEDIA element.");
   1183         return ERROR_MALFORMED;
   1184     }
   1185 
   1186     if (groupType == MediaGroup::TYPE_CC) {
   1187         // TODO: ignore this for now.
   1188         // CC track will be detected by CCDecoder. But we still need to
   1189         // pass the CC track flags (lang, auto) to the app in the future.
   1190         return OK;
   1191     }
   1192 
   1193     uint32_t flags = 0;
   1194     if (haveGroupAutoselect && groupAutoselect) {
   1195         flags |= MediaGroup::FLAG_AUTOSELECT;
   1196     }
   1197     if (haveGroupDefault && groupDefault) {
   1198         flags |= MediaGroup::FLAG_DEFAULT;
   1199     }
   1200     if (haveGroupForced) {
   1201         if (groupType != MediaGroup::TYPE_SUBS) {
   1202             ALOGE("The FORCED attribute MUST not be present on anything "
   1203                   "but SUBS media.");
   1204 
   1205             return ERROR_MALFORMED;
   1206         }
   1207 
   1208         if (groupForced) {
   1209             flags |= MediaGroup::FLAG_FORCED;
   1210         }
   1211     }
   1212     if (haveGroupLanguage) {
   1213         flags |= MediaGroup::FLAG_HAS_LANGUAGE;
   1214     }
   1215     if (haveGroupURI) {
   1216         flags |= MediaGroup::FLAG_HAS_URI;
   1217     }
   1218 
   1219     ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
   1220     sp<MediaGroup> group;
   1221 
   1222     if (groupIndex < 0) {
   1223         group = new MediaGroup(groupType);
   1224         mMediaGroups.add(groupID, group);
   1225     } else {
   1226         group = mMediaGroups.valueAt(groupIndex);
   1227 
   1228         if (group->type() != groupType) {
   1229             ALOGE("Attempt to put media item under group of different type "
   1230                   "(groupType = %d, item type = %d",
   1231                   group->type(),
   1232                   groupType);
   1233 
   1234             return ERROR_MALFORMED;
   1235         }
   1236     }
   1237 
   1238     return group->addMedia(
   1239             groupName.c_str(),
   1240             haveGroupURI ? groupURI.c_str() : NULL,
   1241             haveGroupLanguage ? groupLanguage.c_str() : NULL,
   1242             flags);
   1243 }
   1244 
   1245 // static
   1246 status_t M3UParser::parseDiscontinuitySequence(const AString &line, size_t *seq) {
   1247     ssize_t colonPos = line.find(":");
   1248 
   1249     if (colonPos < 0) {
   1250         return ERROR_MALFORMED;
   1251     }
   1252 
   1253     int32_t x;
   1254     status_t err = ParseInt32(line.c_str() + colonPos + 1, &x);
   1255     if (err != OK) {
   1256         return err;
   1257     }
   1258 
   1259     if (x < 0) {
   1260         return ERROR_MALFORMED;
   1261     }
   1262 
   1263     if (seq) {
   1264         *seq = x;
   1265     }
   1266     return OK;
   1267 }
   1268 
   1269 // static
   1270 status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
   1271     char *end;
   1272     long lval = strtol(s, &end, 10);
   1273 
   1274     if (end == s || (*end != '\0' && *end != ',')) {
   1275         return ERROR_MALFORMED;
   1276     }
   1277 
   1278     *x = (int32_t)lval;
   1279 
   1280     return OK;
   1281 }
   1282 
   1283 // static
   1284 status_t M3UParser::ParseDouble(const char *s, double *x) {
   1285     char *end;
   1286     double dval = strtod(s, &end);
   1287 
   1288     if (end == s || (*end != '\0' && *end != ',')) {
   1289         return ERROR_MALFORMED;
   1290     }
   1291 
   1292     *x = dval;
   1293 
   1294     return OK;
   1295 }
   1296 
   1297 // static
   1298 bool M3UParser::isQuotedString(const AString &str) {
   1299     if (str.size() < 2
   1300             || str.c_str()[0] != '"'
   1301             || str.c_str()[str.size() - 1] != '"') {
   1302         return false;
   1303     }
   1304     return true;
   1305 }
   1306 
   1307 // static
   1308 AString M3UParser::unquoteString(const AString &str) {
   1309      if (!isQuotedString(str)) {
   1310          return str;
   1311      }
   1312      return AString(str, 1, str.size() - 2);
   1313 }
   1314 
   1315 // static
   1316 bool M3UParser::codecIsType(const AString &codec, const char *type) {
   1317     if (codec.size() < 4) {
   1318         return false;
   1319     }
   1320     const char *c = codec.c_str();
   1321     switch (FOURCC(c[0], c[1], c[2], c[3])) {
   1322         // List extracted from http://www.mp4ra.org/codecs.html
   1323         case 'ac-3':
   1324         case 'alac':
   1325         case 'dra1':
   1326         case 'dtsc':
   1327         case 'dtse':
   1328         case 'dtsh':
   1329         case 'dtsl':
   1330         case 'ec-3':
   1331         case 'enca':
   1332         case 'g719':
   1333         case 'g726':
   1334         case 'm4ae':
   1335         case 'mlpa':
   1336         case 'mp4a':
   1337         case 'raw ':
   1338         case 'samr':
   1339         case 'sawb':
   1340         case 'sawp':
   1341         case 'sevc':
   1342         case 'sqcp':
   1343         case 'ssmv':
   1344         case 'twos':
   1345         case 'agsm':
   1346         case 'alaw':
   1347         case 'dvi ':
   1348         case 'fl32':
   1349         case 'fl64':
   1350         case 'ima4':
   1351         case 'in24':
   1352         case 'in32':
   1353         case 'lpcm':
   1354         case 'Qclp':
   1355         case 'QDM2':
   1356         case 'QDMC':
   1357         case 'ulaw':
   1358         case 'vdva':
   1359             return !strcmp("audio", type);
   1360 
   1361         case 'avc1':
   1362         case 'avc2':
   1363         case 'avcp':
   1364         case 'drac':
   1365         case 'encv':
   1366         case 'mjp2':
   1367         case 'mp4v':
   1368         case 'mvc1':
   1369         case 'mvc2':
   1370         case 'resv':
   1371         case 's263':
   1372         case 'svc1':
   1373         case 'vc-1':
   1374         case 'CFHD':
   1375         case 'civd':
   1376         case 'DV10':
   1377         case 'dvh5':
   1378         case 'dvh6':
   1379         case 'dvhp':
   1380         case 'DVOO':
   1381         case 'DVOR':
   1382         case 'DVTV':
   1383         case 'DVVT':
   1384         case 'flic':
   1385         case 'gif ':
   1386         case 'h261':
   1387         case 'h263':
   1388         case 'HD10':
   1389         case 'jpeg':
   1390         case 'M105':
   1391         case 'mjpa':
   1392         case 'mjpb':
   1393         case 'png ':
   1394         case 'PNTG':
   1395         case 'rle ':
   1396         case 'rpza':
   1397         case 'Shr0':
   1398         case 'Shr1':
   1399         case 'Shr2':
   1400         case 'Shr3':
   1401         case 'Shr4':
   1402         case 'SVQ1':
   1403         case 'SVQ3':
   1404         case 'tga ':
   1405         case 'tiff':
   1406         case 'WRLE':
   1407             return !strcmp("video", type);
   1408 
   1409         default:
   1410             return false;
   1411     }
   1412 }
   1413 
   1414 }  // namespace android
   1415