/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MPEG2TSExtractor" #include #include #include "include/MPEG2TSExtractor.h" #include "include/NuCachedSource2.h" #include #include #include #include #include #include #include #include #include #include #include "AnotherPacketSource.h" #include "ATSParser.h" namespace android { static const size_t kTSPacketSize = 188; struct MPEG2TSSource : public MediaSource { MPEG2TSSource( const sp &extractor, const sp &impl, bool doesSeek); virtual status_t start(MetaData *params = NULL); virtual status_t stop(); virtual sp getFormat(); virtual status_t read( MediaBuffer **buffer, const ReadOptions *options = NULL); private: sp mExtractor; sp mImpl; // If there are both audio and video streams, only the video stream // will signal seek on the extractor; otherwise the single stream will seek. bool mDoesSeek; DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSSource); }; MPEG2TSSource::MPEG2TSSource( const sp &extractor, const sp &impl, bool doesSeek) : mExtractor(extractor), mImpl(impl), mDoesSeek(doesSeek) { } status_t MPEG2TSSource::start(MetaData *params) { return mImpl->start(params); } status_t MPEG2TSSource::stop() { return mImpl->stop(); } sp MPEG2TSSource::getFormat() { return mImpl->getFormat(); } status_t MPEG2TSSource::read( MediaBuffer **out, const ReadOptions *options) { *out = NULL; int64_t seekTimeUs; ReadOptions::SeekMode seekMode; if (mDoesSeek && options && options->getSeekTo(&seekTimeUs, &seekMode)) { // seek is needed status_t err = mExtractor->seek(seekTimeUs, seekMode); if (err != OK) { return err; } } if (mExtractor->feedUntilBufferAvailable(mImpl) != OK) { return ERROR_END_OF_STREAM; } return mImpl->read(out, options); } //////////////////////////////////////////////////////////////////////////////// MPEG2TSExtractor::MPEG2TSExtractor(const sp &source) : mDataSource(source), mParser(new ATSParser), mOffset(0) { init(); } size_t MPEG2TSExtractor::countTracks() { return mSourceImpls.size(); } sp MPEG2TSExtractor::getTrack(size_t index) { if (index >= mSourceImpls.size()) { return NULL; } // The seek reference track (video if present; audio otherwise) performs // seek requests, while other tracks ignore requests. return new MPEG2TSSource(this, mSourceImpls.editItemAt(index), (mSeekSyncPoints == &mSyncPoints.editItemAt(index))); } sp MPEG2TSExtractor::getTrackMetaData( size_t index, uint32_t /* flags */) { return index < mSourceImpls.size() ? mSourceImpls.editItemAt(index)->getFormat() : NULL; } sp MPEG2TSExtractor::getMetaData() { sp meta = new MetaData; meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); return meta; } void MPEG2TSExtractor::init() { bool haveAudio = false; bool haveVideo = false; int64_t startTime = ALooper::GetNowUs(); while (feedMore() == OK) { if (haveAudio && haveVideo) { break; } if (!haveVideo) { sp impl = (AnotherPacketSource *)mParser->getSource( ATSParser::VIDEO).get(); if (impl != NULL) { haveVideo = true; mSourceImpls.push(impl); mSyncPoints.push(); mSeekSyncPoints = &mSyncPoints.editTop(); } } if (!haveAudio) { sp impl = (AnotherPacketSource *)mParser->getSource( ATSParser::AUDIO).get(); if (impl != NULL) { haveAudio = true; mSourceImpls.push(impl); mSyncPoints.push(); if (!haveVideo) { mSeekSyncPoints = &mSyncPoints.editTop(); } } } // Wait only for 2 seconds to detect audio/video streams. if (ALooper::GetNowUs() - startTime > 2000000ll) { break; } } off64_t size; if (mDataSource->getSize(&size) == OK && (haveAudio || haveVideo)) { sp impl = haveVideo ? (AnotherPacketSource *)mParser->getSource( ATSParser::VIDEO).get() : (AnotherPacketSource *)mParser->getSource( ATSParser::AUDIO).get(); size_t prevSyncSize = 1; int64_t durationUs = -1; List durations; // Estimate duration --- stabilize until you get <500ms deviation. while (feedMore() == OK && ALooper::GetNowUs() - startTime <= 2000000ll) { if (mSeekSyncPoints->size() > prevSyncSize) { prevSyncSize = mSeekSyncPoints->size(); int64_t diffUs = mSeekSyncPoints->keyAt(prevSyncSize - 1) - mSeekSyncPoints->keyAt(0); off64_t diffOffset = mSeekSyncPoints->valueAt(prevSyncSize - 1) - mSeekSyncPoints->valueAt(0); durationUs = size * diffUs / diffOffset; durations.push_back(durationUs); if (durations.size() > 5) { durations.erase(durations.begin()); int64_t min = *durations.begin(); int64_t max = *durations.begin(); for (List::iterator i = durations.begin(); i != durations.end(); ++i) { if (min > *i) { min = *i; } if (max < *i) { max = *i; } } if (max - min < 500 * 1000) { break; } } } } status_t err; int64_t bufferedDurationUs; bufferedDurationUs = impl->getBufferedDurationUs(&err); if (err == ERROR_END_OF_STREAM) { durationUs = bufferedDurationUs; } if (durationUs > 0) { const sp meta = impl->getFormat(); meta->setInt64(kKeyDuration, durationUs); impl->setFormat(meta); } } ALOGI("haveAudio=%d, haveVideo=%d, elaspedTime=%" PRId64, haveAudio, haveVideo, ALooper::GetNowUs() - startTime); } status_t MPEG2TSExtractor::feedMore() { Mutex::Autolock autoLock(mLock); uint8_t packet[kTSPacketSize]; ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize); if (n < (ssize_t)kTSPacketSize) { if (n >= 0) { mParser->signalEOS(ERROR_END_OF_STREAM); } return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM; } ATSParser::SyncEvent event(mOffset); mOffset += n; status_t err = mParser->feedTSPacket(packet, kTSPacketSize, &event); if (event.isInit()) { for (size_t i = 0; i < mSourceImpls.size(); ++i) { if (mSourceImpls[i].get() == event.getMediaSource().get()) { KeyedVector *syncPoints = &mSyncPoints.editItemAt(i); syncPoints->add(event.getTimeUs(), event.getOffset()); // We're keeping the size of the sync points at most 5mb per a track. size_t size = syncPoints->size(); if (size >= 327680) { int64_t firstTimeUs = syncPoints->keyAt(0); int64_t lastTimeUs = syncPoints->keyAt(size - 1); if (event.getTimeUs() - firstTimeUs > lastTimeUs - event.getTimeUs()) { syncPoints->removeItemsAt(0, 4096); } else { syncPoints->removeItemsAt(size - 4096, 4096); } } break; } } } return err; } uint32_t MPEG2TSExtractor::flags() const { return CAN_PAUSE | CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD; } status_t MPEG2TSExtractor::seek(int64_t seekTimeUs, const MediaSource::ReadOptions::SeekMode &seekMode) { if (mSeekSyncPoints == NULL || mSeekSyncPoints->isEmpty()) { ALOGW("No sync point to seek to."); // ... and therefore we have nothing useful to do here. return OK; } // Determine whether we're seeking beyond the known area. bool shouldSeekBeyond = (seekTimeUs > mSeekSyncPoints->keyAt(mSeekSyncPoints->size() - 1)); // Determine the sync point to seek. size_t index = 0; for (; index < mSeekSyncPoints->size(); ++index) { int64_t timeUs = mSeekSyncPoints->keyAt(index); if (timeUs > seekTimeUs) { break; } } switch (seekMode) { case MediaSource::ReadOptions::SEEK_NEXT_SYNC: if (index == mSeekSyncPoints->size()) { ALOGW("Next sync not found; starting from the latest sync."); --index; } break; case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC: case MediaSource::ReadOptions::SEEK_CLOSEST: ALOGW("seekMode not supported: %d; falling back to PREVIOUS_SYNC", seekMode); // fall-through case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC: if (index == 0) { ALOGW("Previous sync not found; starting from the earliest " "sync."); } else { --index; } break; } if (!shouldSeekBeyond || mOffset <= mSeekSyncPoints->valueAt(index)) { int64_t actualSeekTimeUs = mSeekSyncPoints->keyAt(index); mOffset = mSeekSyncPoints->valueAt(index); status_t err = queueDiscontinuityForSeek(actualSeekTimeUs); if (err != OK) { return err; } } if (shouldSeekBeyond) { status_t err = seekBeyond(seekTimeUs); if (err != OK) { return err; } } // Fast-forward to sync frame. for (size_t i = 0; i < mSourceImpls.size(); ++i) { const sp &impl = mSourceImpls[i]; status_t err; feedUntilBufferAvailable(impl); while (impl->hasBufferAvailable(&err)) { sp meta = impl->getMetaAfterLastDequeued(0); sp buffer; if (meta == NULL) { return UNKNOWN_ERROR; } int32_t sync; if (meta->findInt32("isSync", &sync) && sync) { break; } err = impl->dequeueAccessUnit(&buffer); if (err != OK) { return err; } feedUntilBufferAvailable(impl); } } return OK; } status_t MPEG2TSExtractor::queueDiscontinuityForSeek(int64_t actualSeekTimeUs) { // Signal discontinuity sp extra(new AMessage); extra->setInt64(IStreamListener::kKeyMediaTimeUs, actualSeekTimeUs); mParser->signalDiscontinuity(ATSParser::DISCONTINUITY_TIME, extra); // After discontinuity, impl should only have discontinuities // with the last being what we queued. Dequeue them all here. for (size_t i = 0; i < mSourceImpls.size(); ++i) { const sp &impl = mSourceImpls.itemAt(i); sp buffer; status_t err; while (impl->hasBufferAvailable(&err)) { if (err != OK) { return err; } err = impl->dequeueAccessUnit(&buffer); // If the source contains anything but discontinuity, that's // a programming mistake. CHECK(err == INFO_DISCONTINUITY); } } // Feed until we have a buffer for each source. for (size_t i = 0; i < mSourceImpls.size(); ++i) { const sp &impl = mSourceImpls.itemAt(i); sp buffer; status_t err = feedUntilBufferAvailable(impl); if (err != OK) { return err; } } return OK; } status_t MPEG2TSExtractor::seekBeyond(int64_t seekTimeUs) { // If we're seeking beyond where we know --- read until we reach there. size_t syncPointsSize = mSeekSyncPoints->size(); while (seekTimeUs > mSeekSyncPoints->keyAt( mSeekSyncPoints->size() - 1)) { status_t err; if (syncPointsSize < mSeekSyncPoints->size()) { syncPointsSize = mSeekSyncPoints->size(); int64_t syncTimeUs = mSeekSyncPoints->keyAt(syncPointsSize - 1); // Dequeue buffers before sync point in order to avoid too much // cache building up. sp buffer; for (size_t i = 0; i < mSourceImpls.size(); ++i) { const sp &impl = mSourceImpls[i]; int64_t timeUs; while ((err = impl->nextBufferTime(&timeUs)) == OK) { if (timeUs < syncTimeUs) { impl->dequeueAccessUnit(&buffer); } else { break; } } if (err != OK && err != -EWOULDBLOCK) { return err; } } } if (feedMore() != OK) { return ERROR_END_OF_STREAM; } } return OK; } status_t MPEG2TSExtractor::feedUntilBufferAvailable( const sp &impl) { status_t finalResult; while (!impl->hasBufferAvailable(&finalResult)) { if (finalResult != OK) { return finalResult; } status_t err = feedMore(); if (err != OK) { impl->signalEOS(err); } } return OK; } //////////////////////////////////////////////////////////////////////////////// bool SniffMPEG2TS( const sp &source, String8 *mimeType, float *confidence, sp *) { for (int i = 0; i < 5; ++i) { char header; if (source->readAt(kTSPacketSize * i, &header, 1) != 1 || header != 0x47) { return false; } } *confidence = 0.1f; mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2TS); return true; } } // namespace android