1/* 2 * Copyright (C) 2009 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 "stagefright" 19#include <media/stagefright/foundation/ADebug.h> 20 21#include <sys/time.h> 22 23#include <stdlib.h> 24#include <string.h> 25#include <unistd.h> 26 27#include "SineSource.h" 28 29#include <binder/IServiceManager.h> 30#include <binder/ProcessState.h> 31#include <media/IMediaPlayerService.h> 32#include <media/stagefright/foundation/ALooper.h> 33#include "include/ARTSPController.h" 34#include "include/LiveSource.h" 35#include "include/NuCachedSource2.h" 36#include <media/stagefright/AudioPlayer.h> 37#include <media/stagefright/DataSource.h> 38#include <media/stagefright/JPEGSource.h> 39#include <media/stagefright/MediaDefs.h> 40#include <media/stagefright/MediaErrors.h> 41#include <media/stagefright/MediaExtractor.h> 42#include <media/stagefright/MediaSource.h> 43#include <media/stagefright/MetaData.h> 44#include <media/stagefright/OMXClient.h> 45#include <media/stagefright/OMXCodec.h> 46#include <media/mediametadataretriever.h> 47 48#include <media/stagefright/foundation/hexdump.h> 49#include <media/stagefright/MPEG2TSWriter.h> 50#include <media/stagefright/MPEG4Writer.h> 51 52#include <fcntl.h> 53 54using namespace android; 55 56static long gNumRepetitions; 57static long gMaxNumFrames; // 0 means decode all available. 58static long gReproduceBug; // if not -1. 59static bool gPreferSoftwareCodec; 60static bool gPlaybackAudio; 61static bool gWriteMP4; 62static String8 gWriteMP4Filename; 63 64static int64_t getNowUs() { 65 struct timeval tv; 66 gettimeofday(&tv, NULL); 67 68 return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll; 69} 70 71static void playSource(OMXClient *client, sp<MediaSource> &source) { 72 sp<MetaData> meta = source->getFormat(); 73 74 const char *mime; 75 CHECK(meta->findCString(kKeyMIMEType, &mime)); 76 77 sp<MediaSource> rawSource; 78 if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime)) { 79 rawSource = source; 80 } else { 81 rawSource = OMXCodec::Create( 82 client->interface(), meta, false /* createEncoder */, source, 83 NULL /* matchComponentName */, 84 gPreferSoftwareCodec ? OMXCodec::kPreferSoftwareCodecs : 0); 85 86 if (rawSource == NULL) { 87 fprintf(stderr, "Failed to instantiate decoder for '%s'.\n", mime); 88 return; 89 } 90 } 91 92 source.clear(); 93 94 status_t err = rawSource->start(); 95 96 if (err != OK) { 97 fprintf(stderr, "rawSource returned error %d (0x%08x)\n", err, err); 98 return; 99 } 100 101 if (gPlaybackAudio) { 102 AudioPlayer *player = new AudioPlayer(NULL); 103 player->setSource(rawSource); 104 rawSource.clear(); 105 106 player->start(true /* sourceAlreadyStarted */); 107 108 status_t finalStatus; 109 while (!player->reachedEOS(&finalStatus)) { 110 usleep(100000ll); 111 } 112 113 delete player; 114 player = NULL; 115 116 return; 117 } else if (gReproduceBug >= 3 && gReproduceBug <= 5) { 118 int64_t durationUs; 119 CHECK(meta->findInt64(kKeyDuration, &durationUs)); 120 121 status_t err; 122 MediaBuffer *buffer; 123 MediaSource::ReadOptions options; 124 int64_t seekTimeUs = -1; 125 for (;;) { 126 err = rawSource->read(&buffer, &options); 127 options.clearSeekTo(); 128 129 bool shouldSeek = false; 130 if (err == INFO_FORMAT_CHANGED) { 131 CHECK(buffer == NULL); 132 133 printf("format changed.\n"); 134 continue; 135 } else if (err != OK) { 136 printf("reached EOF.\n"); 137 138 shouldSeek = true; 139 } else { 140 int64_t timestampUs; 141 CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); 142 143 bool failed = false; 144 145 if (seekTimeUs >= 0) { 146 int64_t diff = timestampUs - seekTimeUs; 147 148 if (diff < 0) { 149 diff = -diff; 150 } 151 152 if ((gReproduceBug == 4 && diff > 500000) 153 || (gReproduceBug == 5 && timestampUs < 0)) { 154 printf("wanted: %.2f secs, got: %.2f secs\n", 155 seekTimeUs / 1E6, timestampUs / 1E6); 156 157 printf("ERROR: "); 158 failed = true; 159 } 160 } 161 162 printf("buffer has timestamp %lld us (%.2f secs)\n", 163 timestampUs, timestampUs / 1E6); 164 165 buffer->release(); 166 buffer = NULL; 167 168 if (failed) { 169 break; 170 } 171 172 shouldSeek = ((double)rand() / RAND_MAX) < 0.1; 173 174 if (gReproduceBug == 3) { 175 shouldSeek = false; 176 } 177 } 178 179 seekTimeUs = -1; 180 181 if (shouldSeek) { 182 seekTimeUs = (rand() * (float)durationUs) / RAND_MAX; 183 options.setSeekTo(seekTimeUs); 184 185 printf("seeking to %lld us (%.2f secs)\n", 186 seekTimeUs, seekTimeUs / 1E6); 187 } 188 } 189 190 rawSource->stop(); 191 192 return; 193 } 194 195 int n = 0; 196 int64_t startTime = getNowUs(); 197 198 long numIterationsLeft = gNumRepetitions; 199 MediaSource::ReadOptions options; 200 201 int64_t sumDecodeUs = 0; 202 int64_t totalBytes = 0; 203 204 while (numIterationsLeft-- > 0) { 205 long numFrames = 0; 206 207 MediaBuffer *buffer; 208 209 for (;;) { 210 int64_t startDecodeUs = getNowUs(); 211 status_t err = rawSource->read(&buffer, &options); 212 int64_t delayDecodeUs = getNowUs() - startDecodeUs; 213 214 options.clearSeekTo(); 215 216 if (err != OK) { 217 CHECK(buffer == NULL); 218 219 if (err == INFO_FORMAT_CHANGED) { 220 printf("format changed.\n"); 221 continue; 222 } 223 224 break; 225 } 226 227 if (buffer->range_length() > 0 && (n++ % 16) == 0) { 228 printf("."); 229 fflush(stdout); 230 } 231 232 sumDecodeUs += delayDecodeUs; 233 totalBytes += buffer->range_length(); 234 235 buffer->release(); 236 buffer = NULL; 237 238 ++numFrames; 239 if (gMaxNumFrames > 0 && numFrames == gMaxNumFrames) { 240 break; 241 } 242 243 if (gReproduceBug == 1 && numFrames == 40) { 244 printf("seeking past the end now."); 245 options.setSeekTo(0x7fffffffL); 246 } else if (gReproduceBug == 2 && numFrames == 40) { 247 printf("seeking to 5 secs."); 248 options.setSeekTo(5000000); 249 } 250 } 251 252 printf("$"); 253 fflush(stdout); 254 255 options.setSeekTo(0); 256 } 257 258 rawSource->stop(); 259 printf("\n"); 260 261 int64_t delay = getNowUs() - startTime; 262 if (!strncasecmp("video/", mime, 6)) { 263 printf("avg. %.2f fps\n", n * 1E6 / delay); 264 265 printf("avg. time to decode one buffer %.2f usecs\n", 266 (double)sumDecodeUs / n); 267 268 printf("decoded a total of %d frame(s).\n", n); 269 } else if (!strncasecmp("audio/", mime, 6)) { 270 // Frame count makes less sense for audio, as the output buffer 271 // sizes may be different across decoders. 272 printf("avg. %.2f KB/sec\n", totalBytes / 1024 * 1E6 / delay); 273 274 printf("decoded a total of %lld bytes\n", totalBytes); 275 } 276} 277 278//////////////////////////////////////////////////////////////////////////////// 279 280struct DetectSyncSource : public MediaSource { 281 DetectSyncSource(const sp<MediaSource> &source); 282 283 virtual status_t start(MetaData *params = NULL); 284 virtual status_t stop(); 285 virtual sp<MetaData> getFormat(); 286 287 virtual status_t read( 288 MediaBuffer **buffer, const ReadOptions *options); 289 290private: 291 enum StreamType { 292 AVC, 293 MPEG4, 294 H263, 295 OTHER, 296 }; 297 298 sp<MediaSource> mSource; 299 StreamType mStreamType; 300 301 DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource); 302}; 303 304DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source) 305 : mSource(source), 306 mStreamType(OTHER) { 307 const char *mime; 308 CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); 309 310 if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { 311 mStreamType = AVC; 312 } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)) { 313 mStreamType = MPEG4; 314 CHECK(!"sync frame detection not implemented yet for MPEG4"); 315 } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) { 316 mStreamType = H263; 317 CHECK(!"sync frame detection not implemented yet for H.263"); 318 } 319} 320 321status_t DetectSyncSource::start(MetaData *params) { 322 return mSource->start(params); 323} 324 325status_t DetectSyncSource::stop() { 326 return mSource->stop(); 327} 328 329sp<MetaData> DetectSyncSource::getFormat() { 330 return mSource->getFormat(); 331} 332 333static bool isIDRFrame(MediaBuffer *buffer) { 334 const uint8_t *data = 335 (const uint8_t *)buffer->data() + buffer->range_offset(); 336 size_t size = buffer->range_length(); 337 for (size_t i = 0; i + 3 < size; ++i) { 338 if (!memcmp("\x00\x00\x01", &data[i], 3)) { 339 uint8_t nalType = data[i + 3] & 0x1f; 340 if (nalType == 5) { 341 return true; 342 } 343 } 344 } 345 346 return false; 347} 348 349status_t DetectSyncSource::read( 350 MediaBuffer **buffer, const ReadOptions *options) { 351 status_t err = mSource->read(buffer, options); 352 353 if (err != OK) { 354 return err; 355 } 356 357 if (mStreamType == AVC && isIDRFrame(*buffer)) { 358 (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true); 359 } else { 360 (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true); 361 } 362 363 return OK; 364} 365 366//////////////////////////////////////////////////////////////////////////////// 367 368static void writeSourcesToMP4( 369 Vector<sp<MediaSource> > &sources, bool syncInfoPresent) { 370#if 0 371 sp<MPEG4Writer> writer = 372 new MPEG4Writer(gWriteMP4Filename.string()); 373#else 374 sp<MPEG2TSWriter> writer = 375 new MPEG2TSWriter(gWriteMP4Filename.string()); 376#endif 377 378 // at most one minute. 379 writer->setMaxFileDuration(60000000ll); 380 381 for (size_t i = 0; i < sources.size(); ++i) { 382 sp<MediaSource> source = sources.editItemAt(i); 383 384 CHECK_EQ(writer->addSource( 385 syncInfoPresent ? source : new DetectSyncSource(source)), 386 (status_t)OK); 387 } 388 389 sp<MetaData> params = new MetaData; 390 params->setInt32(kKeyNotRealTime, true); 391 CHECK_EQ(writer->start(params.get()), (status_t)OK); 392 393 while (!writer->reachedEOS()) { 394 usleep(100000); 395 } 396 writer->stop(); 397} 398 399static void performSeekTest(const sp<MediaSource> &source) { 400 CHECK_EQ((status_t)OK, source->start()); 401 402 int64_t durationUs; 403 CHECK(source->getFormat()->findInt64(kKeyDuration, &durationUs)); 404 405 for (int64_t seekTimeUs = 0; seekTimeUs <= durationUs; 406 seekTimeUs += 60000ll) { 407 MediaSource::ReadOptions options; 408 options.setSeekTo( 409 seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); 410 411 MediaBuffer *buffer; 412 status_t err; 413 for (;;) { 414 err = source->read(&buffer, &options); 415 416 options.clearSeekTo(); 417 418 if (err == INFO_FORMAT_CHANGED) { 419 CHECK(buffer == NULL); 420 continue; 421 } 422 423 if (err != OK) { 424 CHECK(buffer == NULL); 425 break; 426 } 427 428 if (buffer->range_length() > 0) { 429 break; 430 } 431 432 CHECK(buffer != NULL); 433 434 buffer->release(); 435 buffer = NULL; 436 } 437 438 if (err == OK) { 439 int64_t timeUs; 440 CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs)); 441 442 printf("%lld\t%lld\t%lld\n", seekTimeUs, timeUs, seekTimeUs - timeUs); 443 444 buffer->release(); 445 buffer = NULL; 446 } else { 447 printf("ERROR\n"); 448 break; 449 } 450 } 451 452 CHECK_EQ((status_t)OK, source->stop()); 453} 454 455static void usage(const char *me) { 456 fprintf(stderr, "usage: %s\n", me); 457 fprintf(stderr, " -h(elp)\n"); 458 fprintf(stderr, " -a(udio)\n"); 459 fprintf(stderr, " -n repetitions\n"); 460 fprintf(stderr, " -l(ist) components\n"); 461 fprintf(stderr, " -m max-number-of-frames-to-decode in each pass\n"); 462 fprintf(stderr, " -b bug to reproduce\n"); 463 fprintf(stderr, " -p(rofiles) dump decoder profiles supported\n"); 464 fprintf(stderr, " -t(humbnail) extract video thumbnail or album art\n"); 465 fprintf(stderr, " -s(oftware) prefer software codec\n"); 466 fprintf(stderr, " -o playback audio\n"); 467 fprintf(stderr, " -w(rite) filename (write to .mp4 file)\n"); 468 fprintf(stderr, " -k seek test\n"); 469} 470 471int main(int argc, char **argv) { 472 android::ProcessState::self()->startThreadPool(); 473 474 bool audioOnly = false; 475 bool listComponents = false; 476 bool dumpProfiles = false; 477 bool extractThumbnail = false; 478 bool seekTest = false; 479 gNumRepetitions = 1; 480 gMaxNumFrames = 0; 481 gReproduceBug = -1; 482 gPreferSoftwareCodec = false; 483 gPlaybackAudio = false; 484 gWriteMP4 = false; 485 486 sp<ALooper> looper; 487 sp<ARTSPController> rtspController; 488 489 int res; 490 while ((res = getopt(argc, argv, "han:lm:b:ptsow:k")) >= 0) { 491 switch (res) { 492 case 'a': 493 { 494 audioOnly = true; 495 break; 496 } 497 498 case 'l': 499 { 500 listComponents = true; 501 break; 502 } 503 504 case 'm': 505 case 'n': 506 case 'b': 507 { 508 char *end; 509 long x = strtol(optarg, &end, 10); 510 511 if (*end != '\0' || end == optarg || x <= 0) { 512 x = 1; 513 } 514 515 if (res == 'n') { 516 gNumRepetitions = x; 517 } else if (res == 'm') { 518 gMaxNumFrames = x; 519 } else { 520 CHECK_EQ(res, 'b'); 521 gReproduceBug = x; 522 } 523 break; 524 } 525 526 case 'w': 527 { 528 gWriteMP4 = true; 529 gWriteMP4Filename.setTo(optarg); 530 break; 531 } 532 533 case 'p': 534 { 535 dumpProfiles = true; 536 break; 537 } 538 539 case 't': 540 { 541 extractThumbnail = true; 542 break; 543 } 544 545 case 's': 546 { 547 gPreferSoftwareCodec = true; 548 break; 549 } 550 551 case 'o': 552 { 553 gPlaybackAudio = true; 554 break; 555 } 556 557 case 'k': 558 { 559 seekTest = true; 560 break; 561 } 562 563 case '?': 564 case 'h': 565 default: 566 { 567 usage(argv[0]); 568 exit(1); 569 break; 570 } 571 } 572 } 573 574 if (gPlaybackAudio && !audioOnly) { 575 // This doesn't make any sense if we're decoding the video track. 576 gPlaybackAudio = false; 577 } 578 579 argc -= optind; 580 argv += optind; 581 582 if (extractThumbnail) { 583 sp<IServiceManager> sm = defaultServiceManager(); 584 sp<IBinder> binder = sm->getService(String16("media.player")); 585 sp<IMediaPlayerService> service = 586 interface_cast<IMediaPlayerService>(binder); 587 588 CHECK(service.get() != NULL); 589 590 sp<IMediaMetadataRetriever> retriever = 591 service->createMetadataRetriever(getpid()); 592 593 CHECK(retriever != NULL); 594 595 for (int k = 0; k < argc; ++k) { 596 const char *filename = argv[k]; 597 598 CHECK_EQ(retriever->setDataSource(filename), (status_t)OK); 599 sp<IMemory> mem = 600 retriever->getFrameAtTime(-1, 601 MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); 602 603 if (mem != NULL) { 604 printf("getFrameAtTime(%s) => OK\n", filename); 605 } else { 606 mem = retriever->extractAlbumArt(); 607 608 if (mem != NULL) { 609 printf("extractAlbumArt(%s) => OK\n", filename); 610 } else { 611 printf("both getFrameAtTime and extractAlbumArt " 612 "failed on file '%s'.\n", filename); 613 } 614 } 615 } 616 617 return 0; 618 } 619 620 if (dumpProfiles) { 621 sp<IServiceManager> sm = defaultServiceManager(); 622 sp<IBinder> binder = sm->getService(String16("media.player")); 623 sp<IMediaPlayerService> service = 624 interface_cast<IMediaPlayerService>(binder); 625 626 CHECK(service.get() != NULL); 627 628 sp<IOMX> omx = service->getOMX(); 629 CHECK(omx.get() != NULL); 630 631 const char *kMimeTypes[] = { 632 MEDIA_MIMETYPE_VIDEO_AVC, MEDIA_MIMETYPE_VIDEO_MPEG4, 633 MEDIA_MIMETYPE_VIDEO_H263, MEDIA_MIMETYPE_AUDIO_AAC, 634 MEDIA_MIMETYPE_AUDIO_AMR_NB, MEDIA_MIMETYPE_AUDIO_AMR_WB, 635 MEDIA_MIMETYPE_AUDIO_MPEG 636 }; 637 638 for (size_t k = 0; k < sizeof(kMimeTypes) / sizeof(kMimeTypes[0]); 639 ++k) { 640 printf("type '%s':\n", kMimeTypes[k]); 641 642 Vector<CodecCapabilities> results; 643 CHECK_EQ(QueryCodecs(omx, kMimeTypes[k], 644 true, // queryDecoders 645 &results), (status_t)OK); 646 647 for (size_t i = 0; i < results.size(); ++i) { 648 printf(" decoder '%s' supports ", 649 results[i].mComponentName.string()); 650 651 if (results[i].mProfileLevels.size() == 0) { 652 printf("NOTHING.\n"); 653 continue; 654 } 655 656 for (size_t j = 0; j < results[i].mProfileLevels.size(); ++j) { 657 const CodecProfileLevel &profileLevel = 658 results[i].mProfileLevels[j]; 659 660 printf("%s%ld/%ld", j > 0 ? ", " : "", 661 profileLevel.mProfile, profileLevel.mLevel); 662 } 663 664 printf("\n"); 665 } 666 } 667 } 668 669 if (listComponents) { 670 sp<IServiceManager> sm = defaultServiceManager(); 671 sp<IBinder> binder = sm->getService(String16("media.player")); 672 sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); 673 674 CHECK(service.get() != NULL); 675 676 sp<IOMX> omx = service->getOMX(); 677 CHECK(omx.get() != NULL); 678 679 List<IOMX::ComponentInfo> list; 680 omx->listNodes(&list); 681 682 for (List<IOMX::ComponentInfo>::iterator it = list.begin(); 683 it != list.end(); ++it) { 684 printf("%s\n", (*it).mName.string()); 685 } 686 } 687 688 DataSource::RegisterDefaultSniffers(); 689 690 OMXClient client; 691 status_t err = client.connect(); 692 693 for (int k = 0; k < argc; ++k) { 694 bool syncInfoPresent = true; 695 696 const char *filename = argv[k]; 697 698 sp<DataSource> dataSource = DataSource::CreateFromURI(filename); 699 700 if (strncasecmp(filename, "sine:", 5) 701 && strncasecmp(filename, "rtsp://", 7) 702 && strncasecmp(filename, "httplive://", 11) 703 && dataSource == NULL) { 704 fprintf(stderr, "Unable to create data source.\n"); 705 return 1; 706 } 707 708 bool isJPEG = false; 709 710 size_t len = strlen(filename); 711 if (len >= 4 && !strcasecmp(filename + len - 4, ".jpg")) { 712 isJPEG = true; 713 } 714 715 Vector<sp<MediaSource> > mediaSources; 716 sp<MediaSource> mediaSource; 717 718 if (isJPEG) { 719 mediaSource = new JPEGSource(dataSource); 720 if (gWriteMP4) { 721 mediaSources.push(mediaSource); 722 } 723 } else if (!strncasecmp("sine:", filename, 5)) { 724 char *end; 725 long sampleRate = strtol(filename + 5, &end, 10); 726 727 if (end == filename + 5) { 728 sampleRate = 44100; 729 } 730 mediaSource = new SineSource(sampleRate, 1); 731 if (gWriteMP4) { 732 mediaSources.push(mediaSource); 733 } 734 } else { 735 sp<MediaExtractor> extractor; 736 737 if (!strncasecmp("rtsp://", filename, 7)) { 738 if (looper == NULL) { 739 looper = new ALooper; 740 looper->start(); 741 } 742 743 rtspController = new ARTSPController(looper); 744 status_t err = rtspController->connect(filename); 745 if (err != OK) { 746 fprintf(stderr, "could not connect to rtsp server.\n"); 747 return -1; 748 } 749 750 extractor = rtspController.get(); 751 752 syncInfoPresent = false; 753 } else if (!strncasecmp("httplive://", filename, 11)) { 754 String8 uri("http://"); 755 uri.append(filename + 11); 756 757 dataSource = new LiveSource(uri.string()); 758 dataSource = new NuCachedSource2(dataSource); 759 760 extractor = 761 MediaExtractor::Create( 762 dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); 763 764 syncInfoPresent = false; 765 } else { 766 extractor = MediaExtractor::Create(dataSource); 767 if (extractor == NULL) { 768 fprintf(stderr, "could not create extractor.\n"); 769 return -1; 770 } 771 } 772 773 size_t numTracks = extractor->countTracks(); 774 775 if (gWriteMP4) { 776 bool haveAudio = false; 777 bool haveVideo = false; 778 for (size_t i = 0; i < numTracks; ++i) { 779 sp<MediaSource> source = extractor->getTrack(i); 780 781 const char *mime; 782 CHECK(source->getFormat()->findCString( 783 kKeyMIMEType, &mime)); 784 785 bool useTrack = false; 786 if (!haveAudio && !strncasecmp("audio/", mime, 6)) { 787 haveAudio = true; 788 useTrack = true; 789 } else if (!haveVideo && !strncasecmp("video/", mime, 6)) { 790 haveVideo = true; 791 useTrack = true; 792 } 793 794 if (useTrack) { 795 mediaSources.push(source); 796 797 if (haveAudio && haveVideo) { 798 break; 799 } 800 } 801 } 802 } else { 803 sp<MetaData> meta; 804 size_t i; 805 for (i = 0; i < numTracks; ++i) { 806 meta = extractor->getTrackMetaData( 807 i, MediaExtractor::kIncludeExtensiveMetaData); 808 809 const char *mime; 810 meta->findCString(kKeyMIMEType, &mime); 811 812 if (audioOnly && !strncasecmp(mime, "audio/", 6)) { 813 break; 814 } 815 816 if (!audioOnly && !strncasecmp(mime, "video/", 6)) { 817 break; 818 } 819 820 meta = NULL; 821 } 822 823 if (meta == NULL) { 824 fprintf(stderr, 825 "No suitable %s track found. The '-a' option will " 826 "target audio tracks only, the default is to target " 827 "video tracks only.\n", 828 audioOnly ? "audio" : "video"); 829 return -1; 830 } 831 832 int64_t thumbTimeUs; 833 if (meta->findInt64(kKeyThumbnailTime, &thumbTimeUs)) { 834 printf("thumbnailTime: %lld us (%.2f secs)\n", 835 thumbTimeUs, thumbTimeUs / 1E6); 836 } 837 838 mediaSource = extractor->getTrack(i); 839 } 840 } 841 842 if (gWriteMP4) { 843 writeSourcesToMP4(mediaSources, syncInfoPresent); 844 } else if (seekTest) { 845 performSeekTest(mediaSource); 846 } else { 847 playSource(&client, mediaSource); 848 } 849 850 if (rtspController != NULL) { 851 rtspController->disconnect(); 852 rtspController.clear(); 853 854 sleep(3); 855 } 856 } 857 858 client.disconnect(); 859 860 return 0; 861} 862