blob: 0102656f2c635625b8fbe73ce5b7ee22bdb75052 [file] [log] [blame]
/*
* 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/avc_utils.h"
#include "include/ESDS.h"
#include "include/FragmentedMP4Parser.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 },
{ FOURCC('s', 'i', 'd', 'x'), 0, &FragmentedMP4Parser::parseSegmentIndex },
};
struct FileSource : public FragmentedMP4Parser::Source {
FileSource(const char *filename)
: mFile(fopen(filename, "rb")) {
CHECK(mFile != NULL);
}
virtual ~FileSource() {
fclose(mFile);
}
virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
fseek(mFile, offset, SEEK_SET);
return fread(data, 1, size, mFile);
}
virtual bool isSeekable() {
return true;
}
private:
FILE *mFile;
DISALLOW_EVIL_CONSTRUCTORS(FileSource);
};
struct ReadTracker : public RefBase {
ReadTracker(off64_t size) {
allocSize = 1 + size / 8192; // 1 bit per kilobyte
bitmap = (char*) calloc(1, allocSize);
}
virtual ~ReadTracker() {
dumpToLog();
free(bitmap);
}
void mark(off64_t offset, size_t size) {
int firstbit = offset / 1024;
int lastbit = (offset + size - 1) / 1024;
for (int i = firstbit; i <= lastbit; i++) {
bitmap[i/8] |= (0x80 >> (i & 7));
}
}
private:
void dumpToLog() {
// 96 chars per line, each char represents one kilobyte, 1 kb per bit
int numlines = allocSize / 12;
char buf[97];
char *cur = bitmap;
for (int i = 0; i < numlines; i++ && cur) {
for (int j = 0; j < 12; j++) {
for (int k = 0; k < 8; k++) {
buf[(j * 8) + k] = (*cur & (0x80 >> k)) ? 'X' : '.';
}
cur++;
}
buf[96] = '\0';
ALOGI("%5dk: %s", i * 96, buf);
}
}
size_t allocSize;
char *bitmap;
};
struct DataSourceSource : public FragmentedMP4Parser::Source {
DataSourceSource(sp<DataSource> &source)
: mDataSource(source) {
CHECK(mDataSource != NULL);
#if 0
off64_t size;
if (source->getSize(&size) == OK) {
mReadTracker = new ReadTracker(size);
} else {
ALOGE("couldn't get data source size");
}
#endif
}
virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
if (mReadTracker != NULL) {
mReadTracker->mark(offset, size);
}
return mDataSource->readAt(offset, data, size);
}
virtual bool isSeekable() {
return true;
}
private:
sp<DataSource> mDataSource;
sp<ReadTracker> mReadTracker;
DISALLOW_EVIL_CONSTRUCTORS(DataSourceSource);
};
FragmentedMP4Parser::FragmentedMP4Parser()
: mBufferPos(0),
mSuspended(false),
mDoneWithMoov(false),
mFirstMoofOffset(0),
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();
ALOGV("Parser::start(%s)", filename);
}
void FragmentedMP4Parser::start(const sp<Source> &source) {
sp<AMessage> msg = new AMessage(kWhatStart, id());
msg->setObject("source", source);
msg->post();
ALOGV("Parser::start(Source)");
}
void FragmentedMP4Parser::start(sp<DataSource> &source) {
sp<AMessage> msg = new AMessage(kWhatStart, id());
msg->setObject("source", new DataSourceSource(source));
msg->post();
ALOGV("Parser::start(DataSource)");
}
sp<AMessage> FragmentedMP4Parser::getFormat(bool audio, bool synchronous) {
while (true) {
bool moovDone = mDoneWithMoov;
sp<AMessage> msg = new AMessage(kWhatGetFormat, id());
msg->setInt32("audio", audio);
sp<AMessage> response;
status_t err = msg->postAndAwaitResponse(&response);
if (err != OK) {
ALOGV("getFormat post failed: %d", err);
return NULL;
}
if (response->findInt32("err", &err) && err != OK) {
if (synchronous && err == -EWOULDBLOCK && !moovDone) {
resumeIfNecessary();
ALOGV("@getFormat parser not ready yet, retrying");
usleep(10000);
continue;
}
ALOGV("getFormat failed: %d", err);
return NULL;
}
sp<AMessage> format;
CHECK(response->findMessage("format", &format));
ALOGV("returning format %s", format->debugString().c_str());
return format;
}
}
status_t FragmentedMP4Parser::seekTo(bool wantAudio, int64_t timeUs) {
sp<AMessage> msg = new AMessage(kWhatSeekTo, id());
msg->setInt32("audio", wantAudio);
msg->setInt64("position", timeUs);
sp<AMessage> response;
status_t err = msg->postAndAwaitResponse(&response);
return err;
}
bool FragmentedMP4Parser::isSeekable() const {
while (mFirstMoofOffset == 0 && mFinalResult == OK) {
usleep(10000);
}
bool seekable = mSource->isSeekable();
for (size_t i = 0; seekable && i < mTracks.size(); i++) {
const TrackInfo *info = &mTracks.valueAt(i);
seekable &= !info->mSidx.empty();
}
return seekable;
}
status_t FragmentedMP4Parser::onSeekTo(bool wantAudio, int64_t position) {
status_t err = -EINVAL;
ssize_t trackIndex = findTrack(wantAudio);
if (trackIndex < 0) {
err = trackIndex;
} else {
TrackInfo *info = &mTracks.editValueAt(trackIndex);
int numSidxEntries = info->mSidx.size();
int64_t totalTime = 0;
off_t totalOffset = mFirstMoofOffset;
for (int i = 0; i < numSidxEntries; i++) {
const SidxEntry *se = &info->mSidx[i];
if (totalTime + se->mDurationUs > position) {
mBuffer->setRange(0,0);
mBufferPos = totalOffset;
if (mFinalResult == ERROR_END_OF_STREAM) {
mFinalResult = OK;
mSuspended = true; // force resume
resumeIfNecessary();
}
info->mFragments.clear();
info->mDecodingTime = totalTime * info->mMediaTimeScale / 1000000ll;
return OK;
}
totalTime += se->mDurationUs;
totalOffset += se->mSize;
}
}
ALOGV("seekTo out of range");
return err;
}
status_t FragmentedMP4Parser::dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit,
bool synchronous) {
while (true) {
sp<AMessage> msg = new AMessage(kWhatDequeueAccessUnit, id());
msg->setInt32("audio", audio);
sp<AMessage> response;
status_t err = msg->postAndAwaitResponse(&response);
if (err != OK) {
ALOGV("dequeue fail 1: %d", err);
return err;
}
if (response->findInt32("err", &err) && err != OK) {
if (synchronous && err == -EWOULDBLOCK) {
resumeIfNecessary();
ALOGV("Parser not ready yet, retrying");
usleep(10000);
continue;
}
ALOGV("dequeue fail 2: %d, %d", err, synchronous);
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) {
ALOGV("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) {
ALOGV("Reached EOF when reading %d @ %d + %d", needed, mBufferPos, mBuffer->size());
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);
sp<AMessage> format = info->mSampleDescs.itemAt(0).mFormat;
if (info->mSidxDuration) {
format->setInt64("durationUs", info->mSidxDuration);
} else {
// this is probably going to be zero. Oh well...
format->setInt64("durationUs",
1000000ll * info->mDuration / info->mMediaTimeScale);
}
response->setMessage(
"format", format);
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;
}
case kWhatSeekTo:
{
ALOGV("kWhatSeekTo");
int32_t wantAudio;
CHECK(msg->findInt32("audio", &wantAudio));
int64_t position;
CHECK(msg->findInt64("position", &position));
status_t err = -EWOULDBLOCK;
sp<AMessage> response = new AMessage;
ssize_t trackIndex = findTrack(wantAudio);
if (trackIndex < 0) {
err = trackIndex;
} else {
err = onSeekTo(wantAudio, position);
}
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', 'o', 'o', 'f')) {
if (mFirstMoofOffset == 0) {
ALOGV("first moof @ %08x", mBufferPos + offset);
mFirstMoofOffset = mBufferPos + offset - 8; // point at the size
}
}
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;
}
ALOGV("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 && !mSource->isSeekable()) {
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;
// XXX FIXME: if one of the tracks is not advanced (e.g. if you play an audio+video
// file with sf2), then mMediaData will not be pruned and keeps growing
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);
if (IsIDR(*accessUnit)) {
(*accessUnit)->meta()->setInt32("is-sync-frame", 1);
}
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);
}
}
if (IsIDR(*accessUnit)) {
(*accessUnit)->meta()->setInt32("is-sync-frame", 1);
}
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();
} else if (container->mType == FOURCC('m', 'o', 'o', 'v')) {
mDoneWithMoov = true;
}
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;
if (info->mDuration == 0xffffffff) {
// ffmpeg sets this to -1, which is incorrect.
info->mDuration = 0;
}
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) {
ALOGV("suspending for now.");
mSuspended = true;
}
return OK;
}
status_t FragmentedMP4Parser::parseSegmentIndex(
uint32_t type, size_t offset, uint64_t size) {
ALOGV("sidx box type %d, offset %d, size %d", type, int(offset), int(size));
// AString sidxstr;
// hexdump(mBuffer->data() + offset, size, 0 /* indent */, &sidxstr);
// ALOGV("raw sidx:");
// ALOGV("%s", sidxstr.c_str());
if (offset + 12 > size) {
return -EINVAL;
}
uint32_t flags = readU32(offset);
uint32_t version = flags >> 24;
flags &= 0xffffff;
ALOGV("sidx version %d", version);
uint32_t referenceId = readU32(offset + 4);
uint32_t timeScale = readU32(offset + 8);
ALOGV("sidx refid/timescale: %d/%d", referenceId, timeScale);
uint64_t earliestPresentationTime;
uint64_t firstOffset;
offset += 12;
if (version == 0) {
if (offset + 8 > size) {
return -EINVAL;
}
earliestPresentationTime = readU32(offset);
firstOffset = readU32(offset + 4);
offset += 8;
} else {
if (offset + 16 > size) {
return -EINVAL;
}
earliestPresentationTime = readU64(offset);
firstOffset = readU64(offset + 8);
offset += 16;
}
ALOGV("sidx pres/off: %Ld/%Ld", earliestPresentationTime, firstOffset);
if (offset + 4 > size) {
return -EINVAL;
}
if (readU16(offset) != 0) { // reserved
return -EINVAL;
}
int32_t referenceCount = readU16(offset + 2);
offset += 4;
ALOGV("refcount: %d", referenceCount);
if (offset + referenceCount * 12 > size) {
return -EINVAL;
}
TrackInfo *info = editTrack(mCurrentTrackID);
uint64_t total_duration = 0;
for (int i = 0; i < referenceCount; i++) {
uint32_t d1 = readU32(offset);
uint32_t d2 = readU32(offset + 4);
uint32_t d3 = readU32(offset + 8);
if (d1 & 0x80000000) {
ALOGW("sub-sidx boxes not supported yet");
}
bool sap = d3 & 0x80000000;
bool saptype = d3 >> 28;
if (!sap || saptype > 2) {
ALOGW("not a stream access point, or unsupported type");
}
total_duration += d2;
offset += 12;
ALOGV(" item %d, %08x %08x %08x", i, d1, d2, d3);
SidxEntry se;
se.mSize = d1 & 0x7fffffff;
se.mDurationUs = 1000000LL * d2 / timeScale;
info->mSidx.add(se);
}
info->mSidxDuration = total_duration * 1000000 / timeScale;
ALOGV("duration: %lld", info->mSidxDuration);
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.mSidxDuration = 0;
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) const {
sp<ABuffer> buf = new ABuffer(size);
memcpy(buf->data(), mBuffer->data() + offset, size);
*dst = buf;
}
} // namespace android