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 "include/M3UParser.h" 22 23 #include <media/stagefright/foundation/ADebug.h> 24 #include <media/stagefright/foundation/AMessage.h> 25 #include <media/stagefright/MediaErrors.h> 26 27 namespace android { 28 29 M3UParser::M3UParser( 30 const char *baseURI, const void *data, size_t size) 31 : mInitCheck(NO_INIT), 32 mBaseURI(baseURI), 33 mIsExtM3U(false), 34 mIsVariantPlaylist(false), 35 mIsComplete(false) { 36 mInitCheck = parse(data, size); 37 } 38 39 M3UParser::~M3UParser() { 40 } 41 42 status_t M3UParser::initCheck() const { 43 return mInitCheck; 44 } 45 46 bool M3UParser::isExtM3U() const { 47 return mIsExtM3U; 48 } 49 50 bool M3UParser::isVariantPlaylist() const { 51 return mIsVariantPlaylist; 52 } 53 54 bool M3UParser::isComplete() const { 55 return mIsComplete; 56 } 57 58 sp<AMessage> M3UParser::meta() { 59 return mMeta; 60 } 61 62 size_t M3UParser::size() { 63 return mItems.size(); 64 } 65 66 bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { 67 if (uri) { 68 uri->clear(); 69 } 70 71 if (meta) { 72 *meta = NULL; 73 } 74 75 if (index >= mItems.size()) { 76 return false; 77 } 78 79 if (uri) { 80 *uri = mItems.itemAt(index).mURI; 81 } 82 83 if (meta) { 84 *meta = mItems.itemAt(index).mMeta; 85 } 86 87 return true; 88 } 89 90 static bool MakeURL(const char *baseURL, const char *url, AString *out) { 91 out->clear(); 92 93 if (strncasecmp("http://", baseURL, 7) 94 && strncasecmp("https://", baseURL, 8) 95 && strncasecmp("file://", baseURL, 7)) { 96 // Base URL must be absolute 97 return false; 98 } 99 100 if (!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) { 101 // "url" is already an absolute URL, ignore base URL. 102 out->setTo(url); 103 104 ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 105 106 return true; 107 } 108 109 if (url[0] == '/') { 110 // URL is an absolute path. 111 112 char *protocolEnd = strstr(baseURL, "//") + 2; 113 char *pathStart = strchr(protocolEnd, '/'); 114 115 if (pathStart != NULL) { 116 out->setTo(baseURL, pathStart - baseURL); 117 } else { 118 out->setTo(baseURL); 119 } 120 121 out->append(url); 122 } else { 123 // URL is a relative path 124 125 size_t n = strlen(baseURL); 126 if (baseURL[n - 1] == '/') { 127 out->setTo(baseURL); 128 out->append(url); 129 } else { 130 const char *slashPos = strrchr(baseURL, '/'); 131 132 if (slashPos > &baseURL[6]) { 133 out->setTo(baseURL, slashPos - baseURL); 134 } else { 135 out->setTo(baseURL); 136 } 137 138 out->append("/"); 139 out->append(url); 140 } 141 } 142 143 ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 144 145 return true; 146 } 147 148 status_t M3UParser::parse(const void *_data, size_t size) { 149 int32_t lineNo = 0; 150 151 sp<AMessage> itemMeta; 152 153 const char *data = (const char *)_data; 154 size_t offset = 0; 155 uint64_t segmentRangeOffset = 0; 156 while (offset < size) { 157 size_t offsetLF = offset; 158 while (offsetLF < size && data[offsetLF] != '\n') { 159 ++offsetLF; 160 } 161 if (offsetLF >= size) { 162 break; 163 } 164 165 AString line; 166 if (offsetLF > offset && data[offsetLF - 1] == '\r') { 167 line.setTo(&data[offset], offsetLF - offset - 1); 168 } else { 169 line.setTo(&data[offset], offsetLF - offset); 170 } 171 172 // ALOGI("#%s#", line.c_str()); 173 174 if (line.empty()) { 175 offset = offsetLF + 1; 176 continue; 177 } 178 179 if (lineNo == 0 && line == "#EXTM3U") { 180 mIsExtM3U = true; 181 } 182 183 if (mIsExtM3U) { 184 status_t err = OK; 185 186 if (line.startsWith("#EXT-X-TARGETDURATION")) { 187 if (mIsVariantPlaylist) { 188 return ERROR_MALFORMED; 189 } 190 err = parseMetaData(line, &mMeta, "target-duration"); 191 } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { 192 if (mIsVariantPlaylist) { 193 return ERROR_MALFORMED; 194 } 195 err = parseMetaData(line, &mMeta, "media-sequence"); 196 } else if (line.startsWith("#EXT-X-KEY")) { 197 if (mIsVariantPlaylist) { 198 return ERROR_MALFORMED; 199 } 200 err = parseCipherInfo(line, &itemMeta, mBaseURI); 201 } else if (line.startsWith("#EXT-X-ENDLIST")) { 202 mIsComplete = true; 203 } else if (line.startsWith("#EXTINF")) { 204 if (mIsVariantPlaylist) { 205 return ERROR_MALFORMED; 206 } 207 err = parseMetaDataDuration(line, &itemMeta, "durationUs"); 208 } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { 209 if (mIsVariantPlaylist) { 210 return ERROR_MALFORMED; 211 } 212 if (itemMeta == NULL) { 213 itemMeta = new AMessage; 214 } 215 itemMeta->setInt32("discontinuity", true); 216 } else if (line.startsWith("#EXT-X-STREAM-INF")) { 217 if (mMeta != NULL) { 218 return ERROR_MALFORMED; 219 } 220 mIsVariantPlaylist = true; 221 err = parseStreamInf(line, &itemMeta); 222 } else if (line.startsWith("#EXT-X-BYTERANGE")) { 223 if (mIsVariantPlaylist) { 224 return ERROR_MALFORMED; 225 } 226 227 uint64_t length, offset; 228 err = parseByteRange(line, segmentRangeOffset, &length, &offset); 229 230 if (err == OK) { 231 if (itemMeta == NULL) { 232 itemMeta = new AMessage; 233 } 234 235 itemMeta->setInt64("range-offset", offset); 236 itemMeta->setInt64("range-length", length); 237 238 segmentRangeOffset = offset + length; 239 } 240 } 241 242 if (err != OK) { 243 return err; 244 } 245 } 246 247 if (!line.startsWith("#")) { 248 if (!mIsVariantPlaylist) { 249 int64_t durationUs; 250 if (itemMeta == NULL 251 || !itemMeta->findInt64("durationUs", &durationUs)) { 252 return ERROR_MALFORMED; 253 } 254 } 255 256 mItems.push(); 257 Item *item = &mItems.editItemAt(mItems.size() - 1); 258 259 CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); 260 261 item->mMeta = itemMeta; 262 263 itemMeta.clear(); 264 } 265 266 offset = offsetLF + 1; 267 ++lineNo; 268 } 269 270 return OK; 271 } 272 273 // static 274 status_t M3UParser::parseMetaData( 275 const AString &line, sp<AMessage> *meta, const char *key) { 276 ssize_t colonPos = line.find(":"); 277 278 if (colonPos < 0) { 279 return ERROR_MALFORMED; 280 } 281 282 int32_t x; 283 status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); 284 285 if (err != OK) { 286 return err; 287 } 288 289 if (meta->get() == NULL) { 290 *meta = new AMessage; 291 } 292 (*meta)->setInt32(key, x); 293 294 return OK; 295 } 296 297 // static 298 status_t M3UParser::parseMetaDataDuration( 299 const AString &line, sp<AMessage> *meta, const char *key) { 300 ssize_t colonPos = line.find(":"); 301 302 if (colonPos < 0) { 303 return ERROR_MALFORMED; 304 } 305 306 double x; 307 status_t err = ParseDouble(line.c_str() + colonPos + 1, &x); 308 309 if (err != OK) { 310 return err; 311 } 312 313 if (meta->get() == NULL) { 314 *meta = new AMessage; 315 } 316 (*meta)->setInt64(key, (int64_t)x * 1E6); 317 318 return OK; 319 } 320 321 // static 322 status_t M3UParser::parseStreamInf( 323 const AString &line, sp<AMessage> *meta) { 324 ssize_t colonPos = line.find(":"); 325 326 if (colonPos < 0) { 327 return ERROR_MALFORMED; 328 } 329 330 size_t offset = colonPos + 1; 331 332 while (offset < line.size()) { 333 ssize_t end = line.find(",", offset); 334 if (end < 0) { 335 end = line.size(); 336 } 337 338 AString attr(line, offset, end - offset); 339 attr.trim(); 340 341 offset = end + 1; 342 343 ssize_t equalPos = attr.find("="); 344 if (equalPos < 0) { 345 continue; 346 } 347 348 AString key(attr, 0, equalPos); 349 key.trim(); 350 351 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 352 val.trim(); 353 354 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 355 356 if (!strcasecmp("bandwidth", key.c_str())) { 357 const char *s = val.c_str(); 358 char *end; 359 unsigned long x = strtoul(s, &end, 10); 360 361 if (end == s || *end != '\0') { 362 // malformed 363 continue; 364 } 365 366 if (meta->get() == NULL) { 367 *meta = new AMessage; 368 } 369 (*meta)->setInt32("bandwidth", x); 370 } 371 } 372 373 return OK; 374 } 375 376 // Find the next occurence of the character "what" at or after "offset", 377 // but ignore occurences between quotation marks. 378 // Return the index of the occurrence or -1 if not found. 379 static ssize_t FindNextUnquoted( 380 const AString &line, char what, size_t offset) { 381 CHECK_NE((int)what, (int)'"'); 382 383 bool quoted = false; 384 while (offset < line.size()) { 385 char c = line.c_str()[offset]; 386 387 if (c == '"') { 388 quoted = !quoted; 389 } else if (c == what && !quoted) { 390 return offset; 391 } 392 393 ++offset; 394 } 395 396 return -1; 397 } 398 399 // static 400 status_t M3UParser::parseCipherInfo( 401 const AString &line, sp<AMessage> *meta, const AString &baseURI) { 402 ssize_t colonPos = line.find(":"); 403 404 if (colonPos < 0) { 405 return ERROR_MALFORMED; 406 } 407 408 size_t offset = colonPos + 1; 409 410 while (offset < line.size()) { 411 ssize_t end = FindNextUnquoted(line, ',', offset); 412 if (end < 0) { 413 end = line.size(); 414 } 415 416 AString attr(line, offset, end - offset); 417 attr.trim(); 418 419 offset = end + 1; 420 421 ssize_t equalPos = attr.find("="); 422 if (equalPos < 0) { 423 continue; 424 } 425 426 AString key(attr, 0, equalPos); 427 key.trim(); 428 429 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 430 val.trim(); 431 432 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 433 434 key.tolower(); 435 436 if (key == "method" || key == "uri" || key == "iv") { 437 if (meta->get() == NULL) { 438 *meta = new AMessage; 439 } 440 441 if (key == "uri") { 442 if (val.size() >= 2 443 && val.c_str()[0] == '"' 444 && val.c_str()[val.size() - 1] == '"') { 445 // Remove surrounding quotes. 446 AString tmp(val, 1, val.size() - 2); 447 val = tmp; 448 } 449 450 AString absURI; 451 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) { 452 val = absURI; 453 } else { 454 ALOGE("failed to make absolute url for '%s'.", 455 val.c_str()); 456 } 457 } 458 459 key.insert(AString("cipher-"), 0); 460 461 (*meta)->setString(key.c_str(), val.c_str(), val.size()); 462 } 463 } 464 465 return OK; 466 } 467 468 // static 469 status_t M3UParser::parseByteRange( 470 const AString &line, uint64_t curOffset, 471 uint64_t *length, uint64_t *offset) { 472 ssize_t colonPos = line.find(":"); 473 474 if (colonPos < 0) { 475 return ERROR_MALFORMED; 476 } 477 478 ssize_t atPos = line.find("@", colonPos + 1); 479 480 AString lenStr; 481 if (atPos < 0) { 482 lenStr = AString(line, colonPos + 1, line.size() - colonPos - 1); 483 } else { 484 lenStr = AString(line, colonPos + 1, atPos - colonPos - 1); 485 } 486 487 lenStr.trim(); 488 489 const char *s = lenStr.c_str(); 490 char *end; 491 *length = strtoull(s, &end, 10); 492 493 if (s == end || *end != '\0') { 494 return ERROR_MALFORMED; 495 } 496 497 if (atPos >= 0) { 498 AString offStr = AString(line, atPos + 1, line.size() - atPos - 1); 499 offStr.trim(); 500 501 const char *s = offStr.c_str(); 502 *offset = strtoull(s, &end, 10); 503 504 if (s == end || *end != '\0') { 505 return ERROR_MALFORMED; 506 } 507 } else { 508 *offset = curOffset; 509 } 510 511 return OK; 512 } 513 514 // static 515 status_t M3UParser::ParseInt32(const char *s, int32_t *x) { 516 char *end; 517 long lval = strtol(s, &end, 10); 518 519 if (end == s || (*end != '\0' && *end != ',')) { 520 return ERROR_MALFORMED; 521 } 522 523 *x = (int32_t)lval; 524 525 return OK; 526 } 527 528 // static 529 status_t M3UParser::ParseDouble(const char *s, double *x) { 530 char *end; 531 double dval = strtod(s, &end); 532 533 if (end == s || (*end != '\0' && *end != ',')) { 534 return ERROR_MALFORMED; 535 } 536 537 *x = dval; 538 539 return OK; 540 } 541 542 } // namespace android 543