/* * Copyright (C) 2012 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 "GenericSource" #include "GenericSource.h" #include "AnotherPacketSource.h" #include #include #include #include #include #include #include #include #include #include #include "../../libstagefright/include/WVMExtractor.h" namespace android { NuPlayer::GenericSource::GenericSource( const sp ¬ify, bool uidValid, uid_t uid) : Source(notify), mFetchSubtitleDataGeneration(0), mFetchTimedTextDataGeneration(0), mDurationUs(0ll), mAudioIsVorbis(false), mIsWidevine(false), mUIDValid(uidValid), mUID(uid) { DataSource::RegisterDefaultSniffers(); } status_t NuPlayer::GenericSource::init( const sp &httpService, const char *url, const KeyedVector *headers) { mIsWidevine = !strncasecmp(url, "widevine://", 11); AString sniffedMIME; sp dataSource = DataSource::CreateFromURI(httpService, url, headers, &sniffedMIME); if (dataSource == NULL) { return UNKNOWN_ERROR; } return initFromDataSource( dataSource, sniffedMIME.empty() ? NULL : sniffedMIME.c_str()); } status_t NuPlayer::GenericSource::init( int fd, int64_t offset, int64_t length) { sp dataSource = new FileSource(dup(fd), offset, length); return initFromDataSource(dataSource, NULL); } status_t NuPlayer::GenericSource::initFromDataSource( const sp &dataSource, const char* mime) { sp extractor; if (mIsWidevine) { String8 mimeType; float confidence; sp dummy; bool success; success = SniffWVM(dataSource, &mimeType, &confidence, &dummy); if (!success || strcasecmp( mimeType.string(), MEDIA_MIMETYPE_CONTAINER_WVM)) { ALOGE("unsupported widevine mime: %s", mimeType.string()); return UNKNOWN_ERROR; } sp wvmExtractor = new WVMExtractor(dataSource); wvmExtractor->setAdaptiveStreamingMode(true); if (mUIDValid) { wvmExtractor->setUID(mUID); } extractor = wvmExtractor; } else { extractor = MediaExtractor::Create(dataSource, mime); } if (extractor == NULL) { return UNKNOWN_ERROR; } sp fileMeta = extractor->getMetaData(); if (fileMeta != NULL) { int64_t duration; if (fileMeta->findInt64(kKeyDuration, &duration)) { mDurationUs = duration; } } for (size_t i = 0; i < extractor->countTracks(); ++i) { sp meta = extractor->getTrackMetaData(i); const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); sp track = extractor->getTrack(i); if (!strncasecmp(mime, "audio/", 6)) { if (mAudioTrack.mSource == NULL) { mAudioTrack.mIndex = i; mAudioTrack.mSource = track; if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { mAudioIsVorbis = true; } else { mAudioIsVorbis = false; } } } else if (!strncasecmp(mime, "video/", 6)) { if (mVideoTrack.mSource == NULL) { mVideoTrack.mIndex = i; mVideoTrack.mSource = track; // check if the source requires secure buffers int32_t secure; if (meta->findInt32(kKeyRequiresSecureBuffers, &secure) && secure) { mIsWidevine = true; if (mUIDValid) { extractor->setUID(mUID); } } } } if (track != NULL) { mSources.push(track); int64_t durationUs; if (meta->findInt64(kKeyDuration, &durationUs)) { if (durationUs > mDurationUs) { mDurationUs = durationUs; } } } } return OK; } status_t NuPlayer::GenericSource::setBuffers(bool audio, Vector &buffers) { if (mIsWidevine && !audio) { return mVideoTrack.mSource->setBuffers(buffers); } return INVALID_OPERATION; } NuPlayer::GenericSource::~GenericSource() { } void NuPlayer::GenericSource::prepareAsync() { if (mVideoTrack.mSource != NULL) { sp meta = mVideoTrack.mSource->getFormat(); int32_t width, height; CHECK(meta->findInt32(kKeyWidth, &width)); CHECK(meta->findInt32(kKeyHeight, &height)); notifyVideoSizeChanged(width, height); } notifyFlagsChanged( (mIsWidevine ? FLAG_SECURE : 0) | FLAG_CAN_PAUSE | FLAG_CAN_SEEK_BACKWARD | FLAG_CAN_SEEK_FORWARD | FLAG_CAN_SEEK); notifyPrepared(); } void NuPlayer::GenericSource::start() { ALOGI("start"); if (mAudioTrack.mSource != NULL) { CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK); mAudioTrack.mPackets = new AnotherPacketSource(mAudioTrack.mSource->getFormat()); readBuffer(MEDIA_TRACK_TYPE_AUDIO); } if (mVideoTrack.mSource != NULL) { CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK); mVideoTrack.mPackets = new AnotherPacketSource(mVideoTrack.mSource->getFormat()); readBuffer(MEDIA_TRACK_TYPE_VIDEO); } } status_t NuPlayer::GenericSource::feedMoreTSData() { return OK; } void NuPlayer::GenericSource::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatFetchSubtitleData: { fetchTextData(kWhatSendSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); break; } case kWhatFetchTimedTextData: { fetchTextData(kWhatSendTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); break; } case kWhatSendSubtitleData: { sendTextData(kWhatSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); break; } case kWhatSendTimedTextData: { sendTextData(kWhatTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); break; } case kWhatChangeAVSource: { int32_t trackIndex; CHECK(msg->findInt32("trackIndex", &trackIndex)); const sp source = mSources.itemAt(trackIndex); Track* track; const char *mime; media_track_type trackType, counterpartType; sp meta = source->getFormat(); meta->findCString(kKeyMIMEType, &mime); if (!strncasecmp(mime, "audio/", 6)) { track = &mAudioTrack; trackType = MEDIA_TRACK_TYPE_AUDIO; counterpartType = MEDIA_TRACK_TYPE_VIDEO;; } else { CHECK(!strncasecmp(mime, "video/", 6)); track = &mVideoTrack; trackType = MEDIA_TRACK_TYPE_VIDEO; counterpartType = MEDIA_TRACK_TYPE_AUDIO;; } if (track->mSource != NULL) { track->mSource->stop(); } track->mSource = source; track->mSource->start(); track->mIndex = trackIndex; status_t avail; if (!track->mPackets->hasBufferAvailable(&avail)) { // sync from other source TRESPASS(); break; } int64_t timeUs, actualTimeUs; const bool formatChange = true; sp latestMeta = track->mPackets->getLatestMeta(); CHECK(latestMeta != NULL && latestMeta->findInt64("timeUs", &timeUs)); readBuffer(trackType, timeUs, &actualTimeUs, formatChange); readBuffer(counterpartType, -1, NULL, formatChange); ALOGV("timeUs %lld actualTimeUs %lld", timeUs, actualTimeUs); break; } default: Source::onMessageReceived(msg); break; } } void NuPlayer::GenericSource::fetchTextData( uint32_t sendWhat, media_track_type type, int32_t curGen, sp packets, sp msg) { int32_t msgGeneration; CHECK(msg->findInt32("generation", &msgGeneration)); if (msgGeneration != curGen) { // stale return; } int32_t avail; if (packets->hasBufferAvailable(&avail)) { return; } int64_t timeUs; CHECK(msg->findInt64("timeUs", &timeUs)); int64_t subTimeUs; readBuffer(type, timeUs, &subTimeUs); int64_t delayUs = subTimeUs - timeUs; if (msg->what() == kWhatFetchSubtitleData) { const int64_t oneSecUs = 1000000ll; delayUs -= oneSecUs; } sp msg2 = new AMessage(sendWhat, id()); msg2->setInt32("generation", msgGeneration); msg2->post(delayUs < 0 ? 0 : delayUs); } void NuPlayer::GenericSource::sendTextData( uint32_t what, media_track_type type, int32_t curGen, sp packets, sp msg) { int32_t msgGeneration; CHECK(msg->findInt32("generation", &msgGeneration)); if (msgGeneration != curGen) { // stale return; } int64_t subTimeUs; if (packets->nextBufferTime(&subTimeUs) != OK) { return; } int64_t nextSubTimeUs; readBuffer(type, -1, &nextSubTimeUs); sp buffer; status_t dequeueStatus = packets->dequeueAccessUnit(&buffer); if (dequeueStatus == OK) { sp notify = dupNotify(); notify->setInt32("what", what); notify->setBuffer("buffer", buffer); notify->post(); const int64_t delayUs = nextSubTimeUs - subTimeUs; msg->post(delayUs < 0 ? 0 : delayUs); } } sp NuPlayer::GenericSource::getFormatMeta(bool audio) { sp source = audio ? mAudioTrack.mSource : mVideoTrack.mSource; if (source == NULL) { return NULL; } return source->getFormat(); } status_t NuPlayer::GenericSource::dequeueAccessUnit( bool audio, sp *accessUnit) { Track *track = audio ? &mAudioTrack : &mVideoTrack; if (track->mSource == NULL) { return -EWOULDBLOCK; } if (mIsWidevine && !audio) { // try to read a buffer as we may not have been able to the last time readBuffer(MEDIA_TRACK_TYPE_VIDEO, -1ll); } status_t finalResult; if (!track->mPackets->hasBufferAvailable(&finalResult)) { return (finalResult == OK ? -EWOULDBLOCK : finalResult); } status_t result = track->mPackets->dequeueAccessUnit(accessUnit); if (!track->mPackets->hasBufferAvailable(&finalResult)) { readBuffer(audio? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO, -1ll); } if (mSubtitleTrack.mSource == NULL && mTimedTextTrack.mSource == NULL) { return result; } if (mSubtitleTrack.mSource != NULL) { CHECK(mSubtitleTrack.mPackets != NULL); } if (mTimedTextTrack.mSource != NULL) { CHECK(mTimedTextTrack.mPackets != NULL); } if (result != OK) { if (mSubtitleTrack.mSource != NULL) { mSubtitleTrack.mPackets->clear(); mFetchSubtitleDataGeneration++; } if (mTimedTextTrack.mSource != NULL) { mTimedTextTrack.mPackets->clear(); mFetchTimedTextDataGeneration++; } return result; } int64_t timeUs; status_t eosResult; // ignored CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs)); if (mSubtitleTrack.mSource != NULL && !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) { sp msg = new AMessage(kWhatFetchSubtitleData, id()); msg->setInt64("timeUs", timeUs); msg->setInt32("generation", mFetchSubtitleDataGeneration); msg->post(); } if (mTimedTextTrack.mSource != NULL && !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) { sp msg = new AMessage(kWhatFetchTimedTextData, id()); msg->setInt64("timeUs", timeUs); msg->setInt32("generation", mFetchTimedTextDataGeneration); msg->post(); } return result; } status_t NuPlayer::GenericSource::getDuration(int64_t *durationUs) { *durationUs = mDurationUs; return OK; } size_t NuPlayer::GenericSource::getTrackCount() const { return mSources.size(); } sp NuPlayer::GenericSource::getTrackInfo(size_t trackIndex) const { size_t trackCount = mSources.size(); if (trackIndex >= trackCount) { return NULL; } sp format = new AMessage(); sp meta = mSources.itemAt(trackIndex)->getFormat(); const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); int32_t trackType; if (!strncasecmp(mime, "video/", 6)) { trackType = MEDIA_TRACK_TYPE_VIDEO; } else if (!strncasecmp(mime, "audio/", 6)) { trackType = MEDIA_TRACK_TYPE_AUDIO; } else if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { trackType = MEDIA_TRACK_TYPE_TIMEDTEXT; } else { trackType = MEDIA_TRACK_TYPE_UNKNOWN; } format->setInt32("type", trackType); const char *lang; if (!meta->findCString(kKeyMediaLanguage, &lang)) { lang = "und"; } format->setString("language", lang); if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { format->setString("mime", mime); int32_t isAutoselect = 1, isDefault = 0, isForced = 0; meta->findInt32(kKeyTrackIsAutoselect, &isAutoselect); meta->findInt32(kKeyTrackIsDefault, &isDefault); meta->findInt32(kKeyTrackIsForced, &isForced); format->setInt32("auto", !!isAutoselect); format->setInt32("default", !!isDefault); format->setInt32("forced", !!isForced); } return format; } ssize_t NuPlayer::GenericSource::getSelectedTrack(media_track_type type) const { const Track *track = NULL; switch (type) { case MEDIA_TRACK_TYPE_VIDEO: track = &mVideoTrack; break; case MEDIA_TRACK_TYPE_AUDIO: track = &mAudioTrack; break; case MEDIA_TRACK_TYPE_TIMEDTEXT: track = &mTimedTextTrack; break; case MEDIA_TRACK_TYPE_SUBTITLE: track = &mSubtitleTrack; break; default: break; } if (track != NULL && track->mSource != NULL) { return track->mIndex; } return -1; } status_t NuPlayer::GenericSource::selectTrack(size_t trackIndex, bool select) { ALOGV("%s track: %zu", select ? "select" : "deselect", trackIndex); if (trackIndex >= mSources.size()) { return BAD_INDEX; } if (!select) { Track* track = NULL; if (mSubtitleTrack.mSource != NULL && trackIndex == mSubtitleTrack.mIndex) { track = &mSubtitleTrack; mFetchSubtitleDataGeneration++; } else if (mTimedTextTrack.mSource != NULL && trackIndex == mTimedTextTrack.mIndex) { track = &mTimedTextTrack; mFetchTimedTextDataGeneration++; } if (track == NULL) { return INVALID_OPERATION; } track->mSource->stop(); track->mSource = NULL; track->mPackets->clear(); return OK; } const sp source = mSources.itemAt(trackIndex); sp meta = source->getFormat(); const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); if (!strncasecmp(mime, "text/", 5)) { bool isSubtitle = strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP); Track *track = isSubtitle ? &mSubtitleTrack : &mTimedTextTrack; if (track->mSource != NULL && track->mIndex == trackIndex) { return OK; } track->mIndex = trackIndex; if (track->mSource != NULL) { track->mSource->stop(); } track->mSource = mSources.itemAt(trackIndex); track->mSource->start(); if (track->mPackets == NULL) { track->mPackets = new AnotherPacketSource(track->mSource->getFormat()); } else { track->mPackets->clear(); track->mPackets->setFormat(track->mSource->getFormat()); } if (isSubtitle) { mFetchSubtitleDataGeneration++; } else { mFetchTimedTextDataGeneration++; } return OK; } else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) { bool audio = !strncasecmp(mime, "audio/", 6); Track *track = audio ? &mAudioTrack : &mVideoTrack; if (track->mSource != NULL && track->mIndex == trackIndex) { return OK; } sp msg = new AMessage(kWhatChangeAVSource, id()); msg->setInt32("trackIndex", trackIndex); msg->post(); return OK; } return INVALID_OPERATION; } status_t NuPlayer::GenericSource::seekTo(int64_t seekTimeUs) { if (mVideoTrack.mSource != NULL) { int64_t actualTimeUs; readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, &actualTimeUs); seekTimeUs = actualTimeUs; } if (mAudioTrack.mSource != NULL) { readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs); } return OK; } sp NuPlayer::GenericSource::mediaBufferToABuffer( MediaBuffer* mb, media_track_type trackType, int64_t *actualTimeUs) { bool audio = trackType == MEDIA_TRACK_TYPE_AUDIO; size_t outLength = mb->range_length(); if (audio && mAudioIsVorbis) { outLength += sizeof(int32_t); } sp ab; if (mIsWidevine && !audio) { // data is already provided in the buffer ab = new ABuffer(NULL, mb->range_length()); ab->meta()->setPointer("mediaBuffer", mb); mb->add_ref(); } else { ab = new ABuffer(outLength); memcpy(ab->data(), (const uint8_t *)mb->data() + mb->range_offset(), mb->range_length()); } if (audio && mAudioIsVorbis) { int32_t numPageSamples; if (!mb->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) { numPageSamples = -1; } uint8_t* abEnd = ab->data() + mb->range_length(); memcpy(abEnd, &numPageSamples, sizeof(numPageSamples)); } sp meta = ab->meta(); int64_t timeUs; CHECK(mb->meta_data()->findInt64(kKeyTime, &timeUs)); meta->setInt64("timeUs", timeUs); if (trackType == MEDIA_TRACK_TYPE_TIMEDTEXT) { const char *mime; CHECK(mTimedTextTrack.mSource != NULL && mTimedTextTrack.mSource->getFormat()->findCString(kKeyMIMEType, &mime)); meta->setString("mime", mime); } int64_t durationUs; if (mb->meta_data()->findInt64(kKeyDuration, &durationUs)) { meta->setInt64("durationUs", durationUs); } if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { meta->setInt32("trackIndex", mSubtitleTrack.mIndex); } if (actualTimeUs) { *actualTimeUs = timeUs; } mb->release(); mb = NULL; return ab; } void NuPlayer::GenericSource::readBuffer( media_track_type trackType, int64_t seekTimeUs, int64_t *actualTimeUs, bool formatChange) { Track *track; switch (trackType) { case MEDIA_TRACK_TYPE_VIDEO: track = &mVideoTrack; break; case MEDIA_TRACK_TYPE_AUDIO: track = &mAudioTrack; break; case MEDIA_TRACK_TYPE_SUBTITLE: track = &mSubtitleTrack; break; case MEDIA_TRACK_TYPE_TIMEDTEXT: track = &mTimedTextTrack; break; default: TRESPASS(); } if (track->mSource == NULL) { return; } if (actualTimeUs) { *actualTimeUs = seekTimeUs; } MediaSource::ReadOptions options; bool seeking = false; if (seekTimeUs >= 0) { options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); seeking = true; } if (mIsWidevine && trackType != MEDIA_TRACK_TYPE_AUDIO) { options.setNonBlocking(); } for (;;) { MediaBuffer *mbuf; status_t err = track->mSource->read(&mbuf, &options); options.clearSeekTo(); if (err == OK) { // formatChange && seeking: track whose source is changed during selection // formatChange && !seeking: track whose source is not changed during selection // !formatChange: normal seek if ((seeking || formatChange) && (trackType == MEDIA_TRACK_TYPE_AUDIO || trackType == MEDIA_TRACK_TYPE_VIDEO)) { ATSParser::DiscontinuityType type = formatChange ? (seeking ? ATSParser::DISCONTINUITY_FORMATCHANGE : ATSParser::DISCONTINUITY_NONE) : ATSParser::DISCONTINUITY_SEEK; track->mPackets->queueDiscontinuity( type, NULL, true /* discard */); } sp buffer = mediaBufferToABuffer(mbuf, trackType, actualTimeUs); track->mPackets->queueAccessUnit(buffer); break; } else if (err == WOULD_BLOCK) { break; } else if (err == INFO_FORMAT_CHANGED) { #if 0 track->mPackets->queueDiscontinuity( ATSParser::DISCONTINUITY_FORMATCHANGE, NULL, false /* discard */); #endif } else { track->mPackets->signalEOS(err); break; } } } } // namespace android