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