GenericSource.cpp revision 474d7c778b63aa33dcf25a92e23a52c1c47f0ac1
1/* 2 * Copyright (C) 2012 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 "GenericSource" 19 20#include "GenericSource.h" 21 22#include "AnotherPacketSource.h" 23 24#include <media/IMediaHTTPService.h> 25#include <media/stagefright/foundation/ABuffer.h> 26#include <media/stagefright/foundation/ADebug.h> 27#include <media/stagefright/foundation/AMessage.h> 28#include <media/stagefright/DataSource.h> 29#include <media/stagefright/FileSource.h> 30#include <media/stagefright/MediaBuffer.h> 31#include <media/stagefright/MediaDefs.h> 32#include <media/stagefright/MediaExtractor.h> 33#include <media/stagefright/MediaSource.h> 34#include <media/stagefright/MetaData.h> 35#include <media/stagefright/Utils.h> 36#include "../../libstagefright/include/DRMExtractor.h" 37#include "../../libstagefright/include/NuCachedSource2.h" 38#include "../../libstagefright/include/WVMExtractor.h" 39#include "../../libstagefright/include/HTTPBase.h" 40 41namespace android { 42 43NuPlayer::GenericSource::GenericSource( 44 const sp<AMessage> ¬ify, 45 bool uidValid, 46 uid_t uid) 47 : Source(notify), 48 mAudioTimeUs(0), 49 mAudioLastDequeueTimeUs(0), 50 mVideoTimeUs(0), 51 mVideoLastDequeueTimeUs(0), 52 mFetchSubtitleDataGeneration(0), 53 mFetchTimedTextDataGeneration(0), 54 mDurationUs(0ll), 55 mAudioIsVorbis(false), 56 mIsWidevine(false), 57 mUIDValid(uidValid), 58 mUID(uid), 59 mFd(-1), 60 mDrmManagerClient(NULL), 61 mMetaDataSize(-1ll), 62 mBitrate(-1ll), 63 mPollBufferingGeneration(0), 64 mPendingReadBufferTypes(0) { 65 resetDataSource(); 66 DataSource::RegisterDefaultSniffers(); 67} 68 69void NuPlayer::GenericSource::resetDataSource() { 70 mHTTPService.clear(); 71 mHttpSource.clear(); 72 mUri.clear(); 73 mUriHeaders.clear(); 74 if (mFd >= 0) { 75 close(mFd); 76 mFd = -1; 77 } 78 mOffset = 0; 79 mLength = 0; 80 setDrmPlaybackStatusIfNeeded(Playback::STOP, 0); 81 mDecryptHandle = NULL; 82 mDrmManagerClient = NULL; 83 mStarted = false; 84 mStopRead = true; 85} 86 87status_t NuPlayer::GenericSource::setDataSource( 88 const sp<IMediaHTTPService> &httpService, 89 const char *url, 90 const KeyedVector<String8, String8> *headers) { 91 resetDataSource(); 92 93 mHTTPService = httpService; 94 mUri = url; 95 96 if (headers) { 97 mUriHeaders = *headers; 98 } 99 100 // delay data source creation to prepareAsync() to avoid blocking 101 // the calling thread in setDataSource for any significant time. 102 return OK; 103} 104 105status_t NuPlayer::GenericSource::setDataSource( 106 int fd, int64_t offset, int64_t length) { 107 resetDataSource(); 108 109 mFd = dup(fd); 110 mOffset = offset; 111 mLength = length; 112 113 // delay data source creation to prepareAsync() to avoid blocking 114 // the calling thread in setDataSource for any significant time. 115 return OK; 116} 117 118sp<MetaData> NuPlayer::GenericSource::getFileFormatMeta() const { 119 return mFileMeta; 120} 121 122status_t NuPlayer::GenericSource::initFromDataSource() { 123 sp<MediaExtractor> extractor; 124 125 CHECK(mDataSource != NULL); 126 127 if (mIsWidevine) { 128 String8 mimeType; 129 float confidence; 130 sp<AMessage> dummy; 131 bool success; 132 133 success = SniffWVM(mDataSource, &mimeType, &confidence, &dummy); 134 if (!success 135 || strcasecmp( 136 mimeType.string(), MEDIA_MIMETYPE_CONTAINER_WVM)) { 137 ALOGE("unsupported widevine mime: %s", mimeType.string()); 138 return UNKNOWN_ERROR; 139 } 140 141 mWVMExtractor = new WVMExtractor(mDataSource); 142 mWVMExtractor->setAdaptiveStreamingMode(true); 143 if (mUIDValid) { 144 mWVMExtractor->setUID(mUID); 145 } 146 extractor = mWVMExtractor; 147 } else { 148 extractor = MediaExtractor::Create(mDataSource, 149 mSniffedMIME.empty() ? NULL: mSniffedMIME.c_str()); 150 } 151 152 if (extractor == NULL) { 153 return UNKNOWN_ERROR; 154 } 155 156 if (extractor->getDrmFlag()) { 157 checkDrmStatus(mDataSource); 158 } 159 160 mFileMeta = extractor->getMetaData(); 161 if (mFileMeta != NULL) { 162 int64_t duration; 163 if (mFileMeta->findInt64(kKeyDuration, &duration)) { 164 mDurationUs = duration; 165 } 166 } 167 168 int32_t totalBitrate = 0; 169 170 size_t numtracks = extractor->countTracks(); 171 if (numtracks == 0) { 172 return UNKNOWN_ERROR; 173 } 174 175 for (size_t i = 0; i < numtracks; ++i) { 176 sp<MediaSource> track = extractor->getTrack(i); 177 178 sp<MetaData> meta = extractor->getTrackMetaData(i); 179 180 const char *mime; 181 CHECK(meta->findCString(kKeyMIMEType, &mime)); 182 183 // Do the string compare immediately with "mime", 184 // we can't assume "mime" would stay valid after another 185 // extractor operation, some extractors might modify meta 186 // during getTrack() and make it invalid. 187 if (!strncasecmp(mime, "audio/", 6)) { 188 if (mAudioTrack.mSource == NULL) { 189 mAudioTrack.mIndex = i; 190 mAudioTrack.mSource = track; 191 mAudioTrack.mPackets = 192 new AnotherPacketSource(mAudioTrack.mSource->getFormat()); 193 194 if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { 195 mAudioIsVorbis = true; 196 } else { 197 mAudioIsVorbis = false; 198 } 199 } 200 } else if (!strncasecmp(mime, "video/", 6)) { 201 if (mVideoTrack.mSource == NULL) { 202 mVideoTrack.mIndex = i; 203 mVideoTrack.mSource = track; 204 mVideoTrack.mPackets = 205 new AnotherPacketSource(mVideoTrack.mSource->getFormat()); 206 207 // check if the source requires secure buffers 208 int32_t secure; 209 if (meta->findInt32(kKeyRequiresSecureBuffers, &secure) 210 && secure) { 211 mIsWidevine = true; 212 if (mUIDValid) { 213 extractor->setUID(mUID); 214 } 215 } 216 } 217 } 218 219 if (track != NULL) { 220 mSources.push(track); 221 int64_t durationUs; 222 if (meta->findInt64(kKeyDuration, &durationUs)) { 223 if (durationUs > mDurationUs) { 224 mDurationUs = durationUs; 225 } 226 } 227 228 int32_t bitrate; 229 if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) { 230 totalBitrate += bitrate; 231 } else { 232 totalBitrate = -1; 233 } 234 } 235 } 236 237 mBitrate = totalBitrate; 238 239 return OK; 240} 241 242void NuPlayer::GenericSource::checkDrmStatus(const sp<DataSource>& dataSource) { 243 dataSource->getDrmInfo(mDecryptHandle, &mDrmManagerClient); 244 if (mDecryptHandle != NULL) { 245 CHECK(mDrmManagerClient); 246 if (RightsStatus::RIGHTS_VALID != mDecryptHandle->status) { 247 sp<AMessage> msg = dupNotify(); 248 msg->setInt32("what", kWhatDrmNoLicense); 249 msg->post(); 250 } 251 } 252} 253 254int64_t NuPlayer::GenericSource::getLastReadPosition() { 255 if (mAudioTrack.mSource != NULL) { 256 return mAudioTimeUs; 257 } else if (mVideoTrack.mSource != NULL) { 258 return mVideoTimeUs; 259 } else { 260 return 0; 261 } 262} 263 264status_t NuPlayer::GenericSource::setBuffers( 265 bool audio, Vector<MediaBuffer *> &buffers) { 266 if (mIsWidevine && !audio) { 267 return mVideoTrack.mSource->setBuffers(buffers); 268 } 269 return INVALID_OPERATION; 270} 271 272NuPlayer::GenericSource::~GenericSource() { 273 if (mLooper != NULL) { 274 mLooper->unregisterHandler(id()); 275 mLooper->stop(); 276 } 277 resetDataSource(); 278} 279 280void NuPlayer::GenericSource::prepareAsync() { 281 if (mLooper == NULL) { 282 mLooper = new ALooper; 283 mLooper->setName("generic"); 284 mLooper->start(); 285 286 mLooper->registerHandler(this); 287 } 288 289 sp<AMessage> msg = new AMessage(kWhatPrepareAsync, id()); 290 msg->post(); 291} 292 293void NuPlayer::GenericSource::onPrepareAsync() { 294 // delayed data source creation 295 if (mDataSource == NULL) { 296 if (!mUri.empty()) { 297 const char* uri = mUri.c_str(); 298 mIsWidevine = !strncasecmp(uri, "widevine://", 11); 299 300 if (!strncasecmp("http://", uri, 7) 301 || !strncasecmp("https://", uri, 8) 302 || mIsWidevine) { 303 mHttpSource = DataSource::CreateMediaHTTP(mHTTPService); 304 if (mHttpSource == NULL) { 305 ALOGE("Failed to create http source!"); 306 notifyPreparedAndCleanup(UNKNOWN_ERROR); 307 return; 308 } 309 } 310 311 mDataSource = DataSource::CreateFromURI( 312 mHTTPService, uri, &mUriHeaders, &mContentType, 313 static_cast<HTTPBase *>(mHttpSource.get())); 314 } else { 315 // set to false first, if the extractor 316 // comes back as secure, set it to true then. 317 mIsWidevine = false; 318 319 mDataSource = new FileSource(mFd, mOffset, mLength); 320 mFd = -1; 321 } 322 323 if (mDataSource == NULL) { 324 ALOGE("Failed to create data source!"); 325 notifyPreparedAndCleanup(UNKNOWN_ERROR); 326 return; 327 } 328 329 if (mDataSource->flags() & DataSource::kIsCachingDataSource) { 330 mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get()); 331 } 332 333 if (mIsWidevine || mCachedSource != NULL) { 334 schedulePollBuffering(); 335 } 336 } 337 338 // check initial caching status 339 status_t err = prefillCacheIfNecessary(); 340 if (err != OK) { 341 if (err == -EAGAIN) { 342 (new AMessage(kWhatPrepareAsync, id()))->post(200000); 343 } else { 344 ALOGE("Failed to prefill data cache!"); 345 notifyPreparedAndCleanup(UNKNOWN_ERROR); 346 } 347 return; 348 } 349 350 // init extrator from data source 351 err = initFromDataSource(); 352 353 if (err != OK) { 354 ALOGE("Failed to init from data source!"); 355 notifyPreparedAndCleanup(err); 356 return; 357 } 358 359 if (mVideoTrack.mSource != NULL) { 360 sp<MetaData> meta = doGetFormatMeta(false /* audio */); 361 sp<AMessage> msg = new AMessage; 362 err = convertMetaDataToMessage(meta, &msg); 363 if(err != OK) { 364 notifyPreparedAndCleanup(err); 365 return; 366 } 367 notifyVideoSizeChanged(msg); 368 } 369 370 notifyFlagsChanged( 371 (mIsWidevine ? FLAG_SECURE : 0) 372 | FLAG_CAN_PAUSE 373 | FLAG_CAN_SEEK_BACKWARD 374 | FLAG_CAN_SEEK_FORWARD 375 | FLAG_CAN_SEEK); 376 377 notifyPrepared(); 378} 379 380void NuPlayer::GenericSource::notifyPreparedAndCleanup(status_t err) { 381 if (err != OK) { 382 mMetaDataSize = -1ll; 383 mContentType = ""; 384 mSniffedMIME = ""; 385 mDataSource.clear(); 386 mCachedSource.clear(); 387 mHttpSource.clear(); 388 389 cancelPollBuffering(); 390 } 391 notifyPrepared(err); 392} 393 394status_t NuPlayer::GenericSource::prefillCacheIfNecessary() { 395 CHECK(mDataSource != NULL); 396 397 if (mCachedSource == NULL) { 398 // no prefill if the data source is not cached 399 return OK; 400 } 401 402 // We're not doing this for streams that appear to be audio-only 403 // streams to ensure that even low bandwidth streams start 404 // playing back fairly instantly. 405 if (!strncasecmp(mContentType.string(), "audio/", 6)) { 406 return OK; 407 } 408 409 // We're going to prefill the cache before trying to instantiate 410 // the extractor below, as the latter is an operation that otherwise 411 // could block on the datasource for a significant amount of time. 412 // During that time we'd be unable to abort the preparation phase 413 // without this prefill. 414 415 // Initially make sure we have at least 192 KB for the sniff 416 // to complete without blocking. 417 static const size_t kMinBytesForSniffing = 192 * 1024; 418 static const size_t kDefaultMetaSize = 200000; 419 420 status_t finalStatus; 421 422 size_t cachedDataRemaining = 423 mCachedSource->approxDataRemaining(&finalStatus); 424 425 if (finalStatus != OK || (mMetaDataSize >= 0 426 && (off64_t)cachedDataRemaining >= mMetaDataSize)) { 427 ALOGV("stop caching, status %d, " 428 "metaDataSize %lld, cachedDataRemaining %zu", 429 finalStatus, mMetaDataSize, cachedDataRemaining); 430 return OK; 431 } 432 433 ALOGV("now cached %zu bytes of data", cachedDataRemaining); 434 435 if (mMetaDataSize < 0 436 && cachedDataRemaining >= kMinBytesForSniffing) { 437 String8 tmp; 438 float confidence; 439 sp<AMessage> meta; 440 if (!mCachedSource->sniff(&tmp, &confidence, &meta)) { 441 return UNKNOWN_ERROR; 442 } 443 444 // We successfully identified the file's extractor to 445 // be, remember this mime type so we don't have to 446 // sniff it again when we call MediaExtractor::Create() 447 mSniffedMIME = tmp.string(); 448 449 if (meta == NULL 450 || !meta->findInt64("meta-data-size", 451 reinterpret_cast<int64_t*>(&mMetaDataSize))) { 452 mMetaDataSize = kDefaultMetaSize; 453 } 454 455 if (mMetaDataSize < 0ll) { 456 ALOGE("invalid metaDataSize = %lld bytes", mMetaDataSize); 457 return UNKNOWN_ERROR; 458 } 459 } 460 461 return -EAGAIN; 462} 463 464void NuPlayer::GenericSource::start() { 465 ALOGI("start"); 466 467 mStopRead = false; 468 if (mAudioTrack.mSource != NULL) { 469 CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK); 470 471 postReadBuffer(MEDIA_TRACK_TYPE_AUDIO); 472 } 473 474 if (mVideoTrack.mSource != NULL) { 475 CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK); 476 477 postReadBuffer(MEDIA_TRACK_TYPE_VIDEO); 478 } 479 480 setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000); 481 mStarted = true; 482} 483 484void NuPlayer::GenericSource::stop() { 485 // nothing to do, just account for DRM playback status 486 setDrmPlaybackStatusIfNeeded(Playback::STOP, 0); 487 mStarted = false; 488 if (mIsWidevine) { 489 // For a widevine source we need to prevent any further reads. 490 sp<AMessage> msg = new AMessage(kWhatStopWidevine, id()); 491 sp<AMessage> response; 492 (void) msg->postAndAwaitResponse(&response); 493 } 494} 495 496void NuPlayer::GenericSource::pause() { 497 // nothing to do, just account for DRM playback status 498 setDrmPlaybackStatusIfNeeded(Playback::PAUSE, 0); 499 mStarted = false; 500} 501 502void NuPlayer::GenericSource::resume() { 503 // nothing to do, just account for DRM playback status 504 setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000); 505 mStarted = true; 506} 507 508void NuPlayer::GenericSource::disconnect() { 509 if (mDataSource != NULL) { 510 // disconnect data source 511 if (mDataSource->flags() & DataSource::kIsCachingDataSource) { 512 static_cast<NuCachedSource2 *>(mDataSource.get())->disconnect(); 513 } 514 } else if (mHttpSource != NULL) { 515 static_cast<HTTPBase *>(mHttpSource.get())->disconnect(); 516 } 517} 518 519void NuPlayer::GenericSource::setDrmPlaybackStatusIfNeeded(int playbackStatus, int64_t position) { 520 if (mDecryptHandle != NULL) { 521 mDrmManagerClient->setPlaybackStatus(mDecryptHandle, playbackStatus, position); 522 } 523 mSubtitleTrack.mPackets = new AnotherPacketSource(NULL); 524 mTimedTextTrack.mPackets = new AnotherPacketSource(NULL); 525} 526 527status_t NuPlayer::GenericSource::feedMoreTSData() { 528 return OK; 529} 530 531void NuPlayer::GenericSource::schedulePollBuffering() { 532 sp<AMessage> msg = new AMessage(kWhatPollBuffering, id()); 533 msg->setInt32("generation", mPollBufferingGeneration); 534 msg->post(1000000ll); 535} 536 537void NuPlayer::GenericSource::cancelPollBuffering() { 538 ++mPollBufferingGeneration; 539} 540 541void NuPlayer::GenericSource::notifyBufferingUpdate(int percentage) { 542 sp<AMessage> msg = dupNotify(); 543 msg->setInt32("what", kWhatBufferingUpdate); 544 msg->setInt32("percentage", percentage); 545 msg->post(); 546} 547 548void NuPlayer::GenericSource::onPollBuffering() { 549 status_t finalStatus = UNKNOWN_ERROR; 550 int64_t cachedDurationUs = 0ll; 551 552 if (mCachedSource != NULL) { 553 size_t cachedDataRemaining = 554 mCachedSource->approxDataRemaining(&finalStatus); 555 556 if (finalStatus == OK) { 557 off64_t size; 558 int64_t bitrate = 0ll; 559 if (mDurationUs > 0 && mCachedSource->getSize(&size) == OK) { 560 bitrate = size * 8000000ll / mDurationUs; 561 } else if (mBitrate > 0) { 562 bitrate = mBitrate; 563 } 564 if (bitrate > 0) { 565 cachedDurationUs = cachedDataRemaining * 8000000ll / bitrate; 566 } 567 } 568 } else if (mWVMExtractor != NULL) { 569 cachedDurationUs 570 = mWVMExtractor->getCachedDurationUs(&finalStatus); 571 } 572 573 if (finalStatus == ERROR_END_OF_STREAM) { 574 notifyBufferingUpdate(100); 575 cancelPollBuffering(); 576 return; 577 } else if (cachedDurationUs > 0ll && mDurationUs > 0ll) { 578 int percentage = 100.0 * cachedDurationUs / mDurationUs; 579 if (percentage > 100) { 580 percentage = 100; 581 } 582 583 notifyBufferingUpdate(percentage); 584 } 585 586 schedulePollBuffering(); 587} 588 589 590void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) { 591 switch (msg->what()) { 592 case kWhatPrepareAsync: 593 { 594 onPrepareAsync(); 595 break; 596 } 597 case kWhatFetchSubtitleData: 598 { 599 fetchTextData(kWhatSendSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, 600 mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); 601 break; 602 } 603 604 case kWhatFetchTimedTextData: 605 { 606 fetchTextData(kWhatSendTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, 607 mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); 608 break; 609 } 610 611 case kWhatSendSubtitleData: 612 { 613 sendTextData(kWhatSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, 614 mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); 615 break; 616 } 617 618 case kWhatSendTimedTextData: 619 { 620 sendTextData(kWhatTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, 621 mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); 622 break; 623 } 624 625 case kWhatChangeAVSource: 626 { 627 int32_t trackIndex; 628 CHECK(msg->findInt32("trackIndex", &trackIndex)); 629 const sp<MediaSource> source = mSources.itemAt(trackIndex); 630 631 Track* track; 632 const char *mime; 633 media_track_type trackType, counterpartType; 634 sp<MetaData> meta = source->getFormat(); 635 meta->findCString(kKeyMIMEType, &mime); 636 if (!strncasecmp(mime, "audio/", 6)) { 637 track = &mAudioTrack; 638 trackType = MEDIA_TRACK_TYPE_AUDIO; 639 counterpartType = MEDIA_TRACK_TYPE_VIDEO;; 640 } else { 641 CHECK(!strncasecmp(mime, "video/", 6)); 642 track = &mVideoTrack; 643 trackType = MEDIA_TRACK_TYPE_VIDEO; 644 counterpartType = MEDIA_TRACK_TYPE_AUDIO;; 645 } 646 647 648 if (track->mSource != NULL) { 649 track->mSource->stop(); 650 } 651 track->mSource = source; 652 track->mSource->start(); 653 track->mIndex = trackIndex; 654 655 int64_t timeUs, actualTimeUs; 656 const bool formatChange = true; 657 if (trackType == MEDIA_TRACK_TYPE_AUDIO) { 658 timeUs = mAudioLastDequeueTimeUs; 659 } else { 660 timeUs = mVideoLastDequeueTimeUs; 661 } 662 readBuffer(trackType, timeUs, &actualTimeUs, formatChange); 663 readBuffer(counterpartType, -1, NULL, formatChange); 664 ALOGV("timeUs %lld actualTimeUs %lld", timeUs, actualTimeUs); 665 666 break; 667 } 668 case kWhatPollBuffering: 669 { 670 int32_t generation; 671 CHECK(msg->findInt32("generation", &generation)); 672 if (generation == mPollBufferingGeneration) { 673 onPollBuffering(); 674 } 675 break; 676 } 677 678 case kWhatGetFormat: 679 { 680 onGetFormatMeta(msg); 681 break; 682 } 683 684 case kWhatGetSelectedTrack: 685 { 686 onGetSelectedTrack(msg); 687 break; 688 } 689 690 case kWhatSelectTrack: 691 { 692 onSelectTrack(msg); 693 break; 694 } 695 696 case kWhatSeek: 697 { 698 onSeek(msg); 699 break; 700 } 701 702 case kWhatReadBuffer: 703 { 704 onReadBuffer(msg); 705 break; 706 } 707 708 case kWhatStopWidevine: 709 { 710 // mStopRead is only used for Widevine to prevent the video source 711 // from being read while the associated video decoder is shutting down. 712 mStopRead = true; 713 if (mVideoTrack.mSource != NULL) { 714 mVideoTrack.mPackets->clear(); 715 } 716 sp<AMessage> response = new AMessage; 717 uint32_t replyID; 718 CHECK(msg->senderAwaitsResponse(&replyID)); 719 response->postReply(replyID); 720 break; 721 } 722 default: 723 Source::onMessageReceived(msg); 724 break; 725 } 726} 727 728void NuPlayer::GenericSource::fetchTextData( 729 uint32_t sendWhat, 730 media_track_type type, 731 int32_t curGen, 732 sp<AnotherPacketSource> packets, 733 sp<AMessage> msg) { 734 int32_t msgGeneration; 735 CHECK(msg->findInt32("generation", &msgGeneration)); 736 if (msgGeneration != curGen) { 737 // stale 738 return; 739 } 740 741 int32_t avail; 742 if (packets->hasBufferAvailable(&avail)) { 743 return; 744 } 745 746 int64_t timeUs; 747 CHECK(msg->findInt64("timeUs", &timeUs)); 748 749 int64_t subTimeUs; 750 readBuffer(type, timeUs, &subTimeUs); 751 752 int64_t delayUs = subTimeUs - timeUs; 753 if (msg->what() == kWhatFetchSubtitleData) { 754 const int64_t oneSecUs = 1000000ll; 755 delayUs -= oneSecUs; 756 } 757 sp<AMessage> msg2 = new AMessage(sendWhat, id()); 758 msg2->setInt32("generation", msgGeneration); 759 msg2->post(delayUs < 0 ? 0 : delayUs); 760} 761 762void NuPlayer::GenericSource::sendTextData( 763 uint32_t what, 764 media_track_type type, 765 int32_t curGen, 766 sp<AnotherPacketSource> packets, 767 sp<AMessage> msg) { 768 int32_t msgGeneration; 769 CHECK(msg->findInt32("generation", &msgGeneration)); 770 if (msgGeneration != curGen) { 771 // stale 772 return; 773 } 774 775 int64_t subTimeUs; 776 if (packets->nextBufferTime(&subTimeUs) != OK) { 777 return; 778 } 779 780 int64_t nextSubTimeUs; 781 readBuffer(type, -1, &nextSubTimeUs); 782 783 sp<ABuffer> buffer; 784 status_t dequeueStatus = packets->dequeueAccessUnit(&buffer); 785 if (dequeueStatus == OK) { 786 sp<AMessage> notify = dupNotify(); 787 notify->setInt32("what", what); 788 notify->setBuffer("buffer", buffer); 789 notify->post(); 790 791 const int64_t delayUs = nextSubTimeUs - subTimeUs; 792 msg->post(delayUs < 0 ? 0 : delayUs); 793 } 794} 795 796sp<MetaData> NuPlayer::GenericSource::getFormatMeta(bool audio) { 797 sp<AMessage> msg = new AMessage(kWhatGetFormat, id()); 798 msg->setInt32("audio", audio); 799 800 sp<AMessage> response; 801 void *format; 802 status_t err = msg->postAndAwaitResponse(&response); 803 if (err == OK && response != NULL) { 804 CHECK(response->findPointer("format", &format)); 805 return (MetaData *)format; 806 } else { 807 return NULL; 808 } 809} 810 811void NuPlayer::GenericSource::onGetFormatMeta(sp<AMessage> msg) const { 812 int32_t audio; 813 CHECK(msg->findInt32("audio", &audio)); 814 815 sp<AMessage> response = new AMessage; 816 sp<MetaData> format = doGetFormatMeta(audio); 817 response->setPointer("format", format.get()); 818 819 uint32_t replyID; 820 CHECK(msg->senderAwaitsResponse(&replyID)); 821 response->postReply(replyID); 822} 823 824sp<MetaData> NuPlayer::GenericSource::doGetFormatMeta(bool audio) const { 825 sp<MediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource; 826 827 if (source == NULL) { 828 return NULL; 829 } 830 831 return source->getFormat(); 832} 833 834status_t NuPlayer::GenericSource::dequeueAccessUnit( 835 bool audio, sp<ABuffer> *accessUnit) { 836 Track *track = audio ? &mAudioTrack : &mVideoTrack; 837 838 if (track->mSource == NULL) { 839 return -EWOULDBLOCK; 840 } 841 842 if (mIsWidevine && !audio) { 843 // try to read a buffer as we may not have been able to the last time 844 postReadBuffer(MEDIA_TRACK_TYPE_VIDEO); 845 } 846 847 status_t finalResult; 848 if (!track->mPackets->hasBufferAvailable(&finalResult)) { 849 return (finalResult == OK ? -EWOULDBLOCK : finalResult); 850 } 851 852 status_t result = track->mPackets->dequeueAccessUnit(accessUnit); 853 854 if (!track->mPackets->hasBufferAvailable(&finalResult)) { 855 postReadBuffer(audio? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO); 856 } 857 858 if (result != OK) { 859 if (mSubtitleTrack.mSource != NULL) { 860 mSubtitleTrack.mPackets->clear(); 861 mFetchSubtitleDataGeneration++; 862 } 863 if (mTimedTextTrack.mSource != NULL) { 864 mTimedTextTrack.mPackets->clear(); 865 mFetchTimedTextDataGeneration++; 866 } 867 return result; 868 } 869 870 int64_t timeUs; 871 status_t eosResult; // ignored 872 CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs)); 873 if (audio) { 874 mAudioLastDequeueTimeUs = timeUs; 875 } else { 876 mVideoLastDequeueTimeUs = timeUs; 877 } 878 879 if (mSubtitleTrack.mSource != NULL 880 && !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) { 881 sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, id()); 882 msg->setInt64("timeUs", timeUs); 883 msg->setInt32("generation", mFetchSubtitleDataGeneration); 884 msg->post(); 885 } 886 887 if (mTimedTextTrack.mSource != NULL 888 && !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) { 889 sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, id()); 890 msg->setInt64("timeUs", timeUs); 891 msg->setInt32("generation", mFetchTimedTextDataGeneration); 892 msg->post(); 893 } 894 895 return result; 896} 897 898status_t NuPlayer::GenericSource::getDuration(int64_t *durationUs) { 899 *durationUs = mDurationUs; 900 return OK; 901} 902 903size_t NuPlayer::GenericSource::getTrackCount() const { 904 return mSources.size(); 905} 906 907sp<AMessage> NuPlayer::GenericSource::getTrackInfo(size_t trackIndex) const { 908 size_t trackCount = mSources.size(); 909 if (trackIndex >= trackCount) { 910 return NULL; 911 } 912 913 sp<AMessage> format = new AMessage(); 914 sp<MetaData> meta = mSources.itemAt(trackIndex)->getFormat(); 915 916 const char *mime; 917 CHECK(meta->findCString(kKeyMIMEType, &mime)); 918 919 int32_t trackType; 920 if (!strncasecmp(mime, "video/", 6)) { 921 trackType = MEDIA_TRACK_TYPE_VIDEO; 922 } else if (!strncasecmp(mime, "audio/", 6)) { 923 trackType = MEDIA_TRACK_TYPE_AUDIO; 924 } else if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { 925 trackType = MEDIA_TRACK_TYPE_TIMEDTEXT; 926 } else { 927 trackType = MEDIA_TRACK_TYPE_UNKNOWN; 928 } 929 format->setInt32("type", trackType); 930 931 const char *lang; 932 if (!meta->findCString(kKeyMediaLanguage, &lang)) { 933 lang = "und"; 934 } 935 format->setString("language", lang); 936 937 if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { 938 format->setString("mime", mime); 939 940 int32_t isAutoselect = 1, isDefault = 0, isForced = 0; 941 meta->findInt32(kKeyTrackIsAutoselect, &isAutoselect); 942 meta->findInt32(kKeyTrackIsDefault, &isDefault); 943 meta->findInt32(kKeyTrackIsForced, &isForced); 944 945 format->setInt32("auto", !!isAutoselect); 946 format->setInt32("default", !!isDefault); 947 format->setInt32("forced", !!isForced); 948 } 949 950 return format; 951} 952 953ssize_t NuPlayer::GenericSource::getSelectedTrack(media_track_type type) const { 954 sp<AMessage> msg = new AMessage(kWhatGetSelectedTrack, id()); 955 msg->setInt32("type", type); 956 957 sp<AMessage> response; 958 int32_t index; 959 status_t err = msg->postAndAwaitResponse(&response); 960 if (err == OK && response != NULL) { 961 CHECK(response->findInt32("index", &index)); 962 return index; 963 } else { 964 return -1; 965 } 966} 967 968void NuPlayer::GenericSource::onGetSelectedTrack(sp<AMessage> msg) const { 969 int32_t tmpType; 970 CHECK(msg->findInt32("type", &tmpType)); 971 media_track_type type = (media_track_type)tmpType; 972 973 sp<AMessage> response = new AMessage; 974 ssize_t index = doGetSelectedTrack(type); 975 response->setInt32("index", index); 976 977 uint32_t replyID; 978 CHECK(msg->senderAwaitsResponse(&replyID)); 979 response->postReply(replyID); 980} 981 982ssize_t NuPlayer::GenericSource::doGetSelectedTrack(media_track_type type) const { 983 const Track *track = NULL; 984 switch (type) { 985 case MEDIA_TRACK_TYPE_VIDEO: 986 track = &mVideoTrack; 987 break; 988 case MEDIA_TRACK_TYPE_AUDIO: 989 track = &mAudioTrack; 990 break; 991 case MEDIA_TRACK_TYPE_TIMEDTEXT: 992 track = &mTimedTextTrack; 993 break; 994 case MEDIA_TRACK_TYPE_SUBTITLE: 995 track = &mSubtitleTrack; 996 break; 997 default: 998 break; 999 } 1000 1001 if (track != NULL && track->mSource != NULL) { 1002 return track->mIndex; 1003 } 1004 1005 return -1; 1006} 1007 1008status_t NuPlayer::GenericSource::selectTrack(size_t trackIndex, bool select, int64_t timeUs) { 1009 ALOGV("%s track: %zu", select ? "select" : "deselect", trackIndex); 1010 sp<AMessage> msg = new AMessage(kWhatSelectTrack, id()); 1011 msg->setInt32("trackIndex", trackIndex); 1012 msg->setInt32("select", select); 1013 msg->setInt64("timeUs", timeUs); 1014 1015 sp<AMessage> response; 1016 status_t err = msg->postAndAwaitResponse(&response); 1017 if (err == OK && response != NULL) { 1018 CHECK(response->findInt32("err", &err)); 1019 } 1020 1021 return err; 1022} 1023 1024void NuPlayer::GenericSource::onSelectTrack(sp<AMessage> msg) { 1025 int32_t trackIndex, select; 1026 int64_t timeUs; 1027 CHECK(msg->findInt32("trackIndex", &trackIndex)); 1028 CHECK(msg->findInt32("select", &select)); 1029 CHECK(msg->findInt64("timeUs", &timeUs)); 1030 1031 sp<AMessage> response = new AMessage; 1032 status_t err = doSelectTrack(trackIndex, select, timeUs); 1033 response->setInt32("err", err); 1034 1035 uint32_t replyID; 1036 CHECK(msg->senderAwaitsResponse(&replyID)); 1037 response->postReply(replyID); 1038} 1039 1040status_t NuPlayer::GenericSource::doSelectTrack(size_t trackIndex, bool select, int64_t timeUs) { 1041 if (trackIndex >= mSources.size()) { 1042 return BAD_INDEX; 1043 } 1044 1045 if (!select) { 1046 Track* track = NULL; 1047 if (mSubtitleTrack.mSource != NULL && trackIndex == mSubtitleTrack.mIndex) { 1048 track = &mSubtitleTrack; 1049 mFetchSubtitleDataGeneration++; 1050 } else if (mTimedTextTrack.mSource != NULL && trackIndex == mTimedTextTrack.mIndex) { 1051 track = &mTimedTextTrack; 1052 mFetchTimedTextDataGeneration++; 1053 } 1054 if (track == NULL) { 1055 return INVALID_OPERATION; 1056 } 1057 track->mSource->stop(); 1058 track->mSource = NULL; 1059 track->mPackets->clear(); 1060 return OK; 1061 } 1062 1063 const sp<MediaSource> source = mSources.itemAt(trackIndex); 1064 sp<MetaData> meta = source->getFormat(); 1065 const char *mime; 1066 CHECK(meta->findCString(kKeyMIMEType, &mime)); 1067 if (!strncasecmp(mime, "text/", 5)) { 1068 bool isSubtitle = strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP); 1069 Track *track = isSubtitle ? &mSubtitleTrack : &mTimedTextTrack; 1070 if (track->mSource != NULL && track->mIndex == trackIndex) { 1071 return OK; 1072 } 1073 track->mIndex = trackIndex; 1074 if (track->mSource != NULL) { 1075 track->mSource->stop(); 1076 } 1077 track->mSource = mSources.itemAt(trackIndex); 1078 track->mSource->start(); 1079 if (track->mPackets == NULL) { 1080 track->mPackets = new AnotherPacketSource(track->mSource->getFormat()); 1081 } else { 1082 track->mPackets->clear(); 1083 track->mPackets->setFormat(track->mSource->getFormat()); 1084 1085 } 1086 1087 if (isSubtitle) { 1088 mFetchSubtitleDataGeneration++; 1089 } else { 1090 mFetchTimedTextDataGeneration++; 1091 } 1092 1093 status_t eosResult; // ignored 1094 if (mSubtitleTrack.mSource != NULL 1095 && !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) { 1096 sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, id()); 1097 msg->setInt64("timeUs", timeUs); 1098 msg->setInt32("generation", mFetchSubtitleDataGeneration); 1099 msg->post(); 1100 } 1101 1102 if (mTimedTextTrack.mSource != NULL 1103 && !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) { 1104 sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, id()); 1105 msg->setInt64("timeUs", timeUs); 1106 msg->setInt32("generation", mFetchTimedTextDataGeneration); 1107 msg->post(); 1108 } 1109 1110 return OK; 1111 } else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) { 1112 bool audio = !strncasecmp(mime, "audio/", 6); 1113 Track *track = audio ? &mAudioTrack : &mVideoTrack; 1114 if (track->mSource != NULL && track->mIndex == trackIndex) { 1115 return OK; 1116 } 1117 1118 sp<AMessage> msg = new AMessage(kWhatChangeAVSource, id()); 1119 msg->setInt32("trackIndex", trackIndex); 1120 msg->post(); 1121 return OK; 1122 } 1123 1124 return INVALID_OPERATION; 1125} 1126 1127status_t NuPlayer::GenericSource::seekTo(int64_t seekTimeUs) { 1128 sp<AMessage> msg = new AMessage(kWhatSeek, id()); 1129 msg->setInt64("seekTimeUs", seekTimeUs); 1130 1131 sp<AMessage> response; 1132 status_t err = msg->postAndAwaitResponse(&response); 1133 if (err == OK && response != NULL) { 1134 CHECK(response->findInt32("err", &err)); 1135 } 1136 1137 return err; 1138} 1139 1140void NuPlayer::GenericSource::onSeek(sp<AMessage> msg) { 1141 int64_t seekTimeUs; 1142 CHECK(msg->findInt64("seekTimeUs", &seekTimeUs)); 1143 1144 sp<AMessage> response = new AMessage; 1145 status_t err = doSeek(seekTimeUs); 1146 response->setInt32("err", err); 1147 1148 uint32_t replyID; 1149 CHECK(msg->senderAwaitsResponse(&replyID)); 1150 response->postReply(replyID); 1151} 1152 1153status_t NuPlayer::GenericSource::doSeek(int64_t seekTimeUs) { 1154 // If the Widevine source is stopped, do not attempt to read any 1155 // more buffers. 1156 if (mStopRead) { 1157 return INVALID_OPERATION; 1158 } 1159 if (mVideoTrack.mSource != NULL) { 1160 int64_t actualTimeUs; 1161 readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, &actualTimeUs); 1162 1163 seekTimeUs = actualTimeUs; 1164 mVideoLastDequeueTimeUs = seekTimeUs; 1165 } 1166 1167 if (mAudioTrack.mSource != NULL) { 1168 readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs); 1169 mAudioLastDequeueTimeUs = seekTimeUs; 1170 } 1171 1172 setDrmPlaybackStatusIfNeeded(Playback::START, seekTimeUs / 1000); 1173 if (!mStarted) { 1174 setDrmPlaybackStatusIfNeeded(Playback::PAUSE, 0); 1175 } 1176 return OK; 1177} 1178 1179sp<ABuffer> NuPlayer::GenericSource::mediaBufferToABuffer( 1180 MediaBuffer* mb, 1181 media_track_type trackType, 1182 int64_t /* seekTimeUs */, 1183 int64_t *actualTimeUs) { 1184 bool audio = trackType == MEDIA_TRACK_TYPE_AUDIO; 1185 size_t outLength = mb->range_length(); 1186 1187 if (audio && mAudioIsVorbis) { 1188 outLength += sizeof(int32_t); 1189 } 1190 1191 sp<ABuffer> ab; 1192 if (mIsWidevine && !audio) { 1193 // data is already provided in the buffer 1194 ab = new ABuffer(NULL, mb->range_length()); 1195 mb->add_ref(); 1196 ab->setMediaBufferBase(mb); 1197 } else { 1198 ab = new ABuffer(outLength); 1199 memcpy(ab->data(), 1200 (const uint8_t *)mb->data() + mb->range_offset(), 1201 mb->range_length()); 1202 } 1203 1204 if (audio && mAudioIsVorbis) { 1205 int32_t numPageSamples; 1206 if (!mb->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) { 1207 numPageSamples = -1; 1208 } 1209 1210 uint8_t* abEnd = ab->data() + mb->range_length(); 1211 memcpy(abEnd, &numPageSamples, sizeof(numPageSamples)); 1212 } 1213 1214 sp<AMessage> meta = ab->meta(); 1215 1216 int64_t timeUs; 1217 CHECK(mb->meta_data()->findInt64(kKeyTime, &timeUs)); 1218 meta->setInt64("timeUs", timeUs); 1219 1220#if 0 1221 // Temporarily disable pre-roll till we have a full solution to handle 1222 // both single seek and continous seek gracefully. 1223 if (seekTimeUs > timeUs) { 1224 sp<AMessage> extra = new AMessage; 1225 extra->setInt64("resume-at-mediaTimeUs", seekTimeUs); 1226 meta->setMessage("extra", extra); 1227 } 1228#endif 1229 1230 if (trackType == MEDIA_TRACK_TYPE_TIMEDTEXT) { 1231 const char *mime; 1232 CHECK(mTimedTextTrack.mSource != NULL 1233 && mTimedTextTrack.mSource->getFormat()->findCString(kKeyMIMEType, &mime)); 1234 meta->setString("mime", mime); 1235 } 1236 1237 int64_t durationUs; 1238 if (mb->meta_data()->findInt64(kKeyDuration, &durationUs)) { 1239 meta->setInt64("durationUs", durationUs); 1240 } 1241 1242 if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { 1243 meta->setInt32("trackIndex", mSubtitleTrack.mIndex); 1244 } 1245 1246 if (actualTimeUs) { 1247 *actualTimeUs = timeUs; 1248 } 1249 1250 mb->release(); 1251 mb = NULL; 1252 1253 return ab; 1254} 1255 1256void NuPlayer::GenericSource::postReadBuffer(media_track_type trackType) { 1257 Mutex::Autolock _l(mReadBufferLock); 1258 1259 if ((mPendingReadBufferTypes & (1 << trackType)) == 0) { 1260 mPendingReadBufferTypes |= (1 << trackType); 1261 sp<AMessage> msg = new AMessage(kWhatReadBuffer, id()); 1262 msg->setInt32("trackType", trackType); 1263 msg->post(); 1264 } 1265} 1266 1267void NuPlayer::GenericSource::onReadBuffer(sp<AMessage> msg) { 1268 int32_t tmpType; 1269 CHECK(msg->findInt32("trackType", &tmpType)); 1270 media_track_type trackType = (media_track_type)tmpType; 1271 { 1272 // only protect the variable change, as readBuffer may 1273 // take considerable time. This may result in one extra 1274 // read being processed, but that is benign. 1275 Mutex::Autolock _l(mReadBufferLock); 1276 mPendingReadBufferTypes &= ~(1 << trackType); 1277 } 1278 readBuffer(trackType); 1279} 1280 1281void NuPlayer::GenericSource::readBuffer( 1282 media_track_type trackType, int64_t seekTimeUs, int64_t *actualTimeUs, bool formatChange) { 1283 // Do not read data if Widevine source is stopped 1284 if (mStopRead) { 1285 return; 1286 } 1287 Track *track; 1288 size_t maxBuffers = 1; 1289 switch (trackType) { 1290 case MEDIA_TRACK_TYPE_VIDEO: 1291 track = &mVideoTrack; 1292 if (mIsWidevine) { 1293 maxBuffers = 2; 1294 } 1295 break; 1296 case MEDIA_TRACK_TYPE_AUDIO: 1297 track = &mAudioTrack; 1298 if (mIsWidevine) { 1299 maxBuffers = 8; 1300 } else { 1301 maxBuffers = 64; 1302 } 1303 break; 1304 case MEDIA_TRACK_TYPE_SUBTITLE: 1305 track = &mSubtitleTrack; 1306 break; 1307 case MEDIA_TRACK_TYPE_TIMEDTEXT: 1308 track = &mTimedTextTrack; 1309 break; 1310 default: 1311 TRESPASS(); 1312 } 1313 1314 if (track->mSource == NULL) { 1315 return; 1316 } 1317 1318 if (actualTimeUs) { 1319 *actualTimeUs = seekTimeUs; 1320 } 1321 1322 MediaSource::ReadOptions options; 1323 1324 bool seeking = false; 1325 1326 if (seekTimeUs >= 0) { 1327 options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); 1328 seeking = true; 1329 } 1330 1331 if (mIsWidevine && trackType != MEDIA_TRACK_TYPE_AUDIO) { 1332 options.setNonBlocking(); 1333 } 1334 1335 for (size_t numBuffers = 0; numBuffers < maxBuffers; ) { 1336 MediaBuffer *mbuf; 1337 status_t err = track->mSource->read(&mbuf, &options); 1338 1339 options.clearSeekTo(); 1340 1341 if (err == OK) { 1342 int64_t timeUs; 1343 CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs)); 1344 if (trackType == MEDIA_TRACK_TYPE_AUDIO) { 1345 mAudioTimeUs = timeUs; 1346 } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) { 1347 mVideoTimeUs = timeUs; 1348 } 1349 1350 // formatChange && seeking: track whose source is changed during selection 1351 // formatChange && !seeking: track whose source is not changed during selection 1352 // !formatChange: normal seek 1353 if ((seeking || formatChange) 1354 && (trackType == MEDIA_TRACK_TYPE_AUDIO 1355 || trackType == MEDIA_TRACK_TYPE_VIDEO)) { 1356 ATSParser::DiscontinuityType type = (formatChange && seeking) 1357 ? ATSParser::DISCONTINUITY_FORMATCHANGE 1358 : ATSParser::DISCONTINUITY_NONE; 1359 track->mPackets->queueDiscontinuity( type, NULL, true /* discard */); 1360 } 1361 1362 sp<ABuffer> buffer = mediaBufferToABuffer( 1363 mbuf, trackType, seekTimeUs, actualTimeUs); 1364 track->mPackets->queueAccessUnit(buffer); 1365 formatChange = false; 1366 seeking = false; 1367 ++numBuffers; 1368 } else if (err == WOULD_BLOCK) { 1369 break; 1370 } else if (err == INFO_FORMAT_CHANGED) { 1371#if 0 1372 track->mPackets->queueDiscontinuity( 1373 ATSParser::DISCONTINUITY_FORMATCHANGE, 1374 NULL, 1375 false /* discard */); 1376#endif 1377 } else { 1378 track->mPackets->signalEOS(err); 1379 break; 1380 } 1381 } 1382} 1383 1384} // namespace android 1385