summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/mp4
diff options
context:
space:
mode:
authorMarco Nelissen <marcone@google.com>2012-08-31 11:07:37 -0700
committerMarco Nelissen <marcone@google.com>2012-08-31 14:14:22 -0700
commit4ecce5026fe3235e783766dcb9fc2b59405da08f (patch)
tree4816008e7706cbbddb25eacc6249bc0f33ebfda6 /media/libstagefright/mp4
parent314079efa77c07255b2a2794eba470fccd1541fb (diff)
downloadframeworks_av-4ecce5026fe3235e783766dcb9fc2b59405da08f.zip
frameworks_av-4ecce5026fe3235e783766dcb9fc2b59405da08f.tar.gz
frameworks_av-4ecce5026fe3235e783766dcb9fc2b59405da08f.tar.bz2
Move fragmented mp4 parser to libstagefright
and rename it from Parser to FragmentedMP4Parser Change-Id: I986f50d0c5c93648aac675d6160e18623b031541
Diffstat (limited to 'media/libstagefright/mp4')
-rw-r--r--media/libstagefright/mp4/FragmentedMP4Parser.cpp1679
-rw-r--r--media/libstagefright/mp4/TrackFragment.cpp364
-rw-r--r--media/libstagefright/mp4/TrackFragment.h122
3 files changed, 2165 insertions, 0 deletions
diff --git a/media/libstagefright/mp4/FragmentedMP4Parser.cpp b/media/libstagefright/mp4/FragmentedMP4Parser.cpp
new file mode 100644
index 0000000..e130a80
--- /dev/null
+++ b/media/libstagefright/mp4/FragmentedMP4Parser.cpp
@@ -0,0 +1,1679 @@
+/*
+ * 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 "FragmentedMP4Parser"
+#include <utils/Log.h>
+
+#include "include/FragmentedMP4Parser.h"
+#include "include/ESDS.h"
+#include "TrackFragment.h"
+
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+static const char *Fourcc2String(uint32_t fourcc) {
+ static char buffer[5];
+ buffer[4] = '\0';
+ buffer[0] = fourcc >> 24;
+ buffer[1] = (fourcc >> 16) & 0xff;
+ buffer[2] = (fourcc >> 8) & 0xff;
+ buffer[3] = fourcc & 0xff;
+
+ return buffer;
+}
+
+static const char *IndentString(size_t n) {
+ static const char kSpace[] = " ";
+ return kSpace + sizeof(kSpace) - 2 * n - 1;
+}
+
+// static
+const FragmentedMP4Parser::DispatchEntry FragmentedMP4Parser::kDispatchTable[] = {
+ { FOURCC('m', 'o', 'o', 'v'), 0, NULL },
+ { FOURCC('t', 'r', 'a', 'k'), FOURCC('m', 'o', 'o', 'v'), NULL },
+ { FOURCC('u', 'd', 't', 'a'), FOURCC('t', 'r', 'a', 'k'), NULL },
+ { FOURCC('u', 'd', 't', 'a'), FOURCC('m', 'o', 'o', 'v'), NULL },
+ { FOURCC('m', 'e', 't', 'a'), FOURCC('u', 'd', 't', 'a'), NULL },
+ { FOURCC('i', 'l', 's', 't'), FOURCC('m', 'e', 't', 'a'), NULL },
+
+ { FOURCC('t', 'k', 'h', 'd'), FOURCC('t', 'r', 'a', 'k'),
+ &FragmentedMP4Parser::parseTrackHeader
+ },
+
+ { FOURCC('m', 'v', 'e', 'x'), FOURCC('m', 'o', 'o', 'v'), NULL },
+
+ { FOURCC('t', 'r', 'e', 'x'), FOURCC('m', 'v', 'e', 'x'),
+ &FragmentedMP4Parser::parseTrackExtends
+ },
+
+ { FOURCC('e', 'd', 't', 's'), FOURCC('t', 'r', 'a', 'k'), NULL },
+ { FOURCC('m', 'd', 'i', 'a'), FOURCC('t', 'r', 'a', 'k'), NULL },
+
+ { FOURCC('m', 'd', 'h', 'd'), FOURCC('m', 'd', 'i', 'a'),
+ &FragmentedMP4Parser::parseMediaHeader
+ },
+
+ { FOURCC('h', 'd', 'l', 'r'), FOURCC('m', 'd', 'i', 'a'),
+ &FragmentedMP4Parser::parseMediaHandler
+ },
+
+ { FOURCC('m', 'i', 'n', 'f'), FOURCC('m', 'd', 'i', 'a'), NULL },
+ { FOURCC('d', 'i', 'n', 'f'), FOURCC('m', 'i', 'n', 'f'), NULL },
+ { FOURCC('s', 't', 'b', 'l'), FOURCC('m', 'i', 'n', 'f'), NULL },
+ { FOURCC('s', 't', 's', 'd'), FOURCC('s', 't', 'b', 'l'), NULL },
+
+ { FOURCC('s', 't', 's', 'z'), FOURCC('s', 't', 'b', 'l'),
+ &FragmentedMP4Parser::parseSampleSizes },
+
+ { FOURCC('s', 't', 'z', '2'), FOURCC('s', 't', 'b', 'l'),
+ &FragmentedMP4Parser::parseCompactSampleSizes },
+
+ { FOURCC('s', 't', 's', 'c'), FOURCC('s', 't', 'b', 'l'),
+ &FragmentedMP4Parser::parseSampleToChunk },
+
+ { FOURCC('s', 't', 'c', 'o'), FOURCC('s', 't', 'b', 'l'),
+ &FragmentedMP4Parser::parseChunkOffsets },
+
+ { FOURCC('c', 'o', '6', '4'), FOURCC('s', 't', 'b', 'l'),
+ &FragmentedMP4Parser::parseChunkOffsets64 },
+
+ { FOURCC('a', 'v', 'c', 'C'), FOURCC('a', 'v', 'c', '1'),
+ &FragmentedMP4Parser::parseAVCCodecSpecificData },
+
+ { FOURCC('e', 's', 'd', 's'), FOURCC('m', 'p', '4', 'a'),
+ &FragmentedMP4Parser::parseESDSCodecSpecificData },
+
+ { FOURCC('e', 's', 'd', 's'), FOURCC('m', 'p', '4', 'v'),
+ &FragmentedMP4Parser::parseESDSCodecSpecificData },
+
+ { FOURCC('m', 'd', 'a', 't'), 0, &FragmentedMP4Parser::parseMediaData },
+
+ { FOURCC('m', 'o', 'o', 'f'), 0, NULL },
+ { FOURCC('t', 'r', 'a', 'f'), FOURCC('m', 'o', 'o', 'f'), NULL },
+
+ { FOURCC('t', 'f', 'h', 'd'), FOURCC('t', 'r', 'a', 'f'),
+ &FragmentedMP4Parser::parseTrackFragmentHeader
+ },
+ { FOURCC('t', 'r', 'u', 'n'), FOURCC('t', 'r', 'a', 'f'),
+ &FragmentedMP4Parser::parseTrackFragmentRun
+ },
+
+ { FOURCC('m', 'f', 'r', 'a'), 0, NULL },
+};
+
+struct FileSource : public FragmentedMP4Parser::Source {
+ FileSource(const char *filename)
+ : mFile(fopen(filename, "rb")) {
+ CHECK(mFile != NULL);
+ }
+
+ virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
+ fseek(mFile, offset, SEEK_SET);
+ return fread(data, 1, size, mFile);
+ }
+
+ private:
+ FILE *mFile;
+
+ DISALLOW_EVIL_CONSTRUCTORS(FileSource);
+};
+
+FragmentedMP4Parser::FragmentedMP4Parser()
+ : mBufferPos(0),
+ mSuspended(false),
+ mFinalResult(OK) {
+}
+
+FragmentedMP4Parser::~FragmentedMP4Parser() {
+}
+
+void FragmentedMP4Parser::start(const char *filename) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setObject("source", new FileSource(filename));
+ msg->post();
+}
+
+void FragmentedMP4Parser::start(const sp<Source> &source) {
+ sp<AMessage> msg = new AMessage(kWhatStart, id());
+ msg->setObject("source", source);
+ msg->post();
+}
+
+sp<AMessage> FragmentedMP4Parser::getFormat(bool audio) {
+ sp<AMessage> msg = new AMessage(kWhatGetFormat, id());
+ msg->setInt32("audio", audio);
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ if (response->findInt32("err", &err) && err != OK) {
+ return NULL;
+ }
+
+ sp<AMessage> format;
+ CHECK(response->findMessage("format", &format));
+
+ ALOGV("returning format %s", format->debugString().c_str());
+ return format;
+}
+
+status_t FragmentedMP4Parser::dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit) {
+ sp<AMessage> msg = new AMessage(kWhatDequeueAccessUnit, id());
+ msg->setInt32("audio", audio);
+
+ sp<AMessage> response;
+ status_t err = msg->postAndAwaitResponse(&response);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (response->findInt32("err", &err) && err != OK) {
+ return err;
+ }
+
+ CHECK(response->findBuffer("accessUnit", accessUnit));
+
+ return OK;
+}
+
+ssize_t FragmentedMP4Parser::findTrack(bool wantAudio) const {
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ const TrackInfo *info = &mTracks.valueAt(i);
+
+ bool isAudio =
+ info->mMediaHandlerType == FOURCC('s', 'o', 'u', 'n');
+
+ bool isVideo =
+ info->mMediaHandlerType == FOURCC('v', 'i', 'd', 'e');
+
+ if ((wantAudio && isAudio) || (!wantAudio && !isAudio)) {
+ if (info->mSampleDescs.empty()) {
+ break;
+ }
+
+ return i;
+ }
+ }
+
+ return -EWOULDBLOCK;
+}
+
+void FragmentedMP4Parser::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatStart:
+ {
+ sp<RefBase> obj;
+ CHECK(msg->findObject("source", &obj));
+
+ mSource = static_cast<Source *>(obj.get());
+
+ mBuffer = new ABuffer(512 * 1024);
+ mBuffer->setRange(0, 0);
+
+ enter(0ll, 0, 0);
+
+ (new AMessage(kWhatProceed, id()))->post();
+ break;
+ }
+
+ case kWhatProceed:
+ {
+ CHECK(!mSuspended);
+
+ status_t err = onProceed();
+
+ if (err == OK) {
+ if (!mSuspended) {
+ msg->post();
+ }
+ } else if (err != -EAGAIN) {
+ ALOGE("onProceed returned error %d", err);
+ }
+
+ break;
+ }
+
+ case kWhatReadMore:
+ {
+ size_t needed;
+ CHECK(msg->findSize("needed", &needed));
+
+ memmove(mBuffer->base(), mBuffer->data(), mBuffer->size());
+ mBufferPos += mBuffer->offset();
+ mBuffer->setRange(0, mBuffer->size());
+
+ size_t maxBytesToRead = mBuffer->capacity() - mBuffer->size();
+
+ if (maxBytesToRead < needed) {
+ ALOGI("resizing buffer.");
+
+ sp<ABuffer> newBuffer =
+ new ABuffer((mBuffer->size() + needed + 1023) & ~1023);
+ memcpy(newBuffer->data(), mBuffer->data(), mBuffer->size());
+ newBuffer->setRange(0, mBuffer->size());
+
+ mBuffer = newBuffer;
+ maxBytesToRead = mBuffer->capacity() - mBuffer->size();
+ }
+
+ CHECK_GE(maxBytesToRead, needed);
+
+ ssize_t n = mSource->readAt(
+ mBufferPos + mBuffer->size(),
+ mBuffer->data() + mBuffer->size(), needed);
+
+ if (n < (ssize_t)needed) {
+ ALOGI("%s", "Reached EOF");
+ if (n < 0) {
+ mFinalResult = n;
+ } else if (n == 0) {
+ mFinalResult = ERROR_END_OF_STREAM;
+ } else {
+ mFinalResult = ERROR_IO;
+ }
+ } else {
+ mBuffer->setRange(0, mBuffer->size() + n);
+ (new AMessage(kWhatProceed, id()))->post();
+ }
+
+ break;
+ }
+
+ case kWhatGetFormat:
+ {
+ int32_t wantAudio;
+ CHECK(msg->findInt32("audio", &wantAudio));
+
+ status_t err = -EWOULDBLOCK;
+ sp<AMessage> response = new AMessage;
+
+ ssize_t trackIndex = findTrack(wantAudio);
+
+ if (trackIndex < 0) {
+ err = trackIndex;
+ } else {
+ TrackInfo *info = &mTracks.editValueAt(trackIndex);
+
+ response->setMessage(
+ "format", info->mSampleDescs.itemAt(0).mFormat);
+
+ err = OK;
+ }
+
+ response->setInt32("err", err);
+
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ response->postReply(replyID);
+ break;
+ }
+
+ case kWhatDequeueAccessUnit:
+ {
+ int32_t wantAudio;
+ CHECK(msg->findInt32("audio", &wantAudio));
+
+ status_t err = -EWOULDBLOCK;
+ sp<AMessage> response = new AMessage;
+
+ ssize_t trackIndex = findTrack(wantAudio);
+
+ if (trackIndex < 0) {
+ err = trackIndex;
+ } else {
+ sp<ABuffer> accessUnit;
+ err = onDequeueAccessUnit(trackIndex, &accessUnit);
+
+ if (err == OK) {
+ response->setBuffer("accessUnit", accessUnit);
+ }
+ }
+
+ response->setInt32("err", err);
+
+ uint32_t replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ response->postReply(replyID);
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+}
+
+status_t FragmentedMP4Parser::onProceed() {
+ status_t err;
+
+ if ((err = need(8)) != OK) {
+ return err;
+ }
+
+ uint64_t size = readU32(0);
+ uint32_t type = readU32(4);
+
+ size_t offset = 8;
+
+ if (size == 1) {
+ if ((err = need(16)) != OK) {
+ return err;
+ }
+
+ size = readU64(offset);
+ offset += 8;
+ }
+
+ uint8_t userType[16];
+
+ if (type == FOURCC('u', 'u', 'i', 'd')) {
+ if ((err = need(offset + 16)) != OK) {
+ return err;
+ }
+
+ memcpy(userType, mBuffer->data() + offset, 16);
+ offset += 16;
+ }
+
+ CHECK(!mStack.isEmpty());
+ uint32_t ptype = mStack.itemAt(mStack.size() - 1).mType;
+
+ static const size_t kNumDispatchers =
+ sizeof(kDispatchTable) / sizeof(kDispatchTable[0]);
+
+ size_t i;
+ for (i = 0; i < kNumDispatchers; ++i) {
+ if (kDispatchTable[i].mType == type
+ && kDispatchTable[i].mParentType == ptype) {
+ break;
+ }
+ }
+
+ // SampleEntry boxes are container boxes that start with a variable
+ // amount of data depending on the media handler type.
+ // We don't look inside 'hint' type SampleEntry boxes.
+
+ bool isSampleEntryBox =
+ (ptype == FOURCC('s', 't', 's', 'd'))
+ && editTrack(mCurrentTrackID)->mMediaHandlerType
+ != FOURCC('h', 'i', 'n', 't');
+
+ if ((i < kNumDispatchers && kDispatchTable[i].mHandler == 0)
+ || isSampleEntryBox || ptype == FOURCC('i', 'l', 's', 't')) {
+ // This is a container box.
+ if (type == FOURCC('m', 'e', 't', 'a')) {
+ if ((err = need(offset + 4)) < OK) {
+ return err;
+ }
+
+ if (readU32(offset) != 0) {
+ return -EINVAL;
+ }
+
+ offset += 4;
+ } else if (type == FOURCC('s', 't', 's', 'd')) {
+ if ((err = need(offset + 8)) < OK) {
+ return err;
+ }
+
+ if (readU32(offset) != 0) {
+ return -EINVAL;
+ }
+
+ if (readU32(offset + 4) == 0) {
+ // We need at least some entries.
+ return -EINVAL;
+ }
+
+ offset += 8;
+ } else if (isSampleEntryBox) {
+ size_t headerSize;
+
+ switch (editTrack(mCurrentTrackID)->mMediaHandlerType) {
+ case FOURCC('v', 'i', 'd', 'e'):
+ {
+ // 8 bytes SampleEntry + 70 bytes VisualSampleEntry
+ headerSize = 78;
+ break;
+ }
+
+ case FOURCC('s', 'o', 'u', 'n'):
+ {
+ // 8 bytes SampleEntry + 20 bytes AudioSampleEntry
+ headerSize = 28;
+ break;
+ }
+
+ case FOURCC('m', 'e', 't', 'a'):
+ {
+ headerSize = 8; // 8 bytes SampleEntry
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ if (offset + headerSize > size) {
+ return -EINVAL;
+ }
+
+ if ((err = need(offset + headerSize)) != OK) {
+ return err;
+ }
+
+ switch (editTrack(mCurrentTrackID)->mMediaHandlerType) {
+ case FOURCC('v', 'i', 'd', 'e'):
+ {
+ err = parseVisualSampleEntry(
+ type, offset, offset + headerSize);
+ break;
+ }
+
+ case FOURCC('s', 'o', 'u', 'n'):
+ {
+ err = parseAudioSampleEntry(
+ type, offset, offset + headerSize);
+ break;
+ }
+
+ case FOURCC('m', 'e', 't', 'a'):
+ {
+ err = OK;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ }
+
+ if (err != OK) {
+ return err;
+ }
+
+ offset += headerSize;
+ }
+
+ skip(offset);
+
+ ALOGV("%sentering box of type '%s'",
+ IndentString(mStack.size()), Fourcc2String(type));
+
+ enter(mBufferPos - offset, type, size - offset);
+ } else {
+ if (!fitsContainer(size)) {
+ return -EINVAL;
+ }
+
+ if (i < kNumDispatchers && kDispatchTable[i].mHandler != 0) {
+ // We have a handler for this box type.
+
+ if ((err = need(size)) != OK) {
+ return err;
+ }
+
+ ALOGV("%sparsing box of type '%s'",
+ IndentString(mStack.size()), Fourcc2String(type));
+
+ if ((err = (this->*kDispatchTable[i].mHandler)(
+ type, offset, size)) != OK) {
+ return err;
+ }
+ } else {
+ // Unknown box type
+
+ ALOGV("%sskipping box of type '%s', size %llu",
+ IndentString(mStack.size()),
+ Fourcc2String(type), size);
+
+ }
+
+ skip(size);
+ }
+
+ return OK;
+}
+
+// static
+int FragmentedMP4Parser::CompareSampleLocation(
+ const SampleInfo &sample, const MediaDataInfo &mdatInfo) {
+ if (sample.mOffset + sample.mSize < mdatInfo.mOffset) {
+ return -1;
+ }
+
+ if (sample.mOffset >= mdatInfo.mOffset + mdatInfo.mBuffer->size()) {
+ return 1;
+ }
+
+ // Otherwise make sure the sample is completely contained within this
+ // media data block.
+
+ CHECK_GE(sample.mOffset, mdatInfo.mOffset);
+
+ CHECK_LE(sample.mOffset + sample.mSize,
+ mdatInfo.mOffset + mdatInfo.mBuffer->size());
+
+ return 0;
+}
+
+void FragmentedMP4Parser::resumeIfNecessary() {
+ if (!mSuspended) {
+ return;
+ }
+
+ ALOGI("resuming.");
+
+ mSuspended = false;
+ (new AMessage(kWhatProceed, id()))->post();
+}
+
+status_t FragmentedMP4Parser::getSample(
+ TrackInfo *info, sp<TrackFragment> *fragment, SampleInfo *sampleInfo) {
+ for (;;) {
+ if (info->mFragments.empty()) {
+ if (mFinalResult != OK) {
+ return mFinalResult;
+ }
+
+ resumeIfNecessary();
+ return -EWOULDBLOCK;
+ }
+
+ *fragment = *info->mFragments.begin();
+
+ status_t err = (*fragment)->getSample(sampleInfo);
+
+ if (err == OK) {
+ return OK;
+ } else if (err != ERROR_END_OF_STREAM) {
+ return err;
+ }
+
+ // Really, end of this fragment...
+
+ info->mFragments.erase(info->mFragments.begin());
+ }
+}
+
+status_t FragmentedMP4Parser::onDequeueAccessUnit(
+ size_t trackIndex, sp<ABuffer> *accessUnit) {
+ TrackInfo *info = &mTracks.editValueAt(trackIndex);
+
+ sp<TrackFragment> fragment;
+ SampleInfo sampleInfo;
+ status_t err = getSample(info, &fragment, &sampleInfo);
+
+ if (err == -EWOULDBLOCK) {
+ resumeIfNecessary();
+ return err;
+ } else if (err != OK) {
+ return err;
+ }
+
+ err = -EWOULDBLOCK;
+
+ bool checkDroppable = false;
+
+ for (size_t i = 0; i < mMediaData.size(); ++i) {
+ const MediaDataInfo &mdatInfo = mMediaData.itemAt(i);
+
+ int cmp = CompareSampleLocation(sampleInfo, mdatInfo);
+
+ if (cmp < 0) {
+ return -EPIPE;
+ } else if (cmp == 0) {
+ if (i > 0) {
+ checkDroppable = true;
+ }
+
+ err = makeAccessUnit(info, sampleInfo, mdatInfo, accessUnit);
+ break;
+ }
+ }
+
+ if (err != OK) {
+ return err;
+ }
+
+ fragment->advance();
+
+ if (!mMediaData.empty() && checkDroppable) {
+ size_t numDroppable = 0;
+ bool done = false;
+
+ for (size_t i = 0; !done && i < mMediaData.size(); ++i) {
+ const MediaDataInfo &mdatInfo = mMediaData.itemAt(i);
+
+ for (size_t j = 0; j < mTracks.size(); ++j) {
+ TrackInfo *info = &mTracks.editValueAt(j);
+
+ sp<TrackFragment> fragment;
+ SampleInfo sampleInfo;
+ err = getSample(info, &fragment, &sampleInfo);
+
+ if (err != OK) {
+ done = true;
+ break;
+ }
+
+ int cmp = CompareSampleLocation(sampleInfo, mdatInfo);
+
+ if (cmp <= 0) {
+ done = true;
+ break;
+ }
+ }
+
+ if (!done) {
+ ++numDroppable;
+ }
+ }
+
+ if (numDroppable > 0) {
+ mMediaData.removeItemsAt(0, numDroppable);
+
+ if (mMediaData.size() < 5) {
+ resumeIfNecessary();
+ }
+ }
+ }
+
+ return err;
+}
+
+static size_t parseNALSize(size_t nalLengthSize, const uint8_t *data) {
+ switch (nalLengthSize) {
+ case 1:
+ return *data;
+ case 2:
+ return U16_AT(data);
+ case 3:
+ return ((size_t)data[0] << 16) | U16_AT(&data[1]);
+ case 4:
+ return U32_AT(data);
+ }
+
+ // This cannot happen, mNALLengthSize springs to life by adding 1 to
+ // a 2-bit integer.
+ TRESPASS();
+
+ return 0;
+}
+
+status_t FragmentedMP4Parser::makeAccessUnit(
+ TrackInfo *info,
+ const SampleInfo &sample,
+ const MediaDataInfo &mdatInfo,
+ sp<ABuffer> *accessUnit) {
+ if (sample.mSampleDescIndex < 1
+ || sample.mSampleDescIndex > info->mSampleDescs.size()) {
+ return ERROR_MALFORMED;
+ }
+
+ int64_t presentationTimeUs =
+ 1000000ll * sample.mPresentationTime / info->mMediaTimeScale;
+
+ const SampleDescription &sampleDesc =
+ info->mSampleDescs.itemAt(sample.mSampleDescIndex - 1);
+
+ size_t nalLengthSize;
+ if (!sampleDesc.mFormat->findSize("nal-length-size", &nalLengthSize)) {
+ *accessUnit = new ABuffer(sample.mSize);
+
+ memcpy((*accessUnit)->data(),
+ mdatInfo.mBuffer->data() + (sample.mOffset - mdatInfo.mOffset),
+ sample.mSize);
+
+ (*accessUnit)->meta()->setInt64("timeUs", presentationTimeUs);
+ return OK;
+ }
+
+ const uint8_t *srcPtr =
+ mdatInfo.mBuffer->data() + (sample.mOffset - mdatInfo.mOffset);
+
+ for (int i = 0; i < 2 ; ++i) {
+ size_t srcOffset = 0;
+ size_t dstOffset = 0;
+
+ while (srcOffset < sample.mSize) {
+ if (srcOffset + nalLengthSize > sample.mSize) {
+ return ERROR_MALFORMED;
+ }
+
+ size_t nalSize = parseNALSize(nalLengthSize, &srcPtr[srcOffset]);
+ srcOffset += nalLengthSize;
+
+ if (srcOffset + nalSize > sample.mSize) {
+ return ERROR_MALFORMED;
+ }
+
+ if (i == 1) {
+ memcpy((*accessUnit)->data() + dstOffset,
+ "\x00\x00\x00\x01",
+ 4);
+
+ memcpy((*accessUnit)->data() + dstOffset + 4,
+ srcPtr + srcOffset,
+ nalSize);
+ }
+
+ srcOffset += nalSize;
+ dstOffset += nalSize + 4;
+ }
+
+ if (i == 0) {
+ (*accessUnit) = new ABuffer(dstOffset);
+ (*accessUnit)->meta()->setInt64(
+ "timeUs", presentationTimeUs);
+ }
+ }
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::need(size_t size) {
+ if (!fitsContainer(size)) {
+ return -EINVAL;
+ }
+
+ if (size <= mBuffer->size()) {
+ return OK;
+ }
+
+ sp<AMessage> msg = new AMessage(kWhatReadMore, id());
+ msg->setSize("needed", size - mBuffer->size());
+ msg->post();
+
+ // ALOGV("need(%d) returning -EAGAIN, only have %d", size, mBuffer->size());
+
+ return -EAGAIN;
+}
+
+void FragmentedMP4Parser::enter(off64_t offset, uint32_t type, uint64_t size) {
+ Container container;
+ container.mOffset = offset;
+ container.mType = type;
+ container.mExtendsToEOF = (size == 0);
+ container.mBytesRemaining = size;
+
+ mStack.push(container);
+}
+
+bool FragmentedMP4Parser::fitsContainer(uint64_t size) const {
+ CHECK(!mStack.isEmpty());
+ const Container &container = mStack.itemAt(mStack.size() - 1);
+
+ return container.mExtendsToEOF || size <= container.mBytesRemaining;
+}
+
+uint16_t FragmentedMP4Parser::readU16(size_t offset) {
+ CHECK_LE(offset + 2, mBuffer->size());
+
+ const uint8_t *ptr = mBuffer->data() + offset;
+ return (ptr[0] << 8) | ptr[1];
+}
+
+uint32_t FragmentedMP4Parser::readU32(size_t offset) {
+ CHECK_LE(offset + 4, mBuffer->size());
+
+ const uint8_t *ptr = mBuffer->data() + offset;
+ return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
+}
+
+uint64_t FragmentedMP4Parser::readU64(size_t offset) {
+ return (((uint64_t)readU32(offset)) << 32) | readU32(offset + 4);
+}
+
+void FragmentedMP4Parser::skip(off_t distance) {
+ CHECK(!mStack.isEmpty());
+ for (size_t i = mStack.size(); i-- > 0;) {
+ Container *container = &mStack.editItemAt(i);
+ if (!container->mExtendsToEOF) {
+ CHECK_LE(distance, (off_t)container->mBytesRemaining);
+
+ container->mBytesRemaining -= distance;
+
+ if (container->mBytesRemaining == 0) {
+ ALOGV("%sleaving box of type '%s'",
+ IndentString(mStack.size() - 1),
+ Fourcc2String(container->mType));
+
+#if 0
+ if (container->mType == FOURCC('s', 't', 's', 'd')) {
+ TrackInfo *trackInfo = editTrack(mCurrentTrackID);
+ for (size_t i = 0;
+ i < trackInfo->mSampleDescs.size(); ++i) {
+ ALOGI("format #%d: %s",
+ i,
+ trackInfo->mSampleDescs.itemAt(i)
+ .mFormat->debugString().c_str());
+ }
+ }
+#endif
+
+ if (container->mType == FOURCC('s', 't', 'b', 'l')) {
+ TrackInfo *trackInfo = editTrack(mCurrentTrackID);
+
+ trackInfo->mStaticFragment->signalCompletion();
+
+ CHECK(trackInfo->mFragments.empty());
+ trackInfo->mFragments.push_back(trackInfo->mStaticFragment);
+ trackInfo->mStaticFragment.clear();
+ } else if (container->mType == FOURCC('t', 'r', 'a', 'f')) {
+ TrackInfo *trackInfo =
+ editTrack(mTrackFragmentHeaderInfo.mTrackID);
+
+ const sp<TrackFragment> &fragment =
+ *--trackInfo->mFragments.end();
+
+ static_cast<DynamicTrackFragment *>(
+ fragment.get())->signalCompletion();
+ }
+
+ container = NULL;
+ mStack.removeItemsAt(i);
+ }
+ }
+ }
+
+ if (distance < (off_t)mBuffer->size()) {
+ mBuffer->setRange(mBuffer->offset() + distance, mBuffer->size() - distance);
+ mBufferPos += distance;
+ return;
+ }
+
+ mBuffer->setRange(0, 0);
+ mBufferPos += distance;
+}
+
+status_t FragmentedMP4Parser::parseTrackHeader(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ uint32_t flags = readU32(offset);
+
+ uint32_t version = flags >> 24;
+ flags &= 0xffffff;
+
+ uint32_t trackID;
+ uint64_t duration;
+
+ if (version == 1) {
+ if (offset + 36 > size) {
+ return -EINVAL;
+ }
+
+ trackID = readU32(offset + 20);
+ duration = readU64(offset + 28);
+
+ offset += 36;
+ } else if (version == 0) {
+ if (offset + 24 > size) {
+ return -EINVAL;
+ }
+
+ trackID = readU32(offset + 12);
+ duration = readU32(offset + 20);
+
+ offset += 24;
+ } else {
+ return -EINVAL;
+ }
+
+ TrackInfo *info = editTrack(trackID, true /* createIfNecessary */);
+ info->mFlags = flags;
+ info->mDuration = duration;
+
+ info->mStaticFragment = new StaticTrackFragment;
+
+ mCurrentTrackID = trackID;
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseMediaHeader(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ uint32_t versionAndFlags = readU32(offset);
+
+ if (versionAndFlags & 0xffffff) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t version = versionAndFlags >> 24;
+
+ TrackInfo *info = editTrack(mCurrentTrackID);
+
+ if (version == 1) {
+ if (offset + 4 + 32 > size) {
+ return -EINVAL;
+ }
+ info->mMediaTimeScale = U32_AT(mBuffer->data() + offset + 20);
+ } else if (version == 0) {
+ if (offset + 4 + 20 > size) {
+ return -EINVAL;
+ }
+ info->mMediaTimeScale = U32_AT(mBuffer->data() + offset + 12);
+ } else {
+ return ERROR_MALFORMED;
+ }
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseMediaHandler(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 12 > size) {
+ return -EINVAL;
+ }
+
+ if (readU32(offset) != 0) {
+ return -EINVAL;
+ }
+
+ uint32_t handlerType = readU32(offset + 8);
+
+ switch (handlerType) {
+ case FOURCC('v', 'i', 'd', 'e'):
+ case FOURCC('s', 'o', 'u', 'n'):
+ case FOURCC('h', 'i', 'n', 't'):
+ case FOURCC('m', 'e', 't', 'a'):
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ editTrack(mCurrentTrackID)->mMediaHandlerType = handlerType;
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseVisualSampleEntry(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 78 > size) {
+ return -EINVAL;
+ }
+
+ TrackInfo *trackInfo = editTrack(mCurrentTrackID);
+
+ trackInfo->mSampleDescs.push();
+ SampleDescription *sampleDesc =
+ &trackInfo->mSampleDescs.editItemAt(
+ trackInfo->mSampleDescs.size() - 1);
+
+ sampleDesc->mType = type;
+ sampleDesc->mDataRefIndex = readU16(offset + 6);
+
+ sp<AMessage> format = new AMessage;
+
+ switch (type) {
+ case FOURCC('a', 'v', 'c', '1'):
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC);
+ break;
+ case FOURCC('m', 'p', '4', 'v'):
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_MPEG4);
+ break;
+ case FOURCC('s', '2', '6', '3'):
+ case FOURCC('h', '2', '6', '3'):
+ case FOURCC('H', '2', '6', '3'):
+ format->setString("mime", MEDIA_MIMETYPE_VIDEO_H263);
+ break;
+ default:
+ format->setString("mime", "application/octet-stream");
+ break;
+ }
+
+ format->setInt32("width", readU16(offset + 8 + 16));
+ format->setInt32("height", readU16(offset + 8 + 18));
+
+ sampleDesc->mFormat = format;
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseAudioSampleEntry(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 28 > size) {
+ return -EINVAL;
+ }
+
+ TrackInfo *trackInfo = editTrack(mCurrentTrackID);
+
+ trackInfo->mSampleDescs.push();
+ SampleDescription *sampleDesc =
+ &trackInfo->mSampleDescs.editItemAt(
+ trackInfo->mSampleDescs.size() - 1);
+
+ sampleDesc->mType = type;
+ sampleDesc->mDataRefIndex = readU16(offset + 6);
+
+ sp<AMessage> format = new AMessage;
+
+ format->setInt32("channel-count", readU16(offset + 8 + 8));
+ format->setInt32("sample-size", readU16(offset + 8 + 10));
+ format->setInt32("sample-rate", readU32(offset + 8 + 16) / 65536.0f);
+
+ switch (type) {
+ case FOURCC('m', 'p', '4', 'a'):
+ format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC);
+ break;
+
+ case FOURCC('s', 'a', 'm', 'r'):
+ format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_NB);
+ format->setInt32("channel-count", 1);
+ format->setInt32("sample-rate", 8000);
+ break;
+
+ case FOURCC('s', 'a', 'w', 'b'):
+ format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_WB);
+ format->setInt32("channel-count", 1);
+ format->setInt32("sample-rate", 16000);
+ break;
+ default:
+ format->setString("mime", "application/octet-stream");
+ break;
+ }
+
+ sampleDesc->mFormat = format;
+
+ return OK;
+}
+
+static void addCodecSpecificData(
+ const sp<AMessage> &format, int32_t index,
+ const void *data, size_t size,
+ bool insertStartCode = false) {
+ sp<ABuffer> csd = new ABuffer(insertStartCode ? size + 4 : size);
+
+ memcpy(csd->data() + (insertStartCode ? 4 : 0), data, size);
+
+ if (insertStartCode) {
+ memcpy(csd->data(), "\x00\x00\x00\x01", 4);
+ }
+
+ csd->meta()->setInt32("csd", true);
+ csd->meta()->setInt64("timeUs", 0ll);
+
+ format->setBuffer(StringPrintf("csd-%d", index).c_str(), csd);
+}
+
+status_t FragmentedMP4Parser::parseSampleSizes(
+ uint32_t type, size_t offset, uint64_t size) {
+ return editTrack(mCurrentTrackID)->mStaticFragment->parseSampleSizes(
+ this, type, offset, size);
+}
+
+status_t FragmentedMP4Parser::parseCompactSampleSizes(
+ uint32_t type, size_t offset, uint64_t size) {
+ return editTrack(mCurrentTrackID)->mStaticFragment->parseCompactSampleSizes(
+ this, type, offset, size);
+}
+
+status_t FragmentedMP4Parser::parseSampleToChunk(
+ uint32_t type, size_t offset, uint64_t size) {
+ return editTrack(mCurrentTrackID)->mStaticFragment->parseSampleToChunk(
+ this, type, offset, size);
+}
+
+status_t FragmentedMP4Parser::parseChunkOffsets(
+ uint32_t type, size_t offset, uint64_t size) {
+ return editTrack(mCurrentTrackID)->mStaticFragment->parseChunkOffsets(
+ this, type, offset, size);
+}
+
+status_t FragmentedMP4Parser::parseChunkOffsets64(
+ uint32_t type, size_t offset, uint64_t size) {
+ return editTrack(mCurrentTrackID)->mStaticFragment->parseChunkOffsets64(
+ this, type, offset, size);
+}
+
+status_t FragmentedMP4Parser::parseAVCCodecSpecificData(
+ uint32_t type, size_t offset, uint64_t size) {
+ TrackInfo *trackInfo = editTrack(mCurrentTrackID);
+
+ SampleDescription *sampleDesc =
+ &trackInfo->mSampleDescs.editItemAt(
+ trackInfo->mSampleDescs.size() - 1);
+
+ if (sampleDesc->mType != FOURCC('a', 'v', 'c', '1')) {
+ return -EINVAL;
+ }
+
+ const uint8_t *ptr = mBuffer->data() + offset;
+
+ size -= offset;
+ offset = 0;
+
+ if (size < 7 || ptr[0] != 0x01) {
+ return ERROR_MALFORMED;
+ }
+
+ sampleDesc->mFormat->setSize("nal-length-size", 1 + (ptr[4] & 3));
+
+ size_t numSPS = ptr[5] & 31;
+
+ ptr += 6;
+ size -= 6;
+
+ for (size_t i = 0; i < numSPS; ++i) {
+ if (size < 2) {
+ return ERROR_MALFORMED;
+ }
+
+ size_t length = U16_AT(ptr);
+
+ ptr += 2;
+ size -= 2;
+
+ if (size < length) {
+ return ERROR_MALFORMED;
+ }
+
+ addCodecSpecificData(
+ sampleDesc->mFormat, i, ptr, length,
+ true /* insertStartCode */);
+
+ ptr += length;
+ size -= length;
+ }
+
+ if (size < 1) {
+ return ERROR_MALFORMED;
+ }
+
+ size_t numPPS = *ptr;
+ ++ptr;
+ --size;
+
+ for (size_t i = 0; i < numPPS; ++i) {
+ if (size < 2) {
+ return ERROR_MALFORMED;
+ }
+
+ size_t length = U16_AT(ptr);
+
+ ptr += 2;
+ size -= 2;
+
+ if (size < length) {
+ return ERROR_MALFORMED;
+ }
+
+ addCodecSpecificData(
+ sampleDesc->mFormat, numSPS + i, ptr, length,
+ true /* insertStartCode */);
+
+ ptr += length;
+ size -= length;
+ }
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseESDSCodecSpecificData(
+ uint32_t type, size_t offset, uint64_t size) {
+ TrackInfo *trackInfo = editTrack(mCurrentTrackID);
+
+ SampleDescription *sampleDesc =
+ &trackInfo->mSampleDescs.editItemAt(
+ trackInfo->mSampleDescs.size() - 1);
+
+ if (sampleDesc->mType != FOURCC('m', 'p', '4', 'a')
+ && sampleDesc->mType != FOURCC('m', 'p', '4', 'v')) {
+ return -EINVAL;
+ }
+
+ const uint8_t *ptr = mBuffer->data() + offset;
+
+ size -= offset;
+ offset = 0;
+
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ if (U32_AT(ptr) != 0) {
+ return -EINVAL;
+ }
+
+ ptr += 4;
+ size -=4;
+
+ ESDS esds(ptr, size);
+
+ uint8_t objectTypeIndication;
+ if (esds.getObjectTypeIndication(&objectTypeIndication) != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ const uint8_t *csd;
+ size_t csd_size;
+ if (esds.getCodecSpecificInfo(
+ (const void **)&csd, &csd_size) != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ addCodecSpecificData(sampleDesc->mFormat, 0, csd, csd_size);
+
+ if (sampleDesc->mType != FOURCC('m', 'p', '4', 'a')) {
+ return OK;
+ }
+
+ if (csd_size == 0) {
+ // There's no further information, i.e. no codec specific data
+ // Let's assume that the information provided in the mpeg4 headers
+ // is accurate and hope for the best.
+
+ return OK;
+ }
+
+ if (csd_size < 2) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t objectType = csd[0] >> 3;
+
+ if (objectType == 31) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ uint32_t freqIndex = (csd[0] & 7) << 1 | (csd[1] >> 7);
+ int32_t sampleRate = 0;
+ int32_t numChannels = 0;
+ if (freqIndex == 15) {
+ if (csd_size < 5) {
+ return ERROR_MALFORMED;
+ }
+
+ sampleRate = (csd[1] & 0x7f) << 17
+ | csd[2] << 9
+ | csd[3] << 1
+ | (csd[4] >> 7);
+
+ numChannels = (csd[4] >> 3) & 15;
+ } else {
+ static uint32_t kSamplingRate[] = {
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+ 16000, 12000, 11025, 8000, 7350
+ };
+
+ if (freqIndex == 13 || freqIndex == 14) {
+ return ERROR_MALFORMED;
+ }
+
+ sampleRate = kSamplingRate[freqIndex];
+ numChannels = (csd[1] >> 3) & 15;
+ }
+
+ if (numChannels == 0) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ sampleDesc->mFormat->setInt32("sample-rate", sampleRate);
+ sampleDesc->mFormat->setInt32("channel-count", numChannels);
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseMediaData(
+ uint32_t type, size_t offset, uint64_t size) {
+ ALOGV("skipping 'mdat' chunk at offsets 0x%08lx-0x%08llx.",
+ mBufferPos + offset, mBufferPos + size);
+
+ sp<ABuffer> buffer = new ABuffer(size - offset);
+ memcpy(buffer->data(), mBuffer->data() + offset, size - offset);
+
+ mMediaData.push();
+ MediaDataInfo *info = &mMediaData.editItemAt(mMediaData.size() - 1);
+ info->mBuffer = buffer;
+ info->mOffset = mBufferPos + offset;
+
+ if (mMediaData.size() > 10) {
+ ALOGI("suspending for now.");
+ mSuspended = true;
+ }
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseTrackExtends(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 24 > size) {
+ return -EINVAL;
+ }
+
+ if (readU32(offset) != 0) {
+ return -EINVAL;
+ }
+
+ uint32_t trackID = readU32(offset + 4);
+
+ TrackInfo *info = editTrack(trackID, true /* createIfNecessary */);
+ info->mDefaultSampleDescriptionIndex = readU32(offset + 8);
+ info->mDefaultSampleDuration = readU32(offset + 12);
+ info->mDefaultSampleSize = readU32(offset + 16);
+ info->mDefaultSampleFlags = readU32(offset + 20);
+
+ return OK;
+}
+
+FragmentedMP4Parser::TrackInfo *FragmentedMP4Parser::editTrack(
+ uint32_t trackID, bool createIfNecessary) {
+ ssize_t i = mTracks.indexOfKey(trackID);
+
+ if (i >= 0) {
+ return &mTracks.editValueAt(i);
+ }
+
+ if (!createIfNecessary) {
+ return NULL;
+ }
+
+ TrackInfo info;
+ info.mTrackID = trackID;
+ info.mFlags = 0;
+ info.mDuration = 0xffffffff;
+ info.mMediaTimeScale = 0;
+ info.mMediaHandlerType = 0;
+ info.mDefaultSampleDescriptionIndex = 0;
+ info.mDefaultSampleDuration = 0;
+ info.mDefaultSampleSize = 0;
+ info.mDefaultSampleFlags = 0;
+
+ info.mDecodingTime = 0;
+
+ mTracks.add(trackID, info);
+ return &mTracks.editValueAt(mTracks.indexOfKey(trackID));
+}
+
+status_t FragmentedMP4Parser::parseTrackFragmentHeader(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 8 > size) {
+ return -EINVAL;
+ }
+
+ uint32_t flags = readU32(offset);
+
+ if (flags & 0xff000000) {
+ return -EINVAL;
+ }
+
+ mTrackFragmentHeaderInfo.mFlags = flags;
+
+ mTrackFragmentHeaderInfo.mTrackID = readU32(offset + 4);
+ offset += 8;
+
+ if (flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent) {
+ if (offset + 8 > size) {
+ return -EINVAL;
+ }
+
+ mTrackFragmentHeaderInfo.mBaseDataOffset = readU64(offset);
+ offset += 8;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kSampleDescriptionIndexPresent) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ mTrackFragmentHeaderInfo.mSampleDescriptionIndex = readU32(offset);
+ offset += 4;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ mTrackFragmentHeaderInfo.mDefaultSampleDuration = readU32(offset);
+ offset += 4;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ mTrackFragmentHeaderInfo.mDefaultSampleSize = readU32(offset);
+ offset += 4;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ mTrackFragmentHeaderInfo.mDefaultSampleFlags = readU32(offset);
+ offset += 4;
+ }
+
+ if (!(flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent)) {
+ // This should point to the position of the first byte of the
+ // enclosing 'moof' container for the first track and
+ // the end of the data of the preceding fragment for subsequent
+ // tracks.
+
+ CHECK_GE(mStack.size(), 2u);
+
+ mTrackFragmentHeaderInfo.mBaseDataOffset =
+ mStack.itemAt(mStack.size() - 2).mOffset;
+
+ // XXX TODO: This does not do the right thing for the 2nd and
+ // subsequent tracks yet.
+ }
+
+ mTrackFragmentHeaderInfo.mDataOffset =
+ mTrackFragmentHeaderInfo.mBaseDataOffset;
+
+ TrackInfo *trackInfo = editTrack(mTrackFragmentHeaderInfo.mTrackID);
+
+ if (trackInfo->mFragments.empty()
+ || (*trackInfo->mFragments.begin())->complete()) {
+ trackInfo->mFragments.push_back(new DynamicTrackFragment);
+ }
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::parseTrackFragmentRun(
+ uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 8 > size) {
+ return -EINVAL;
+ }
+
+ enum {
+ kDataOffsetPresent = 0x01,
+ kFirstSampleFlagsPresent = 0x04,
+ kSampleDurationPresent = 0x100,
+ kSampleSizePresent = 0x200,
+ kSampleFlagsPresent = 0x400,
+ kSampleCompositionTimeOffsetPresent = 0x800,
+ };
+
+ uint32_t flags = readU32(offset);
+
+ if (flags & 0xff000000) {
+ return -EINVAL;
+ }
+
+ if ((flags & kFirstSampleFlagsPresent) && (flags & kSampleFlagsPresent)) {
+ // These two shall not be used together.
+ return -EINVAL;
+ }
+
+ uint32_t sampleCount = readU32(offset + 4);
+ offset += 8;
+
+ uint64_t dataOffset = mTrackFragmentHeaderInfo.mDataOffset;
+
+ uint32_t firstSampleFlags = 0;
+
+ if (flags & kDataOffsetPresent) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ int32_t dataOffsetDelta = (int32_t)readU32(offset);
+
+ dataOffset = mTrackFragmentHeaderInfo.mBaseDataOffset + dataOffsetDelta;
+
+ offset += 4;
+ }
+
+ if (flags & kFirstSampleFlagsPresent) {
+ if (offset + 4 > size) {
+ return -EINVAL;
+ }
+
+ firstSampleFlags = readU32(offset);
+ offset += 4;
+ }
+
+ TrackInfo *info = editTrack(mTrackFragmentHeaderInfo.mTrackID);
+
+ if (info == NULL) {
+ return -EINVAL;
+ }
+
+ uint32_t sampleDuration = 0, sampleSize = 0, sampleFlags = 0,
+ sampleCtsOffset = 0;
+
+ size_t bytesPerSample = 0;
+ if (flags & kSampleDurationPresent) {
+ bytesPerSample += 4;
+ } else if (mTrackFragmentHeaderInfo.mFlags
+ & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) {
+ sampleDuration = mTrackFragmentHeaderInfo.mDefaultSampleDuration;
+ } else {
+ sampleDuration = info->mDefaultSampleDuration;
+ }
+
+ if (flags & kSampleSizePresent) {
+ bytesPerSample += 4;
+ } else if (mTrackFragmentHeaderInfo.mFlags
+ & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) {
+ sampleSize = mTrackFragmentHeaderInfo.mDefaultSampleSize;
+ } else {
+ sampleSize = info->mDefaultSampleSize;
+ }
+
+ if (flags & kSampleFlagsPresent) {
+ bytesPerSample += 4;
+ } else if (mTrackFragmentHeaderInfo.mFlags
+ & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) {
+ sampleFlags = mTrackFragmentHeaderInfo.mDefaultSampleFlags;
+ } else {
+ sampleFlags = info->mDefaultSampleFlags;
+ }
+
+ if (flags & kSampleCompositionTimeOffsetPresent) {
+ bytesPerSample += 4;
+ } else {
+ sampleCtsOffset = 0;
+ }
+
+ if (offset + sampleCount * bytesPerSample > size) {
+ return -EINVAL;
+ }
+
+ uint32_t sampleDescIndex =
+ (mTrackFragmentHeaderInfo.mFlags
+ & TrackFragmentHeaderInfo::kSampleDescriptionIndexPresent)
+ ? mTrackFragmentHeaderInfo.mSampleDescriptionIndex
+ : info->mDefaultSampleDescriptionIndex;
+
+ for (uint32_t i = 0; i < sampleCount; ++i) {
+ if (flags & kSampleDurationPresent) {
+ sampleDuration = readU32(offset);
+ offset += 4;
+ }
+
+ if (flags & kSampleSizePresent) {
+ sampleSize = readU32(offset);
+ offset += 4;
+ }
+
+ if (flags & kSampleFlagsPresent) {
+ sampleFlags = readU32(offset);
+ offset += 4;
+ }
+
+ if (flags & kSampleCompositionTimeOffsetPresent) {
+ sampleCtsOffset = readU32(offset);
+ offset += 4;
+ }
+
+ ALOGV("adding sample at offset 0x%08llx, size %u, duration %u, "
+ "sampleDescIndex=%u, flags 0x%08x",
+ dataOffset, sampleSize, sampleDuration,
+ sampleDescIndex,
+ (flags & kFirstSampleFlagsPresent) && i == 0
+ ? firstSampleFlags : sampleFlags);
+
+ const sp<TrackFragment> &fragment = *--info->mFragments.end();
+
+ uint32_t decodingTime = info->mDecodingTime;
+ info->mDecodingTime += sampleDuration;
+ uint32_t presentationTime = decodingTime + sampleCtsOffset;
+
+ static_cast<DynamicTrackFragment *>(
+ fragment.get())->addSample(
+ dataOffset,
+ sampleSize,
+ presentationTime,
+ sampleDescIndex,
+ ((flags & kFirstSampleFlagsPresent) && i == 0)
+ ? firstSampleFlags : sampleFlags);
+
+ dataOffset += sampleSize;
+ }
+
+ mTrackFragmentHeaderInfo.mDataOffset = dataOffset;
+
+ return OK;
+}
+
+void FragmentedMP4Parser::copyBuffer(
+ sp<ABuffer> *dst, size_t offset, uint64_t size, size_t extra) const {
+ sp<ABuffer> buf = new ABuffer(size + extra);
+ memcpy(buf->data(), mBuffer->data() + offset, size);
+
+ *dst = buf;
+}
+
+} // namespace android
diff --git a/media/libstagefright/mp4/TrackFragment.cpp b/media/libstagefright/mp4/TrackFragment.cpp
new file mode 100644
index 0000000..3699038
--- /dev/null
+++ b/media/libstagefright/mp4/TrackFragment.cpp
@@ -0,0 +1,364 @@
+/*
+ * 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 "TrackFragment"
+#include <utils/Log.h>
+
+#include "TrackFragment.h"
+
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+namespace android {
+
+FragmentedMP4Parser::DynamicTrackFragment::DynamicTrackFragment()
+ : mComplete(false),
+ mSampleIndex(0) {
+}
+
+FragmentedMP4Parser::DynamicTrackFragment::~DynamicTrackFragment() {
+}
+
+status_t FragmentedMP4Parser::DynamicTrackFragment::getSample(SampleInfo *info) {
+ if (mSampleIndex >= mSamples.size()) {
+ return mComplete ? ERROR_END_OF_STREAM : -EWOULDBLOCK;
+ }
+
+ *info = mSamples.itemAt(mSampleIndex);
+
+ return OK;
+}
+
+void FragmentedMP4Parser::DynamicTrackFragment::advance() {
+ ++mSampleIndex;
+}
+
+void FragmentedMP4Parser::DynamicTrackFragment::addSample(
+ off64_t dataOffset, size_t sampleSize,
+ uint32_t presentationTime,
+ size_t sampleDescIndex,
+ uint32_t flags) {
+ mSamples.push();
+ SampleInfo *sampleInfo = &mSamples.editItemAt(mSamples.size() - 1);
+
+ sampleInfo->mOffset = dataOffset;
+ sampleInfo->mSize = sampleSize;
+ sampleInfo->mPresentationTime = presentationTime;
+ sampleInfo->mSampleDescIndex = sampleDescIndex;
+ sampleInfo->mFlags = flags;
+}
+
+status_t FragmentedMP4Parser::DynamicTrackFragment::signalCompletion() {
+ mComplete = true;
+
+ return OK;
+}
+
+bool FragmentedMP4Parser::DynamicTrackFragment::complete() const {
+ return mComplete;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+FragmentedMP4Parser::StaticTrackFragment::StaticTrackFragment()
+ : mSampleIndex(0),
+ mSampleCount(0),
+ mChunkIndex(0),
+ mSampleToChunkIndex(-1),
+ mSampleToChunkRemaining(0),
+ mPrevChunkIndex(0xffffffff),
+ mNextSampleOffset(0) {
+}
+
+FragmentedMP4Parser::StaticTrackFragment::~StaticTrackFragment() {
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::getSample(SampleInfo *info) {
+ if (mSampleIndex >= mSampleCount) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ *info = mSampleInfo;
+
+ ALOGV("returning sample %d at [0x%08llx, 0x%08llx)",
+ mSampleIndex,
+ info->mOffset, info->mOffset + info->mSize);
+
+ return OK;
+}
+
+void FragmentedMP4Parser::StaticTrackFragment::updateSampleInfo() {
+ if (mSampleIndex >= mSampleCount) {
+ return;
+ }
+
+ if (mSampleSizes != NULL) {
+ uint32_t defaultSampleSize = U32_AT(mSampleSizes->data() + 4);
+ if (defaultSampleSize > 0) {
+ mSampleInfo.mSize = defaultSampleSize;
+ } else {
+ mSampleInfo.mSize= U32_AT(mSampleSizes->data() + 12 + 4 * mSampleIndex);
+ }
+ } else {
+ CHECK(mCompactSampleSizes != NULL);
+
+ uint32_t fieldSize = U32_AT(mCompactSampleSizes->data() + 4);
+
+ switch (fieldSize) {
+ case 4:
+ {
+ unsigned byte = mCompactSampleSizes->data()[12 + mSampleIndex / 2];
+ mSampleInfo.mSize = (mSampleIndex & 1) ? byte & 0x0f : byte >> 4;
+ break;
+ }
+
+ case 8:
+ {
+ mSampleInfo.mSize = mCompactSampleSizes->data()[12 + mSampleIndex];
+ break;
+ }
+
+ default:
+ {
+ CHECK_EQ(fieldSize, 16);
+ mSampleInfo.mSize =
+ U16_AT(mCompactSampleSizes->data() + 12 + mSampleIndex * 2);
+ break;
+ }
+ }
+ }
+
+ CHECK_GT(mSampleToChunkRemaining, 0);
+
+ // The sample desc index is 1-based... XXX
+ mSampleInfo.mSampleDescIndex =
+ U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex + 8);
+
+ if (mChunkIndex != mPrevChunkIndex) {
+ mPrevChunkIndex = mChunkIndex;
+
+ if (mChunkOffsets != NULL) {
+ uint32_t entryCount = U32_AT(mChunkOffsets->data() + 4);
+
+ if (mChunkIndex >= entryCount) {
+ mSampleIndex = mSampleCount;
+ return;
+ }
+
+ mNextSampleOffset =
+ U32_AT(mChunkOffsets->data() + 8 + 4 * mChunkIndex);
+ } else {
+ CHECK(mChunkOffsets64 != NULL);
+
+ uint32_t entryCount = U32_AT(mChunkOffsets64->data() + 4);
+
+ if (mChunkIndex >= entryCount) {
+ mSampleIndex = mSampleCount;
+ return;
+ }
+
+ mNextSampleOffset =
+ U64_AT(mChunkOffsets64->data() + 8 + 8 * mChunkIndex);
+ }
+ }
+
+ mSampleInfo.mOffset = mNextSampleOffset;
+
+ mSampleInfo.mPresentationTime = 0;
+ mSampleInfo.mFlags = 0;
+}
+
+void FragmentedMP4Parser::StaticTrackFragment::advance() {
+ mNextSampleOffset += mSampleInfo.mSize;
+
+ ++mSampleIndex;
+ if (--mSampleToChunkRemaining == 0) {
+ ++mChunkIndex;
+
+ uint32_t entryCount = U32_AT(mSampleToChunk->data() + 4);
+
+ // If this is the last entry in the sample to chunk table, we will
+ // stay on this entry.
+ if ((uint32_t)(mSampleToChunkIndex + 1) < entryCount) {
+ uint32_t nextChunkIndex =
+ U32_AT(mSampleToChunk->data() + 8 + 12 * (mSampleToChunkIndex + 1));
+
+ CHECK_GE(nextChunkIndex, 1u);
+ --nextChunkIndex;
+
+ if (mChunkIndex >= nextChunkIndex) {
+ CHECK_EQ(mChunkIndex, nextChunkIndex);
+ ++mSampleToChunkIndex;
+ }
+ }
+
+ mSampleToChunkRemaining =
+ U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex + 4);
+ }
+
+ updateSampleInfo();
+}
+
+static void setU32At(uint8_t *ptr, uint32_t x) {
+ ptr[0] = x >> 24;
+ ptr[1] = (x >> 16) & 0xff;
+ ptr[2] = (x >> 8) & 0xff;
+ ptr[3] = x & 0xff;
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::signalCompletion() {
+ mSampleToChunkIndex = 0;
+
+ mSampleToChunkRemaining =
+ (mSampleToChunk == NULL)
+ ? 0
+ : U32_AT(mSampleToChunk->data() + 8 + 12 * mSampleToChunkIndex + 4);
+
+ updateSampleInfo();
+
+ return OK;
+}
+
+bool FragmentedMP4Parser::StaticTrackFragment::complete() const {
+ return true;
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::parseSampleSizes(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 12 > size) {
+ return ERROR_MALFORMED;
+ }
+
+ if (parser->readU32(offset) != 0) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t sampleSize = parser->readU32(offset + 4);
+ uint32_t sampleCount = parser->readU32(offset + 8);
+
+ if (sampleSize == 0 && offset + 12 + sampleCount * 4 != size) {
+ return ERROR_MALFORMED;
+ }
+
+ parser->copyBuffer(&mSampleSizes, offset, size);
+
+ mSampleCount = sampleCount;
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::parseCompactSampleSizes(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 12 > size) {
+ return ERROR_MALFORMED;
+ }
+
+ if (parser->readU32(offset) != 0) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t fieldSize = parser->readU32(offset + 4);
+
+ if (fieldSize != 4 && fieldSize != 8 && fieldSize != 16) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t sampleCount = parser->readU32(offset + 8);
+
+ if (offset + 12 + (sampleCount * fieldSize + 4) / 8 != size) {
+ return ERROR_MALFORMED;
+ }
+
+ parser->copyBuffer(&mCompactSampleSizes, offset, size);
+
+ mSampleCount = sampleCount;
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::parseSampleToChunk(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 8 > size) {
+ return ERROR_MALFORMED;
+ }
+
+ if (parser->readU32(offset) != 0) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t entryCount = parser->readU32(offset + 4);
+
+ if (entryCount == 0) {
+ return OK;
+ }
+
+ if (offset + 8 + entryCount * 12 != size) {
+ return ERROR_MALFORMED;
+ }
+
+ parser->copyBuffer(&mSampleToChunk, offset, size);
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::parseChunkOffsets(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 8 > size) {
+ return ERROR_MALFORMED;
+ }
+
+ if (parser->readU32(offset) != 0) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t entryCount = parser->readU32(offset + 4);
+
+ if (offset + 8 + entryCount * 4 != size) {
+ return ERROR_MALFORMED;
+ }
+
+ parser->copyBuffer(&mChunkOffsets, offset, size);
+
+ return OK;
+}
+
+status_t FragmentedMP4Parser::StaticTrackFragment::parseChunkOffsets64(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size) {
+ if (offset + 8 > size) {
+ return ERROR_MALFORMED;
+ }
+
+ if (parser->readU32(offset) != 0) {
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t entryCount = parser->readU32(offset + 4);
+
+ if (offset + 8 + entryCount * 8 != size) {
+ return ERROR_MALFORMED;
+ }
+
+ parser->copyBuffer(&mChunkOffsets64, offset, size);
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/mp4/TrackFragment.h b/media/libstagefright/mp4/TrackFragment.h
new file mode 100644
index 0000000..e1ad46e
--- /dev/null
+++ b/media/libstagefright/mp4/TrackFragment.h
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#ifndef TRACK_FRAGMENT_H_
+
+#define TRACK_FRAGMENT_H_
+
+#include "include/FragmentedMP4Parser.h"
+
+namespace android {
+
+struct FragmentedMP4Parser::TrackFragment : public RefBase {
+ TrackFragment() {}
+
+ virtual status_t getSample(SampleInfo *info) = 0;
+ virtual void advance() = 0;
+
+ virtual status_t signalCompletion() = 0;
+ virtual bool complete() const = 0;
+
+protected:
+ virtual ~TrackFragment() {}
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(TrackFragment);
+};
+
+struct FragmentedMP4Parser::DynamicTrackFragment : public FragmentedMP4Parser::TrackFragment {
+ DynamicTrackFragment();
+
+ virtual status_t getSample(SampleInfo *info);
+ virtual void advance();
+
+ void addSample(
+ off64_t dataOffset, size_t sampleSize,
+ uint32_t presentationTime,
+ size_t sampleDescIndex,
+ uint32_t flags);
+
+ // No more samples will be added to this fragment.
+ virtual status_t signalCompletion();
+
+ virtual bool complete() const;
+
+protected:
+ virtual ~DynamicTrackFragment();
+
+private:
+ bool mComplete;
+ size_t mSampleIndex;
+ Vector<SampleInfo> mSamples;
+
+ DISALLOW_EVIL_CONSTRUCTORS(DynamicTrackFragment);
+};
+
+struct FragmentedMP4Parser::StaticTrackFragment : public FragmentedMP4Parser::TrackFragment {
+ StaticTrackFragment();
+
+ virtual status_t getSample(SampleInfo *info);
+ virtual void advance();
+
+ virtual status_t signalCompletion();
+ virtual bool complete() const;
+
+ status_t parseSampleSizes(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size);
+
+ status_t parseCompactSampleSizes(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size);
+
+ status_t parseSampleToChunk(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size);
+
+ status_t parseChunkOffsets(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size);
+
+ status_t parseChunkOffsets64(
+ FragmentedMP4Parser *parser, uint32_t type, size_t offset, uint64_t size);
+
+protected:
+ virtual ~StaticTrackFragment();
+
+private:
+ size_t mSampleIndex;
+ size_t mSampleCount;
+ uint32_t mChunkIndex;
+
+ SampleInfo mSampleInfo;
+
+ sp<ABuffer> mSampleSizes;
+ sp<ABuffer> mCompactSampleSizes;
+
+ sp<ABuffer> mSampleToChunk;
+ ssize_t mSampleToChunkIndex;
+ size_t mSampleToChunkRemaining;
+
+ sp<ABuffer> mChunkOffsets;
+ sp<ABuffer> mChunkOffsets64;
+ uint32_t mPrevChunkIndex;
+ uint64_t mNextSampleOffset;
+
+ void updateSampleInfo();
+
+ DISALLOW_EVIL_CONSTRUCTORS(StaticTrackFragment);
+};
+
+} // namespace android
+
+#endif // TRACK_FRAGMENT_H_