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 90 M3UParser::MediaGroup::MediaGroup(Type type) 91 : mType(type), 92 mSelectedIndex(-1) { 93 } 94 95 M3UParser::MediaGroup::~MediaGroup() { 96 } 97 98 M3UParser::MediaGroup::Type M3UParser::MediaGroup::type() const { 99 return mType; 100 } 101 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 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 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 194 size_t M3UParser::MediaGroup::countTracks() const { 195 return mMediaItems.size(); 196 } 197 198 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 232 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 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 268 M3UParser::~M3UParser() { 269 } 270 271 status_t M3UParser::initCheck() const { 272 return mInitCheck; 273 } 274 275 bool M3UParser::isExtM3U() const { 276 return mIsExtM3U; 277 } 278 279 bool M3UParser::isVariantPlaylist() const { 280 return mIsVariantPlaylist; 281 } 282 283 bool M3UParser::isComplete() const { 284 return mIsComplete; 285 } 286 287 bool M3UParser::isEvent() const { 288 return mIsEvent; 289 } 290 291 size_t M3UParser::getDiscontinuitySeq() const { 292 return mDiscontinuitySeq; 293 } 294 295 int64_t M3UParser::getTargetDuration() const { 296 return mTargetDurationUs; 297 } 298 299 int32_t M3UParser::getFirstSeqNumber() const { 300 return mFirstSeqNumber; 301 } 302 303 void M3UParser::getSeqNumberRange(int32_t *firstSeq, int32_t *lastSeq) const { 304 *firstSeq = mFirstSeqNumber; 305 *lastSeq = mLastSeqNumber; 306 } 307 308 sp<AMessage> M3UParser::meta() { 309 return mMeta; 310 } 311 312 size_t M3UParser::size() { 313 return mItems.size(); 314 } 315 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 340 void M3UParser::pickRandomMediaItems() { 341 for (size_t i = 0; i < mMediaGroups.size(); ++i) { 342 mMediaGroups.valueAt(i)->pickRandomMediaItems(); 343 } 344 } 345 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 362 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 370 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 382 ssize_t M3UParser::getSelectedIndex() const { 383 return mSelectedIndex; 384 } 385 386 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 418 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 479 bool M3UParser::hasType(size_t index, const char *key) const { 480 return getTypeURI(index, key, NULL /* uri */); 481 } 482 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 556 AString M3UParser::Item::makeURL(const char *baseURL) const { 557 AString out; 558 CHECK(MakeURL(baseURL, mURI.c_str(), &out)); 559 return out; 560 } 561 562 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 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 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 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. 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 824 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 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 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 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 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 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 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 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 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 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