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