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