summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/FragmentedMP4Extractor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/FragmentedMP4Extractor.cpp')
-rw-r--r--media/libstagefright/FragmentedMP4Extractor.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/media/libstagefright/FragmentedMP4Extractor.cpp b/media/libstagefright/FragmentedMP4Extractor.cpp
new file mode 100644
index 0000000..82712ef
--- /dev/null
+++ b/media/libstagefright/FragmentedMP4Extractor.cpp
@@ -0,0 +1,460 @@
+/*
+ * 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 <utils/Log.h>
+
+#include "include/FragmentedMP4Extractor.h"
+#include "include/SampleTable.h"
+#include "include/ESDS.h"
+
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cutils/properties.h> // for property_get
+
+#include <media/stagefright/foundation/ABitReader.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+#include <utils/String8.h>
+
+namespace android {
+
+class FragmentedMPEG4Source : public MediaSource {
+public:
+ // Caller retains ownership of the Parser
+ FragmentedMPEG4Source(bool audio,
+ const sp<MetaData> &format,
+ const sp<FragmentedMP4Parser> &parser,
+ const sp<FragmentedMP4Extractor> &extractor);
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+protected:
+ virtual ~FragmentedMPEG4Source();
+
+private:
+ Mutex mLock;
+
+ sp<MetaData> mFormat;
+ sp<FragmentedMP4Parser> mParser;
+ sp<FragmentedMP4Extractor> 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<DataSource> &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<MetaData> FragmentedMP4Extractor::getMetaData() {
+ return mFileMetaData;
+}
+
+size_t FragmentedMP4Extractor::countTracks() {
+ return mTrackCount;
+}
+
+
+sp<MetaData> FragmentedMP4Extractor::getTrackMetaData(
+ size_t index, uint32_t flags) {
+ if (index >= countTracks()) {
+ return NULL;
+ }
+
+ sp<AMessage> msg = mParser->getFormat(index == mAudioTrackIndex, true /* synchronous */);
+
+ if (msg == NULL) {
+ ALOGV("got null format for track %d", index);
+ return NULL;
+ }
+
+ sp<MetaData> 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<MediaSource> 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<MetaData> &format,
+ const sp<FragmentedMP4Parser> &parser,
+ const sp<FragmentedMP4Extractor> &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<MetaData> 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<ABuffer> parseBuffer;
+
+ status_t ret = mParser->dequeueAccessUnit(mIsAudioTrack, &parseBuffer, true /* synchronous */);
+ if (ret != OK) {
+ buffer->release();
+ ALOGV("returning %d", ret);
+ return ret;
+ }
+ sp<AMessage> 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<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *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<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *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