From 4ecce5026fe3235e783766dcb9fc2b59405da08f Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Fri, 31 Aug 2012 11:07:37 -0700 Subject: Move fragmented mp4 parser to libstagefright and rename it from Parser to FragmentedMP4Parser Change-Id: I986f50d0c5c93648aac675d6160e18623b031541 --- media/libstagefright/mp4/FragmentedMP4Parser.cpp | 1679 ++++++++++++++++++++++ media/libstagefright/mp4/TrackFragment.cpp | 364 +++++ media/libstagefright/mp4/TrackFragment.h | 122 ++ 3 files changed, 2165 insertions(+) create mode 100644 media/libstagefright/mp4/FragmentedMP4Parser.cpp create mode 100644 media/libstagefright/mp4/TrackFragment.cpp create mode 100644 media/libstagefright/mp4/TrackFragment.h (limited to 'media/libstagefright/mp4') 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 + +#include "include/FragmentedMP4Parser.h" +#include "include/ESDS.h" +#include "TrackFragment.h" + + +#include +#include +#include +#include +#include +#include +#include + +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 msg = new AMessage(kWhatStart, id()); + msg->setObject("source", new FileSource(filename)); + msg->post(); +} + +void FragmentedMP4Parser::start(const sp &source) { + sp msg = new AMessage(kWhatStart, id()); + msg->setObject("source", source); + msg->post(); +} + +sp FragmentedMP4Parser::getFormat(bool audio) { + sp msg = new AMessage(kWhatGetFormat, id()); + msg->setInt32("audio", audio); + + sp response; + status_t err = msg->postAndAwaitResponse(&response); + + if (err != OK) { + return NULL; + } + + if (response->findInt32("err", &err) && err != OK) { + return NULL; + } + + sp format; + CHECK(response->findMessage("format", &format)); + + ALOGV("returning format %s", format->debugString().c_str()); + return format; +} + +status_t FragmentedMP4Parser::dequeueAccessUnit(bool audio, sp *accessUnit) { + sp msg = new AMessage(kWhatDequeueAccessUnit, id()); + msg->setInt32("audio", audio); + + sp 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 &msg) { + switch (msg->what()) { + case kWhatStart: + { + sp obj; + CHECK(msg->findObject("source", &obj)); + + mSource = static_cast(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 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 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 response = new AMessage; + + ssize_t trackIndex = findTrack(wantAudio); + + if (trackIndex < 0) { + err = trackIndex; + } else { + sp 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 *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 *accessUnit) { + TrackInfo *info = &mTracks.editValueAt(trackIndex); + + sp 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 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 *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 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 &fragment = + *--trackInfo->mFragments.end(); + + static_cast( + 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 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 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 &format, int32_t index, + const void *data, size_t size, + bool insertStartCode = false) { + sp 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 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 &fragment = *--info->mFragments.end(); + + uint32_t decodingTime = info->mDecodingTime; + info->mDecodingTime += sampleDuration; + uint32_t presentationTime = decodingTime + sampleCtsOffset; + + static_cast( + fragment.get())->addSample( + dataOffset, + sampleSize, + presentationTime, + sampleDescIndex, + ((flags & kFirstSampleFlagsPresent) && i == 0) + ? firstSampleFlags : sampleFlags); + + dataOffset += sampleSize; + } + + mTrackFragmentHeaderInfo.mDataOffset = dataOffset; + + return OK; +} + +void FragmentedMP4Parser::copyBuffer( + sp *dst, size_t offset, uint64_t size, size_t extra) const { + sp 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 + +#include "TrackFragment.h" + +#include +#include +#include +#include +#include + +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 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 mSampleSizes; + sp mCompactSampleSizes; + + sp mSampleToChunk; + ssize_t mSampleToChunkIndex; + size_t mSampleToChunkRemaining; + + sp mChunkOffsets; + sp mChunkOffsets64; + uint32_t mPrevChunkIndex; + uint64_t mNextSampleOffset; + + void updateSampleInfo(); + + DISALLOW_EVIL_CONSTRUCTORS(StaticTrackFragment); +}; + +} // namespace android + +#endif // TRACK_FRAGMENT_H_ -- cgit v1.1