blob: 1dfa86811748abc4571e4f979709382a0fc102fb [file] [log] [blame]
/*
* Copyright (C) 2009 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 "MPEG4Extractor"
#include <ctype.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <utils/Log.h>
#include "include/MPEG4Extractor.h"
#include "include/SampleTable.h"
#include "include/ESDS.h"
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/foundation/ColorUtils.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
#include <utils/String8.h>
#include <byteswap.h>
#include "include/ID3.h"
#include "include/avc_utils.h"
#ifndef UINT32_MAX
#define UINT32_MAX (4294967295U)
#endif
namespace android {
enum {
// max track header chunk to return
kMaxTrackHeaderSize = 32,
// maximum size of an atom. Some atoms can be bigger according to the spec,
// but we only allow up to this size.
kMaxAtomSize = 64 * 1024 * 1024,
};
class MPEG4Source : public MediaSource {
public:
// Caller retains ownership of both "dataSource" and "sampleTable".
MPEG4Source(const sp<MPEG4Extractor> &owner,
const sp<MetaData> &format,
const sp<DataSource> &dataSource,
int32_t timeScale,
const sp<SampleTable> &sampleTable,
Vector<SidxEntry> &sidx,
const Trex *trex,
off64_t firstMoofOffset);
virtual status_t init();
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
virtual sp<MetaData> getFormat();
virtual status_t read(MediaBuffer **buffer, const ReadOptions *options = NULL);
virtual bool supportNonblockingRead() { return true; }
virtual status_t fragmentedRead(MediaBuffer **buffer, const ReadOptions *options = NULL);
protected:
virtual ~MPEG4Source();
private:
Mutex mLock;
// keep the MPEG4Extractor around, since we're referencing its data
sp<MPEG4Extractor> mOwner;
sp<MetaData> mFormat;
sp<DataSource> mDataSource;
int32_t mTimescale;
sp<SampleTable> mSampleTable;
uint32_t mCurrentSampleIndex;
uint32_t mCurrentFragmentIndex;
Vector<SidxEntry> &mSegments;
const Trex *mTrex;
off64_t mFirstMoofOffset;
off64_t mCurrentMoofOffset;
off64_t mNextMoofOffset;
uint32_t mCurrentTime;
int32_t mLastParsedTrackId;
int32_t mTrackId;
int32_t mCryptoMode; // passed in from extractor
int32_t mDefaultIVSize; // passed in from extractor
uint8_t mCryptoKey[16]; // passed in from extractor
uint32_t mCurrentAuxInfoType;
uint32_t mCurrentAuxInfoTypeParameter;
int32_t mCurrentDefaultSampleInfoSize;
uint32_t mCurrentSampleInfoCount;
uint32_t mCurrentSampleInfoAllocSize;
uint8_t* mCurrentSampleInfoSizes;
uint32_t mCurrentSampleInfoOffsetCount;
uint32_t mCurrentSampleInfoOffsetsAllocSize;
uint64_t* mCurrentSampleInfoOffsets;
bool mIsAVC;
bool mIsHEVC;
size_t mNALLengthSize;
bool mStarted;
MediaBufferGroup *mGroup;
MediaBuffer *mBuffer;
bool mWantsNALFragments;
uint8_t *mSrcBuffer;
size_t parseNALSize(const uint8_t *data) const;
status_t parseChunk(off64_t *offset);
status_t parseTrackFragmentHeader(off64_t offset, off64_t size);
status_t parseTrackFragmentRun(off64_t offset, off64_t size);
status_t parseSampleAuxiliaryInformationSizes(off64_t offset, off64_t size);
status_t parseSampleAuxiliaryInformationOffsets(off64_t offset, off64_t size);
struct TrackFragmentHeaderInfo {
enum Flags {
kBaseDataOffsetPresent = 0x01,
kSampleDescriptionIndexPresent = 0x02,
kDefaultSampleDurationPresent = 0x08,
kDefaultSampleSizePresent = 0x10,
kDefaultSampleFlagsPresent = 0x20,
kDurationIsEmpty = 0x10000,
};
uint32_t mTrackID;
uint32_t mFlags;
uint64_t mBaseDataOffset;
uint32_t mSampleDescriptionIndex;
uint32_t mDefaultSampleDuration;
uint32_t mDefaultSampleSize;
uint32_t mDefaultSampleFlags;
uint64_t mDataOffset;
};
TrackFragmentHeaderInfo mTrackFragmentHeaderInfo;
struct Sample {
off64_t offset;
size_t size;
uint32_t duration;
int32_t compositionOffset;
uint8_t iv[16];
Vector<size_t> clearsizes;
Vector<size_t> encryptedsizes;
};
Vector<Sample> mCurrentSamples;
MPEG4Source(const MPEG4Source &);
MPEG4Source &operator=(const MPEG4Source &);
};
// This custom data source wraps an existing one and satisfies requests
// falling entirely within a cached range from the cache while forwarding
// all remaining requests to the wrapped datasource.
// This is used to cache the full sampletable metadata for a single track,
// possibly wrapping multiple times to cover all tracks, i.e.
// Each MPEG4DataSource caches the sampletable metadata for a single track.
struct MPEG4DataSource : public DataSource {
MPEG4DataSource(const sp<DataSource> &source);
virtual status_t initCheck() const;
virtual ssize_t readAt(off64_t offset, void *data, size_t size);
virtual status_t getSize(off64_t *size);
virtual uint32_t flags();
status_t setCachedRange(off64_t offset, size_t size);
protected:
virtual ~MPEG4DataSource();
private:
Mutex mLock;
sp<DataSource> mSource;
off64_t mCachedOffset;
size_t mCachedSize;
uint8_t *mCache;
void clearCache();
MPEG4DataSource(const MPEG4DataSource &);
MPEG4DataSource &operator=(const MPEG4DataSource &);
};
MPEG4DataSource::MPEG4DataSource(const sp<DataSource> &source)
: mSource(source),
mCachedOffset(0),
mCachedSize(0),
mCache(NULL) {
}
MPEG4DataSource::~MPEG4DataSource() {
clearCache();
}
void MPEG4DataSource::clearCache() {
if (mCache) {
free(mCache);
mCache = NULL;
}
mCachedOffset = 0;
mCachedSize = 0;
}
status_t MPEG4DataSource::initCheck() const {
return mSource->initCheck();
}
ssize_t MPEG4DataSource::readAt(off64_t offset, void *data, size_t size) {
Mutex::Autolock autoLock(mLock);
if (isInRange(mCachedOffset, mCachedSize, offset, size)) {
memcpy(data, &mCache[offset - mCachedOffset], size);
return size;
}
return mSource->readAt(offset, data, size);
}
status_t MPEG4DataSource::getSize(off64_t *size) {
return mSource->getSize(size);
}
uint32_t MPEG4DataSource::flags() {
return mSource->flags();
}
status_t MPEG4DataSource::setCachedRange(off64_t offset, size_t size) {
Mutex::Autolock autoLock(mLock);
clearCache();
mCache = (uint8_t *)malloc(size);
if (mCache == NULL) {
return -ENOMEM;
}
mCachedOffset = offset;
mCachedSize = size;
ssize_t err = mSource->readAt(mCachedOffset, mCache, mCachedSize);
if (err < (ssize_t)size) {
clearCache();
return ERROR_IO;
}
return OK;
}
////////////////////////////////////////////////////////////////////////////////
static const bool kUseHexDump = false;
static void hexdump(const void *_data, size_t size) {
const uint8_t *data = (const uint8_t *)_data;
size_t offset = 0;
while (offset < size) {
printf("0x%04zx ", offset);
size_t n = size - offset;
if (n > 16) {
n = 16;
}
for (size_t i = 0; i < 16; ++i) {
if (i == 8) {
printf(" ");
}
if (offset + i < size) {
printf("%02x ", data[offset + i]);
} else {
printf(" ");
}
}
printf(" ");
for (size_t i = 0; i < n; ++i) {
if (isprint(data[offset + i])) {
printf("%c", data[offset + i]);
} else {
printf(".");
}
}
printf("\n");
offset += 16;
}
}
static const char *FourCC2MIME(uint32_t fourcc) {
switch (fourcc) {
case FOURCC('m', 'p', '4', 'a'):
return MEDIA_MIMETYPE_AUDIO_AAC;
case FOURCC('s', 'a', 'm', 'r'):
return MEDIA_MIMETYPE_AUDIO_AMR_NB;
case FOURCC('s', 'a', 'w', 'b'):
return MEDIA_MIMETYPE_AUDIO_AMR_WB;
case FOURCC('m', 'p', '4', 'v'):
return MEDIA_MIMETYPE_VIDEO_MPEG4;
case FOURCC('s', '2', '6', '3'):
case FOURCC('h', '2', '6', '3'):
case FOURCC('H', '2', '6', '3'):
return MEDIA_MIMETYPE_VIDEO_H263;
case FOURCC('a', 'v', 'c', '1'):
return MEDIA_MIMETYPE_VIDEO_AVC;
case FOURCC('h', 'v', 'c', '1'):
case FOURCC('h', 'e', 'v', '1'):
return MEDIA_MIMETYPE_VIDEO_HEVC;
default:
CHECK(!"should not be here.");
return NULL;
}
}
static bool AdjustChannelsAndRate(uint32_t fourcc, uint32_t *channels, uint32_t *rate) {
if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, FourCC2MIME(fourcc))) {
// AMR NB audio is always mono, 8kHz
*channels = 1;
*rate = 8000;
return true;
} else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_WB, FourCC2MIME(fourcc))) {
// AMR WB audio is always mono, 16kHz
*channels = 1;
*rate = 16000;
return true;
}
return false;
}
MPEG4Extractor::MPEG4Extractor(const sp<DataSource> &source)
: mMoofOffset(0),
mMoofFound(false),
mMdatFound(false),
mDataSource(source),
mInitCheck(NO_INIT),
mHeaderTimescale(0),
mIsQT(false),
mFirstTrack(NULL),
mLastTrack(NULL),
mFileMetaData(new MetaData),
mFirstSINF(NULL),
mIsDrm(false) {
}
MPEG4Extractor::~MPEG4Extractor() {
Track *track = mFirstTrack;
while (track) {
Track *next = track->next;
delete track;
track = next;
}
mFirstTrack = mLastTrack = NULL;
SINF *sinf = mFirstSINF;
while (sinf) {
SINF *next = sinf->next;
delete[] sinf->IPMPData;
delete sinf;
sinf = next;
}
mFirstSINF = NULL;
for (size_t i = 0; i < mPssh.size(); i++) {
delete [] mPssh[i].data;
}
}
uint32_t MPEG4Extractor::flags() const {
return CAN_PAUSE |
((mMoofOffset == 0 || mSidxEntries.size() != 0) ?
(CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK) : 0);
}
sp<MetaData> MPEG4Extractor::getMetaData() {
status_t err;
if ((err = readMetaData()) != OK) {
return new MetaData;
}
return mFileMetaData;
}
size_t MPEG4Extractor::countTracks() {
status_t err;
if ((err = readMetaData()) != OK) {
ALOGV("MPEG4Extractor::countTracks: no tracks");
return 0;
}
size_t n = 0;
Track *track = mFirstTrack;
while (track) {
++n;
track = track->next;
}
ALOGV("MPEG4Extractor::countTracks: %zu tracks", n);
return n;
}
sp<MetaData> MPEG4Extractor::getTrackMetaData(
size_t index, uint32_t flags) {
status_t err;
if ((err = readMetaData()) != OK) {
return NULL;
}
Track *track = mFirstTrack;
while (index > 0) {
if (track == NULL) {
return NULL;
}
track = track->next;
--index;
}
if (track == NULL) {
return NULL;
}
if ((flags & kIncludeExtensiveMetaData)
&& !track->includes_expensive_metadata) {
track->includes_expensive_metadata = true;
const char *mime;
CHECK(track->meta->findCString(kKeyMIMEType, &mime));
if (!strncasecmp("video/", mime, 6)) {
// MPEG2 tracks do not provide CSD, so read the stream header
if (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2)) {
off64_t offset;
size_t size;
if (track->sampleTable->getMetaDataForSample(
0 /* sampleIndex */, &offset, &size, NULL /* sampleTime */) == OK) {
if (size > kMaxTrackHeaderSize) {
size = kMaxTrackHeaderSize;
}
uint8_t header[kMaxTrackHeaderSize];
if (mDataSource->readAt(offset, &header, size) == (ssize_t)size) {
track->meta->setData(kKeyStreamHeader, 'mdat', header, size);
}
}
}
if (mMoofOffset > 0) {
int64_t duration;
if (track->meta->findInt64(kKeyDuration, &duration)) {
// nothing fancy, just pick a frame near 1/4th of the duration
track->meta->setInt64(
kKeyThumbnailTime, duration / 4);
}
} else {
uint32_t sampleIndex;
uint32_t sampleTime;
if (track->timescale != 0 &&
track->sampleTable->findThumbnailSample(&sampleIndex) == OK
&& track->sampleTable->getMetaDataForSample(
sampleIndex, NULL /* offset */, NULL /* size */,
&sampleTime) == OK) {
track->meta->setInt64(
kKeyThumbnailTime,
((int64_t)sampleTime * 1000000) / track->timescale);
}
}
}
}
return track->meta;
}
static void MakeFourCCString(uint32_t x, char *s) {
s[0] = x >> 24;
s[1] = (x >> 16) & 0xff;
s[2] = (x >> 8) & 0xff;
s[3] = x & 0xff;
s[4] = '\0';
}
status_t MPEG4Extractor::readMetaData() {
if (mInitCheck != NO_INIT) {
return mInitCheck;
}
off64_t offset = 0;
status_t err;
bool sawMoovOrSidx = false;
while (!(sawMoovOrSidx && (mMdatFound || mMoofFound))) {
off64_t orig_offset = offset;
err = parseChunk(&offset, 0);
if (err != OK && err != UNKNOWN_ERROR) {
break;
} else if (offset <= orig_offset) {
// only continue parsing if the offset was advanced,
// otherwise we might end up in an infinite loop
ALOGE("did not advance: %lld->%lld", (long long)orig_offset, (long long)offset);
err = ERROR_MALFORMED;
break;
} else if (err == UNKNOWN_ERROR) {
sawMoovOrSidx = true;
}
}
if (mInitCheck == OK) {
if (findTrackByMimePrefix("video/") != NULL) {
mFileMetaData->setCString(
kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG4);
} else if (findTrackByMimePrefix("audio/") != NULL) {
mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
} else {
mFileMetaData->setCString(kKeyMIMEType, "application/octet-stream");
}
} else {
mInitCheck = err;
}
CHECK_NE(err, (status_t)NO_INIT);
// copy pssh data into file metadata
uint64_t psshsize = 0;
for (size_t i = 0; i < mPssh.size(); i++) {
psshsize += 20 + mPssh[i].datalen;
}
if (psshsize > 0 && psshsize <= UINT32_MAX) {
char *buf = (char*)malloc(psshsize);
if (!buf) {
ALOGE("b/28471206");
return NO_MEMORY;
}
char *ptr = buf;
for (size_t i = 0; i < mPssh.size(); i++) {
memcpy(ptr, mPssh[i].uuid, 20); // uuid + length
memcpy(ptr + 20, mPssh[i].data, mPssh[i].datalen);
ptr += (20 + mPssh[i].datalen);
}
mFileMetaData->setData(kKeyPssh, 'pssh', buf, psshsize);
free(buf);
}
return mInitCheck;
}
char* MPEG4Extractor::getDrmTrackInfo(size_t trackID, int *len) {
if (mFirstSINF == NULL) {
return NULL;
}
SINF *sinf = mFirstSINF;
while (sinf && (trackID != sinf->trackID)) {
sinf = sinf->next;
}
if (sinf == NULL) {
return NULL;
}
*len = sinf->len;
return sinf->IPMPData;
}
// Reads an encoded integer 7 bits at a time until it encounters the high bit clear.
static int32_t readSize(off64_t offset,
const sp<DataSource> DataSource, uint8_t *numOfBytes) {
uint32_t size = 0;
uint8_t data;
bool moreData = true;
*numOfBytes = 0;
while (moreData) {
if (DataSource->readAt(offset, &data, 1) < 1) {
return -1;
}
offset ++;
moreData = (data >= 128) ? true : false;
size = (size << 7) | (data & 0x7f); // Take last 7 bits
(*numOfBytes) ++;
}
return size;
}
status_t MPEG4Extractor::parseDrmSINF(
off64_t * /* offset */, off64_t data_offset) {
uint8_t updateIdTag;
if (mDataSource->readAt(data_offset, &updateIdTag, 1) < 1) {
return ERROR_IO;
}
data_offset ++;
if (0x01/*OBJECT_DESCRIPTOR_UPDATE_ID_TAG*/ != updateIdTag) {
return ERROR_MALFORMED;
}
uint8_t numOfBytes;
int32_t size = readSize(data_offset, mDataSource, &numOfBytes);
if (size < 0) {
return ERROR_IO;
}
data_offset += numOfBytes;
while(size >= 11 ) {
uint8_t descriptorTag;
if (mDataSource->readAt(data_offset, &descriptorTag, 1) < 1) {
return ERROR_IO;
}
data_offset ++;
if (0x11/*OBJECT_DESCRIPTOR_ID_TAG*/ != descriptorTag) {
return ERROR_MALFORMED;
}
uint8_t buffer[8];
//ObjectDescriptorID and ObjectDescriptor url flag
if (mDataSource->readAt(data_offset, buffer, 2) < 2) {
return ERROR_IO;
}
data_offset += 2;
if ((buffer[1] >> 5) & 0x0001) { //url flag is set
return ERROR_MALFORMED;
}
if (mDataSource->readAt(data_offset, buffer, 8) < 8) {
return ERROR_IO;
}
data_offset += 8;
if ((0x0F/*ES_ID_REF_TAG*/ != buffer[1])
|| ( 0x0A/*IPMP_DESCRIPTOR_POINTER_ID_TAG*/ != buffer[5])) {
return ERROR_MALFORMED;
}
SINF *sinf = new SINF;
sinf->trackID = U16_AT(&buffer[3]);
sinf->IPMPDescriptorID = buffer[7];
sinf->next = mFirstSINF;
mFirstSINF = sinf;
size -= (8 + 2 + 1);
}
if (size != 0) {
return ERROR_MALFORMED;
}
if (mDataSource->readAt(data_offset, &updateIdTag, 1) < 1) {
return ERROR_IO;
}
data_offset ++;
if(0x05/*IPMP_DESCRIPTOR_UPDATE_ID_TAG*/ != updateIdTag) {
return ERROR_MALFORMED;
}
size = readSize(data_offset, mDataSource, &numOfBytes);
if (size < 0) {
return ERROR_IO;
}
data_offset += numOfBytes;
while (size > 0) {
uint8_t tag;
int32_t dataLen;
if (mDataSource->readAt(data_offset, &tag, 1) < 1) {
return ERROR_IO;
}
data_offset ++;
if (0x0B/*IPMP_DESCRIPTOR_ID_TAG*/ == tag) {
uint8_t id;
dataLen = readSize(data_offset, mDataSource, &numOfBytes);
if (dataLen < 0) {
return ERROR_IO;
} else if (dataLen < 4) {
return ERROR_MALFORMED;
}
data_offset += numOfBytes;
if (mDataSource->readAt(data_offset, &id, 1) < 1) {
return ERROR_IO;
}
data_offset ++;
SINF *sinf = mFirstSINF;
while (sinf && (sinf->IPMPDescriptorID != id)) {
sinf = sinf->next;
}
if (sinf == NULL) {
return ERROR_MALFORMED;
}
sinf->len = dataLen - 3;
sinf->IPMPData = new (std::nothrow) char[sinf->len];
if (sinf->IPMPData == NULL) {
return ERROR_MALFORMED;
}
data_offset += 2;
if (mDataSource->readAt(data_offset, sinf->IPMPData, sinf->len) < sinf->len) {
return ERROR_IO;
}
data_offset += sinf->len;
size -= (dataLen + numOfBytes + 1);
}
}
if (size != 0) {
return ERROR_MALFORMED;
}
return UNKNOWN_ERROR; // Return a dummy error.
}
struct PathAdder {
PathAdder(Vector<uint32_t> *path, uint32_t chunkType)
: mPath(path) {
mPath->push(chunkType);
}
~PathAdder() {
mPath->pop();
}
private:
Vector<uint32_t> *mPath;
PathAdder(const PathAdder &);
PathAdder &operator=(const PathAdder &);
};
static bool underMetaDataPath(const Vector<uint32_t> &path) {
return path.size() >= 5
&& path[0] == FOURCC('m', 'o', 'o', 'v')
&& path[1] == FOURCC('u', 'd', 't', 'a')
&& path[2] == FOURCC('m', 'e', 't', 'a')
&& path[3] == FOURCC('i', 'l', 's', 't');
}
static bool underQTMetaPath(const Vector<uint32_t> &path, int32_t depth) {
return path.size() >= 2
&& path[0] == FOURCC('m', 'o', 'o', 'v')
&& path[1] == FOURCC('m', 'e', 't', 'a')
&& (depth == 2
|| (depth == 3
&& (path[2] == FOURCC('h', 'd', 'l', 'r')
|| path[2] == FOURCC('i', 'l', 's', 't')
|| path[2] == FOURCC('k', 'e', 'y', 's'))));
}
// Given a time in seconds since Jan 1 1904, produce a human-readable string.
static bool convertTimeToDate(int64_t time_1904, String8 *s) {
// delta between mpeg4 time and unix epoch time
static const int64_t delta = (((66 * 365 + 17) * 24) * 3600);
if (time_1904 < INT64_MIN + delta) {
return false;
}
time_t time_1970 = time_1904 - delta;
char tmp[32];
struct tm* tm = gmtime(&time_1970);
if (tm != NULL &&
strftime(tmp, sizeof(tmp), "%Y%m%dT%H%M%S.000Z", tm) > 0) {
s->setTo(tmp);
return true;
}
return false;
}
status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
ALOGV("entering parseChunk %lld/%d", (long long)*offset, depth);
if (*offset < 0) {
ALOGE("b/23540914");
return ERROR_MALFORMED;
}
uint32_t hdr[2];
if (mDataSource->readAt(*offset, hdr, 8) < 8) {
return ERROR_IO;
}
uint64_t chunk_size = ntohl(hdr[0]);
int32_t chunk_type = ntohl(hdr[1]);
off64_t data_offset = *offset + 8;
if (chunk_size == 1) {
if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
return ERROR_IO;
}
chunk_size = ntoh64(chunk_size);
data_offset += 8;
if (chunk_size < 16) {
// The smallest valid chunk is 16 bytes long in this case.
return ERROR_MALFORMED;
}
} else if (chunk_size == 0) {
if (depth == 0) {
// atom extends to end of file
off64_t sourceSize;
if (mDataSource->getSize(&sourceSize) == OK) {
chunk_size = (sourceSize - *offset);
} else {
// XXX could we just pick a "sufficiently large" value here?
ALOGE("atom size is 0, and data source has no size");
return ERROR_MALFORMED;
}
} else {
// not allowed for non-toplevel atoms, skip it
*offset += 4;
return OK;
}
} else if (chunk_size < 8) {
// The smallest valid chunk is 8 bytes long.
ALOGE("invalid chunk size: %" PRIu64, chunk_size);
return ERROR_MALFORMED;
}
char chunk[5];
MakeFourCCString(chunk_type, chunk);
ALOGV("chunk: %s @ %lld, %d", chunk, (long long)*offset, depth);
if (kUseHexDump) {
static const char kWhitespace[] = " ";
const char *indent = &kWhitespace[sizeof(kWhitespace) - 1 - 2 * depth];
printf("%sfound chunk '%s' of size %" PRIu64 "\n", indent, chunk, chunk_size);
char buffer[256];
size_t n = chunk_size;
if (n > sizeof(buffer)) {
n = sizeof(buffer);
}
if (mDataSource->readAt(*offset, buffer, n)
< (ssize_t)n) {
return ERROR_IO;
}
hexdump(buffer, n);
}
PathAdder autoAdder(&mPath, chunk_type);
// (data_offset - *offset) is either 8 or 16
off64_t chunk_data_size = chunk_size - (data_offset - *offset);
if (chunk_data_size < 0) {
ALOGE("b/23540914");
return ERROR_MALFORMED;
}
if (chunk_type != FOURCC('m', 'd', 'a', 't') && chunk_data_size > kMaxAtomSize) {
char errMsg[100];
sprintf(errMsg, "%s atom has size %" PRId64, chunk, chunk_data_size);
ALOGE("%s (b/28615448)", errMsg);
android_errorWriteWithInfoLog(0x534e4554, "28615448", -1, errMsg, strlen(errMsg));
return ERROR_MALFORMED;
}
if (chunk_type != FOURCC('c', 'p', 'r', 't')
&& chunk_type != FOURCC('c', 'o', 'v', 'r')
&& mPath.size() == 5 && underMetaDataPath(mPath)) {
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset;
while (*offset < stop_offset) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
}
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
return OK;
}
switch(chunk_type) {
case FOURCC('m', 'o', 'o', 'v'):
case FOURCC('t', 'r', 'a', 'k'):
case FOURCC('m', 'd', 'i', 'a'):
case FOURCC('m', 'i', 'n', 'f'):
case FOURCC('d', 'i', 'n', 'f'):
case FOURCC('s', 't', 'b', 'l'):
case FOURCC('m', 'v', 'e', 'x'):
case FOURCC('m', 'o', 'o', 'f'):
case FOURCC('t', 'r', 'a', 'f'):
case FOURCC('m', 'f', 'r', 'a'):
case FOURCC('u', 'd', 't', 'a'):
case FOURCC('i', 'l', 's', 't'):
case FOURCC('s', 'i', 'n', 'f'):
case FOURCC('s', 'c', 'h', 'i'):
case FOURCC('e', 'd', 't', 's'):
case FOURCC('w', 'a', 'v', 'e'):
{
if (chunk_type == FOURCC('m', 'o', 'o', 'v') && depth != 0) {
ALOGE("moov: depth %d", depth);
return ERROR_MALFORMED;
}
if (chunk_type == FOURCC('m', 'o', 'o', 'f') && !mMoofFound) {
// store the offset of the first segment
mMoofFound = true;
mMoofOffset = *offset;
}
if (chunk_type == FOURCC('s', 't', 'b', 'l')) {
ALOGV("sampleTable chunk is %" PRIu64 " bytes long.", chunk_size);
if (mDataSource->flags()
& (DataSource::kWantsPrefetching
| DataSource::kIsCachingDataSource)) {
sp<MPEG4DataSource> cachedSource =
new MPEG4DataSource(mDataSource);
if (cachedSource->setCachedRange(*offset, chunk_size) == OK) {
mDataSource = cachedSource;
}
}
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->sampleTable = new SampleTable(mDataSource);
}
bool isTrack = false;
if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
if (depth != 1) {
ALOGE("trak: depth %d", depth);
return ERROR_MALFORMED;
}
isTrack = true;
Track *track = new Track;
track->next = NULL;
if (mLastTrack) {
mLastTrack->next = track;
} else {
mFirstTrack = track;
}
mLastTrack = track;
track->meta = new MetaData;
track->includes_expensive_metadata = false;
track->skipTrack = false;
track->timescale = 0;
track->meta->setCString(kKeyMIMEType, "application/octet-stream");
}
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset;
while (*offset < stop_offset) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
if (isTrack) {
mLastTrack->skipTrack = true;
break;
}
return err;
}
}
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
if (isTrack) {
int32_t trackId;
// There must be exact one track header per track.
if (!mLastTrack->meta->findInt32(kKeyTrackID, &trackId)) {
mLastTrack->skipTrack = true;
}
if (mLastTrack->skipTrack) {
Track *cur = mFirstTrack;
if (cur == mLastTrack) {
delete cur;
mFirstTrack = mLastTrack = NULL;
} else {
while (cur && cur->next != mLastTrack) {
cur = cur->next;
}
cur->next = NULL;
delete mLastTrack;
mLastTrack = cur;
}
return OK;
}
status_t err = verifyTrack(mLastTrack);
if (err != OK) {
return err;
}
} else if (chunk_type == FOURCC('m', 'o', 'o', 'v')) {
mInitCheck = OK;
if (!mIsDrm) {
return UNKNOWN_ERROR; // Return a dummy error.
} else {
return OK;
}
}
break;
}
case FOURCC('e', 'l', 's', 't'):
{
*offset += chunk_size;
// See 14496-12 8.6.6
uint8_t version;
if (mDataSource->readAt(data_offset, &version, 1) < 1) {
return ERROR_IO;
}
uint32_t entry_count;
if (!mDataSource->getUInt32(data_offset + 4, &entry_count)) {
return ERROR_IO;
}
if (entry_count != 1) {
// we only support a single entry at the moment, for gapless playback
ALOGW("ignoring edit list with %d entries", entry_count);
} else if (mHeaderTimescale == 0) {
ALOGW("ignoring edit list because timescale is 0");
} else {
off64_t entriesoffset = data_offset + 8;
uint64_t segment_duration;
int64_t media_time;
if (version == 1) {
if (!mDataSource->getUInt64(entriesoffset, &segment_duration) ||
!mDataSource->getUInt64(entriesoffset + 8, (uint64_t*)&media_time)) {
return ERROR_IO;
}
} else if (version == 0) {
uint32_t sd;
int32_t mt;
if (!mDataSource->getUInt32(entriesoffset, &sd) ||
!mDataSource->getUInt32(entriesoffset + 4, (uint32_t*)&mt)) {
return ERROR_IO;
}
segment_duration = sd;
media_time = mt;
} else {
return ERROR_IO;
}
uint64_t halfscale = mHeaderTimescale / 2;
segment_duration = (segment_duration * 1000000 + halfscale)/ mHeaderTimescale;
media_time = (media_time * 1000000 + halfscale) / mHeaderTimescale;
int64_t duration;
int32_t samplerate;
if (!mLastTrack) {
return ERROR_MALFORMED;
}
if (mLastTrack->meta->findInt64(kKeyDuration, &duration) &&
mLastTrack->meta->findInt32(kKeySampleRate, &samplerate)) {
int64_t delay = (media_time * samplerate + 500000) / 1000000;
mLastTrack->meta->setInt32(kKeyEncoderDelay, delay);
int64_t paddingus = duration - (int64_t)(segment_duration + media_time);
if (paddingus < 0) {
// track duration from media header (which is what kKeyDuration is) might
// be slightly shorter than the segment duration, which would make the
// padding negative. Clamp to zero.
paddingus = 0;
}
int64_t paddingsamples = (paddingus * samplerate + 500000) / 1000000;
mLastTrack->meta->setInt32(kKeyEncoderPadding, paddingsamples);
}
}
break;
}
case FOURCC('f', 'r', 'm', 'a'):
{
*offset += chunk_size;
uint32_t original_fourcc;
if (mDataSource->readAt(data_offset, &original_fourcc, 4) < 4) {
return ERROR_IO;
}
original_fourcc = ntohl(original_fourcc);
ALOGV("read original format: %d", original_fourcc);
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(original_fourcc));
uint32_t num_channels = 0;
uint32_t sample_rate = 0;
if (AdjustChannelsAndRate(original_fourcc, &num_channels, &sample_rate)) {
mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
mLastTrack->meta->setInt32(kKeySampleRate, sample_rate);
}
break;
}
case FOURCC('t', 'e', 'n', 'c'):
{
*offset += chunk_size;
if (chunk_size < 32) {
return ERROR_MALFORMED;
}
// tenc box contains 1 byte version, 3 byte flags, 3 byte default algorithm id, one byte
// default IV size, 16 bytes default KeyID
// (ISO 23001-7)
char buf[4];
memset(buf, 0, 4);
if (mDataSource->readAt(data_offset + 4, buf + 1, 3) < 3) {
return ERROR_IO;
}
uint32_t defaultAlgorithmId = ntohl(*((int32_t*)buf));
if (defaultAlgorithmId > 1) {
// only 0 (clear) and 1 (AES-128) are valid
return ERROR_MALFORMED;
}
memset(buf, 0, 4);
if (mDataSource->readAt(data_offset + 7, buf + 3, 1) < 1) {
return ERROR_IO;
}
uint32_t defaultIVSize = ntohl(*((int32_t*)buf));
if ((defaultAlgorithmId == 0 && defaultIVSize != 0) ||
(defaultAlgorithmId != 0 && defaultIVSize == 0)) {
// only unencrypted data must have 0 IV size
return ERROR_MALFORMED;
} else if (defaultIVSize != 0 &&
defaultIVSize != 8 &&
defaultIVSize != 16) {
// only supported sizes are 0, 8 and 16
return ERROR_MALFORMED;
}
uint8_t defaultKeyId[16];
if (mDataSource->readAt(data_offset + 8, &defaultKeyId, 16) < 16) {
return ERROR_IO;
}
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->meta->setInt32(kKeyCryptoMode, defaultAlgorithmId);
mLastTrack->meta->setInt32(kKeyCryptoDefaultIVSize, defaultIVSize);
mLastTrack->meta->setData(kKeyCryptoKey, 'tenc', defaultKeyId, 16);
break;
}
case FOURCC('t', 'k', 'h', 'd'):
{
*offset += chunk_size;
status_t err;
if ((err = parseTrackHeader(data_offset, chunk_data_size)) != OK) {
return err;
}
break;
}
case FOURCC('p', 's', 's', 'h'):
{
*offset += chunk_size;
PsshInfo pssh;
if (mDataSource->readAt(data_offset + 4, &pssh.uuid, 16) < 16) {
return ERROR_IO;
}
uint32_t psshdatalen = 0;
if (mDataSource->readAt(data_offset + 20, &psshdatalen, 4) < 4) {
return ERROR_IO;
}
pssh.datalen = ntohl(psshdatalen);
ALOGV("pssh data size: %d", pssh.datalen);
if (chunk_size < 20 || pssh.datalen > chunk_size - 20) {
// pssh data length exceeds size of containing box
return ERROR_MALFORMED;
}
pssh.data = new (std::nothrow) uint8_t[pssh.datalen];
if (pssh.data == NULL) {
return ERROR_MALFORMED;
}
ALOGV("allocated pssh @ %p", pssh.data);
ssize_t requested = (ssize_t) pssh.datalen;
if (mDataSource->readAt(data_offset + 24, pssh.data, requested) < requested) {
delete[] pssh.data;
return ERROR_IO;
}
mPssh.push_back(pssh);
break;
}
case FOURCC('m', 'd', 'h', 'd'):
{
*offset += chunk_size;
if (chunk_data_size < 4 || mLastTrack == NULL) {
return ERROR_MALFORMED;
}
uint8_t version;
if (mDataSource->readAt(
data_offset, &version, sizeof(version))
< (ssize_t)sizeof(version)) {
return ERROR_IO;
}
off64_t timescale_offset;
if (version == 1) {
timescale_offset = data_offset + 4 + 16;
} else if (version == 0) {
timescale_offset = data_offset + 4 + 8;
} else {
return ERROR_IO;
}
uint32_t timescale;
if (mDataSource->readAt(
timescale_offset, &timescale, sizeof(timescale))
< (ssize_t)sizeof(timescale)) {
return ERROR_IO;
}
if (!timescale) {
ALOGE("timescale should not be ZERO.");
return ERROR_MALFORMED;
}
mLastTrack->timescale = ntohl(timescale);
// 14496-12 says all ones means indeterminate, but some files seem to use
// 0 instead. We treat both the same.
int64_t duration = 0;
if (version == 1) {
if (mDataSource->readAt(
timescale_offset + 4, &duration, sizeof(duration))
< (ssize_t)sizeof(duration)) {
return ERROR_IO;
}
if (duration != -1) {
duration = ntoh64(duration);
}
} else {
uint32_t duration32;
if (mDataSource->readAt(
timescale_offset + 4, &duration32, sizeof(duration32))
< (ssize_t)sizeof(duration32)) {
return ERROR_IO;
}
if (duration32 != 0xffffffff) {
duration = ntohl(duration32);
}
}
if (duration != 0 && mLastTrack->timescale != 0) {
mLastTrack->meta->setInt64(
kKeyDuration, (duration * 1000000) / mLastTrack->timescale);
}
uint8_t lang[2];
off64_t lang_offset;
if (version == 1) {
lang_offset = timescale_offset + 4 + 8;
} else if (version == 0) {
lang_offset = timescale_offset + 4 + 4;
} else {
return ERROR_IO;
}
if (mDataSource->readAt(lang_offset, &lang, sizeof(lang))
< (ssize_t)sizeof(lang)) {
return ERROR_IO;
}
// To get the ISO-639-2/T three character language code
// 1 bit pad followed by 3 5-bits characters. Each character
// is packed as the difference between its ASCII value and 0x60.
char lang_code[4];
lang_code[0] = ((lang[0] >> 2) & 0x1f) + 0x60;
lang_code[1] = ((lang[0] & 0x3) << 3 | (lang[1] >> 5)) + 0x60;
lang_code[2] = (lang[1] & 0x1f) + 0x60;
lang_code[3] = '\0';
mLastTrack->meta->setCString(
kKeyMediaLanguage, lang_code);
break;
}
case FOURCC('s', 't', 's', 'd'):
{
uint8_t buffer[8];
if (chunk_data_size < (off64_t)sizeof(buffer)) {
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, 8) < 8) {
return ERROR_IO;
}
if (U32_AT(buffer) != 0) {
// Should be version 0, flags 0.
return ERROR_MALFORMED;
}
uint32_t entry_count = U32_AT(&buffer[4]);
if (entry_count > 1) {
// For 3GPP timed text, there could be multiple tx3g boxes contain
// multiple text display formats. These formats will be used to
// display the timed text.
// For encrypted files, there may also be more than one entry.
const char *mime;
if (mLastTrack == NULL)
return ERROR_MALFORMED;
CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP) &&
strcasecmp(mime, "application/octet-stream")) {
// For now we only support a single type of media per track.
mLastTrack->skipTrack = true;
*offset += chunk_size;
break;
}
}
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset + 8;
for (uint32_t i = 0; i < entry_count; ++i) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
}
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
break;
}
case FOURCC('m', 'p', '4', 'a'):
case FOURCC('e', 'n', 'c', 'a'):
case FOURCC('s', 'a', 'm', 'r'):
case FOURCC('s', 'a', 'w', 'b'):
{
if (mIsQT && chunk_type == FOURCC('m', 'p', '4', 'a')
&& depth >= 1 && mPath[depth - 1] == FOURCC('w', 'a', 'v', 'e')) {
// Ignore mp4a embedded in QT wave atom
*offset += chunk_size;
break;
}
uint8_t buffer[8 + 20];
if (chunk_data_size < (ssize_t)sizeof(buffer)) {
// Basic AudioSampleEntry size.
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
uint16_t data_ref_index __unused = U16_AT(&buffer[6]);
uint16_t version = U16_AT(&buffer[8]);
uint32_t num_channels = U16_AT(&buffer[16]);
uint16_t sample_size = U16_AT(&buffer[18]);
uint32_t sample_rate = U32_AT(&buffer[24]) >> 16;
if (mLastTrack == NULL)
return ERROR_MALFORMED;
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset + sizeof(buffer);
if (mIsQT && chunk_type == FOURCC('m', 'p', '4', 'a')) {
if (version == 1) {
if (mDataSource->readAt(*offset, buffer, 16) < 16) {
return ERROR_IO;
}
#if 0
U32_AT(buffer); // samples per packet
U32_AT(&buffer[4]); // bytes per packet
U32_AT(&buffer[8]); // bytes per frame
U32_AT(&buffer[12]); // bytes per sample
#endif
*offset += 16;
} else if (version == 2) {
uint8_t v2buffer[36];
if (mDataSource->readAt(*offset, v2buffer, 36) < 36) {
return ERROR_IO;
}
#if 0
U32_AT(v2buffer); // size of struct only
sample_rate = (uint32_t)U64_AT(&v2buffer[4]); // audio sample rate
num_channels = U32_AT(&v2buffer[12]); // num audio channels
U32_AT(&v2buffer[16]); // always 0x7f000000
sample_size = (uint16_t)U32_AT(&v2buffer[20]); // const bits per channel
U32_AT(&v2buffer[24]); // format specifc flags
U32_AT(&v2buffer[28]); // const bytes per audio packet
U32_AT(&v2buffer[32]); // const LPCM frames per audio packet
#endif
*offset += 36;
}
}
if (chunk_type != FOURCC('e', 'n', 'c', 'a')) {
// if the chunk type is enca, we'll get the type from the sinf/frma box later
mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
AdjustChannelsAndRate(chunk_type, &num_channels, &sample_rate);
}
ALOGV("*** coding='%s' %d channels, size %d, rate %d\n",
chunk, num_channels, sample_size, sample_rate);
mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
mLastTrack->meta->setInt32(kKeySampleRate, sample_rate);
while (*offset < stop_offset) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
}
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
break;
}
case FOURCC('m', 'p', '4', 'v'):
case FOURCC('e', 'n', 'c', 'v'):
case FOURCC('s', '2', '6', '3'):
case FOURCC('H', '2', '6', '3'):
case FOURCC('h', '2', '6', '3'):
case FOURCC('a', 'v', 'c', '1'):
case FOURCC('h', 'v', 'c', '1'):
case FOURCC('h', 'e', 'v', '1'):
{
uint8_t buffer[78];
if (chunk_data_size < (ssize_t)sizeof(buffer)) {
// Basic VideoSampleEntry size.
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
return ERROR_IO;
}
uint16_t data_ref_index __unused = U16_AT(&buffer[6]);
uint16_t width = U16_AT(&buffer[6 + 18]);
uint16_t height = U16_AT(&buffer[6 + 20]);
// The video sample is not standard-compliant if it has invalid dimension.
// Use some default width and height value, and
// let the decoder figure out the actual width and height (and thus
// be prepared for INFO_FOMRAT_CHANGED event).
if (width == 0) width = 352;
if (height == 0) height = 288;
// printf("*** coding='%s' width=%d height=%d\n",
// chunk, width, height);
if (mLastTrack == NULL)
return ERROR_MALFORMED;
if (chunk_type != FOURCC('e', 'n', 'c', 'v')) {
// if the chunk type is encv, we'll get the type from the sinf/frma box later
mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
}
mLastTrack->meta->setInt32(kKeyWidth, width);
mLastTrack->meta->setInt32(kKeyHeight, height);
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset + sizeof(buffer);
while (*offset < stop_offset) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
}
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
break;
}
case FOURCC('s', 't', 'c', 'o'):
case FOURCC('c', 'o', '6', '4'):
{
if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL))
return ERROR_MALFORMED;
status_t err =
mLastTrack->sampleTable->setChunkOffsetParams(
chunk_type, data_offset, chunk_data_size);
*offset += chunk_size;
if (err != OK) {
return err;
}
break;
}
case FOURCC('s', 't', 's', 'c'):
{
if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL))
return ERROR_MALFORMED;
status_t err =
mLastTrack->sampleTable->setSampleToChunkParams(
data_offset, chunk_data_size);
*offset += chunk_size;
if (err != OK) {
return err;
}
break;
}
case FOURCC('s', 't', 's', 'z'):
case FOURCC('s', 't', 'z', '2'):
{
if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL))
return ERROR_MALFORMED;
status_t err =
mLastTrack->sampleTable->setSampleSizeParams(
chunk_type, data_offset, chunk_data_size);
*offset += chunk_size;
if (err != OK) {
return err;
}
size_t max_size;
err = mLastTrack->sampleTable->getMaxSampleSize(&max_size);
if (err != OK) {
return err;
}
if (max_size != 0) {
// Assume that a given buffer only contains at most 10 chunks,
// each chunk originally prefixed with a 2 byte length will
// have a 4 byte header (0x00 0x00 0x00 0x01) after conversion,
// and thus will grow by 2 bytes per chunk.
if (max_size > SIZE_MAX - 10 * 2) {
ALOGE("max sample size too big: %zu", max_size);
return ERROR_MALFORMED;
}
mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size + 10 * 2);
} else {
// No size was specified. Pick a conservatively large size.
uint32_t width, height;
if (!mLastTrack->meta->findInt32(kKeyWidth, (int32_t*)&width) ||
!mLastTrack->meta->findInt32(kKeyHeight,(int32_t*) &height)) {
ALOGE("No width or height, assuming worst case 1080p");
width = 1920;
height = 1080;
} else {
// A resolution was specified, check that it's not too big. The values below
// were chosen so that the calculations below don't cause overflows, they're
// not indicating that resolutions up to 32kx32k are actually supported.
if (width > 32768 || height > 32768) {
ALOGE("can't support %u x %u video", width, height);
return ERROR_MALFORMED;
}
}
const char *mime;
CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
if (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)
|| !strcmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) {
// AVC & HEVC requires compression ratio of at least 2, and uses
// macroblocks
max_size = ((width + 15) / 16) * ((height + 15) / 16) * 192;
} else {
// For all other formats there is no minimum compression
// ratio. Use compression ratio of 1.
max_size = width * height * 3 / 2;
}
mLastTrack->meta->setInt32(kKeyMaxInputSize, max_size);
}
// NOTE: setting another piece of metadata invalidates any pointers (such as the
// mimetype) previously obtained, so don't cache them.
const char *mime;
CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
// Calculate average frame rate.
if (!strncasecmp("video/", mime, 6)) {
size_t nSamples = mLastTrack->sampleTable->countSamples();
if (nSamples == 0) {
int32_t trackId;
if (mLastTrack->meta->findInt32(kKeyTrackID, &trackId)) {
for (size_t i = 0; i < mTrex.size(); i++) {
Trex *t = &mTrex.editItemAt(i);
if (t->track_ID == (uint32_t) trackId) {
if (t->default_sample_duration > 0) {
int32_t frameRate =
mLastTrack->timescale / t->default_sample_duration;
mLastTrack->meta->setInt32(kKeyFrameRate, frameRate);
}
break;
}
}
}
} else {
int64_t durationUs;
if (mLastTrack->meta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > 0) {
int32_t frameRate = (nSamples * 1000000LL +
(durationUs >> 1)) / durationUs;
mLastTrack->meta->setInt32(kKeyFrameRate, frameRate);
}
}
}
}
break;
}
case FOURCC('s', 't', 't', 's'):
{
if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL))
return ERROR_MALFORMED;
*offset += chunk_size;
status_t err =
mLastTrack->sampleTable->setTimeToSampleParams(
data_offset, chunk_data_size);
if (err != OK) {
return err;
}
break;
}
case FOURCC('c', 't', 't', 's'):
{
if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL))
return ERROR_MALFORMED;
*offset += chunk_size;
status_t err =
mLastTrack->sampleTable->setCompositionTimeToSampleParams(
data_offset, chunk_data_size);
if (err != OK) {
return err;
}
break;
}
case FOURCC('s', 't', 's', 's'):
{
if ((mLastTrack == NULL) || (mLastTrack->sampleTable == NULL))
return ERROR_MALFORMED;
*offset += chunk_size;
status_t err =
mLastTrack->sampleTable->setSyncSampleParams(
data_offset, chunk_data_size);
if (err != OK) {
return err;
}
break;
}
// \xA9xyz
case FOURCC(0xA9, 'x', 'y', 'z'):
{
*offset += chunk_size;
// Best case the total data length inside "\xA9xyz" box
// would be 8, for instance "\xA9xyz" + "\x00\x04\x15\xc7" + "0+0/",
// where "\x00\x04" is the text string length with value = 4,
// "\0x15\xc7" is the language code = en, and "0+0" is a
// location (string) value with longitude = 0 and latitude = 0.
if (chunk_data_size < 8) {
return ERROR_MALFORMED;
}
// Worst case the location string length would be 18,
// for instance +90.0000-180.0000, without the trailing "/" and
// the string length + language code, and some devices include
// an additional 8 bytes of altitude, e.g. +007.186
char buffer[18 + 8];
// Substracting 5 from the data size is because the text string length +
// language code takes 4 bytes, and the trailing slash "/" takes 1 byte.
off64_t location_length = chunk_data_size - 5;
if (location_length >= (off64_t) sizeof(buffer)) {
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset + 4, buffer, location_length) < location_length) {
return ERROR_IO;
}
buffer[location_length] = '\0';
mFileMetaData->setCString(kKeyLocation, buffer);
break;
}
case FOURCC('e', 's', 'd', 's'):
{
*offset += chunk_size;
if (chunk_data_size < 4) {
return ERROR_MALFORMED;
}
uint8_t buffer[256];
if (chunk_data_size > (off64_t)sizeof(buffer)) {
return ERROR_BUFFER_TOO_SMALL;
}
if (mDataSource->readAt(
data_offset, buffer, chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
if (U32_AT(buffer) != 0) {
// Should be version 0, flags 0.
return ERROR_MALFORMED;
}
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->meta->setData(
kKeyESDS, kTypeESDS, &buffer[4], chunk_data_size - 4);
if (mPath.size() >= 2
&& mPath[mPath.size() - 2] == FOURCC('m', 'p', '4', 'a')) {
// Information from the ESDS must be relied on for proper
// setup of sample rate and channel count for MPEG4 Audio.
// The generic header appears to only contain generic
// information...
status_t err = updateAudioTrackInfoFromESDS_MPEG4Audio(
&buffer[4], chunk_data_size - 4);
if (err != OK) {
return err;
}
}
if (mPath.size() >= 2
&& mPath[mPath.size() - 2] == FOURCC('m', 'p', '4', 'v')) {
// Check if the video is MPEG2
ESDS esds(&buffer[4], chunk_data_size - 4);
uint8_t objectTypeIndication;
if (esds.getObjectTypeIndication(&objectTypeIndication) == OK) {
if (objectTypeIndication >= 0x60 && objectTypeIndication <= 0x65) {
mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG2);
}
}
}
break;
}
case FOURCC('b', 't', 'r', 't'):
{
*offset += chunk_size;
if (mLastTrack == NULL) {
return ERROR_MALFORMED;
}
uint8_t buffer[12];
if (chunk_data_size != sizeof(buffer)) {
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
uint32_t maxBitrate = U32_AT(&buffer[4]);
uint32_t avgBitrate = U32_AT(&buffer[8]);
if (maxBitrate > 0 && maxBitrate < INT32_MAX) {
mLastTrack->meta->setInt32(kKeyMaxBitRate, (int32_t)maxBitrate);
}
if (avgBitrate > 0 && avgBitrate < INT32_MAX) {
mLastTrack->meta->setInt32(kKeyBitRate, (int32_t)avgBitrate);
}
break;
}
case FOURCC('a', 'v', 'c', 'C'):
{
*offset += chunk_size;
sp<ABuffer> buffer = new ABuffer(chunk_data_size);
if (buffer->data() == NULL) {
ALOGE("b/28471206");
return NO_MEMORY;
}
if (mDataSource->readAt(
data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->meta->setData(
kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
break;
}
case FOURCC('h', 'v', 'c', 'C'):
{
sp<ABuffer> buffer = new ABuffer(chunk_data_size);
if (buffer->data() == NULL) {
ALOGE("b/28471206");
return NO_MEMORY;
}
if (mDataSource->readAt(
data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->meta->setData(
kKeyHVCC, kTypeHVCC, buffer->data(), chunk_data_size);
*offset += chunk_size;
break;
}
case FOURCC('d', '2', '6', '3'):
{
*offset += chunk_size;
/*
* d263 contains a fixed 7 bytes part:
* vendor - 4 bytes
* version - 1 byte
* level - 1 byte
* profile - 1 byte
* optionally, "d263" box itself may contain a 16-byte
* bit rate box (bitr)
* average bit rate - 4 bytes
* max bit rate - 4 bytes
*/
char buffer[23];
if (chunk_data_size != 7 &&
chunk_data_size != 23) {
ALOGE("Incorrect D263 box size %lld", (long long)chunk_data_size);
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
if (mLastTrack == NULL)
return ERROR_MALFORMED;
mLastTrack->meta->setData(kKeyD263, kTypeD263, buffer, chunk_data_size);
break;
}
case FOURCC('m', 'e', 't', 'a'):
{
off64_t stop_offset = *offset + chunk_size;
*offset = data_offset;
bool isParsingMetaKeys = underQTMetaPath(mPath, 2);
if (!isParsingMetaKeys) {
uint8_t buffer[4];
if (chunk_data_size < (off64_t)sizeof(buffer)) {
*offset = stop_offset;
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
data_offset, buffer, 4) < 4) {
*offset = stop_offset;
return ERROR_IO;
}
if (U32_AT(buffer) != 0) {
// Should be version 0, flags 0.
// If it's not, let's assume this is one of those
// apparently malformed chunks that don't have flags
// and completely different semantics than what's
// in the MPEG4 specs and skip it.
*offset = stop_offset;
return OK;
}
*offset += sizeof(buffer);
}
while (*offset < stop_offset) {
status_t err = parseChunk(offset, depth + 1);
if (err != OK) {
return err;
}
}
if (*offset != stop_offset) {
return ERROR_MALFORMED;
}
break;
}
case FOURCC('m', 'e', 'a', 'n'):
case FOURCC('n', 'a', 'm', 'e'):
case FOURCC('d', 'a', 't', 'a'):
{
*offset += chunk_size;
if (mPath.size() == 6 && underMetaDataPath(mPath)) {
status_t err = parseITunesMetaData(data_offset, chunk_data_size);
if (err != OK) {
return err;
}
}
break;
}
case FOURCC('m', 'v', 'h', 'd'):
{
*offset += chunk_size;
if (depth != 1) {
ALOGE("mvhd: depth %d", depth);
return ERROR_MALFORMED;
}
if (chunk_data_size < 32) {
return ERROR_MALFORMED;
}
uint8_t header[32];
if (mDataSource->readAt(
data_offset, header, sizeof(header))
< (ssize_t)sizeof(header)) {
return ERROR_IO;
}
uint64_t creationTime;
uint64_t duration = 0;
if (header[0] == 1) {
creationTime = U64_AT(&header[4]);
mHeaderTimescale = U32_AT(&header[20]);
duration = U64_AT(&header[24]);
if (duration == 0xffffffffffffffff) {
duration = 0;
}
} else if (header[0] != 0) {
return ERROR_MALFORMED;
} else {
creationTime = U32_AT(&header[4]);
mHeaderTimescale = U32_AT(&header[12]);
uint32_t d32 = U32_AT(&header[16]);
if (d32 == 0xffffffff) {
d32 = 0;
}
duration = d32;
}
if (duration != 0 && mHeaderTimescale != 0 && duration < UINT64_MAX / 1000000) {
mFileMetaData->setInt64(kKeyDuration, duration * 1000000 / mHeaderTimescale);
}
String8 s;
if (convertTimeToDate(creationTime, &s)) {
mFileMetaData->setCString(kKeyDate, s.string());
}
break;
}
case FOURCC('m', 'e', 'h', 'd'):
{
*offset += chunk_size;
if (chunk_data_size < 8) {
return ERROR_MALFORMED;
}
uint8_t flags[4];
if (mDataSource->readAt(
data_offset, flags, sizeof(flags))
< (ssize_t)sizeof(flags)) {
return ERROR_IO;
}
uint64_t duration = 0;
if (flags[0] == 1) {
// 64 bit
if (chunk_data_size < 12) {
return ERROR_MALFORMED;
}
mDataSource->getUInt64(data_offset + 4, &duration);
if (duration == 0xffffffffffffffff) {
duration = 0;
}
} else if (flags[0] == 0) {
// 32 bit
uint32_t d32;
mDataSource->getUInt32(data_offset + 4, &d32);
if (d32 == 0xffffffff) {
d32 = 0;
}
duration = d32;
} else {
return ERROR_MALFORMED;
}
if (duration != 0 && mHeaderTimescale != 0) {
mFileMetaData->setInt64(kKeyDuration, duration * 1000000 / mHeaderTimescale);
}
break;
}
case FOURCC('m', 'd', 'a', 't'):
{
ALOGV("mdat chunk, drm: %d", mIsDrm);
mMdatFound = true;
if (!mIsDrm) {
*offset += chunk_size;
break;
}
if (chunk_size < 8) {
return ERROR_MALFORMED;
}
return parseDrmSINF(offset, data_offset);
}
case FOURCC('h', 'd', 'l', 'r'):
{
*offset += chunk_size;
if (underQTMetaPath(mPath, 3)) {
break;
}
uint32_t buffer;
if (mDataSource->readAt(
data_offset + 8, &buffer, 4) < 4) {
return ERROR_IO;
}
uint32_t type = ntohl(buffer);
// For the 3GPP file format, the handler-type within the 'hdlr' box
// shall be 'text'. We also want to support 'sbtl' handler type
// for a practical reason as various MPEG4 containers use it.
if (type == FOURCC('t', 'e', 'x', 't') || type == FOURCC('s', 'b', 't', 'l')) {
if (mLastTrack != NULL) {
mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP);
}
}
break;
}
case FOURCC('k', 'e', 'y', 's'):
{
*offset += chunk_size;
if (underQTMetaPath(mPath, 3)) {
status_t err = parseQTMetaKey(data_offset, chunk_data_size);
if (err != OK) {
return err;
}
}
break;
}
case FOURCC('t', 'r', 'e', 'x'):
{
*offset += chunk_size;
if (chunk_data_size < 24) {
return ERROR_IO;
}
Trex trex;
if (!mDataSource->getUInt32(data_offset + 4, &trex.track_ID) ||
!mDataSource->getUInt32(data_offset + 8, &trex.default_sample_description_index) ||
!mDataSource->getUInt32(data_offset + 12, &trex.default_sample_duration) ||
!mDataSource->getUInt32(data_offset + 16, &trex.default_sample_size) ||
!mDataSource->getUInt32(data_offset + 20, &trex.default_sample_flags)) {
return ERROR_IO;
}
mTrex.add(trex);
break;
}
case FOURCC('t', 'x', '3', 'g'):
{
if (mLastTrack == NULL)
return ERROR_MALFORMED;
uint32_t type;
const void *data;
size_t size = 0;
if (!mLastTrack->meta->findData(
kKeyTextFormatData, &type, &data, &size)) {
size = 0;
}
if ((chunk_size > SIZE_MAX) || (SIZE_MAX - chunk_size <= size)) {
return ERROR_MALFORMED;
}
uint8_t *buffer = new (std::nothrow) uint8_t[size + chunk_size];
if (buffer == NULL) {
return ERROR_MALFORMED;
}
if (size > 0) {
memcpy(buffer, data, size);
}
if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
< chunk_size) {
delete[] buffer;
buffer = NULL;
// advance read pointer so we don't end up reading this again
*offset += chunk_size;
return ERROR_IO;
}
mLastTrack->meta->setData(
kKeyTextFormatData, 0, buffer, size + chunk_size);
delete[] buffer;
*offset += chunk_size;
break;
}
case FOURCC('c', 'o', 'v', 'r'):
{
*offset += chunk_size;
if (mFileMetaData != NULL) {
ALOGV("chunk_data_size = %" PRId64 " and data_offset = %" PRId64,
chunk_data_size, data_offset);
if (chunk_data_size < 0 || static_cast<uint64_t>(chunk_data_size) >= SIZE_MAX - 1) {
return ERROR_MALFORMED;
}
sp<ABuffer> buffer = new ABuffer(chunk_data_size + 1);
if (buffer->data() == NULL) {
ALOGE("b/28471206");
return NO_MEMORY;
}
if (mDataSource->readAt(
data_offset, buffer->data(), chunk_data_size) != (ssize_t)chunk_data_size) {
return ERROR_IO;
}
const int kSkipBytesOfDataBox = 16;
if (chunk_data_size <= kSkipBytesOfDataBox) {
return ERROR_MALFORMED;
}
mFileMetaData->setData(
kKeyAlbumArt, MetaData::TYPE_NONE,
buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
}
break;
}
case FOURCC('c', 'o', 'l', 'r'):
{
*offset += chunk_size;
// this must be in a VisualSampleEntry box under the Sample Description Box ('stsd')
// ignore otherwise
if (depth >= 2 && mPath[depth - 2] == FOURCC('s', 't', 's', 'd')) {
status_t err = parseColorInfo(data_offset, chunk_data_size);
if (err != OK) {
return err;
}
}
break;
}
case FOURCC('t', 'i', 't', 'l'):
case FOURCC('p', 'e', 'r', 'f'):
case FOURCC('a', 'u', 't', 'h'):
case FOURCC('g', 'n', 'r', 'e'):
case FOURCC('a', 'l', 'b', 'm'):
case FOURCC('y', 'r', 'r', 'c'):
{
*offset += chunk_size;
status_t err = parse3GPPMetaData(data_offset, chunk_data_size, depth);
if (err != OK) {
return err;
}
break;
}
case FOURCC('I', 'D', '3', '2'):
{
*offset += chunk_size;
if (chunk_data_size < 6) {
return ERROR_MALFORMED;
}
parseID3v2MetaData(data_offset + 6);
break;
}
case FOURCC('-', '-', '-', '-'):
{
mLastCommentMean.clear();
mLastCommentName.clear();
mLastCommentData.clear();
*offset += chunk_size;
break;
}
case FOURCC('s', 'i', 'd', 'x'):
{
status_t err = parseSegmentIndex(data_offset, chunk_data_size);
if (err != OK) {
return err;
}
*offset += chunk_size;
return UNKNOWN_ERROR; // stop parsing after sidx
}
case FOURCC('f', 't', 'y', 'p'):
{
if (chunk_data_size < 8 || depth != 0) {
return ERROR_MALFORMED;
}
off64_t stop_offset = *offset + chunk_size;
uint32_t numCompatibleBrands = (chunk_data_size - 8) / 4;
for (size_t i = 0; i < numCompatibleBrands + 2; ++i) {
if (i == 1) {
// Skip this index, it refers to the minorVersion,
// not a brand.
continue;
}
uint32_t brand;
if (mDataSource->readAt(data_offset + 4 * i, &brand, 4) < 4) {
return ERROR_MALFORMED;
}
brand = ntohl(brand);
if (brand == FOURCC('q', 't', ' ', ' ')) {
mIsQT = true;
break;
}
}
*offset = stop_offset;
break;
}
default:
{
// check if we're parsing 'ilst' for meta keys
// if so, treat type as a number (key-id).
if (underQTMetaPath(mPath, 3)) {
status_t err = parseQTMetaVal(chunk_type, data_offset, chunk_data_size);
if (err != OK) {
return err;
}
}
*offset += chunk_size;
break;
}
}
return OK;
}
status_t MPEG4Extractor::parseSegmentIndex(off64_t offset, size_t size) {
ALOGV("MPEG4Extractor::parseSegmentIndex");
if (size < 12) {
return -EINVAL;
}
uint32_t flags;
if (!mDataSource->getUInt32(offset, &flags)) {
return ERROR_MALFORMED;
}
uint32_t version = flags >> 24;
flags &= 0xffffff;
ALOGV("sidx version %d", version);
uint32_t referenceId;
if (!mDataSource->getUInt32(offset + 4, &referenceId)) {
return ERROR_MALFORMED;
}
uint32_t timeScale;
if (!mDataSource->getUInt32(offset + 8, &timeScale)) {
return ERROR_MALFORMED;
}
ALOGV("sidx refid/timescale: %d/%d", referenceId, timeScale);
if (timeScale == 0)
return ERROR_MALFORMED;
uint64_t earliestPresentationTime;
uint64_t firstOffset;
offset += 12;
size -= 12;
if (version == 0) {
if (size < 8) {
return -EINVAL;
}
uint32_t tmp;
if (!mDataSource->getUInt32(offset, &tmp)) {
return ERROR_MALFORMED;
}
earliestPresentationTime = tmp;
if (!mDataSource->getUInt32(offset + 4, &tmp)) {
return ERROR_MALFORMED;
}
firstOffset = tmp;
offset += 8;
size -= 8;
} else {
if (size < 16) {
return -EINVAL;
}
if (!mDataSource->getUInt64(offset, &earliestPresentationTime)) {
return ERROR_MALFORMED;
}
if (!mDataSource->getUInt64(offset + 8, &firstOffset)) {
return ERROR_MALFORMED;
}
offset += 16;
size -= 16;
}
ALOGV("sidx pres/off: %" PRIu64 "/%" PRIu64, earliestPresentationTime, firstOffset);
if (size < 4) {
return -EINVAL;
}
uint16_t referenceCount;
if (!mDataSource->getUInt16(offset + 2, &referenceCount)) {
return ERROR_MALFORMED;
}
offset += 4;
size -= 4;
ALOGV("refcount: %d", referenceCount);
if (size < referenceCount * 12) {
return -EINVAL;
}
uint64_t total_duration = 0;
for (unsigned int i = 0; i < referenceCount; i++) {
uint32_t d1, d2, d3;
if (!mDataSource->getUInt32(offset, &d1) || // size
!mDataSource->getUInt32(offset + 4, &d2) || // duration
!mDataSource->getUInt32(offset + 8, &d3)) { // flags
return ERROR_MALFORMED;
}
if (d1 & 0x80000000) {
ALOGW("sub-sidx boxes not supported yet");
}
bool sap = d3 & 0x80000000;
uint32_t saptype = (d3 >> 28) & 7;
if (!sap || (saptype != 1 && saptype != 2)) {
// type 1 and 2 are sync samples
ALOGW("not a stream access point, or unsupported type: %08x", d3);
}
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;
mSidxEntries.add(se);
}
uint64_t sidxDuration = total_duration * 1000000 / timeScale;
if (mLastTrack == NULL)
return ERROR_MALFORMED;
int64_t metaDuration;
if (!mLastTrack->meta->findInt64(kKeyDuration, &metaDuration) || metaDuration == 0) {
mLastTrack->meta->setInt64(kKeyDuration, sidxDuration);
}
return OK;
}
status_t MPEG4Extractor::parseQTMetaKey(off64_t offset, size_t size) {
if (size < 8) {
return ERROR_MALFORMED;
}
uint32_t count;
if (!mDataSource->getUInt32(offset + 4, &count)) {
return ERROR_MALFORMED;
}
if (mMetaKeyMap.size() > 0) {
ALOGW("'keys' atom seen again, discarding existing entries");
mMetaKeyMap.clear();
}
off64_t keyOffset = offset + 8;
off64_t stopOffset = offset + size;
for (size_t i = 1; i <= count; i++) {
if (keyOffset + 8 > stopOffset) {
return ERROR_MALFORMED;
}
uint32_t keySize;
if (!mDataSource->getUInt32(keyOffset, &keySize)
|| keySize < 8
|| keyOffset + keySize > stopOffset) {
return ERROR_MALFORMED;
}
uint32_t type;
if (!mDataSource->getUInt32(keyOffset + 4, &type)
|| type != FOURCC('m', 'd', 't', 'a')) {
return ERROR_MALFORMED;
}
keySize -= 8;
keyOffset += 8;
sp<ABuffer> keyData = new ABuffer(keySize);
if (keyData->data() == NULL) {
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
keyOffset, keyData->data(), keySize) < (ssize_t) keySize) {
return ERROR_MALFORMED;
}
AString key((const char *)keyData->data(), keySize);
mMetaKeyMap.add(i, key);
keyOffset += keySize;
}
return OK;
}
status_t MPEG4Extractor::parseQTMetaVal(
int32_t keyId, off64_t offset, size_t size) {
ssize_t index = mMetaKeyMap.indexOfKey(keyId);
if