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