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