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