/* * 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 "FragmentedMP4Extractor" #include #include "include/FragmentedMP4Extractor.h" #include "include/SampleTable.h" #include "include/ESDS.h" #include #include #include #include #include #include // for property_get #include #include #include #include #include #include #include #include #include #include #include #include namespace android { class FragmentedMPEG4Source : public MediaSource { public: // Caller retains ownership of the Parser FragmentedMPEG4Source(bool audio, const sp &format, const sp &parser, const sp &extractor); virtual status_t start(MetaData *params = NULL); virtual status_t stop(); virtual sp getFormat(); virtual status_t read( MediaBuffer **buffer, const ReadOptions *options = NULL); protected: virtual ~FragmentedMPEG4Source(); private: Mutex mLock; sp mFormat; sp mParser; sp mExtractor; bool mIsAudioTrack; uint32_t mCurrentSampleIndex; bool mIsAVC; size_t mNALLengthSize; bool mStarted; MediaBufferGroup *mGroup; bool mWantsNALFragments; uint8_t *mSrcBuffer; FragmentedMPEG4Source(const FragmentedMPEG4Source &); FragmentedMPEG4Source &operator=(const FragmentedMPEG4Source &); }; FragmentedMP4Extractor::FragmentedMP4Extractor(const sp &source) : mLooper(new ALooper), mParser(new FragmentedMP4Parser()), mDataSource(source), mInitCheck(NO_INIT), mFileMetaData(new MetaData) { ALOGV("FragmentedMP4Extractor"); mLooper->registerHandler(mParser); mLooper->start(false /* runOnCallingThread */); mParser->start(mDataSource); bool hasVideo = mParser->getFormat(false /* audio */, true /* synchronous */) != NULL; bool hasAudio = mParser->getFormat(true /* audio */, true /* synchronous */) != NULL; ALOGV("number of tracks: %d", countTracks()); if (hasVideo) { mFileMetaData->setCString( kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG4); } else if (hasAudio) { mFileMetaData->setCString(kKeyMIMEType, "audio/mp4"); } else { ALOGE("no audio and no video, no idea what file type this is"); } // tracks are numbered such that video track is first, audio track is second if (hasAudio && hasVideo) { mTrackCount = 2; mAudioTrackIndex = 1; } else if (hasAudio) { mTrackCount = 1; mAudioTrackIndex = 0; } else if (hasVideo) { mTrackCount = 1; mAudioTrackIndex = -1; } else { mTrackCount = 0; mAudioTrackIndex = -1; } } FragmentedMP4Extractor::~FragmentedMP4Extractor() { ALOGV("~FragmentedMP4Extractor"); mLooper->stop(); } uint32_t FragmentedMP4Extractor::flags() const { return CAN_PAUSE | (mParser->isSeekable() ? (CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK) : 0); } sp FragmentedMP4Extractor::getMetaData() { return mFileMetaData; } size_t FragmentedMP4Extractor::countTracks() { return mTrackCount; } sp FragmentedMP4Extractor::getTrackMetaData( size_t index, uint32_t flags) { if (index >= countTracks()) { return NULL; } sp msg = mParser->getFormat(index == mAudioTrackIndex, true /* synchronous */); if (msg == NULL) { ALOGV("got null format for track %d", index); return NULL; } sp meta = new MetaData(); convertMessageToMetaData(msg, meta); return meta; } static void MakeFourCCString(uint32_t x, char *s) { s[0] = x >> 24; s[1] = (x >> 16) & 0xff; s[2] = (x >> 8) & 0xff; s[3] = x & 0xff; s[4] = '\0'; } sp FragmentedMP4Extractor::getTrack(size_t index) { if (index >= countTracks()) { return NULL; } return new FragmentedMPEG4Source(index == mAudioTrackIndex, getTrackMetaData(index, 0), mParser, this); } //////////////////////////////////////////////////////////////////////////////// FragmentedMPEG4Source::FragmentedMPEG4Source( bool audio, const sp &format, const sp &parser, const sp &extractor) : mFormat(format), mParser(parser), mExtractor(extractor), mIsAudioTrack(audio), mStarted(false), mGroup(NULL), mWantsNALFragments(false), mSrcBuffer(NULL) { } FragmentedMPEG4Source::~FragmentedMPEG4Source() { if (mStarted) { stop(); } } status_t FragmentedMPEG4Source::start(MetaData *params) { Mutex::Autolock autoLock(mLock); CHECK(!mStarted); int32_t val; if (params && params->findInt32(kKeyWantsNALFragments, &val) && val != 0) { mWantsNALFragments = true; } else { mWantsNALFragments = false; } ALOGV("caller wants NAL fragments: %s", mWantsNALFragments ? "yes" : "no"); mGroup = new MediaBufferGroup; int32_t max_size = 65536; // XXX CHECK(mFormat->findInt32(kKeyMaxInputSize, &max_size)); mGroup->add_buffer(new MediaBuffer(max_size)); mSrcBuffer = new uint8_t[max_size]; mStarted = true; return OK; } status_t FragmentedMPEG4Source::stop() { Mutex::Autolock autoLock(mLock); CHECK(mStarted); delete[] mSrcBuffer; mSrcBuffer = NULL; delete mGroup; mGroup = NULL; mStarted = false; mCurrentSampleIndex = 0; return OK; } sp FragmentedMPEG4Source::getFormat() { Mutex::Autolock autoLock(mLock); return mFormat; } status_t FragmentedMPEG4Source::read( MediaBuffer **out, const ReadOptions *options) { int64_t seekTimeUs; ReadOptions::SeekMode mode; if (options && options->getSeekTo(&seekTimeUs, &mode)) { mParser->seekTo(mIsAudioTrack, seekTimeUs); } MediaBuffer *buffer = NULL; mGroup->acquire_buffer(&buffer); sp parseBuffer; status_t ret = mParser->dequeueAccessUnit(mIsAudioTrack, &parseBuffer, true /* synchronous */); if (ret != OK) { buffer->release(); ALOGV("returning %d", ret); return ret; } sp meta = parseBuffer->meta(); int64_t timeUs; CHECK(meta->findInt64("timeUs", &timeUs)); buffer->meta_data()->setInt64(kKeyTime, timeUs); buffer->set_range(0, parseBuffer->size()); memcpy(buffer->data(), parseBuffer->data(), parseBuffer->size()); *out = buffer; return OK; } static bool isCompatibleBrand(uint32_t fourcc) { static const uint32_t kCompatibleBrands[] = { FOURCC('i', 's', 'o', 'm'), FOURCC('i', 's', 'o', '2'), FOURCC('a', 'v', 'c', '1'), FOURCC('3', 'g', 'p', '4'), FOURCC('m', 'p', '4', '1'), FOURCC('m', 'p', '4', '2'), // Won't promise that the following file types can be played. // Just give these file types a chance. FOURCC('q', 't', ' ', ' '), // Apple's QuickTime FOURCC('M', 'S', 'N', 'V'), // Sony's PSP FOURCC('3', 'g', '2', 'a'), // 3GPP2 FOURCC('3', 'g', '2', 'b'), }; for (size_t i = 0; i < sizeof(kCompatibleBrands) / sizeof(kCompatibleBrands[0]); ++i) { if (kCompatibleBrands[i] == fourcc) { return true; } } return false; } // Attempt to actually parse the 'ftyp' atom and determine if a suitable // compatible brand is present. // Also try to identify where this file's metadata ends // (end of the 'moov' atom) and report it to the caller as part of // the metadata. static bool Sniff( const sp &source, String8 *mimeType, float *confidence, sp *meta) { // We scan up to 128k bytes to identify this file as an MP4. static const off64_t kMaxScanOffset = 128ll * 1024ll; off64_t offset = 0ll; bool foundGoodFileType = false; bool isFragmented = false; off64_t moovAtomEndOffset = -1ll; bool done = false; while (!done && offset < kMaxScanOffset) { uint32_t hdr[2]; if (source->readAt(offset, hdr, 8) < 8) { return false; } uint64_t chunkSize = ntohl(hdr[0]); uint32_t chunkType = ntohl(hdr[1]); off64_t chunkDataOffset = offset + 8; if (chunkSize == 1) { if (source->readAt(offset + 8, &chunkSize, 8) < 8) { return false; } chunkSize = ntoh64(chunkSize); chunkDataOffset += 8; if (chunkSize < 16) { // The smallest valid chunk is 16 bytes long in this case. return false; } } else if (chunkSize < 8) { // The smallest valid chunk is 8 bytes long. return false; } off64_t chunkDataSize = offset + chunkSize - chunkDataOffset; char chunkstring[5]; MakeFourCCString(chunkType, chunkstring); ALOGV("saw chunk type %s, size %lld @ %lld", chunkstring, chunkSize, offset); switch (chunkType) { case FOURCC('f', 't', 'y', 'p'): { if (chunkDataSize < 8) { return false; } uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4; for (size_t i = 0; i < numCompatibleBrands + 2; ++i) { if (i == 1) { // Skip this index, it refers to the minorVersion, // not a brand. continue; } uint32_t brand; if (source->readAt( chunkDataOffset + 4 * i, &brand, 4) < 4) { return false; } brand = ntohl(brand); char brandstring[5]; MakeFourCCString(brand, brandstring); ALOGV("Brand: %s", brandstring); if (isCompatibleBrand(brand)) { foundGoodFileType = true; break; } } if (!foundGoodFileType) { return false; } break; } case FOURCC('m', 'o', 'o', 'v'): { moovAtomEndOffset = offset + chunkSize; break; } case FOURCC('m', 'o', 'o', 'f'): { // this is kind of broken, since we might not actually find a // moof box in the first 128k. isFragmented = true; done = true; break; } default: break; } offset += chunkSize; } if (!foundGoodFileType || !isFragmented) { return false; } *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4; *confidence = 0.5f; // slightly more than MPEG4Extractor if (moovAtomEndOffset >= 0) { *meta = new AMessage; (*meta)->setInt64("meta-data-size", moovAtomEndOffset); (*meta)->setInt32("fragmented", 1); // tell MediaExtractor what to instantiate ALOGV("found metadata size: %lld", moovAtomEndOffset); } return true; } // used by DataSource::RegisterDefaultSniffers bool SniffFragmentedMP4( const sp &source, String8 *mimeType, float *confidence, sp *meta) { ALOGV("SniffFragmentedMP4"); char prop[PROPERTY_VALUE_MAX]; if (property_get("media.stagefright.use-fragmp4", prop, NULL) && (!strcmp(prop, "1") || !strcasecmp(prop, "true"))) { return Sniff(source, mimeType, confidence, meta); } return false; } } // namespace android