Make extractors separate libraries
instead of being integrated into libstagefright
Test: thoroughly
Change-Id: I11cdfde6d2daf248c8e0f99237db1c74be260eb0
diff --git a/media/extractors/mp4/Android.bp b/media/extractors/mp4/Android.bp
new file mode 100644
index 0000000..a957e98
--- /dev/null
+++ b/media/extractors/mp4/Android.bp
@@ -0,0 +1,48 @@
+cc_library_shared {
+
+ srcs: [
+ "ItemTable.cpp",
+ "MPEG4Extractor.cpp",
+ "SampleIterator.cpp",
+ "SampleTable.cpp",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright/",
+ ],
+
+ shared_libs: [
+ "libstagefright",
+ "libmedia",
+ "libstagefright_foundation",
+ "libutils",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libstagefright_id3",
+ ],
+
+ name: "libmp4extractor",
+ relative_install_path: "extractors",
+
+ compile_multilib: "first",
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-fvisibility=hidden",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ diag: {
+ cfi: true,
+ },
+ },
+
+}
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
new file mode 100644
index 0000000..558a2bc
--- /dev/null
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -0,0 +1,1553 @@
+/*
+ * Copyright (C) 2017 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 "ItemTable"
+
+#include <ItemTable.h>
+#include <media/MediaDefs.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <utils/Log.h>
+
+namespace android {
+
+namespace heif {
+
+/////////////////////////////////////////////////////////////////////
+//
+// struct to keep track of one image item
+//
+
+struct ImageItem {
+ friend struct ItemReference;
+ friend struct ItemProperty;
+
+ ImageItem() : ImageItem(0) {}
+ ImageItem(uint32_t _type) : type(_type),
+ rows(0), columns(0), width(0), height(0), rotation(0),
+ offset(0), size(0), nextTileIndex(0) {}
+
+ bool isGrid() const {
+ return type == FOURCC('g', 'r', 'i', 'd');
+ }
+
+ status_t getNextTileItemId(uint32_t *nextTileItemId, bool reset) {
+ if (reset) {
+ nextTileIndex = 0;
+ }
+ if (nextTileIndex >= dimgRefs.size()) {
+ return ERROR_END_OF_STREAM;
+ }
+ *nextTileItemId = dimgRefs[nextTileIndex++];
+ return OK;
+ }
+
+ uint32_t type;
+ int32_t rows;
+ int32_t columns;
+ int32_t width;
+ int32_t height;
+ int32_t rotation;
+ off64_t offset;
+ size_t size;
+ sp<ABuffer> hvcc;
+ sp<ABuffer> icc;
+
+ Vector<uint32_t> thumbnails;
+ Vector<uint32_t> dimgRefs;
+ size_t nextTileIndex;
+};
+
+
+/////////////////////////////////////////////////////////////////////
+//
+// ISO boxes
+//
+
+struct Box {
+protected:
+ Box(const sp<DataSource> source, uint32_t type) :
+ mDataSource(source), mType(type) {}
+
+ virtual ~Box() {}
+
+ virtual status_t onChunkData(
+ uint32_t /*type*/, off64_t /*offset*/, size_t /*size*/) {
+ return OK;
+ }
+
+ inline uint32_t type() const { return mType; }
+
+ inline sp<DataSource> source() const { return mDataSource; }
+
+ status_t parseChunk(off64_t *offset);
+
+ status_t parseChunks(off64_t offset, size_t size);
+
+private:
+ sp<DataSource> mDataSource;
+ uint32_t mType;
+};
+
+status_t Box::parseChunk(off64_t *offset) {
+ 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) {
+ // This shouldn't happen since we should never be top level
+ ALOGE("invalid chunk size 0 for non-top level box");
+ return ERROR_MALFORMED;
+ } else if (chunk_size < 8) {
+ // The smallest valid chunk is 8 bytes long.
+ ALOGE("invalid chunk size: %lld", (long long)chunk_size);
+ return ERROR_MALFORMED;
+ }
+
+ char chunk[5];
+ MakeFourCCString(chunk_type, chunk);
+ ALOGV("chunk: %s @ %lld", chunk, (long long)*offset);
+
+ off64_t chunk_data_size = chunk_size - (data_offset - *offset);
+ if (chunk_data_size < 0) {
+ ALOGE("b/23540914");
+ return ERROR_MALFORMED;
+ }
+
+ status_t err = onChunkData(chunk_type, data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+ *offset += chunk_size;
+ return OK;
+}
+
+status_t Box::parseChunks(off64_t offset, size_t size) {
+ off64_t stopOffset = offset + size;
+ while (offset < stopOffset) {
+ status_t err = parseChunk(&offset);
+ if (err != OK) {
+ return err;
+ }
+ }
+ if (offset != stopOffset) {
+ return ERROR_MALFORMED;
+ }
+ return OK;
+}
+
+///////////////////////////////////////////////////////////////////////
+
+struct FullBox : public Box {
+protected:
+ FullBox(const sp<DataSource> source, uint32_t type) :
+ Box(source, type), mVersion(0), mFlags(0) {}
+
+ inline uint8_t version() const { return mVersion; }
+
+ inline uint32_t flags() const { return mFlags; }
+
+ status_t parseFullBoxHeader(off64_t *offset, size_t *size);
+
+private:
+ uint8_t mVersion;
+ uint32_t mFlags;
+};
+
+status_t FullBox::parseFullBoxHeader(off64_t *offset, size_t *size) {
+ if (*size < 4) {
+ return ERROR_MALFORMED;
+ }
+ if (!source()->readAt(*offset, &mVersion, 1)) {
+ return ERROR_IO;
+ }
+ if (!source()->getUInt24(*offset + 1, &mFlags)) {
+ return ERROR_IO;
+ }
+ *offset += 4;
+ *size -= 4;
+ return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// PrimaryImage box
+//
+
+struct PitmBox : public FullBox {
+ PitmBox(const sp<DataSource> source) :
+ FullBox(source, FOURCC('p', 'i', 't', 'm')) {}
+
+ status_t parse(off64_t offset, size_t size, uint32_t *primaryItemId);
+};
+
+status_t PitmBox::parse(off64_t offset, size_t size, uint32_t *primaryItemId) {
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+
+ size_t itemIdSize = (version() == 0) ? 2 : 4;
+ if (size < itemIdSize) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t itemId;
+ if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
+ return ERROR_IO;
+ }
+
+ ALOGV("primary id %d", itemId);
+ *primaryItemId = itemId;
+
+ return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// ItemLocation related boxes
+//
+
+struct ExtentEntry {
+ uint64_t extentIndex;
+ uint64_t extentOffset;
+ uint64_t extentLength;
+};
+
+struct ItemLoc {
+ ItemLoc() : ItemLoc(0, 0, 0, 0) {}
+ ItemLoc(uint32_t item_id, uint16_t construction_method,
+ uint16_t data_reference_index, uint64_t base_offset) :
+ itemId(item_id),
+ constructionMethod(construction_method),
+ dataReferenceIndex(data_reference_index),
+ baseOffset(base_offset) {}
+
+ void addExtent(const ExtentEntry& extent) {
+ extents.push_back(extent);
+ }
+
+ status_t getLoc(off64_t *offset, size_t *size,
+ off64_t idatOffset, size_t idatSize) const {
+ // TODO: fix extent handling, fix constructionMethod = 2
+ CHECK(extents.size() == 1);
+ if (constructionMethod == 0) {
+ *offset = baseOffset + extents[0].extentOffset;
+ *size = extents[0].extentLength;
+ return OK;
+ } else if (constructionMethod == 1) {
+ if (baseOffset + extents[0].extentOffset + extents[0].extentLength
+ > idatSize) {
+ return ERROR_MALFORMED;
+ }
+ *offset = baseOffset + extents[0].extentOffset + idatOffset;
+ *size = extents[0].extentLength;
+ return OK;
+ }
+ return ERROR_UNSUPPORTED;
+ }
+
+ // parsed info
+ uint32_t itemId;
+ uint16_t constructionMethod;
+ uint16_t dataReferenceIndex;
+ off64_t baseOffset;
+ Vector<ExtentEntry> extents;
+};
+
+struct IlocBox : public FullBox {
+ IlocBox(const sp<DataSource> source, KeyedVector<uint32_t, ItemLoc> *itemLocs) :
+ FullBox(source, FOURCC('i', 'l', 'o', 'c')),
+ mItemLocs(itemLocs), mHasConstructMethod1(false) {}
+
+ status_t parse(off64_t offset, size_t size);
+
+ bool hasConstructMethod1() { return mHasConstructMethod1; }
+
+private:
+ static bool isSizeFieldValid(uint32_t offset_size) {
+ return offset_size == 0 || offset_size == 4 || offset_size == 8;
+ }
+ KeyedVector<uint32_t, ItemLoc> *mItemLocs;
+ bool mHasConstructMethod1;
+};
+
+status_t IlocBox::parse(off64_t offset, size_t size) {
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+ if (version() > 2) {
+ ALOGE("%s: invalid version %d", __FUNCTION__, version());
+ return ERROR_MALFORMED;
+ }
+
+ if (size < 2) {
+ return ERROR_MALFORMED;
+ }
+ uint8_t offset_size;
+ if (!source()->readAt(offset++, &offset_size, 1)) {
+ return ERROR_IO;
+ }
+ uint8_t length_size = (offset_size & 0xF);
+ offset_size >>= 4;
+
+ uint8_t base_offset_size;
+ if (!source()->readAt(offset++, &base_offset_size, 1)) {
+ return ERROR_IO;
+ }
+ uint8_t index_size = 0;
+ if (version() == 1 || version() == 2) {
+ index_size = (base_offset_size & 0xF);
+ }
+ base_offset_size >>= 4;
+ size -= 2;
+
+ if (!isSizeFieldValid(offset_size)
+ || !isSizeFieldValid(length_size)
+ || !isSizeFieldValid(base_offset_size)
+ || !isSizeFieldValid((index_size))) {
+ ALOGE("%s: offset size not valid: %d, %d, %d, %d", __FUNCTION__,
+ offset_size, length_size, base_offset_size, index_size);
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t item_count;
+ size_t itemFieldSize = version() < 2 ? 2 : 4;
+ if (size < itemFieldSize) {
+ return ERROR_MALFORMED;
+ }
+ if (!source()->getUInt32Var(offset, &item_count, itemFieldSize)) {
+ return ERROR_IO;
+ }
+
+ ALOGV("item_count %lld", (long long) item_count);
+ offset += itemFieldSize;
+ size -= itemFieldSize;
+
+ for (size_t i = 0; i < item_count; i++) {
+ uint32_t item_id;
+ if (!source()->getUInt32Var(offset, &item_id, itemFieldSize)) {
+ return ERROR_IO;
+ }
+ ALOGV("item[%zu]: id %lld", i, (long long)item_id);
+ offset += itemFieldSize;
+
+ uint8_t construction_method = 0;
+ if (version() == 1 || version() == 2) {
+ uint8_t buf[2];
+ if (!source()->readAt(offset, buf, 2)) {
+ return ERROR_IO;
+ }
+ construction_method = (buf[1] & 0xF);
+ ALOGV("construction_method %d", construction_method);
+ if (construction_method == 1) {
+ mHasConstructMethod1 = true;
+ }
+
+ offset += 2;
+ }
+
+ uint16_t data_reference_index;
+ if (!source()->getUInt16(offset, &data_reference_index)) {
+ return ERROR_IO;
+ }
+ ALOGV("data_reference_index %d", data_reference_index);
+ if (data_reference_index != 0) {
+ // we don't support reference to other files
+ return ERROR_UNSUPPORTED;
+ }
+ offset += 2;
+
+ uint64_t base_offset = 0;
+ if (base_offset_size != 0) {
+ if (!source()->getUInt64Var(offset, &base_offset, base_offset_size)) {
+ return ERROR_IO;
+ }
+ offset += base_offset_size;
+ }
+ ALOGV("base_offset %lld", (long long) base_offset);
+
+ ssize_t index = mItemLocs->add(item_id, ItemLoc(
+ item_id, construction_method, data_reference_index, base_offset));
+ ItemLoc &item = mItemLocs->editValueAt(index);
+
+ uint16_t extent_count;
+ if (!source()->getUInt16(offset, &extent_count)) {
+ return ERROR_IO;
+ }
+ ALOGV("extent_count %d", extent_count);
+
+ if (extent_count > 1 && (offset_size == 0 || length_size == 0)) {
+ // if the item is dividec into more than one extents, offset and
+ // length must be present.
+ return ERROR_MALFORMED;
+ }
+ offset += 2;
+
+ for (size_t j = 0; j < extent_count; j++) {
+ uint64_t extent_index = 1; // default=1
+ if ((version() == 1 || version() == 2) && (index_size > 0)) {
+ if (!source()->getUInt64Var(offset, &extent_index, index_size)) {
+ return ERROR_IO;
+ }
+ // TODO: add support for this mode
+ offset += index_size;
+ ALOGV("extent_index %lld", (long long)extent_index);
+ }
+
+ uint64_t extent_offset = 0; // default=0
+ if (offset_size > 0) {
+ if (!source()->getUInt64Var(offset, &extent_offset, offset_size)) {
+ return ERROR_IO;
+ }
+ offset += offset_size;
+ }
+ ALOGV("extent_offset %lld", (long long)extent_offset);
+
+ uint64_t extent_length = 0; // this indicates full length of file
+ if (length_size > 0) {
+ if (!source()->getUInt64Var(offset, &extent_length, length_size)) {
+ return ERROR_IO;
+ }
+ offset += length_size;
+ }
+ ALOGV("extent_length %lld", (long long)extent_length);
+
+ item.addExtent({ extent_index, extent_offset, extent_length });
+ }
+ }
+ return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// ItemReference related boxes
+//
+
+struct ItemReference : public Box, public RefBase {
+ ItemReference(const sp<DataSource> source, uint32_t type, uint32_t itemIdSize) :
+ Box(source, type), mItemId(0), mRefIdSize(itemIdSize) {}
+
+ status_t parse(off64_t offset, size_t size);
+
+ uint32_t itemId() { return mItemId; }
+
+ void apply(KeyedVector<uint32_t, ImageItem> &itemIdToImageMap) const {
+ ssize_t imageIndex = itemIdToImageMap.indexOfKey(mItemId);
+
+ // ignore non-image items
+ if (imageIndex < 0) {
+ return;
+ }
+
+ ALOGV("attach reference type 0x%x to item id %d)", type(), mItemId);
+
+ if (type() == FOURCC('d', 'i', 'm', 'g')) {
+ ImageItem &image = itemIdToImageMap.editValueAt(imageIndex);
+ if (!image.dimgRefs.empty()) {
+ ALOGW("dimgRefs if not clean!");
+ }
+ image.dimgRefs.appendVector(mRefs);
+ } else if (type() == FOURCC('t', 'h', 'm', 'b')) {
+ for (size_t i = 0; i < mRefs.size(); i++) {
+ imageIndex = itemIdToImageMap.indexOfKey(mRefs[i]);
+
+ // ignore non-image items
+ if (imageIndex < 0) {
+ continue;
+ }
+ ALOGV("Image item id %d uses thumbnail item id %d", mRefs[i], mItemId);
+ ImageItem &image = itemIdToImageMap.editValueAt(imageIndex);
+ if (!image.thumbnails.empty()) {
+ ALOGW("already has thumbnails!");
+ }
+ image.thumbnails.push_back(mItemId);
+ }
+ } else {
+ ALOGW("ignoring unsupported ref type 0x%x", type());
+ }
+ }
+
+private:
+ uint32_t mItemId;
+ uint32_t mRefIdSize;
+ Vector<uint32_t> mRefs;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ItemReference);
+};
+
+status_t ItemReference::parse(off64_t offset, size_t size) {
+ if (size < mRefIdSize + 2) {
+ return ERROR_MALFORMED;
+ }
+ if (!source()->getUInt32Var(offset, &mItemId, mRefIdSize)) {
+ return ERROR_IO;
+ }
+ offset += mRefIdSize;
+
+ uint16_t count;
+ if (!source()->getUInt16(offset, &count)) {
+ return ERROR_IO;
+ }
+ offset += 2;
+ size -= (mRefIdSize + 2);
+
+ if (size < count * mRefIdSize) {
+ return ERROR_MALFORMED;
+ }
+
+ for (size_t i = 0; i < count; i++) {
+ uint32_t refItemId;
+ if (!source()->getUInt32Var(offset, &refItemId, mRefIdSize)) {
+ return ERROR_IO;
+ }
+ offset += mRefIdSize;
+ mRefs.push_back(refItemId);
+ ALOGV("item id %d: referencing item id %d", mItemId, refItemId);
+ }
+
+ return OK;
+}
+
+struct IrefBox : public FullBox {
+ IrefBox(const sp<DataSource> source, Vector<sp<ItemReference> > *itemRefs) :
+ FullBox(source, FOURCC('i', 'r', 'e', 'f')), mRefIdSize(0), mItemRefs(itemRefs) {}
+
+ status_t parse(off64_t offset, size_t size);
+
+protected:
+ status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+ uint32_t mRefIdSize;
+ Vector<sp<ItemReference> > *mItemRefs;
+};
+
+status_t IrefBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+
+ mRefIdSize = (version() == 0) ? 2 : 4;
+ return parseChunks(offset, size);
+}
+
+status_t IrefBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+ sp<ItemReference> itemRef = new ItemReference(source(), type, mRefIdSize);
+
+ status_t err = itemRef->parse(offset, size);
+ if (err != OK) {
+ return err;
+ }
+ mItemRefs->push_back(itemRef);
+ return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// ItemProperty related boxes
+//
+
+struct AssociationEntry {
+ uint32_t itemId;
+ bool essential;
+ uint16_t index;
+};
+
+struct ItemProperty : public RefBase {
+ ItemProperty() {}
+
+ virtual void attachTo(ImageItem &/*image*/) const {
+ ALOGW("Unrecognized property");
+ }
+ virtual status_t parse(off64_t /*offset*/, size_t /*size*/) {
+ ALOGW("Unrecognized property");
+ return OK;
+ }
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(ItemProperty);
+};
+
+struct IspeBox : public FullBox, public ItemProperty {
+ IspeBox(const sp<DataSource> source) :
+ FullBox(source, FOURCC('i', 's', 'p', 'e')), mWidth(0), mHeight(0) {}
+
+ status_t parse(off64_t offset, size_t size) override;
+
+ void attachTo(ImageItem &image) const override {
+ image.width = mWidth;
+ image.height = mHeight;
+ }
+
+private:
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
+
+status_t IspeBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+
+ if (size < 8) {
+ return ERROR_MALFORMED;
+ }
+ if (!source()->getUInt32(offset, &mWidth)
+ || !source()->getUInt32(offset + 4, &mHeight)) {
+ return ERROR_IO;
+ }
+ ALOGV("property ispe: %dx%d", mWidth, mHeight);
+
+ return OK;
+}
+
+struct HvccBox : public Box, public ItemProperty {
+ HvccBox(const sp<DataSource> source) :
+ Box(source, FOURCC('h', 'v', 'c', 'C')) {}
+
+ status_t parse(off64_t offset, size_t size) override;
+
+ void attachTo(ImageItem &image) const override {
+ image.hvcc = mHVCC;
+ }
+
+private:
+ sp<ABuffer> mHVCC;
+};
+
+status_t HvccBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ mHVCC = new ABuffer(size);
+
+ if (mHVCC->data() == NULL) {
+ ALOGE("b/28471206");
+ return NO_MEMORY;
+ }
+
+ if (source()->readAt(offset, mHVCC->data(), size) < (ssize_t)size) {
+ return ERROR_IO;
+ }
+
+ ALOGV("property hvcC");
+
+ return OK;
+}
+
+struct IrotBox : public Box, public ItemProperty {
+ IrotBox(const sp<DataSource> source) :
+ Box(source, FOURCC('i', 'r', 'o', 't')), mAngle(0) {}
+
+ status_t parse(off64_t offset, size_t size) override;
+
+ void attachTo(ImageItem &image) const override {
+ image.rotation = mAngle * 90;
+ }
+
+private:
+ uint8_t mAngle;
+};
+
+status_t IrotBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ if (size < 1) {
+ return ERROR_MALFORMED;
+ }
+ if (source()->readAt(offset, &mAngle, 1) != 1) {
+ return ERROR_IO;
+ }
+ mAngle &= 0x3;
+ ALOGV("property irot: %d", mAngle);
+
+ return OK;
+}
+
+struct ColrBox : public Box, public ItemProperty {
+ ColrBox(const sp<DataSource> source) :
+ Box(source, FOURCC('c', 'o', 'l', 'r')) {}
+
+ status_t parse(off64_t offset, size_t size) override;
+
+ void attachTo(ImageItem &image) const override {
+ image.icc = mICCData;
+ }
+
+private:
+ sp<ABuffer> mICCData;
+};
+
+status_t ColrBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ if (size < 4) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t colour_type;
+ if (!source()->getUInt32(offset, &colour_type)) {
+ return ERROR_IO;
+ }
+ offset += 4;
+ size -= 4;
+ if (colour_type == FOURCC('n', 'c', 'l', 'x')) {
+ return OK;
+ }
+ if ((colour_type != FOURCC('r', 'I', 'C', 'C')) &&
+ (colour_type != FOURCC('p', 'r', 'o', 'f'))) {
+ return ERROR_MALFORMED;
+ }
+
+ mICCData = new ABuffer(size);
+ if (mICCData->data() == NULL) {
+ ALOGE("b/28471206");
+ return NO_MEMORY;
+ }
+
+ if (source()->readAt(offset, mICCData->data(), size) != (ssize_t)size) {
+ return ERROR_IO;
+ }
+
+ ALOGV("property Colr: size %zd", size);
+ return OK;
+}
+
+struct IpmaBox : public FullBox {
+ IpmaBox(const sp<DataSource> source, Vector<AssociationEntry> *associations) :
+ FullBox(source, FOURCC('i', 'p', 'm', 'a')), mAssociations(associations) {}
+
+ status_t parse(off64_t offset, size_t size);
+private:
+ Vector<AssociationEntry> *mAssociations;
+};
+
+status_t IpmaBox::parse(off64_t offset, size_t size) {
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+
+ if (size < 4) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t entryCount;
+ if (!source()->getUInt32(offset, &entryCount)) {
+ return ERROR_IO;
+ }
+ offset += 4;
+ size -= 4;
+
+ for (size_t k = 0; k < entryCount; ++k) {
+ uint32_t itemId = 0;
+ size_t itemIdSize = (version() < 1) ? 2 : 4;
+
+ if (size < itemIdSize + 1) {
+ return ERROR_MALFORMED;
+ }
+
+ if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
+ return ERROR_IO;
+ }
+ offset += itemIdSize;
+ size -= itemIdSize;
+
+ uint8_t associationCount;
+ if (!source()->readAt(offset, &associationCount, 1)) {
+ return ERROR_IO;
+ }
+ offset++;
+ size--;
+
+ for (size_t i = 0; i < associationCount; ++i) {
+ size_t propIndexSize = (flags() & 1) ? 2 : 1;
+ if (size < propIndexSize) {
+ return ERROR_MALFORMED;
+ }
+ uint16_t propIndex;
+ if (!source()->getUInt16Var(offset, &propIndex, propIndexSize)) {
+ return ERROR_IO;
+ }
+ offset += propIndexSize;
+ size -= propIndexSize;
+ uint16_t bitmask = (1 << (8 * propIndexSize - 1));
+ AssociationEntry entry = {
+ .itemId = itemId,
+ .essential = !!(propIndex & bitmask),
+ .index = (uint16_t) (propIndex & ~bitmask)
+ };
+
+ ALOGV("item id %d associated to property %d (essential %d)",
+ itemId, entry.index, entry.essential);
+
+ mAssociations->push_back(entry);
+ }
+ }
+
+ return OK;
+}
+
+struct IpcoBox : public Box {
+ IpcoBox(const sp<DataSource> source, Vector<sp<ItemProperty> > *properties) :
+ Box(source, FOURCC('i', 'p', 'c', 'o')), mItemProperties(properties) {}
+
+ status_t parse(off64_t offset, size_t size);
+protected:
+ status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+ Vector<sp<ItemProperty> > *mItemProperties;
+};
+
+status_t IpcoBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+ // push dummy as the index is 1-based
+ mItemProperties->push_back(new ItemProperty());
+ return parseChunks(offset, size);
+}
+
+status_t IpcoBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+ sp<ItemProperty> itemProperty;
+ switch(type) {
+ case FOURCC('h', 'v', 'c', 'C'):
+ {
+ itemProperty = new HvccBox(source());
+ break;
+ }
+ case FOURCC('i', 's', 'p', 'e'):
+ {
+ itemProperty = new IspeBox(source());
+ break;
+ }
+ case FOURCC('i', 'r', 'o', 't'):
+ {
+ itemProperty = new IrotBox(source());
+ break;
+ }
+ case FOURCC('c', 'o', 'l', 'r'):
+ {
+ itemProperty = new ColrBox(source());
+ break;
+ }
+ default:
+ {
+ // push dummy to maintain correct item property index
+ itemProperty = new ItemProperty();
+ break;
+ }
+ }
+ status_t err = itemProperty->parse(offset, size);
+ if (err != OK) {
+ return err;
+ }
+ mItemProperties->push_back(itemProperty);
+ return OK;
+}
+
+struct IprpBox : public Box {
+ IprpBox(const sp<DataSource> source,
+ Vector<sp<ItemProperty> > *properties,
+ Vector<AssociationEntry> *associations) :
+ Box(source, FOURCC('i', 'p', 'r', 'p')),
+ mProperties(properties), mAssociations(associations) {}
+
+ status_t parse(off64_t offset, size_t size);
+protected:
+ status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+ Vector<sp<ItemProperty> > *mProperties;
+ Vector<AssociationEntry> *mAssociations;
+};
+
+status_t IprpBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ status_t err = parseChunks(offset, size);
+ if (err != OK) {
+ return err;
+ }
+ return OK;
+}
+
+status_t IprpBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+ switch(type) {
+ case FOURCC('i', 'p', 'c', 'o'):
+ {
+ IpcoBox ipcoBox(source(), mProperties);
+ return ipcoBox.parse(offset, size);
+ }
+ case FOURCC('i', 'p', 'm', 'a'):
+ {
+ IpmaBox ipmaBox(source(), mAssociations);
+ return ipmaBox.parse(offset, size);
+ }
+ default:
+ {
+ ALOGW("Unrecognized box.");
+ break;
+ }
+ }
+ return OK;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// ItemInfo related boxes
+//
+struct ItemInfo {
+ uint32_t itemId;
+ uint32_t itemType;
+};
+
+struct InfeBox : public FullBox {
+ InfeBox(const sp<DataSource> source) :
+ FullBox(source, FOURCC('i', 'n', 'f', 'e')) {}
+
+ status_t parse(off64_t offset, size_t size, ItemInfo *itemInfo);
+
+private:
+ bool parseNullTerminatedString(off64_t *offset, size_t *size, String8 *out);
+};
+
+bool InfeBox::parseNullTerminatedString(
+ off64_t *offset, size_t *size, String8 *out) {
+ char tmp[256];
+ size_t len = 0;
+ off64_t newOffset = *offset;
+ off64_t stopOffset = *offset + *size;
+ while (newOffset < stopOffset) {
+ if (!source()->readAt(newOffset++, &tmp[len], 1)) {
+ return false;
+ }
+ if (tmp[len] == 0) {
+ out->append(tmp, len);
+
+ *offset = newOffset;
+ *size = stopOffset - newOffset;
+
+ return true;
+ }
+ if (++len >= sizeof(tmp)) {
+ out->append(tmp, len);
+ len = 0;
+ }
+ }
+ return false;
+}
+
+status_t InfeBox::parse(off64_t offset, size_t size, ItemInfo *itemInfo) {
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+
+ if (version() == 0 || version() == 1) {
+ if (size < 4) {
+ return ERROR_MALFORMED;
+ }
+ uint16_t item_id;
+ if (!source()->getUInt16(offset, &item_id)) {
+ return ERROR_IO;
+ }
+ ALOGV("item_id %d", item_id);
+ uint16_t item_protection_index;
+ if (!source()->getUInt16(offset + 2, &item_protection_index)) {
+ return ERROR_IO;
+ }
+ offset += 4;
+ size -= 4;
+
+ String8 item_name;
+ if (!parseNullTerminatedString(&offset, &size, &item_name)) {
+ return ERROR_MALFORMED;
+ }
+
+ String8 content_type;
+ if (!parseNullTerminatedString(&offset, &size, &content_type)) {
+ return ERROR_MALFORMED;
+ }
+
+ String8 content_encoding;
+ if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (version() == 1) {
+ uint32_t extension_type;
+ if (!source()->getUInt32(offset, &extension_type)) {
+ return ERROR_IO;
+ }
+ offset++;
+ size--;
+ // TODO: handle this case
+ }
+ } else { // version >= 2
+ uint32_t item_id;
+ size_t itemIdSize = (version() == 2) ? 2 : 4;
+ if (size < itemIdSize + 6) {
+ return ERROR_MALFORMED;
+ }
+ if (!source()->getUInt32Var(offset, &item_id, itemIdSize)) {
+ return ERROR_IO;
+ }
+ ALOGV("item_id %d", item_id);
+ offset += itemIdSize;
+ uint16_t item_protection_index;
+ if (!source()->getUInt16(offset, &item_protection_index)) {
+ return ERROR_IO;
+ }
+ ALOGV("item_protection_index %d", item_protection_index);
+ offset += 2;
+ uint32_t item_type;
+ if (!source()->getUInt32(offset, &item_type)) {
+ return ERROR_IO;
+ }
+
+ itemInfo->itemId = item_id;
+ itemInfo->itemType = item_type;
+
+ char itemTypeString[5];
+ MakeFourCCString(item_type, itemTypeString);
+ ALOGV("item_type %s", itemTypeString);
+ offset += 4;
+ size -= itemIdSize + 6;
+
+ String8 item_name;
+ if (!parseNullTerminatedString(&offset, &size, &item_name)) {
+ return ERROR_MALFORMED;
+ }
+ ALOGV("item_name %s", item_name.c_str());
+
+ if (item_type == FOURCC('m', 'i', 'm', 'e')) {
+ String8 content_type;
+ if (!parseNullTerminatedString(&offset, &size, &content_type)) {
+ return ERROR_MALFORMED;
+ }
+
+ String8 content_encoding;
+ if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
+ return ERROR_MALFORMED;
+ }
+ } else if (item_type == FOURCC('u', 'r', 'i', ' ')) {
+ String8 item_uri_type;
+ if (!parseNullTerminatedString(&offset, &size, &item_uri_type)) {
+ return ERROR_MALFORMED;
+ }
+ }
+ }
+ return OK;
+}
+
+struct IinfBox : public FullBox {
+ IinfBox(const sp<DataSource> source, Vector<ItemInfo> *itemInfos) :
+ FullBox(source, FOURCC('i', 'i', 'n', 'f')),
+ mItemInfos(itemInfos), mHasGrids(false) {}
+
+ status_t parse(off64_t offset, size_t size);
+
+ bool hasGrids() { return mHasGrids; }
+
+protected:
+ status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
+
+private:
+ Vector<ItemInfo> *mItemInfos;
+ bool mHasGrids;
+};
+
+status_t IinfBox::parse(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ status_t err = parseFullBoxHeader(&offset, &size);
+ if (err != OK) {
+ return err;
+ }
+
+ size_t entryCountSize = version() == 0 ? 2 : 4;
+ if (size < entryCountSize) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t entry_count;
+ if (!source()->getUInt32Var(offset, &entry_count, entryCountSize)) {
+ return ERROR_IO;
+ }
+ ALOGV("entry_count %d", entry_count);
+
+ off64_t stopOffset = offset + size;
+ offset += entryCountSize;
+ for (size_t i = 0; i < entry_count && offset < stopOffset; i++) {
+ ALOGV("entry %zu", i);
+ status_t err = parseChunk(&offset);
+ if (err != OK) {
+ return err;
+ }
+ }
+ if (offset != stopOffset) {
+ return ERROR_MALFORMED;
+ }
+
+ return OK;
+}
+
+status_t IinfBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
+ if (type != FOURCC('i', 'n', 'f', 'e')) {
+ return OK;
+ }
+
+ InfeBox infeBox(source());
+ ItemInfo itemInfo;
+ status_t err = infeBox.parse(offset, size, &itemInfo);
+ if (err != OK) {
+ return err;
+ }
+ mItemInfos->push_back(itemInfo);
+ mHasGrids |= (itemInfo.itemType == FOURCC('g', 'r', 'i', 'd'));
+ return OK;
+}
+
+//////////////////////////////////////////////////////////////////
+
+ItemTable::ItemTable(const sp<DataSource> &source)
+ : mDataSource(source),
+ mPrimaryItemId(0),
+ mIdatOffset(0),
+ mIdatSize(0),
+ mImageItemsValid(false),
+ mCurrentImageIndex(0) {
+ mRequiredBoxes.insert('iprp');
+ mRequiredBoxes.insert('iloc');
+ mRequiredBoxes.insert('pitm');
+ mRequiredBoxes.insert('iinf');
+}
+
+ItemTable::~ItemTable() {}
+
+status_t ItemTable::parse(uint32_t type, off64_t data_offset, size_t chunk_data_size) {
+ switch(type) {
+ case FOURCC('i', 'l', 'o', 'c'):
+ {
+ return parseIlocBox(data_offset, chunk_data_size);
+ }
+ case FOURCC('i', 'i', 'n', 'f'):
+ {
+ return parseIinfBox(data_offset, chunk_data_size);
+ }
+ case FOURCC('i', 'p', 'r', 'p'):
+ {
+ return parseIprpBox(data_offset, chunk_data_size);
+ }
+ case FOURCC('p', 'i', 't', 'm'):
+ {
+ return parsePitmBox(data_offset, chunk_data_size);
+ }
+ case FOURCC('i', 'd', 'a', 't'):
+ {
+ return parseIdatBox(data_offset, chunk_data_size);
+ }
+ case FOURCC('i', 'r', 'e', 'f'):
+ {
+ return parseIrefBox(data_offset, chunk_data_size);
+ }
+ case FOURCC('i', 'p', 'r', 'o'):
+ {
+ ALOGW("ipro box not supported!");
+ break;
+ }
+ default:
+ {
+ ALOGW("unrecognized box type: 0x%x", type);
+ break;
+ }
+ }
+ return ERROR_UNSUPPORTED;
+}
+
+status_t ItemTable::parseIlocBox(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ IlocBox ilocBox(mDataSource, &mItemLocs);
+ status_t err = ilocBox.parse(offset, size);
+ if (err != OK) {
+ return err;
+ }
+
+ if (ilocBox.hasConstructMethod1()) {
+ mRequiredBoxes.insert('idat');
+ }
+
+ return buildImageItemsIfPossible('iloc');
+}
+
+status_t ItemTable::parseIinfBox(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ IinfBox iinfBox(mDataSource, &mItemInfos);
+ status_t err = iinfBox.parse(offset, size);
+ if (err != OK) {
+ return err;
+ }
+
+ if (iinfBox.hasGrids()) {
+ mRequiredBoxes.insert('iref');
+ }
+
+ return buildImageItemsIfPossible('iinf');
+}
+
+status_t ItemTable::parsePitmBox(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ PitmBox pitmBox(mDataSource);
+ status_t err = pitmBox.parse(offset, size, &mPrimaryItemId);
+ if (err != OK) {
+ return err;
+ }
+
+ return buildImageItemsIfPossible('pitm');
+}
+
+status_t ItemTable::parseIprpBox(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ IprpBox iprpBox(mDataSource, &mItemProperties, &mAssociations);
+ status_t err = iprpBox.parse(offset, size);
+ if (err != OK) {
+ return err;
+ }
+
+ return buildImageItemsIfPossible('iprp');
+}
+
+status_t ItemTable::parseIdatBox(off64_t offset, size_t size) {
+ ALOGV("%s: idat offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ // only remember the offset and size of idat box for later use
+ mIdatOffset = offset;
+ mIdatSize = size;
+
+ return buildImageItemsIfPossible('idat');
+}
+
+status_t ItemTable::parseIrefBox(off64_t offset, size_t size) {
+ ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
+
+ IrefBox irefBox(mDataSource, &mItemReferences);
+ status_t err = irefBox.parse(offset, size);
+ if (err != OK) {
+ return err;
+ }
+
+ return buildImageItemsIfPossible('iref');
+}
+
+status_t ItemTable::buildImageItemsIfPossible(uint32_t type) {
+ if (mImageItemsValid) {
+ return OK;
+ }
+
+ mBoxesSeen.insert(type);
+
+ // need at least 'iprp', 'iloc', 'pitm', 'iinf';
+ // need 'idat' if any items used construction_method of 2;
+ // need 'iref' if there are grids.
+ if (!std::includes(
+ mBoxesSeen.begin(), mBoxesSeen.end(),
+ mRequiredBoxes.begin(), mRequiredBoxes.end())) {
+ return OK;
+ }
+
+ ALOGV("building image table...");
+
+ for (size_t i = 0; i < mItemInfos.size(); i++) {
+ const ItemInfo &info = mItemInfos[i];
+
+
+ // ignore non-image items
+ if (info.itemType != FOURCC('g', 'r', 'i', 'd') &&
+ info.itemType != FOURCC('h', 'v', 'c', '1')) {
+ continue;
+ }
+
+ ssize_t imageIndex = mItemIdToImageMap.indexOfKey(info.itemId);
+ if (imageIndex >= 0) {
+ ALOGW("ignoring duplicate image item id %d", info.itemId);
+ continue;
+ }
+
+ ssize_t ilocIndex = mItemLocs.indexOfKey(info.itemId);
+ if (ilocIndex < 0) {
+ ALOGE("iloc missing for image item id %d", info.itemId);
+ continue;
+ }
+ const ItemLoc &iloc = mItemLocs[ilocIndex];
+
+ off64_t offset;
+ size_t size;
+ if (iloc.getLoc(&offset, &size, mIdatOffset, mIdatSize) != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ ImageItem image(info.itemType);
+
+ ALOGV("adding %s: itemId %d", image.isGrid() ? "grid" : "image", info.itemId);
+
+ if (image.isGrid()) {
+ if (size > 12) {
+ return ERROR_MALFORMED;
+ }
+ uint8_t buf[12];
+ if (!mDataSource->readAt(offset, buf, size)) {
+ return ERROR_IO;
+ }
+
+ image.rows = buf[2] + 1;
+ image.columns = buf[3] + 1;
+
+ ALOGV("rows %d, columans %d", image.rows, image.columns);
+ } else {
+ image.offset = offset;
+ image.size = size;
+ }
+ mItemIdToImageMap.add(info.itemId, image);
+ }
+
+ for (size_t i = 0; i < mAssociations.size(); i++) {
+ attachProperty(mAssociations[i]);
+ }
+
+ for (size_t i = 0; i < mItemReferences.size(); i++) {
+ mItemReferences[i]->apply(mItemIdToImageMap);
+ }
+
+ mImageItemsValid = true;
+ return OK;
+}
+
+void ItemTable::attachProperty(const AssociationEntry &association) {
+ ssize_t imageIndex = mItemIdToImageMap.indexOfKey(association.itemId);
+
+ // ignore non-image items
+ if (imageIndex < 0) {
+ return;
+ }
+
+ uint16_t propertyIndex = association.index;
+ if (propertyIndex >= mItemProperties.size()) {
+ ALOGW("Ignoring invalid property index %d", propertyIndex);
+ return;
+ }
+
+ ALOGV("attach property %d to item id %d)",
+ propertyIndex, association.itemId);
+
+ mItemProperties[propertyIndex]->attachTo(
+ mItemIdToImageMap.editValueAt(imageIndex));
+}
+
+sp<MetaData> ItemTable::getImageMeta() {
+ if (!mImageItemsValid) {
+ return NULL;
+ }
+
+ ssize_t imageIndex = mItemIdToImageMap.indexOfKey(mPrimaryItemId);
+ if (imageIndex < 0) {
+ ALOGE("Primary item id %d not found!", mPrimaryItemId);
+ return NULL;
+ }
+
+ ALOGV("primary image index %zu", imageIndex);
+
+ const ImageItem *image = &mItemIdToImageMap[imageIndex];
+
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_HEVC);
+
+ ALOGV("setting image size %dx%d", image->width, image->height);
+ meta->setInt32(kKeyWidth, image->width);
+ meta->setInt32(kKeyHeight, image->height);
+ if (image->rotation != 0) {
+ meta->setInt32(kKeyRotation, image->rotation);
+ }
+ meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
+
+ if (!image->thumbnails.empty()) {
+ ssize_t thumbnailIndex = mItemIdToImageMap.indexOfKey(image->thumbnails[0]);
+ if (thumbnailIndex >= 0) {
+ const ImageItem &thumbnail = mItemIdToImageMap[thumbnailIndex];
+
+ meta->setInt32(kKeyThumbnailWidth, thumbnail.width);
+ meta->setInt32(kKeyThumbnailHeight, thumbnail.height);
+ meta->setData(kKeyThumbnailHVCC, kTypeHVCC,
+ thumbnail.hvcc->data(), thumbnail.hvcc->size());
+ ALOGV("thumbnail meta: %dx%d, index %zd",
+ thumbnail.width, thumbnail.height, thumbnailIndex);
+ } else {
+ ALOGW("Referenced thumbnail does not exist!");
+ }
+ }
+
+ if (image->isGrid()) {
+ ssize_t tileIndex = mItemIdToImageMap.indexOfKey(image->dimgRefs[0]);
+ if (tileIndex < 0) {
+ return NULL;
+ }
+ // when there are tiles, (kKeyWidth, kKeyHeight) is the full tiled area,
+ // and (kKeyDisplayWidth, kKeyDisplayHeight) may be smaller than that.
+ meta->setInt32(kKeyDisplayWidth, image->width);
+ meta->setInt32(kKeyDisplayHeight, image->height);
+ int32_t gridRows = image->rows, gridCols = image->columns;
+
+ // point image to the first tile for grid size and HVCC
+ image = &mItemIdToImageMap.editValueAt(tileIndex);
+ meta->setInt32(kKeyWidth, image->width * gridCols);
+ meta->setInt32(kKeyHeight, image->height * gridRows);
+ meta->setInt32(kKeyGridWidth, image->width);
+ meta->setInt32(kKeyGridHeight, image->height);
+ meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
+ }
+
+ if (image->hvcc == NULL) {
+ ALOGE("hvcc is missing!");
+ return NULL;
+ }
+ meta->setData(kKeyHVCC, kTypeHVCC, image->hvcc->data(), image->hvcc->size());
+
+ if (image->icc != NULL) {
+ meta->setData(kKeyIccProfile, 0, image->icc->data(), image->icc->size());
+ }
+ return meta;
+}
+
+uint32_t ItemTable::countImages() const {
+ return mImageItemsValid ? mItemIdToImageMap.size() : 0;
+}
+
+status_t ItemTable::findPrimaryImage(uint32_t *imageIndex) {
+ if (!mImageItemsValid) {
+ return INVALID_OPERATION;
+ }
+
+ ssize_t index = mItemIdToImageMap.indexOfKey(mPrimaryItemId);
+ if (index < 0) {
+ return ERROR_MALFORMED;
+ }
+
+ *imageIndex = index;
+ return OK;
+}
+
+status_t ItemTable::findThumbnail(uint32_t *imageIndex) {
+ if (!mImageItemsValid) {
+ return INVALID_OPERATION;
+ }
+
+ ssize_t primaryIndex = mItemIdToImageMap.indexOfKey(mPrimaryItemId);
+ if (primaryIndex < 0) {
+ ALOGE("Primary item id %d not found!", mPrimaryItemId);
+ return ERROR_MALFORMED;
+ }
+
+ const ImageItem &primaryImage = mItemIdToImageMap[primaryIndex];
+ if (primaryImage.thumbnails.empty()) {
+ ALOGW("Using primary in place of thumbnail.");
+ *imageIndex = primaryIndex;
+ return OK;
+ }
+
+ ssize_t thumbnailIndex = mItemIdToImageMap.indexOfKey(
+ primaryImage.thumbnails[0]);
+ if (thumbnailIndex < 0) {
+ ALOGE("Thumbnail item id %d not found!", primaryImage.thumbnails[0]);
+ return ERROR_MALFORMED;
+ }
+
+ *imageIndex = thumbnailIndex;
+ return OK;
+}
+
+status_t ItemTable::getImageOffsetAndSize(
+ uint32_t *imageIndex, off64_t *offset, size_t *size) {
+ if (!mImageItemsValid) {
+ return INVALID_OPERATION;
+ }
+
+ if (imageIndex != NULL) {
+ if (*imageIndex >= mItemIdToImageMap.size()) {
+ ALOGE("Bad image index!");
+ return BAD_VALUE;
+ }
+ mCurrentImageIndex = *imageIndex;
+ }
+
+ ImageItem &image = mItemIdToImageMap.editValueAt(mCurrentImageIndex);
+ if (image.isGrid()) {
+ uint32_t tileItemId;
+ status_t err = image.getNextTileItemId(&tileItemId, imageIndex != NULL);
+ if (err != OK) {
+ return err;
+ }
+ ssize_t tileImageIndex = mItemIdToImageMap.indexOfKey(tileItemId);
+ if (tileImageIndex < 0) {
+ return ERROR_END_OF_STREAM;
+ }
+ *offset = mItemIdToImageMap[tileImageIndex].offset;
+ *size = mItemIdToImageMap[tileImageIndex].size;
+ } else {
+ if (imageIndex == NULL) {
+ // For single images, we only allow it to be read once, after that
+ // it's EOS. New image index must be requested each time.
+ return ERROR_END_OF_STREAM;
+ }
+ *offset = mItemIdToImageMap[mCurrentImageIndex].offset;
+ *size = mItemIdToImageMap[mCurrentImageIndex].size;
+ }
+
+ return OK;
+}
+
+} // namespace heif
+
+} // namespace android
diff --git a/media/extractors/mp4/ItemTable.h b/media/extractors/mp4/ItemTable.h
new file mode 100644
index 0000000..5a6af5e
--- /dev/null
+++ b/media/extractors/mp4/ItemTable.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ITEM_TABLE_H_
+#define ITEM_TABLE_H_
+
+#include <set>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class DataSource;
+class MetaData;
+
+namespace heif {
+
+struct AssociationEntry;
+struct ImageItem;
+struct ItemLoc;
+struct ItemInfo;
+struct ItemProperty;
+struct ItemReference;
+
+/*
+ * ItemTable keeps track of all image items (including coded images, grids and
+ * tiles) inside a HEIF still image (ISO/IEC FDIS 23008-12.2:2017(E)).
+ */
+
+class ItemTable : public RefBase {
+public:
+ explicit ItemTable(const sp<DataSource> &source);
+
+ status_t parse(uint32_t type, off64_t offset, size_t size);
+
+ bool isValid() { return mImageItemsValid; }
+ sp<MetaData> getImageMeta();
+ uint32_t countImages() const;
+ status_t findPrimaryImage(uint32_t *imageIndex);
+ status_t findThumbnail(uint32_t *thumbnailIndex);
+ status_t getImageOffsetAndSize(
+ uint32_t *imageIndex, off64_t *offset, size_t *size);
+
+protected:
+ ~ItemTable();
+
+private:
+ sp<DataSource> mDataSource;
+
+ KeyedVector<uint32_t, ItemLoc> mItemLocs;
+ Vector<ItemInfo> mItemInfos;
+ Vector<AssociationEntry> mAssociations;
+ Vector<sp<ItemProperty> > mItemProperties;
+ Vector<sp<ItemReference> > mItemReferences;
+
+ uint32_t mPrimaryItemId;
+ off64_t mIdatOffset;
+ size_t mIdatSize;
+
+ std::set<uint32_t> mRequiredBoxes;
+ std::set<uint32_t> mBoxesSeen;
+
+ bool mImageItemsValid;
+ uint32_t mCurrentImageIndex;
+ KeyedVector<uint32_t, ImageItem> mItemIdToImageMap;
+
+ status_t parseIlocBox(off64_t offset, size_t size);
+ status_t parseIinfBox(off64_t offset, size_t size);
+ status_t parsePitmBox(off64_t offset, size_t size);
+ status_t parseIprpBox(off64_t offset, size_t size);
+ status_t parseIdatBox(off64_t offset, size_t size);
+ status_t parseIrefBox(off64_t offset, size_t size);
+
+ void attachProperty(const AssociationEntry &association);
+ status_t buildImageItemsIfPossible(uint32_t type);
+
+ DISALLOW_EVIL_CONSTRUCTORS(ItemTable);
+};
+
+} // namespace heif
+} // namespace android
+
+#endif // ITEM_TABLE_H_
diff --git a/media/extractors/mp4/MODULE_LICENSE_APACHE2 b/media/extractors/mp4/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/extractors/mp4/MODULE_LICENSE_APACHE2
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
new file mode 100644
index 0000000..a625e24
--- /dev/null
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -0,0 +1,5515 @@
+/*
+ * 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 <memory>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <utils/Log.h>
+
+#include "MPEG4Extractor.h"
+#include "SampleTable.h"
+#include "ItemTable.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/foundation/hexdump.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,
+ const sp<ItemTable> &itemTable);
+ 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;
+
+ bool mIsHEIF;
+ sp<ItemTable> mItemTable;
+
+ 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 {
+ explicit 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 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),
+ mIsHEIF(false),
+ mFirstTrack(NULL),
+ mLastTrack(NULL),
+ mFileMetaData(new MetaData),
+ mFirstSINF(NULL),
+ mIsDrm(false) {
+}
+
+MPEG4Extractor::~MPEG4Extractor() {
+ release();
+}
+
+void MPEG4Extractor::release() {
+ 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;
+ }
+ mPssh.clear();
+
+ if (mDataSource != NULL) {
+ mDataSource->close();
+ mDataSource.clear();
+ }
+}
+
+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;
+ }
+
+ int64_t duration;
+ int32_t samplerate;
+ if (track->has_elst && mHeaderTimescale != 0 &&
+ track->meta->findInt64(kKeyDuration, &duration) &&
+ track->meta->findInt32(kKeySampleRate, &samplerate)) {
+
+ track->has_elst = false;
+
+ if (track->elst_segment_duration > INT64_MAX) {
+ goto editlistoverflow;
+ }
+ int64_t segment_duration = track->elst_segment_duration;
+ int64_t media_time = track->elst_media_time;
+ int64_t halfscale = mHeaderTimescale / 2;
+
+ int64_t delay;
+ // delay = ((media_time * samplerate) + halfscale) / mHeaderTimescale;
+ if (__builtin_mul_overflow(media_time, samplerate, &delay) ||
+ __builtin_add_overflow(delay, halfscale, &delay) ||
+ (delay /= mHeaderTimescale, false) ||
+ delay > INT32_MAX ||
+ delay < INT32_MIN) {
+ goto editlistoverflow;
+ }
+ track->meta->setInt32(kKeyEncoderDelay, delay);
+
+ int64_t scaled_duration;
+ // scaled_duration = ((duration * mHeaderTimescale) + 500000) / 1000000;
+ if (__builtin_mul_overflow(duration, mHeaderTimescale, &scaled_duration) ||
+ __builtin_add_overflow(scaled_duration, 500000, &scaled_duration)) {
+ goto editlistoverflow;
+ }
+ scaled_duration /= 1000000;
+
+ int64_t segment_end;
+ int64_t padding;
+ if (__builtin_add_overflow(segment_duration, media_time, &segment_end) ||
+ __builtin_sub_overflow(scaled_duration, segment_end, &padding)) {
+ goto editlistoverflow;
+ }
+
+ if (padding < 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.
+ padding = 0;
+ }
+
+ int64_t paddingsamples;
+ // paddingsamples = ((padding * samplerate) + halfscale) / mHeaderTimescale;
+ if (__builtin_mul_overflow(padding, samplerate, &paddingsamples) ||
+ __builtin_add_overflow(paddingsamples, halfscale, &paddingsamples) ||
+ (paddingsamples /= mHeaderTimescale, false) ||
+ paddingsamples > INT32_MAX) {
+ goto editlistoverflow;
+ }
+ track->meta->setInt32(kKeyEncoderPadding, paddingsamples);
+ }
+ editlistoverflow:
+
+ 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;
+}
+
+status_t MPEG4Extractor::readMetaData() {
+ if (mInitCheck != NO_INIT) {
+ return mInitCheck;
+ }
+
+ off64_t offset = 0;
+ status_t err;
+ bool sawMoovOrSidx = false;
+
+ while (!((sawMoovOrSidx && (mMdatFound || mMoofFound)) ||
+ (mIsHEIF && (mItemTable != NULL) && mItemTable->isValid()))) {
+ 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);
+ }
+
+ if (mIsHEIF) {
+ sp<MetaData> meta = mItemTable->getImageMeta();
+ if (meta == NULL) {
+ return ERROR_MALFORMED;
+ }
+
+ Track *track = mLastTrack;
+ if (track != NULL) {
+ ALOGW("track is set before metadata is fully processed");
+ } else {
+ track = new Track;
+ track->next = NULL;
+ mFirstTrack = mLastTrack = track;
+ }
+
+ track->meta = meta;
+ track->meta->setInt32(kKeyTrackID, 0);
+ track->includes_expensive_metadata = false;
+ track->skipTrack = false;
+ track->timescale = 0;
+ }
+
+ 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;
+ }
+ if (depth > 100) {
+ ALOGE("b/27456299");
+ 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', 'v') && mInitCheck == OK) {
+ ALOGE("duplicate moov");
+ 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");
+ track->has_elst = false;
+ }
+
+ 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;
+ }
+
+ status_t err = verifyTrack(mLastTrack);
+ if (err != OK) {
+ 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;
+ }
+ if (cur) {
+ cur->next = NULL;
+ }
+ delete mLastTrack;
+ mLastTrack = cur;
+ }
+
+ return OK;
+ }
+ } 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;
+
+ if (!mLastTrack) {
+ return ERROR_MALFORMED;
+ }
+
+ // 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 {
+ 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;
+ }
+
+ // save these for later, because the elst atom might precede
+ // the atoms that actually gives us the duration and sample rate
+ // needed to calculate the padding and delay values
+ mLastTrack->has_elst = true;
+ mLastTrack->elst_media_time = media_time;
+ mLastTrack->elst_segment_duration = segment_duration;
+ }
+ 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, ×cale, 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', 'e', 't', 't'):
+ {
+ *offset += chunk_size;
+
+ if (mLastTrack == NULL)
+ return ERROR_MALFORMED;
+
+ sp<ABuffer> buffer = new ABuffer(chunk_data_size);
+ if (buffer->data() == NULL) {
+ return NO_MEMORY;
+ }
+
+ if (mDataSource->readAt(
+ data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+ return ERROR_IO;
+ }
+
+ String8 mimeFormat((const char *)(buffer->data()), chunk_data_size);
+ mLastTrack->meta->setCString(kKeyMIMEType, mimeFormat.string());
+
+ 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 9, for instance "\xA9xyz" + "\x00\x05\x15\xc7" + "+0+0/",
+ // where "\x00\x05" is the text string length with value = 5,
+ // "\0x15\xc7" is the language code = en, and "+0+0/" is a
+ // location (string) value with longitude = 0 and latitude = 0.
+ // Since some devices encountered in the wild omit the trailing
+ // slash, we'll allow that.
+ if (chunk_data_size < 8) { // 8 instead of 9 to allow for missing /
+ return ERROR_MALFORMED;
+ }
+
+ uint16_t len;
+ if (!mDataSource->getUInt16(data_offset, &len)) {
+ return ERROR_IO;
+ }
+
+ // allow "+0+0" without trailing slash
+ if (len < 4 || len > chunk_data_size - 4) {
+ return ERROR_MALFORMED;
+ }
+ // The location string following the language code is formatted
+ // according to ISO 6709:2008 (https://en.wikipedia.org/wiki/ISO_6709).
+ // Allocate 2 extra bytes, in case we need to add a trailing slash,
+ // and to add a terminating 0.
+ std::unique_ptr<char[]> buffer(new (std::nothrow) char[len+2]());
+ if (!buffer) {
+ return NO_MEMORY;
+ }
+
+ if (mDataSource->readAt(
+ data_offset + 4, &buffer[0], len) < len) {
+ return ERROR_IO;
+ }
+
+ len = strlen(&buffer[0]);
+ if (len < 4) {
+ return ERROR_MALFORMED;
+ }
+ // Add a trailing slash if there wasn't one.
+ if (buffer[len - 1] != '/') {
+ buffer[len] = '/';
+ }
+ mFileMetaData->setCString(kKeyLocation, &buffer[0]);
+ 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('i', 'l', 'o', 'c'):
+ case FOURCC('i', 'i', 'n', 'f'):
+ case FOURCC('i', 'p', 'r', 'p'):
+ case FOURCC('p', 'i', 't', 'm'):
+ case FOURCC('i', 'd', 'a', 't'):
+ case FOURCC('i', 'r', 'e', 'f'):
+ case FOURCC('i', 'p', 'r', 'o'):
+ {
+ if (mIsHEIF) {
+ if (mItemTable == NULL) {
+ mItemTable = new ItemTable(mDataSource);
+ }
+ status_t err = mItemTable->parse(
+ chunk_type, data_offset, chunk_data_size);
+ if (err != OK) {
+ return err;
+ }
+ }
+ *offset += chunk_size;
+ 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('a', 'c', '-', '3'):
+ {
+ *offset += chunk_size;
+ return parseAC3SampleEntry(data_offset);
+ }
+
+ 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;
+ std::set<uint32_t> brandSet;
+ 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);
+ brandSet.insert(brand);
+ }
+
+ if (brandSet.count(FOURCC('q', 't', ' ', ' ')) > 0) {
+ mIsQT = true;
+ } else if (brandSet.count(FOURCC('m', 'i', 'f', '1')) > 0
+ && brandSet.count(FOURCC('h', 'e', 'i', 'c')) > 0) {
+ mIsHEIF = true;
+ ALOGV("identified HEIF image");
+ }
+
+ *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::parseAC3SampleEntry(off64_t offset) {
+ // skip 16 bytes:
+ // + 6-byte reserved,
+ // + 2-byte data reference index,
+ // + 8-byte reserved
+ offset += 16;
+ uint16_t channelCount;
+ if (!mDataSource->getUInt16(offset, &channelCount)) {
+ return ERROR_MALFORMED;
+ }
+ // skip 8 bytes:
+ // + 2-byte channelCount,
+ // + 2-byte sample size,
+ // + 4-byte reserved
+ offset += 8;
+ uint16_t sampleRate;
+ if (!mDataSource->getUInt16(offset, &sampleRate)) {
+ ALOGE("MPEG4Extractor: error while reading ac-3 block: cannot read sample rate");
+ return ERROR_MALFORMED;
+ }
+
+ // skip 4 bytes:
+ // + 2-byte sampleRate,
+ // + 2-byte reserved
+ offset += 4;
+ return parseAC3SpecificBox(offset, sampleRate);
+}
+
+status_t MPEG4Extractor::parseAC3SpecificBox(
+ off64_t offset, uint16_t sampleRate) {
+ uint32_t size;
+ // + 4-byte size
+ // + 4-byte type
+ // + 3-byte payload
+ const uint32_t kAC3SpecificBoxSize = 11;
+ if (!mDataSource->getUInt32(offset, &size) || size < kAC3SpecificBoxSize) {
+ ALOGE("MPEG4Extractor: error while reading ac-3 block: cannot read specific box size");
+ return ERROR_MALFORMED;
+ }
+
+ offset += 4;
+ uint32_t type;
+ if (!mDataSource->getUInt32(offset, &type) || type != FOURCC('d', 'a', 'c', '3')) {
+ ALOGE("MPEG4Extractor: error while reading ac-3 specific block: header not dac3");
+ return ERROR_MALFORMED;
+ }
+
+ offset += 4;
+ const uint32_t kAC3SpecificBoxPayloadSize = 3;
+ uint8_t chunk[kAC3SpecificBoxPayloadSize];
+ if (mDataSource->readAt(offset, chunk, sizeof(chunk)) != sizeof(chunk)) {
+ ALOGE("MPEG4Extractor: error while reading ac-3 specific block: bitstream fields");
+ return ERROR_MALFORMED;
+ }
+
+ ABitReader br(chunk, sizeof(chunk));
+ static const unsigned channelCountTable[] = {2, 1, 2, 3, 3, 4, 4, 5};
+ static const unsigned sampleRateTable[] = {48000, 44100, 32000};
+
+ unsigned fscod = br.getBits(2);
+ if (fscod == 3) {
+ ALOGE("Incorrect fscod (3) in AC3 header");
+ return ERROR_MALFORMED;
+ }
+ unsigned boxSampleRate = sampleRateTable[fscod];
+ if (boxSampleRate != sampleRate) {
+ ALOGE("sample rate mismatch: boxSampleRate = %d, sampleRate = %d",
+ boxSampleRate, sampleRate);
+ return ERROR_MALFORMED;
+ }
+
+ unsigned bsid = br.getBits(5);
+ if (bsid > 8) {
+ ALOGW("Incorrect bsid in AC3 header. Possibly E-AC-3?");
+ return ERROR_MALFORMED;
+ }
+
+ // skip
+ unsigned bsmod __unused = br.getBits(3);
+
+ unsigned acmod = br.getBits(3);
+ unsigned lfeon = br.getBits(1);
+ unsigned channelCount = channelCountTable[acmod] + lfeon;
+
+ if (mLastTrack == NULL) {
+ return ERROR_MALFORMED;
+ }
+ mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AC3);
+ mLastTrack->meta->setInt32(kKeyChannelCount, channelCount);
+ mLastTrack->meta->setInt32(kKeySampleRate, sampleRate);
+ 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 (index < 0) {
+ // corresponding key is not present, ignore
+ return ERROR_MALFORMED;
+ }
+
+ if (size <= 16) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t dataSize;
+ if (!mDataSource->getUInt32(offset, &dataSize)
+ || dataSize > size || dataSize <= 16) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t atomFourCC;
+ if (!mDataSource->getUInt32(offset + 4, &atomFourCC)
+ || atomFourCC != FOURCC('d', 'a', 't', 'a')) {
+ return ERROR_MALFORMED;
+ }
+ uint32_t dataType;
+ if (!mDataSource->getUInt32(offset + 8, &dataType)
+ || ((dataType & 0xff000000) != 0)) {
+ // not well-known type
+ return ERROR_MALFORMED;
+ }
+
+ dataSize -= 16;
+ offset += 16;
+
+ if (dataType == 23 && dataSize >= 4) {
+ // BE Float32
+ uint32_t val;
+ if (!mDataSource->getUInt32(offset, &val)) {
+ return ERROR_MALFORMED;
+ }
+ if (!strcasecmp(mMetaKeyMap[index].c_str(), "com.android.capture.fps")) {
+ mFileMetaData->setFloat(kKeyCaptureFramerate, *(float *)&val);
+ }
+ } else if (dataType == 67 && dataSize >= 4) {
+ // BE signed int32
+ uint32_t val;
+ if (!mDataSource->getUInt32(offset, &val)) {
+ return ERROR_MALFORMED;
+ }
+ if (!strcasecmp(mMetaKeyMap[index].c_str(), "com.android.video.temporal_layers_count")) {
+ mFileMetaData->setInt32(kKeyTemporalLayerCount, val);
+ }
+ } else {
+ // add more keys if needed
+ ALOGV("ignoring key: type %d, size %d", dataType, dataSize);
+ }
+
+ return OK;
+}
+
+status_t MPEG4Extractor::parseTrackHeader(
+ off64_t data_offset, off64_t data_size) {
+ if (data_size < 4) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t version;
+ if (mDataSource->readAt(data_offset, &version, 1) < 1) {
+ return ERROR_IO;
+ }
+
+ size_t dynSize = (version == 1) ? 36 : 24;
+
+ uint8_t buffer[36 + 60];
+
+ if (data_size != (off64_t)dynSize + 60) {
+ return ERROR_MALFORMED;
+ }
+
+ if (mDataSource->readAt(
+ data_offset, buffer, data_size) < (ssize_t)data_size) {
+ return ERROR_IO;
+ }
+
+ uint64_t ctime __unused, mtime __unused, duration __unused;
+ int32_t id;
+
+ if (version == 1) {
+ ctime = U64_AT(&buffer[4]);
+ mtime = U64_AT(&buffer[12]);
+ id = U32_AT(&buffer[20]);
+ duration = U64_AT(&buffer[28]);
+ } else if (version == 0) {
+ ctime = U32_AT(&buffer[4]);
+ mtime = U32_AT(&buffer[8]);
+ id = U32_AT(&buffer[12]);
+ duration = U32_AT(&buffer[20]);
+ } else {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (mLastTrack == NULL)
+ return ERROR_MALFORMED;
+
+ mLastTrack->meta->setInt32(kKeyTrackID, id);
+
+ size_t matrixOffset = dynSize + 16;
+ int32_t a00 = U32_AT(&buffer[matrixOffset]);
+ int32_t a01 = U32_AT(&buffer[matrixOffset + 4]);
+ int32_t a10 = U32_AT(&buffer[matrixOffset + 12]);
+ int32_t a11 = U32_AT(&buffer[matrixOffset + 16]);
+
+#if 0
+ int32_t dx = U32_AT(&buffer[matrixOffset + 8]);
+ int32_t dy = U32_AT(&buffer[matrixOffset + 20]);
+
+ ALOGI("x' = %.2f * x + %.2f * y + %.2f",
+ a00 / 65536.0f, a01 / 65536.0f, dx / 65536.0f);
+ ALOGI("y' = %.2f * x + %.2f * y + %.2f",
+ a10 / 65536.0f, a11 / 65536.0f, dy / 65536.0f);
+#endif
+
+ uint32_t rotationDegrees;
+
+ static const int32_t kFixedOne = 0x10000;
+ if (a00 == kFixedOne && a01 == 0 && a10 == 0 && a11 == kFixedOne) {
+ // Identity, no rotation
+ rotationDegrees = 0;
+ } else if (a00 == 0 && a01 == kFixedOne && a10 == -kFixedOne && a11 == 0) {
+ rotationDegrees = 90;
+ } else if (a00 == 0 && a01 == -kFixedOne && a10 == kFixedOne && a11 == 0) {
+ rotationDegrees = 270;
+ } else if (a00 == -kFixedOne && a01 == 0 && a10 == 0 && a11 == -kFixedOne) {
+ rotationDegrees = 180;
+ } else {
+ ALOGW("We only support 0,90,180,270 degree rotation matrices");
+ rotationDegrees = 0;
+ }
+
+ if (rotationDegrees != 0) {
+ mLastTrack->meta->setInt32(kKeyRotation, rotationDegrees);
+ }
+
+ // Handle presentation display size, which could be different
+ // from the image size indicated by kKeyWidth and kKeyHeight.
+ uint32_t width = U32_AT(&buffer[dynSize + 52]);
+ uint32_t height = U32_AT(&buffer[dynSize + 56]);
+ mLastTrack->meta->setInt32(kKeyDisplayWidth, width >> 16);
+ mLastTrack->meta->setInt32(kKeyDisplayHeight, height >> 16);
+
+ return OK;
+}
+
+status_t MPEG4Extractor::parseITunesMetaData(off64_t offset, size_t size) {
+ if (size == 0) {
+ return OK;
+ }
+
+ if (size < 4 || size == SIZE_MAX) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t *buffer = new (std::nothrow) uint8_t[size + 1];
+ if (buffer == NULL) {
+ return ERROR_MALFORMED;
+ }
+ if (mDataSource->readAt(
+ offset, buffer, size) != (ssize_t)size) {
+ delete[] buffer;
+ buffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ uint32_t flags = U32_AT(buffer);
+
+ uint32_t metadataKey = 0;
+ char chunk[5];
+ MakeFourCCString(mPath[4], chunk);
+ ALOGV("meta: %s @ %lld", chunk, (long long)offset);
+ switch ((int32_t)mPath[4]) {
+ case FOURCC(0xa9, 'a', 'l', 'b'):
+ {
+ metadataKey = kKeyAlbum;
+ break;
+ }
+ case FOURCC(0xa9, 'A', 'R', 'T'):
+ {
+ metadataKey = kKeyArtist;
+ break;
+ }
+ case FOURCC('a', 'A', 'R', 'T'):
+ {
+ metadataKey = kKeyAlbumArtist;
+ break;
+ }
+ case FOURCC(0xa9, 'd', 'a', 'y'):
+ {
+ metadataKey = kKeyYear;
+ break;
+ }
+ case FOURCC(0xa9, 'n', 'a', 'm'):
+ {
+ metadataKey = kKeyTitle;
+ break;
+ }
+ case FOURCC(0xa9, 'w', 'r', 't'):
+ {
+ metadataKey = kKeyWriter;
+ break;
+ }
+ case FOURCC('c', 'o', 'v', 'r'):
+ {
+ metadataKey = kKeyAlbumArt;
+ break;
+ }
+ case FOURCC('g', 'n', 'r', 'e'):
+ {
+ metadataKey = kKeyGenre;
+ break;
+ }
+ case FOURCC(0xa9, 'g', 'e', 'n'):
+ {
+ metadataKey = kKeyGenre;
+ break;
+ }
+ case FOURCC('c', 'p', 'i', 'l'):
+ {
+ if (size == 9 && flags == 21) {
+ char tmp[16];
+ sprintf(tmp, "%d",
+ (int)buffer[size - 1]);
+
+ mFileMetaData->setCString(kKeyCompilation, tmp);
+ }
+ break;
+ }
+ case FOURCC('t', 'r', 'k', 'n'):
+ {
+ if (size == 16 && flags == 0) {
+ char tmp[16];
+ uint16_t* pTrack = (uint16_t*)&buffer[10];
+ uint16_t* pTotalTracks = (uint16_t*)&buffer[12];
+ sprintf(tmp, "%d/%d", ntohs(*pTrack), ntohs(*pTotalTracks));
+
+ mFileMetaData->setCString(kKeyCDTrackNumber, tmp);
+ }
+ break;
+ }
+ case FOURCC('d', 'i', 's', 'k'):
+ {
+ if ((size == 14 || size == 16) && flags == 0) {
+ char tmp[16];
+ uint16_t* pDisc = (uint16_t*)&buffer[10];
+ uint16_t* pTotalDiscs = (uint16_t*)&buffer[12];
+ sprintf(tmp, "%d/%d", ntohs(*pDisc), ntohs(*pTotalDiscs));
+
+ mFileMetaData->setCString(kKeyDiscNumber, tmp);
+ }
+ break;
+ }
+ case FOURCC('-', '-', '-', '-'):
+ {
+ buffer[size] = '\0';
+ switch (mPath[5]) {
+ case FOURCC('m', 'e', 'a', 'n'):
+ mLastCommentMean.setTo((const char *)buffer + 4);
+ break;
+ case FOURCC('n', 'a', 'm', 'e'):
+ mLastCommentName.setTo((const char *)buffer + 4);
+ break;
+ case FOURCC('d', 'a', 't', 'a'):
+ if (size < 8) {
+ delete[] buffer;
+ buffer = NULL;
+ ALOGE("b/24346430");
+ return ERROR_MALFORMED;
+ }
+ mLastCommentData.setTo((const char *)buffer + 8);
+ break;
+ }
+
+ // Once we have a set of mean/name/data info, go ahead and process
+ // it to see if its something we are interested in. Whether or not
+ // were are interested in the specific tag, make sure to clear out
+ // the set so we can be ready to process another tuple should one
+ // show up later in the file.
+ if ((mLastCommentMean.length() != 0) &&
+ (mLastCommentName.length() != 0) &&
+ (mLastCommentData.length() != 0)) {
+
+ if (mLastCommentMean == "com.apple.iTunes"
+ && mLastCommentName == "iTunSMPB") {
+ int32_t delay, padding;
+ if (sscanf(mLastCommentData,
+ " %*x %x %x %*x", &delay, &padding) == 2) {
+ if (mLastTrack == NULL) {
+ delete[] buffer;
+ return ERROR_MALFORMED;
+ }
+
+ mLastTrack->meta->setInt32(kKeyEncoderDelay, delay);
+ mLastTrack->meta->setInt32(kKeyEncoderPadding, padding);
+ }
+ }
+
+ mLastCommentMean.clear();
+ mLastCommentName.clear();
+ mLastCommentData.clear();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (size >= 8 && metadataKey && !mFileMetaData->hasData(metadataKey)) {
+ if (metadataKey == kKeyAlbumArt) {
+ mFileMetaData->setData(
+ kKeyAlbumArt, MetaData::TYPE_NONE,
+ buffer + 8, size - 8);
+ } else if (metadataKey == kKeyGenre) {
+ if (flags == 0) {
+ // uint8_t genre code, iTunes genre codes are
+ // the standard id3 codes, except they start
+ // at 1 instead of 0 (e.g. Pop is 14, not 13)
+ // We use standard id3 numbering, so subtract 1.
+ int genrecode = (int)buffer[size - 1];
+ genrecode--;
+ if (genrecode < 0) {
+ genrecode = 255; // reserved for 'unknown genre'
+ }
+ char genre[10];
+ sprintf(genre, "%d", genrecode);
+
+ mFileMetaData->setCString(metadataKey, genre);
+ } else if (flags == 1) {
+ // custom genre string
+ buffer[size] = '\0';
+
+ mFileMetaData->setCString(
+ metadataKey, (const char *)buffer + 8);
+ }
+ } else {
+ buffer[size] = '\0';
+
+ mFileMetaData->setCString(
+ metadataKey, (const char *)buffer + 8);
+ }
+ }
+
+ delete[] buffer;
+ buffer = NULL;
+
+ return OK;
+}
+
+status_t MPEG4Extractor::parseColorInfo(off64_t offset, size_t size) {
+ if (size < 4 || size == SIZE_MAX || mLastTrack == NULL) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t *buffer = new (std::nothrow) uint8_t[size + 1];
+ if (buffer == NULL) {
+ return ERROR_MALFORMED;
+ }
+ if (mDataSource->readAt(offset, buffer, size) != (ssize_t)size) {
+ delete[] buffer;
+ buffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ int32_t type = U32_AT(&buffer[0]);
+ if ((type == FOURCC('n', 'c', 'l', 'x') && size >= 11)
+ || (type == FOURCC('n', 'c', 'l', 'c') && size >= 10)) {
+ int32_t primaries = U16_AT(&buffer[4]);
+ int32_t transfer = U16_AT(&buffer[6]);
+ int32_t coeffs = U16_AT(&buffer[8]);
+ bool fullRange = (type == FOURCC('n', 'c', 'l', 'x')) && (buffer[10] & 128);
+
+ ColorAspects aspects;
+ ColorUtils::convertIsoColorAspectsToCodecAspects(
+ primaries, transfer, coeffs, fullRange, aspects);
+
+ // only store the first color specification
+ if (!mLastTrack->meta->hasData(kKeyColorPrimaries)) {
+ mLastTrack->meta->setInt32(kKeyColorPrimaries, aspects.mPrimaries);
+ mLastTrack->meta->setInt32(kKeyTransferFunction, aspects.mTransfer);
+ mLastTrack->meta->setInt32(kKeyColorMatrix, aspects.mMatrixCoeffs);
+ mLastTrack->meta->setInt32(kKeyColorRange, aspects.mRange);
+ }
+ }
+
+ delete[] buffer;
+ buffer = NULL;
+
+ return OK;
+}
+
+status_t MPEG4Extractor::parse3GPPMetaData(off64_t offset, size_t size, int depth) {
+ if (size < 4 || size == SIZE_MAX) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t *buffer = new (std::nothrow) uint8_t[size + 1];
+ if (buffer == NULL) {
+ return ERROR_MALFORMED;
+ }
+ if (mDataSource->readAt(
+ offset, buffer, size) != (ssize_t)size) {
+ delete[] buffer;
+ buffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ uint32_t metadataKey = 0;
+ switch (mPath[depth]) {
+ case FOURCC('t', 'i', 't', 'l'):
+ {
+ metadataKey = kKeyTitle;
+ break;
+ }
+ case FOURCC('p', 'e', 'r', 'f'):
+ {
+ metadataKey = kKeyArtist;
+ break;
+ }
+ case FOURCC('a', 'u', 't', 'h'):
+ {
+ metadataKey = kKeyWriter;
+ break;
+ }
+ case FOURCC('g', 'n', 'r', 'e'):
+ {
+ metadataKey = kKeyGenre;
+ break;
+ }
+ case FOURCC('a', 'l', 'b', 'm'):
+ {
+ if (buffer[size - 1] != '\0') {
+ char tmp[4];
+ sprintf(tmp, "%u", buffer[size - 1]);
+
+ mFileMetaData->setCString(kKeyCDTrackNumber, tmp);
+ }
+
+ metadataKey = kKeyAlbum;
+ break;
+ }
+ case FOURCC('y', 'r', 'r', 'c'):
+ {
+ if (size < 6) {
+ delete[] buffer;
+ buffer = NULL;
+ ALOGE("b/62133227");
+ android_errorWriteLog(0x534e4554, "62133227");
+ return ERROR_MALFORMED;
+ }
+ char tmp[5];
+ uint16_t year = U16_AT(&buffer[4]);
+
+ if (year < 10000) {
+ sprintf(tmp, "%u", year);
+
+ mFileMetaData->setCString(kKeyYear, tmp);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (metadataKey > 0) {
+ bool isUTF8 = true; // Common case
+ char16_t *framedata = NULL;
+ int len16 = 0; // Number of UTF-16 characters
+
+ // smallest possible valid UTF-16 string w BOM: 0xfe 0xff 0x00 0x00
+ if (size < 6) {
+ delete[] buffer;
+ buffer = NULL;
+ return ERROR_MALFORMED;
+ }
+
+ if (size - 6 >= 4) {
+ len16 = ((size - 6) / 2) - 1; // don't include 0x0000 terminator
+ framedata = (char16_t *)(buffer + 6);
+ if (0xfffe == *framedata) {
+ // endianness marker (BOM) doesn't match host endianness
+ for (int i = 0; i < len16; i++) {
+ framedata[i] = bswap_16(framedata[i]);
+ }
+ // BOM is now swapped to 0xfeff, we will execute next block too
+ }
+
+ if (0xfeff == *framedata) {
+ // Remove the BOM
+ framedata++;
+ len16--;
+ isUTF8 = false;
+ }
+ // else normal non-zero-length UTF-8 string
+ // we can't handle UTF-16 without BOM as there is no other
+ // indication of encoding.
+ }
+
+ if (isUTF8) {
+ buffer[size] = 0;
+ mFileMetaData->setCString(metadataKey, (const char *)buffer + 6);
+ } else {
+ // Convert from UTF-16 string to UTF-8 string.
+ String8 tmpUTF8str(framedata, len16);
+ mFileMetaData->setCString(metadataKey, tmpUTF8str.string());
+ }
+ }
+
+ delete[] buffer;
+ buffer = NULL;
+
+ return OK;
+}
+
+void MPEG4Extractor::parseID3v2MetaData(off64_t offset) {
+ ID3 id3(mDataSource, true /* ignorev1 */, offset);
+
+ if (id3.isValid()) {
+ struct Map {
+ int key;
+ const char *tag1;
+ const char *tag2;
+ };
+ static const Map kMap[] = {
+ { kKeyAlbum, "TALB", "TAL" },
+ { kKeyArtist, "TPE1", "TP1" },
+ { kKeyAlbumArtist, "TPE2", "TP2" },
+ { kKeyComposer, "TCOM", "TCM" },
+ { kKeyGenre, "TCON", "TCO" },
+ { kKeyTitle, "TIT2", "TT2" },
+ { kKeyYear, "TYE", "TYER" },
+ { kKeyAuthor, "TXT", "TEXT" },
+ { kKeyCDTrackNumber, "TRK", "TRCK" },
+ { kKeyDiscNumber, "TPA", "TPOS" },
+ { kKeyCompilation, "TCP", "TCMP" },
+ };
+ static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
+
+ for (size_t i = 0; i < kNumMapEntries; ++i) {
+ if (!mFileMetaData->hasData(kMap[i].key)) {
+ ID3::Iterator *it = new ID3::Iterator(id3, kMap[i].tag1);
+ if (it->done()) {
+ delete it;
+ it = new ID3::Iterator(id3, kMap[i].tag2);
+ }
+
+ if (it->done()) {
+ delete it;
+ continue;
+ }
+
+ String8 s;
+ it->getString(&s);
+ delete it;
+
+ mFileMetaData->setCString(kMap[i].key, s);
+ }
+ }
+
+ size_t dataSize;
+ String8 mime;
+ const void *data = id3.getAlbumArt(&dataSize, &mime);
+
+ if (data) {
+ mFileMetaData->setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize);
+ mFileMetaData->setCString(kKeyAlbumArtMIME, mime.string());
+ }
+ }
+}
+
+sp<MediaSource> MPEG4Extractor::getTrack(size_t index) {
+ 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;
+ }
+
+
+ Trex *trex = NULL;
+ int32_t trackId;
+ if (track->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) {
+ trex = t;
+ break;
+ }
+ }
+ } else {
+ ALOGE("b/21657957");
+ return NULL;
+ }
+
+ ALOGV("getTrack called, pssh: %zu", mPssh.size());
+
+ const char *mime;
+ if (!track->meta->findCString(kKeyMIMEType, &mime)) {
+ return NULL;
+ }
+
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!track->meta->findData(kKeyAVCC, &type, &data, &size)) {
+ return NULL;
+ }
+
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ if (size < 7 || ptr[0] != 1) { // configurationVersion == 1
+ return NULL;
+ }
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!track->meta->findData(kKeyHVCC, &type, &data, &size)) {
+ return NULL;
+ }
+
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ if (size < 22 || ptr[0] != 1) { // configurationVersion == 1
+ return NULL;
+ }
+ }
+
+ sp<MPEG4Source> source = new MPEG4Source(this,
+ track->meta, mDataSource, track->timescale, track->sampleTable,
+ mSidxEntries, trex, mMoofOffset, mItemTable);
+ if (source->init() != OK) {
+ return NULL;
+ }
+ return source;
+}
+
+// static
+status_t MPEG4Extractor::verifyTrack(Track *track) {
+ const char *mime;
+ CHECK(track->meta->findCString(kKeyMIMEType, &mime));
+
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ if (!track->meta->findData(kKeyAVCC, &type, &data, &size)
+ || type != kTypeAVCC) {
+ return ERROR_MALFORMED;
+ }
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) {
+ if (!track->meta->findData(kKeyHVCC, &type, &data, &size)
+ || type != kTypeHVCC) {
+ return ERROR_MALFORMED;
+ }
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)
+ || !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2)
+ || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
+ if (!track->meta->findData(kKeyESDS, &type, &data, &size)
+ || type != kTypeESDS) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ if (track->sampleTable == NULL || !track->sampleTable->isValid()) {
+ // Make sure we have all the metadata we need.
+ ALOGE("stbl atom missing/invalid.");
+ return ERROR_MALFORMED;
+ }
+
+ if (track->timescale == 0) {
+ ALOGE("timescale invalid.");
+ return ERROR_MALFORMED;
+ }
+
+ return OK;
+}
+
+typedef enum {
+ //AOT_NONE = -1,
+ //AOT_NULL_OBJECT = 0,
+ //AOT_AAC_MAIN = 1, /**< Main profile */
+ AOT_AAC_LC = 2, /**< Low Complexity object */
+ //AOT_AAC_SSR = 3,
+ //AOT_AAC_LTP = 4,
+ AOT_SBR = 5,
+ //AOT_AAC_SCAL = 6,
+ //AOT_TWIN_VQ = 7,
+ //AOT_CELP = 8,
+ //AOT_HVXC = 9,
+ //AOT_RSVD_10 = 10, /**< (reserved) */
+ //AOT_RSVD_11 = 11, /**< (reserved) */
+ //AOT_TTSI = 12, /**< TTSI Object */
+ //AOT_MAIN_SYNTH = 13, /**< Main Synthetic object */
+ //AOT_WAV_TAB_SYNTH = 14, /**< Wavetable Synthesis object */
+ //AOT_GEN_MIDI = 15, /**< General MIDI object */
+ //AOT_ALG_SYNTH_AUD_FX = 16, /**< Algorithmic Synthesis and Audio FX object */
+ AOT_ER_AAC_LC = 17, /**< Error Resilient(ER) AAC Low Complexity */
+ //AOT_RSVD_18 = 18, /**< (reserved) */
+ //AOT_ER_AAC_LTP = 19, /**< Error Resilient(ER) AAC LTP object */
+ AOT_ER_AAC_SCAL = 20, /**< Error Resilient(ER) AAC Scalable object */
+ //AOT_ER_TWIN_VQ = 21, /**< Error Resilient(ER) TwinVQ object */
+ AOT_ER_BSAC = 22, /**< Error Resilient(ER) BSAC object */
+ AOT_ER_AAC_LD = 23, /**< Error Resilient(ER) AAC LowDelay object */
+ //AOT_ER_CELP = 24, /**< Error Resilient(ER) CELP object */
+ //AOT_ER_HVXC = 25, /**< Error Resilient(ER) HVXC object */
+ //AOT_ER_HILN = 26, /**< Error Resilient(ER) HILN object */
+ //AOT_ER_PARA = 27, /**< Error Resilient(ER) Parametric object */
+ //AOT_RSVD_28 = 28, /**< might become SSC */
+ AOT_PS = 29, /**< PS, Parametric Stereo (includes SBR) */
+ //AOT_MPEGS = 30, /**< MPEG Surround */
+
+ AOT_ESCAPE = 31, /**< Signal AOT uses more than 5 bits */
+
+ //AOT_MP3ONMP4_L1 = 32, /**< MPEG-Layer1 in mp4 */
+ //AOT_MP3ONMP4_L2 = 33, /**< MPEG-Layer2 in mp4 */
+ //AOT_MP3ONMP4_L3 = 34, /**< MPEG-Layer3 in mp4 */
+ //AOT_RSVD_35 = 35, /**< might become DST */
+ //AOT_RSVD_36 = 36, /**< might become ALS */
+ //AOT_AAC_SLS = 37, /**< AAC + SLS */
+ //AOT_SLS = 38, /**< SLS */
+ //AOT_ER_AAC_ELD = 39, /**< AAC Enhanced Low Delay */
+
+ //AOT_USAC = 42, /**< USAC */
+ //AOT_SAOC = 43, /**< SAOC */
+ //AOT_LD_MPEGS = 44, /**< Low Delay MPEG Surround */
+
+ //AOT_RSVD50 = 50, /**< Interim AOT for Rsvd50 */
+} AUDIO_OBJECT_TYPE;
+
+status_t MPEG4Extractor::updateAudioTrackInfoFromESDS_MPEG4Audio(
+ const void *esds_data, size_t esds_size) {
+ ESDS esds(esds_data, esds_size);
+
+ uint8_t objectTypeIndication;
+ if (esds.getObjectTypeIndication(&objectTypeIndication) != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ if (objectTypeIndication == 0xe1) {
+ // This isn't MPEG4 audio at all, it's QCELP 14k...
+ if (mLastTrack == NULL)
+ return ERROR_MALFORMED;
+
+ mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_QCELP);
+ return OK;
+ }
+
+ if (objectTypeIndication == 0x6b) {
+ // The media subtype is MP3 audio
+ // Our software MP3 audio decoder may not be able to handle
+ // packetized MP3 audio; for now, lets just return ERROR_UNSUPPORTED
+ ALOGE("MP3 track in MP4/3GPP file is not supported");
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (mLastTrack != NULL) {
+ uint32_t maxBitrate = 0;
+ uint32_t avgBitrate = 0;
+ esds.getBitRate(&maxBitrate, &avgBitrate);
+ 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);
+ }
+ }
+
+ const uint8_t *csd;
+ size_t csd_size;
+ if (esds.getCodecSpecificInfo(
+ (const void **)&csd, &csd_size) != OK) {
+ return ERROR_MALFORMED;
+ }
+
+ if (kUseHexDump) {
+ printf("ESD of size %zu\n", csd_size);
+ hexdump(csd, csd_size);
+ }
+
+ 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;
+ }
+
+ static uint32_t kSamplingRate[] = {
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+ 16000, 12000, 11025, 8000, 7350
+ };
+
+ ABitReader br(csd, csd_size);
+ uint32_t objectType = br.getBits(5);
+
+ if (objectType == 31) { // AAC-ELD => additional 6 bits
+ objectType = 32 + br.getBits(6);
+ }
+
+ if (mLastTrack == NULL)
+ return ERROR_MALFORMED;
+
+ //keep AOT type
+ mLastTrack->meta->setInt32(kKeyAACAOT, objectType);
+
+ uint32_t freqIndex = br.getBits(4);
+
+ int32_t sampleRate = 0;
+ int32_t numChannels = 0;
+ if (freqIndex == 15) {
+ if (br.numBitsLeft() < 28) return ERROR_MALFORMED;
+ sampleRate = br.getBits(24);
+ numChannels = br.getBits(4);
+ } else {
+ if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
+ numChannels = br.getBits(4);
+
+ if (freqIndex == 13 || freqIndex == 14) {
+ return ERROR_MALFORMED;
+ }
+
+ sampleRate = kSamplingRate[freqIndex];
+ }
+
+ if (objectType == AOT_SBR || objectType == AOT_PS) {//SBR specific config per 14496-3 table 1.13
+ if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
+ uint32_t extFreqIndex = br.getBits(4);
+ int32_t extSampleRate __unused;
+ if (extFreqIndex == 15) {
+ if (csd_size < 8) {
+ return ERROR_MALFORMED;
+ }
+ if (br.numBitsLeft() < 24) return ERROR_MALFORMED;
+ extSampleRate = br.getBits(24);
+ } else {
+ if (extFreqIndex == 13 || extFreqIndex == 14) {
+ return ERROR_MALFORMED;
+ }
+ extSampleRate = kSamplingRate[extFreqIndex];
+ }
+ //TODO: save the extension sampling rate value in meta data =>
+ // mLastTrack->meta->setInt32(kKeyExtSampleRate, extSampleRate);
+ }
+
+ switch (numChannels) {
+ // values defined in 14496-3_2009 amendment-4 Table 1.19 - Channel Configuration
+ case 0:
+ case 1:// FC
+ case 2:// FL FR
+ case 3:// FC, FL FR
+ case 4:// FC, FL FR, RC
+ case 5:// FC, FL FR, SL SR
+ case 6:// FC, FL FR, SL SR, LFE
+ //numChannels already contains the right value
+ break;
+ case 11:// FC, FL FR, SL SR, RC, LFE
+ numChannels = 7;
+ break;
+ case 7: // FC, FCL FCR, FL FR, SL SR, LFE
+ case 12:// FC, FL FR, SL SR, RL RR, LFE
+ case 14:// FC, FL FR, SL SR, LFE, FHL FHR
+ numChannels = 8;
+ break;
+ default:
+ return ERROR_UNSUPPORTED;
+ }
+
+ {
+ if (objectType == AOT_SBR || objectType == AOT_PS) {
+ if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
+ objectType = br.getBits(5);
+
+ if (objectType == AOT_ESCAPE) {
+ if (br.numBitsLeft() < 6) return ERROR_MALFORMED;
+ objectType = 32 + br.getBits(6);
+ }
+ }
+ if (objectType == AOT_AAC_LC || objectType == AOT_ER_AAC_LC ||
+ objectType == AOT_ER_AAC_LD || objectType == AOT_ER_AAC_SCAL ||
+ objectType == AOT_ER_BSAC) {
+ if (br.numBitsLeft() < 2) return ERROR_MALFORMED;
+ const int32_t frameLengthFlag __unused = br.getBits(1);
+
+ const int32_t dependsOnCoreCoder = br.getBits(1);
+
+ if (dependsOnCoreCoder ) {
+ if (br.numBitsLeft() < 14) return ERROR_MALFORMED;
+ const int32_t coreCoderDelay __unused = br.getBits(14);
+ }
+
+ int32_t extensionFlag = -1;
+ if (br.numBitsLeft() > 0) {
+ extensionFlag = br.getBits(1);
+ } else {
+ switch (objectType) {
+ // 14496-3 4.5.1.1 extensionFlag
+ case AOT_AAC_LC:
+ extensionFlag = 0;
+ break;
+ case AOT_ER_AAC_LC:
+ case AOT_ER_AAC_SCAL:
+ case AOT_ER_BSAC:
+ case AOT_ER_AAC_LD:
+ extensionFlag = 1;
+ break;
+ default:
+ return ERROR_MALFORMED;
+ break;
+ }
+ ALOGW("csd missing extension flag; assuming %d for object type %u.",
+ extensionFlag, objectType);
+ }
+
+ if (numChannels == 0) {
+ int32_t channelsEffectiveNum = 0;
+ int32_t channelsNum = 0;
+ if (br.numBitsLeft() < 32) {
+ return ERROR_MALFORMED;
+ }
+ const int32_t ElementInstanceTag __unused = br.getBits(4);
+ const int32_t Profile __unused = br.getBits(2);
+ const int32_t SamplingFrequencyIndex __unused = br.getBits(4);
+ const int32_t NumFrontChannelElements = br.getBits(4);
+ const int32_t NumSideChannelElements = br.getBits(4);
+ const int32_t NumBackChannelElements = br.getBits(4);
+ const int32_t NumLfeChannelElements = br.getBits(2);
+ const int32_t NumAssocDataElements __unused = br.getBits(3);
+ const int32_t NumValidCcElements __unused = br.getBits(4);
+
+ const int32_t MonoMixdownPresent = br.getBits(1);
+
+ if (MonoMixdownPresent != 0) {
+ if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
+ const int32_t MonoMixdownElementNumber __unused = br.getBits(4);
+ }
+
+ if (br.numBitsLeft() < 1) return ERROR_MALFORMED;
+ const int32_t StereoMixdownPresent = br.getBits(1);
+ if (StereoMixdownPresent != 0) {
+ if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
+ const int32_t StereoMixdownElementNumber __unused = br.getBits(4);
+ }
+
+ if (br.numBitsLeft() < 1) return ERROR_MALFORMED;
+ const int32_t MatrixMixdownIndexPresent = br.getBits(1);
+ if (MatrixMixdownIndexPresent != 0) {
+ if (br.numBitsLeft() < 3) return ERROR_MALFORMED;
+ const int32_t MatrixMixdownIndex __unused = br.getBits(2);
+ const int32_t PseudoSurroundEnable __unused = br.getBits(1);
+ }
+
+ int i;
+ for (i=0; i < NumFrontChannelElements; i++) {
+ if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
+ const int32_t FrontElementIsCpe = br.getBits(1);
+ const int32_t FrontElementTagSelect __unused = br.getBits(4);
+ channelsNum += FrontElementIsCpe ? 2 : 1;
+ }
+
+ for (i=0; i < NumSideChannelElements; i++) {
+ if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
+ const int32_t SideElementIsCpe = br.getBits(1);
+ const int32_t SideElementTagSelect __unused = br.getBits(4);
+ channelsNum += SideElementIsCpe ? 2 : 1;
+ }
+
+ for (i=0; i < NumBackChannelElements; i++) {
+ if (br.numBitsLeft() < 5) return ERROR_MALFORMED;
+ const int32_t BackElementIsCpe = br.getBits(1);
+ const int32_t BackElementTagSelect __unused = br.getBits(4);
+ channelsNum += BackElementIsCpe ? 2 : 1;
+ }
+ channelsEffectiveNum = channelsNum;
+
+ for (i=0; i < NumLfeChannelElements; i++) {
+ if (br.numBitsLeft() < 4) return ERROR_MALFORMED;
+ const int32_t LfeElementTagSelect __unused = br.getBits(4);
+ channelsNum += 1;
+ }
+ ALOGV("mpeg4 audio channelsNum = %d", channelsNum);
+ ALOGV("mpeg4 audio channelsEffectiveNum = %d", channelsEffectiveNum);
+ numChannels = channelsNum;
+ }
+ }
+ }
+
+ if (numChannels == 0) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ if (mLastTrack == NULL)
+ return ERROR_MALFORMED;
+
+ int32_t prevSampleRate;
+ CHECK(mLastTrack->meta->findInt32(kKeySampleRate, &prevSampleRate));
+
+ if (prevSampleRate != sampleRate) {
+ ALOGV("mpeg4 audio sample rate different from previous setting. "
+ "was: %d, now: %d", prevSampleRate, sampleRate);
+ }
+
+ mLastTrack->meta->setInt32(kKeySampleRate, sampleRate);
+
+ int32_t prevChannelCount;
+ CHECK(mLastTrack->meta->findInt32(kKeyChannelCount, &prevChannelCount));
+
+ if (prevChannelCount != numChannels) {
+ ALOGV("mpeg4 audio channel count different from previous setting. "
+ "was: %d, now: %d", prevChannelCount, numChannels);
+ }
+
+ mLastTrack->meta->setInt32(kKeyChannelCount, numChannels);
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MPEG4Source::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,
+ const sp<ItemTable> &itemTable)
+ : mOwner(owner),
+ mFormat(format),
+ mDataSource(dataSource),
+ mTimescale(timeScale),
+ mSampleTable(sampleTable),
+ mCurrentSampleIndex(0),
+ mCurrentFragmentIndex(0),
+ mSegments(sidx),
+ mTrex(trex),
+ mFirstMoofOffset(firstMoofOffset),
+ mCurrentMoofOffset(firstMoofOffset),
+ mNextMoofOffset(-1),
+ mCurrentTime(0),
+ mCurrentSampleInfoAllocSize(0),
+ mCurrentSampleInfoSizes(NULL),
+ mCurrentSampleInfoOffsetsAllocSize(0),
+ mCurrentSampleInfoOffsets(NULL),
+ mIsAVC(false),
+ mIsHEVC(false),
+ mNALLengthSize(0),
+ mStarted(false),
+ mGroup(NULL),
+ mBuffer(NULL),
+ mWantsNALFragments(false),
+ mSrcBuffer(NULL),
+ mIsHEIF(itemTable != NULL),
+ mItemTable(itemTable) {
+
+ memset(&mTrackFragmentHeaderInfo, 0, sizeof(mTrackFragmentHeaderInfo));
+
+ mFormat->findInt32(kKeyCryptoMode, &mCryptoMode);
+ mDefaultIVSize = 0;
+ mFormat->findInt32(kKeyCryptoDefaultIVSize, &mDefaultIVSize);
+ uint32_t keytype;
+ const void *key;
+ size_t keysize;
+ if (mFormat->findData(kKeyCryptoKey, &keytype, &key, &keysize)) {
+ CHECK(keysize <= 16);
+ memset(mCryptoKey, 0, 16);
+ memcpy(mCryptoKey, key, keysize);
+ }
+
+ const char *mime;
+ bool success = mFormat->findCString(kKeyMIMEType, &mime);
+ CHECK(success);
+
+ mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+ mIsHEVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC);
+
+ if (mIsAVC) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ CHECK(format->findData(kKeyAVCC, &type, &data, &size));
+
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ CHECK(size >= 7);
+ CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1
+
+ // The number of bytes used to encode the length of a NAL unit.
+ mNALLengthSize = 1 + (ptr[4] & 3);
+ } else if (mIsHEVC) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ CHECK(format->findData(kKeyHVCC, &type, &data, &size));
+
+ const uint8_t *ptr = (const uint8_t *)data;
+
+ CHECK(size >= 22);
+ CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1
+
+ mNALLengthSize = 1 + (ptr[14 + 7] & 3);
+ }
+
+ CHECK(format->findInt32(kKeyTrackID, &mTrackId));
+
+}
+
+status_t MPEG4Source::init() {
+ if (mFirstMoofOffset != 0) {
+ off64_t offset = mFirstMoofOffset;
+ return parseChunk(&offset);
+ }
+ return OK;
+}
+
+MPEG4Source::~MPEG4Source() {
+ if (mStarted) {
+ stop();
+ }
+ free(mCurrentSampleInfoSizes);
+ free(mCurrentSampleInfoOffsets);
+}
+
+status_t MPEG4Source::start(MetaData *params) {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(!mStarted);
+
+ int32_t val;
+ if (params && params->findInt32(kKeyWantsNALFragments, &val)
+ && val != 0) {
+ mWantsNALFragments = true;
+ } else {
+ mWantsNALFragments = false;
+ }
+
+ int32_t tmp;
+ CHECK(mFormat->findInt32(kKeyMaxInputSize, &tmp));
+ size_t max_size = tmp;
+
+ // A somewhat arbitrary limit that should be sufficient for 8k video frames
+ // If you see the message below for a valid input stream: increase the limit
+ const size_t kMaxBufferSize = 64 * 1024 * 1024;
+ if (max_size > kMaxBufferSize) {
+ ALOGE("bogus max input size: %zu > %zu", max_size, kMaxBufferSize);
+ return ERROR_MALFORMED;
+ }
+ if (max_size == 0) {
+ ALOGE("zero max input size");
+ return ERROR_MALFORMED;
+ }
+
+ // Allow up to kMaxBuffers, but not if the total exceeds kMaxBufferSize.
+ const size_t kMaxBuffers = 8;
+ const size_t buffers = min(kMaxBufferSize / max_size, kMaxBuffers);
+ mGroup = new MediaBufferGroup(buffers, max_size);
+ mSrcBuffer = new (std::nothrow) uint8_t[max_size];
+ if (mSrcBuffer == NULL) {
+ // file probably specified a bad max size
+ delete mGroup;
+ mGroup = NULL;
+ return ERROR_MALFORMED;
+ }
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t MPEG4Source::stop() {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mStarted);
+
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ delete[] mSrcBuffer;
+ mSrcBuffer = NULL;
+
+ delete mGroup;
+ mGroup = NULL;
+
+ mStarted = false;
+ mCurrentSampleIndex = 0;
+
+ return OK;
+}
+
+status_t MPEG4Source::parseChunk(off64_t *offset) {
+ uint32_t hdr[2];
+ if (mDataSource->readAt(*offset, hdr, 8) < 8) {
+ return ERROR_IO;
+ }
+ uint64_t chunk_size = ntohl(hdr[0]);
+ uint32_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 < 8) {
+ // The smallest valid chunk is 8 bytes long.
+ return ERROR_MALFORMED;
+ }
+
+ char chunk[5];
+ MakeFourCCString(chunk_type, chunk);
+ ALOGV("MPEG4Source chunk %s @ %#llx", chunk, (long long)*offset);
+
+ off64_t chunk_data_size = *offset + chunk_size - data_offset;
+
+ switch(chunk_type) {
+
+ case FOURCC('t', 'r', 'a', 'f'):
+ case FOURCC('m', 'o', 'o', 'f'): {
+ off64_t stop_offset = *offset + chunk_size;
+ *offset = data_offset;
+ while (*offset < stop_offset) {
+ status_t err = parseChunk(offset);
+ if (err != OK) {
+ return err;
+ }
+ }
+ if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
+ // *offset points to the box following this moof. Find the next moof from there.
+
+ while (true) {
+ if (mDataSource->readAt(*offset, hdr, 8) < 8) {
+ // no more box to the end of file.
+ break;
+ }
+ chunk_size = ntohl(hdr[0]);
+ chunk_type = ntohl(hdr[1]);
+ if (chunk_size == 1) {
+ // ISO/IEC 14496-12:2012, 8.8.4 Movie Fragment Box, moof is a Box
+ // which is defined in 4.2 Object Structure.
+ // When chunk_size==1, 8 bytes follows as "largesize".
+ if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
+ return ERROR_IO;
+ }
+ chunk_size = ntoh64(chunk_size);
+ if (chunk_size < 16) {
+ // The smallest valid chunk is 16 bytes long in this case.
+ return ERROR_MALFORMED;
+ }
+ } else if (chunk_size == 0) {
+ // next box extends to end of file.
+ } else if (chunk_size < 8) {
+ // The smallest valid chunk is 8 bytes long in this case.
+ return ERROR_MALFORMED;
+ }
+
+ if (chunk_type == FOURCC('m', 'o', 'o', 'f')) {
+ mNextMoofOffset = *offset;
+ break;
+ } else if (chunk_size == 0) {
+ break;
+ }
+ *offset += chunk_size;
+ }
+ }
+ break;
+ }
+
+ case FOURCC('t', 'f', 'h', 'd'): {
+ status_t err;
+ if ((err = parseTrackFragmentHeader(data_offset, chunk_data_size)) != OK) {
+ return err;
+ }
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('t', 'r', 'u', 'n'): {
+ status_t err;
+ if (mLastParsedTrackId == mTrackId) {
+ if ((err = parseTrackFragmentRun(data_offset, chunk_data_size)) != OK) {
+ return err;
+ }
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('s', 'a', 'i', 'z'): {
+ status_t err;
+ if ((err = parseSampleAuxiliaryInformationSizes(data_offset, chunk_data_size)) != OK) {
+ return err;
+ }
+ *offset += chunk_size;
+ break;
+ }
+ case FOURCC('s', 'a', 'i', 'o'): {
+ status_t err;
+ if ((err = parseSampleAuxiliaryInformationOffsets(data_offset, chunk_data_size)) != OK) {
+ return err;
+ }
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('m', 'd', 'a', 't'): {
+ // parse DRM info if present
+ ALOGV("MPEG4Source::parseChunk mdat");
+ // if saiz/saoi was previously observed, do something with the sampleinfos
+ *offset += chunk_size;
+ break;
+ }
+
+ default: {
+ *offset += chunk_size;
+ break;
+ }
+ }
+ return OK;
+}
+
+status_t MPEG4Source::parseSampleAuxiliaryInformationSizes(
+ off64_t offset, off64_t /* size */) {
+ ALOGV("parseSampleAuxiliaryInformationSizes");
+ // 14496-12 8.7.12
+ uint8_t version;
+ if (mDataSource->readAt(
+ offset, &version, sizeof(version))
+ < (ssize_t)sizeof(version)) {
+ return ERROR_IO;
+ }
+
+ if (version != 0) {
+ return ERROR_UNSUPPORTED;
+ }
+ offset++;
+
+ uint32_t flags;
+ if (!mDataSource->getUInt24(offset, &flags)) {
+ return ERROR_IO;
+ }
+ offset += 3;
+
+ if (flags & 1) {
+ uint32_t tmp;
+ if (!mDataSource->getUInt32(offset, &tmp)) {
+ return ERROR_MALFORMED;
+ }
+ mCurrentAuxInfoType = tmp;
+ offset += 4;
+ if (!mDataSource->getUInt32(offset, &tmp)) {
+ return ERROR_MALFORMED;
+ }
+ mCurrentAuxInfoTypeParameter = tmp;
+ offset += 4;
+ }
+
+ uint8_t defsize;
+ if (mDataSource->readAt(offset, &defsize, 1) != 1) {
+ return ERROR_MALFORMED;
+ }
+ mCurrentDefaultSampleInfoSize = defsize;
+ offset++;
+
+ uint32_t smplcnt;
+ if (!mDataSource->getUInt32(offset, &smplcnt)) {
+ return ERROR_MALFORMED;
+ }
+ mCurrentSampleInfoCount = smplcnt;
+ offset += 4;
+
+ if (mCurrentDefaultSampleInfoSize != 0) {
+ ALOGV("@@@@ using default sample info size of %d", mCurrentDefaultSampleInfoSize);
+ return OK;
+ }
+ if (smplcnt > mCurrentSampleInfoAllocSize) {
+ uint8_t * newPtr = (uint8_t*) realloc(mCurrentSampleInfoSizes, smplcnt);
+ if (newPtr == NULL) {
+ ALOGE("failed to realloc %u -> %u", mCurrentSampleInfoAllocSize, smplcnt);
+ return NO_MEMORY;
+ }
+ mCurrentSampleInfoSizes = newPtr;
+ mCurrentSampleInfoAllocSize = smplcnt;
+ }
+
+ mDataSource->readAt(offset, mCurrentSampleInfoSizes, smplcnt);
+ return OK;
+}
+
+status_t MPEG4Source::parseSampleAuxiliaryInformationOffsets(
+ off64_t offset, off64_t /* size */) {
+ ALOGV("parseSampleAuxiliaryInformationOffsets");
+ // 14496-12 8.7.13
+ uint8_t version;
+ if (mDataSource->readAt(offset, &version, sizeof(version)) != 1) {
+ return ERROR_IO;
+ }
+ offset++;
+
+ uint32_t flags;
+ if (!mDataSource->getUInt24(offset, &flags)) {
+ return ERROR_IO;
+ }
+ offset += 3;
+
+ uint32_t entrycount;
+ if (!mDataSource->getUInt32(offset, &entrycount)) {
+ return ERROR_IO;
+ }
+ offset += 4;
+ if (entrycount == 0) {
+ return OK;
+ }
+ if (entrycount > UINT32_MAX / 8) {
+ return ERROR_MALFORMED;
+ }
+
+ if (entrycount > mCurrentSampleInfoOffsetsAllocSize) {
+ uint64_t *newPtr = (uint64_t *)realloc(mCurrentSampleInfoOffsets, entrycount * 8);
+ if (newPtr == NULL) {
+ ALOGE("failed to realloc %u -> %u", mCurrentSampleInfoOffsetsAllocSize, entrycount * 8);
+ return NO_MEMORY;
+ }
+ mCurrentSampleInfoOffsets = newPtr;
+ mCurrentSampleInfoOffsetsAllocSize = entrycount;
+ }
+ mCurrentSampleInfoOffsetCount = entrycount;
+
+ if (mCurrentSampleInfoOffsets == NULL) {
+ return OK;
+ }
+
+ for (size_t i = 0; i < entrycount; i++) {
+ if (version == 0) {
+ uint32_t tmp;
+ if (!mDataSource->getUInt32(offset, &tmp)) {
+ return ERROR_IO;
+ }
+ mCurrentSampleInfoOffsets[i] = tmp;
+ offset += 4;
+ } else {
+ uint64_t tmp;
+ if (!mDataSource->getUInt64(offset, &tmp)) {
+ return ERROR_IO;
+ }
+ mCurrentSampleInfoOffsets[i] = tmp;
+ offset += 8;
+ }
+ }
+
+ // parse clear/encrypted data
+
+ off64_t drmoffset = mCurrentSampleInfoOffsets[0]; // from moof
+
+ drmoffset += mCurrentMoofOffset;
+ int ivlength;
+ CHECK(mFormat->findInt32(kKeyCryptoDefaultIVSize, &ivlength));
+
+ // only 0, 8 and 16 byte initialization vectors are supported
+ if (ivlength != 0 && ivlength != 8 && ivlength != 16) {
+ ALOGW("unsupported IV length: %d", ivlength);
+ return ERROR_MALFORMED;
+ }
+ // read CencSampleAuxiliaryDataFormats
+ for (size_t i = 0; i < mCurrentSampleInfoCount; i++) {
+ if (i >= mCurrentSamples.size()) {
+ ALOGW("too few samples");
+ break;
+ }
+ Sample *smpl = &mCurrentSamples.editItemAt(i);
+
+ memset(smpl->iv, 0, 16);
+ if (mDataSource->readAt(drmoffset, smpl->iv, ivlength) != ivlength) {
+ return ERROR_IO;
+ }
+
+ drmoffset += ivlength;
+
+ int32_t smplinfosize = mCurrentDefaultSampleInfoSize;
+ if (smplinfosize == 0) {
+ smplinfosize = mCurrentSampleInfoSizes[i];
+ }
+ if (smplinfosize > ivlength) {
+ uint16_t numsubsamples;
+ if (!mDataSource->getUInt16(drmoffset, &numsubsamples)) {
+ return ERROR_IO;
+ }
+ drmoffset += 2;
+ for (size_t j = 0; j < numsubsamples; j++) {
+ uint16_t numclear;
+ uint32_t numencrypted;
+ if (!mDataSource->getUInt16(drmoffset, &numclear)) {
+ return ERROR_IO;
+ }
+ drmoffset += 2;
+ if (!mDataSource->getUInt32(drmoffset, &numencrypted)) {
+ return ERROR_IO;
+ }
+ drmoffset += 4;
+ smpl->clearsizes.add(numclear);
+ smpl->encryptedsizes.add(numencrypted);
+ }
+ } else {
+ smpl->clearsizes.add(0);
+ smpl->encryptedsizes.add(smpl->size);
+ }
+ }
+
+
+ return OK;
+}
+
+status_t MPEG4Source::parseTrackFragmentHeader(off64_t offset, off64_t size) {
+
+ if (size < 8) {
+ return -EINVAL;
+ }
+
+ uint32_t flags;
+ if (!mDataSource->getUInt32(offset, &flags)) { // actually version + flags
+ return ERROR_MALFORMED;
+ }
+
+ if (flags & 0xff000000) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt32(offset + 4, (uint32_t*)&mLastParsedTrackId)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (mLastParsedTrackId != mTrackId) {
+ // this is not the right track, skip it
+ return OK;
+ }
+
+ mTrackFragmentHeaderInfo.mFlags = flags;
+ mTrackFragmentHeaderInfo.mTrackID = mLastParsedTrackId;
+ offset += 8;
+ size -= 8;
+
+ ALOGV("fragment header: %08x %08x", flags, mTrackFragmentHeaderInfo.mTrackID);
+
+ if (flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent) {
+ if (size < 8) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt64(offset, &mTrackFragmentHeaderInfo.mBaseDataOffset)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 8;
+ size -= 8;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kSampleDescriptionIndexPresent) {
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mSampleDescriptionIndex)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ size -= 4;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kDefaultSampleDurationPresent) {
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mDefaultSampleDuration)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ size -= 4;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) {
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mDefaultSampleSize)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ size -= 4;
+ }
+
+ if (flags & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) {
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt32(offset, &mTrackFragmentHeaderInfo.mDefaultSampleFlags)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ size -= 4;
+ }
+
+ if (!(flags & TrackFragmentHeaderInfo::kBaseDataOffsetPresent)) {
+ mTrackFragmentHeaderInfo.mBaseDataOffset = mCurrentMoofOffset;
+ }
+
+ mTrackFragmentHeaderInfo.mDataOffset = 0;
+ return OK;
+}
+
+status_t MPEG4Source::parseTrackFragmentRun(off64_t offset, off64_t size) {
+
+ ALOGV("MPEG4Extractor::parseTrackFragmentRun");
+ if (size < 8) {
+ return -EINVAL;
+ }
+
+ enum {
+ kDataOffsetPresent = 0x01,
+ kFirstSampleFlagsPresent = 0x04,
+ kSampleDurationPresent = 0x100,
+ kSampleSizePresent = 0x200,
+ kSampleFlagsPresent = 0x400,
+ kSampleCompositionTimeOffsetPresent = 0x800,
+ };
+
+ uint32_t flags;
+ if (!mDataSource->getUInt32(offset, &flags)) {
+ return ERROR_MALFORMED;
+ }
+ // |version| only affects SampleCompositionTimeOffset field.
+ // If version == 0, SampleCompositionTimeOffset is uint32_t;
+ // Otherwise, SampleCompositionTimeOffset is int32_t.
+ // Sample.compositionOffset is defined as int32_t.
+ uint8_t version = flags >> 24;
+ flags &= 0xffffff;
+ ALOGV("fragment run version: 0x%02x, flags: 0x%06x", version, flags);
+
+ if ((flags & kFirstSampleFlagsPresent) && (flags & kSampleFlagsPresent)) {
+ // These two shall not be used together.
+ return -EINVAL;
+ }
+
+ uint32_t sampleCount;
+ if (!mDataSource->getUInt32(offset + 4, &sampleCount)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 8;
+ size -= 8;
+
+ uint64_t dataOffset = mTrackFragmentHeaderInfo.mDataOffset;
+
+ uint32_t firstSampleFlags = 0;
+
+ if (flags & kDataOffsetPresent) {
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ int32_t dataOffsetDelta;
+ if (!mDataSource->getUInt32(offset, (uint32_t*)&dataOffsetDelta)) {
+ return ERROR_MALFORMED;
+ }
+
+ dataOffset = mTrackFragmentHeaderInfo.mBaseDataOffset + dataOffsetDelta;
+
+ offset += 4;
+ size -= 4;
+ }
+
+ if (flags & kFirstSampleFlagsPresent) {
+ if (size < 4) {
+ return -EINVAL;
+ }
+
+ if (!mDataSource->getUInt32(offset, &firstSampleFlags)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ size -= 4;
+ }
+
+ 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 if (mTrex) {
+ sampleDuration = mTrex->default_sample_duration;
+ }
+
+ if (flags & kSampleSizePresent) {
+ bytesPerSample += 4;
+ } else if (mTrackFragmentHeaderInfo.mFlags
+ & TrackFragmentHeaderInfo::kDefaultSampleSizePresent) {
+ sampleSize = mTrackFragmentHeaderInfo.mDefaultSampleSize;
+ } else {
+ sampleSize = mTrackFragmentHeaderInfo.mDefaultSampleSize;
+ }
+
+ if (flags & kSampleFlagsPresent) {
+ bytesPerSample += 4;
+ } else if (mTrackFragmentHeaderInfo.mFlags
+ & TrackFragmentHeaderInfo::kDefaultSampleFlagsPresent) {
+ sampleFlags = mTrackFragmentHeaderInfo.mDefaultSampleFlags;
+ } else {
+ sampleFlags = mTrackFragmentHeaderInfo.mDefaultSampleFlags;
+ }
+
+ if (flags & kSampleCompositionTimeOffsetPresent) {
+ bytesPerSample += 4;
+ } else {
+ sampleCtsOffset = 0;
+ }
+
+ if (size < (off64_t)(sampleCount * bytesPerSample)) {
+ return -EINVAL;
+ }
+
+ Sample tmp;
+ for (uint32_t i = 0; i < sampleCount; ++i) {
+ if (flags & kSampleDurationPresent) {
+ if (!mDataSource->getUInt32(offset, &sampleDuration)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ }
+
+ if (flags & kSampleSizePresent) {
+ if (!mDataSource->getUInt32(offset, &sampleSize)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ }
+
+ if (flags & kSampleFlagsPresent) {
+ if (!mDataSource->getUInt32(offset, &sampleFlags)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ }
+
+ if (flags & kSampleCompositionTimeOffsetPresent) {
+ if (!mDataSource->getUInt32(offset, &sampleCtsOffset)) {
+ return ERROR_MALFORMED;
+ }
+ offset += 4;
+ }
+
+ ALOGV("adding sample %d at offset 0x%08" PRIx64 ", size %u, duration %u, "
+ " flags 0x%08x", i + 1,
+ dataOffset, sampleSize, sampleDuration,
+ (flags & kFirstSampleFlagsPresent) && i == 0
+ ? firstSampleFlags : sampleFlags);
+ tmp.offset = dataOffset;
+ tmp.size = sampleSize;
+ tmp.duration = sampleDuration;
+ tmp.compositionOffset = sampleCtsOffset;
+ mCurrentSamples.add(tmp);
+
+ dataOffset += sampleSize;
+ }
+
+ mTrackFragmentHeaderInfo.mDataOffset = dataOffset;
+
+ return OK;
+}
+
+sp<MetaData> MPEG4Source::getFormat() {
+ Mutex::Autolock autoLock(mLock);
+
+ return mFormat;
+}
+
+size_t MPEG4Source::parseNALSize(const uint8_t *data) const {
+ switch (mNALLengthSize) {
+ 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.
+ CHECK(!"Should not be here.");
+
+ return 0;
+}
+
+status_t MPEG4Source::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ Mutex::Autolock autoLock(mLock);
+
+ CHECK(mStarted);
+
+ if (options != nullptr && options->getNonBlocking() && !mGroup->has_buffers()) {
+ *out = nullptr;
+ return WOULD_BLOCK;
+ }
+
+ if (mFirstMoofOffset > 0) {
+ return fragmentedRead(out, options);
+ }
+
+ *out = NULL;
+
+ int64_t targetSampleTimeUs = -1;
+
+ int64_t seekTimeUs;
+ ReadOptions::SeekMode mode;
+ if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+ if (mIsHEIF) {
+ CHECK(mSampleTable == NULL);
+ CHECK(mItemTable != NULL);
+
+ status_t err;
+ if (seekTimeUs >= 0) {
+ err = mItemTable->findPrimaryImage(&mCurrentSampleIndex);
+ } else {
+ err = mItemTable->findThumbnail(&mCurrentSampleIndex);
+ }
+ if (err != OK) {
+ return err;
+ }
+ } else {
+ uint32_t findFlags = 0;
+ switch (mode) {
+ case ReadOptions::SEEK_PREVIOUS_SYNC:
+ findFlags = SampleTable::kFlagBefore;
+ break;
+ case ReadOptions::SEEK_NEXT_SYNC:
+ findFlags = SampleTable::kFlagAfter;
+ break;
+ case ReadOptions::SEEK_CLOSEST_SYNC:
+ case ReadOptions::SEEK_CLOSEST:
+ findFlags = SampleTable::kFlagClosest;
+ break;
+ default:
+ CHECK(!"Should not be here.");
+ break;
+ }
+
+ uint32_t sampleIndex;
+ status_t err = mSampleTable->findSampleAtTime(
+ seekTimeUs, 1000000, mTimescale,
+ &sampleIndex, findFlags);
+
+ if (mode == ReadOptions::SEEK_CLOSEST) {
+ // We found the closest sample already, now we want the sync
+ // sample preceding it (or the sample itself of course), even
+ // if the subsequent sync sample is closer.
+ findFlags = SampleTable::kFlagBefore;
+ }
+
+ uint32_t syncSampleIndex;
+ if (err == OK) {
+ err = mSampleTable->findSyncSampleNear(
+ sampleIndex, &syncSampleIndex, findFlags);
+ }
+
+ uint32_t sampleTime;
+ if (err == OK) {
+ err = mSampleTable->getMetaDataForSample(
+ sampleIndex, NULL, NULL, &sampleTime);
+ }
+
+ if (err != OK) {
+ if (err == ERROR_OUT_OF_RANGE) {
+ // An attempt to seek past the end of the stream would
+ // normally cause this ERROR_OUT_OF_RANGE error. Propagating
+ // this all the way to the MediaPlayer would cause abnormal
+ // termination. Legacy behaviour appears to be to behave as if
+ // we had seeked to the end of stream, ending normally.
+ err = ERROR_END_OF_STREAM;
+ }
+ ALOGV("end of stream");
+ return err;
+ }
+
+ if (mode == ReadOptions::SEEK_CLOSEST) {
+ targetSampleTimeUs = (sampleTime * 1000000ll) / mTimescale;
+ }
+
+#if 0
+ uint32_t syncSampleTime;
+ CHECK_EQ(OK, mSampleTable->getMetaDataForSample(
+ syncSampleIndex, NULL, NULL, &syncSampleTime));
+
+ ALOGI("seek to time %lld us => sample at time %lld us, "
+ "sync sample at time %lld us",
+ seekTimeUs,
+ sampleTime * 1000000ll / mTimescale,
+ syncSampleTime * 1000000ll / mTimescale);
+#endif
+
+ mCurrentSampleIndex = syncSampleIndex;
+ }
+
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ // fall through
+ }
+
+ off64_t offset = 0;
+ size_t size = 0;
+ uint32_t cts, stts;
+ bool isSyncSample;
+ bool newBuffer = false;
+ if (mBuffer == NULL) {
+ newBuffer = true;
+
+ status_t err;
+ if (!mIsHEIF) {
+ err = mSampleTable->getMetaDataForSample(
+ mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample, &stts);
+ } else {
+ err = mItemTable->getImageOffsetAndSize(
+ options && options->getSeekTo(&seekTimeUs, &mode) ?
+ &mCurrentSampleIndex : NULL, &offset, &size);
+
+ cts = stts = 0;
+ isSyncSample = 0;
+ ALOGV("image offset %lld, size %zu", (long long)offset, size);
+ }
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = mGroup->acquire_buffer(&mBuffer);
+
+ if (err != OK) {
+ CHECK(mBuffer == NULL);
+ return err;
+ }
+ if (size > mBuffer->size()) {
+ ALOGE("buffer too small: %zu > %zu", size, mBuffer->size());
+ return ERROR_BUFFER_TOO_SMALL;
+ }
+ }
+
+ if ((!mIsAVC && !mIsHEVC) || mWantsNALFragments) {
+ if (newBuffer) {
+ ssize_t num_bytes_read =
+ mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size);
+
+ if (num_bytes_read < (ssize_t)size) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(0, size);
+ mBuffer->meta_data()->clear();
+ mBuffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
+ mBuffer->meta_data()->setInt64(
+ kKeyDuration, ((int64_t)stts * 1000000) / mTimescale);
+
+ if (targetSampleTimeUs >= 0) {
+ mBuffer->meta_data()->setInt64(
+ kKeyTargetTime, targetSampleTimeUs);
+ }
+
+ if (isSyncSample) {
+ mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
+ ++mCurrentSampleIndex;
+ }
+
+ if (!mIsAVC && !mIsHEVC) {
+ *out = mBuffer;
+ mBuffer = NULL;
+
+ return OK;
+ }
+
+ // Each NAL unit is split up into its constituent fragments and
+ // each one of them returned in its own buffer.
+
+ CHECK(mBuffer->range_length() >= mNALLengthSize);
+
+ const uint8_t *src =
+ (const uint8_t *)mBuffer->data() + mBuffer->range_offset();
+
+ size_t nal_size = parseNALSize(src);
+ if (mNALLengthSize > SIZE_MAX - nal_size) {
+ ALOGE("b/24441553, b/24445122");
+ }
+ if (mBuffer->range_length() - mNALLengthSize < nal_size) {
+ ALOGE("incomplete NAL unit.");
+
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return ERROR_MALFORMED;
+ }
+
+ MediaBuffer *clone = mBuffer->clone();
+ CHECK(clone != NULL);
+ clone->set_range(mBuffer->range_offset() + mNALLengthSize, nal_size);
+
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(
+ mBuffer->range_offset() + mNALLengthSize + nal_size,
+ mBuffer->range_length() - mNALLengthSize - nal_size);
+
+ if (mBuffer->range_length() == 0) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ *out = clone;
+
+ return OK;
+ } else {
+ // Whole NAL units are returned but each fragment is prefixed by
+ // the start code (0x00 00 00 01).
+ ssize_t num_bytes_read = 0;
+ int32_t drm = 0;
+ bool usesDRM = (mFormat->findInt32(kKeyIsDRM, &drm) && drm != 0);
+ if (usesDRM) {
+ num_bytes_read =
+ mDataSource->readAt(offset, (uint8_t*)mBuffer->data(), size);
+ } else {
+ num_bytes_read = mDataSource->readAt(offset, mSrcBuffer, size);
+ }
+
+ if (num_bytes_read < (ssize_t)size) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ if (usesDRM) {
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(0, size);
+
+ } else {
+ uint8_t *dstData = (uint8_t *)mBuffer->data();
+ size_t srcOffset = 0;
+ size_t dstOffset = 0;
+
+ while (srcOffset < size) {
+ bool isMalFormed = !isInRange((size_t)0u, size, srcOffset, mNALLengthSize);
+ size_t nalLength = 0;
+ if (!isMalFormed) {
+ nalLength = parseNALSize(&mSrcBuffer[srcOffset]);
+ srcOffset += mNALLengthSize;
+ isMalFormed = !isInRange((size_t)0u, size, srcOffset, nalLength);
+ }
+
+ if (isMalFormed) {
+ ALOGE("Video is malformed");
+ mBuffer->release();
+ mBuffer = NULL;
+ return ERROR_MALFORMED;
+ }
+
+ if (nalLength == 0) {
+ continue;
+ }
+
+ if (dstOffset > SIZE_MAX - 4 ||
+ dstOffset + 4 > SIZE_MAX - nalLength ||
+ dstOffset + 4 + nalLength > mBuffer->size()) {
+ ALOGE("b/27208621 : %zu %zu", dstOffset, mBuffer->size());
+ android_errorWriteLog(0x534e4554, "27208621");
+ mBuffer->release();
+ mBuffer = NULL;
+ return ERROR_MALFORMED;
+ }
+
+ dstData[dstOffset++] = 0;
+ dstData[dstOffset++] = 0;
+ dstData[dstOffset++] = 0;
+ dstData[dstOffset++] = 1;
+ memcpy(&dstData[dstOffset], &mSrcBuffer[srcOffset], nalLength);
+ srcOffset += nalLength;
+ dstOffset += nalLength;
+ }
+ CHECK_EQ(srcOffset, size);
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(0, dstOffset);
+ }
+
+ mBuffer->meta_data()->clear();
+ mBuffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
+ mBuffer->meta_data()->setInt64(
+ kKeyDuration, ((int64_t)stts * 1000000) / mTimescale);
+
+ if (targetSampleTimeUs >= 0) {
+ mBuffer->meta_data()->setInt64(
+ kKeyTargetTime, targetSampleTimeUs);
+ }
+
+ if (mIsAVC) {
+ uint32_t layerId = FindAVCLayerId(
+ (const uint8_t *)mBuffer->data(), mBuffer->range_length());
+ mBuffer->meta_data()->setInt32(kKeyTemporalLayerId, layerId);
+ }
+
+ if (isSyncSample) {
+ mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
+ ++mCurrentSampleIndex;
+
+ *out = mBuffer;
+ mBuffer = NULL;
+
+ return OK;
+ }
+}
+
+status_t MPEG4Source::fragmentedRead(
+ MediaBuffer **out, const ReadOptions *options) {
+
+ ALOGV("MPEG4Source::fragmentedRead");
+
+ CHECK(mStarted);
+
+ *out = NULL;
+
+ int64_t targetSampleTimeUs = -1;
+
+ int64_t seekTimeUs;
+ ReadOptions::SeekMode mode;
+ if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+
+ int numSidxEntries = mSegments.size();
+ if (numSidxEntries != 0) {
+ int64_t totalTime = 0;
+ off64_t totalOffset = mFirstMoofOffset;
+ for (int i = 0; i < numSidxEntries; i++) {
+ const SidxEntry *se = &mSegments[i];
+ if (totalTime + se->mDurationUs > seekTimeUs) {
+ // The requested time is somewhere in this segment
+ if ((mode == ReadOptions::SEEK_NEXT_SYNC && seekTimeUs > totalTime) ||
+ (mode == ReadOptions::SEEK_CLOSEST_SYNC &&
+ (seekTimeUs - totalTime) > (totalTime + se->mDurationUs - seekTimeUs))) {
+ // requested next sync, or closest sync and it was closer to the end of
+ // this segment
+ totalTime += se->mDurationUs;
+ totalOffset += se->mSize;
+ }
+ break;
+ }
+ totalTime += se->mDurationUs;
+ totalOffset += se->mSize;
+ }
+ mCurrentMoofOffset = totalOffset;
+ mNextMoofOffset = -1;
+ mCurrentSamples.clear();
+ mCurrentSampleIndex = 0;
+ status_t err = parseChunk(&totalOffset);
+ if (err != OK) {
+ return err;
+ }
+ mCurrentTime = totalTime * mTimescale / 1000000ll;
+ } else {
+ // without sidx boxes, we can only seek to 0
+ mCurrentMoofOffset = mFirstMoofOffset;
+ mNextMoofOffset = -1;
+ mCurrentSamples.clear();
+ mCurrentSampleIndex = 0;
+ off64_t tmp = mCurrentMoofOffset;
+ status_t err = parseChunk(&tmp);
+ if (err != OK) {
+ return err;
+ }
+ mCurrentTime = 0;
+ }
+
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ // fall through
+ }
+
+ off64_t offset = 0;
+ size_t size = 0;
+ uint32_t cts = 0;
+ bool isSyncSample = false;
+ bool newBuffer = false;
+ if (mBuffer == NULL) {
+ newBuffer = true;
+
+ if (mCurrentSampleIndex >= mCurrentSamples.size()) {
+ // move to next fragment if there is one
+ if (mNextMoofOffset <= mCurrentMoofOffset) {
+ return ERROR_END_OF_STREAM;
+ }
+ off64_t nextMoof = mNextMoofOffset;
+ mCurrentMoofOffset = nextMoof;
+ mCurrentSamples.clear();
+ mCurrentSampleIndex = 0;
+ status_t err = parseChunk(&nextMoof);
+ if (err != OK) {
+ return err;
+ }
+ if (mCurrentSampleIndex >= mCurrentSamples.size()) {
+ return ERROR_END_OF_STREAM;
+ }
+ }
+
+ const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex];
+ offset = smpl->offset;
+ size = smpl->size;
+ cts = mCurrentTime + smpl->compositionOffset;
+ mCurrentTime += smpl->duration;
+ isSyncSample = (mCurrentSampleIndex == 0); // XXX
+
+ status_t err = mGroup->acquire_buffer(&mBuffer);
+
+ if (err != OK) {
+ CHECK(mBuffer == NULL);
+ ALOGV("acquire_buffer returned %d", err);
+ return err;
+ }
+ if (size > mBuffer->size()) {
+ ALOGE("buffer too small: %zu > %zu", size, mBuffer->size());
+ return ERROR_BUFFER_TOO_SMALL;
+ }
+ }
+
+ const Sample *smpl = &mCurrentSamples[mCurrentSampleIndex];
+ const sp<MetaData> bufmeta = mBuffer->meta_data();
+ bufmeta->clear();
+ if (smpl->encryptedsizes.size()) {
+ // store clear/encrypted lengths in metadata
+ bufmeta->setData(kKeyPlainSizes, 0,
+ smpl->clearsizes.array(), smpl->clearsizes.size() * 4);
+ bufmeta->setData(kKeyEncryptedSizes, 0,
+ smpl->encryptedsizes.array(), smpl->encryptedsizes.size() * 4);
+ bufmeta->setData(kKeyCryptoIV, 0, smpl->iv, 16); // use 16 or the actual size?
+ bufmeta->setInt32(kKeyCryptoDefaultIVSize, mDefaultIVSize);
+ bufmeta->setInt32(kKeyCryptoMode, mCryptoMode);
+ bufmeta->setData(kKeyCryptoKey, 0, mCryptoKey, 16);
+ }
+
+ if ((!mIsAVC && !mIsHEVC)|| mWantsNALFragments) {
+ if (newBuffer) {
+ if (!isInRange((size_t)0u, mBuffer->size(), size)) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ ALOGE("fragmentedRead ERROR_MALFORMED size %zu", size);
+ return ERROR_MALFORMED;
+ }
+
+ ssize_t num_bytes_read =
+ mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size);
+
+ if (num_bytes_read < (ssize_t)size) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ ALOGE("i/o error");
+ return ERROR_IO;
+ }
+
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(0, size);
+ mBuffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
+ mBuffer->meta_data()->setInt64(
+ kKeyDuration, ((int64_t)smpl->duration * 1000000) / mTimescale);
+
+ if (targetSampleTimeUs >= 0) {
+ mBuffer->meta_data()->setInt64(
+ kKeyTargetTime, targetSampleTimeUs);
+ }
+
+ if (mIsAVC) {
+ uint32_t layerId = FindAVCLayerId(
+ (const uint8_t *)mBuffer->data(), mBuffer->range_length());
+ mBuffer->meta_data()->setInt32(kKeyTemporalLayerId, layerId);
+ }
+
+ if (isSyncSample) {
+ mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
+ ++mCurrentSampleIndex;
+ }
+
+ if (!mIsAVC && !mIsHEVC) {
+ *out = mBuffer;
+ mBuffer = NULL;
+
+ return OK;
+ }
+
+ // Each NAL unit is split up into its constituent fragments and
+ // each one of them returned in its own buffer.
+
+ CHECK(mBuffer->range_length() >= mNALLengthSize);
+
+ const uint8_t *src =
+ (const uint8_t *)mBuffer->data() + mBuffer->range_offset();
+
+ size_t nal_size = parseNALSize(src);
+ if (mNALLengthSize > SIZE_MAX - nal_size) {
+ ALOGE("b/24441553, b/24445122");
+ }
+
+ if (mBuffer->range_length() - mNALLengthSize < nal_size) {
+ ALOGE("incomplete NAL unit.");
+
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return ERROR_MALFORMED;
+ }
+
+ MediaBuffer *clone = mBuffer->clone();
+ CHECK(clone != NULL);
+ clone->set_range(mBuffer->range_offset() + mNALLengthSize, nal_size);
+
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(
+ mBuffer->range_offset() + mNALLengthSize + nal_size,
+ mBuffer->range_length() - mNALLengthSize - nal_size);
+
+ if (mBuffer->range_length() == 0) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ *out = clone;
+
+ return OK;
+ } else {
+ ALOGV("whole NAL");
+ // Whole NAL units are returned but each fragment is prefixed by
+ // the start code (0x00 00 00 01).
+ ssize_t num_bytes_read = 0;
+ int32_t drm = 0;
+ bool usesDRM = (mFormat->findInt32(kKeyIsDRM, &drm) && drm != 0);
+ void *data = NULL;
+ bool isMalFormed = false;
+ if (usesDRM) {
+ if (mBuffer == NULL || !isInRange((size_t)0u, mBuffer->size(), size)) {
+ isMalFormed = true;
+ } else {
+ data = mBuffer->data();
+ }
+ } else {
+ int32_t max_size;
+ if (mFormat == NULL
+ || !mFormat->findInt32(kKeyMaxInputSize, &max_size)
+ || !isInRange((size_t)0u, (size_t)max_size, size)) {
+ isMalFormed = true;
+ } else {
+ data = mSrcBuffer;
+ }
+ }
+
+ if (isMalFormed || data == NULL) {
+ ALOGE("isMalFormed size %zu", size);
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+ return ERROR_MALFORMED;
+ }
+ num_bytes_read = mDataSource->readAt(offset, data, size);
+
+ if (num_bytes_read < (ssize_t)size) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ ALOGE("i/o error");
+ return ERROR_IO;
+ }
+
+ if (usesDRM) {
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(0, size);
+
+ } else {
+ uint8_t *dstData = (uint8_t *)mBuffer->data();
+ size_t srcOffset = 0;
+ size_t dstOffset = 0;
+
+ while (srcOffset < size) {
+ isMalFormed = !isInRange((size_t)0u, size, srcOffset, mNALLengthSize);
+ size_t nalLength = 0;
+ if (!isMalFormed) {
+ nalLength = parseNALSize(&mSrcBuffer[srcOffset]);
+ srcOffset += mNALLengthSize;
+ isMalFormed = !isInRange((size_t)0u, size, srcOffset, nalLength)
+ || !isInRange((size_t)0u, mBuffer->size(), dstOffset, (size_t)4u)
+ || !isInRange((size_t)0u, mBuffer->size(), dstOffset + 4, nalLength);
+ }
+
+ if (isMalFormed) {
+ ALOGE("Video is malformed; nalLength %zu", nalLength);
+ mBuffer->release();
+ mBuffer = NULL;
+ return ERROR_MALFORMED;
+ }
+
+ if (nalLength == 0) {
+ continue;
+ }
+
+ if (dstOffset > SIZE_MAX - 4 ||
+ dstOffset + 4 > SIZE_MAX - nalLength ||
+ dstOffset + 4 + nalLength > mBuffer->size()) {
+ ALOGE("b/26365349 : %zu %zu", dstOffset, mBuffer->size());
+ android_errorWriteLog(0x534e4554, "26365349");
+ mBuffer->release();
+ mBuffer = NULL;
+ return ERROR_MALFORMED;
+ }
+
+ dstData[dstOffset++] = 0;
+ dstData[dstOffset++] = 0;
+ dstData[dstOffset++] = 0;
+ dstData[dstOffset++] = 1;
+ memcpy(&dstData[dstOffset], &mSrcBuffer[srcOffset], nalLength);
+ srcOffset += nalLength;
+ dstOffset += nalLength;
+ }
+ CHECK_EQ(srcOffset, size);
+ CHECK(mBuffer != NULL);
+ mBuffer->set_range(0, dstOffset);
+ }
+
+ mBuffer->meta_data()->setInt64(
+ kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
+ mBuffer->meta_data()->setInt64(
+ kKeyDuration, ((int64_t)smpl->duration * 1000000) / mTimescale);
+
+ if (targetSampleTimeUs >= 0) {
+ mBuffer->meta_data()->setInt64(
+ kKeyTargetTime, targetSampleTimeUs);
+ }
+
+ if (isSyncSample) {
+ mBuffer->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
+ ++mCurrentSampleIndex;
+
+ *out = mBuffer;
+ mBuffer = NULL;
+
+ return OK;
+ }
+}
+
+MPEG4Extractor::Track *MPEG4Extractor::findTrackByMimePrefix(
+ const char *mimePrefix) {
+ for (Track *track = mFirstTrack; track != NULL; track = track->next) {
+ const char *mime;
+ if (track->meta != NULL
+ && track->meta->findCString(kKeyMIMEType, &mime)
+ && !strncasecmp(mime, mimePrefix, strlen(mimePrefix))) {
+ return track;
+ }
+ }
+
+ return NULL;
+}
+
+void MPEG4Extractor::populateMetrics() {
+ ALOGV("MPEG4Extractor::populateMetrics");
+ // write into mAnalyticsItem
+}
+
+static bool LegacySniffMPEG4(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence) {
+ uint8_t header[8];
+
+ ssize_t n = source->readAt(4, header, sizeof(header));
+ if (n < (ssize_t)sizeof(header)) {
+ return false;
+ }
+
+ if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8)
+ || !memcmp(header, "ftyp3gr6", 8) || !memcmp(header, "ftyp3gs6", 8)
+ || !memcmp(header, "ftyp3ge6", 8) || !memcmp(header, "ftyp3gg6", 8)
+ || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)
+ || !memcmp(header, "ftypM4A ", 8) || !memcmp(header, "ftypf4v ", 8)
+ || !memcmp(header, "ftypkddi", 8) || !memcmp(header, "ftypM4VP", 8)
+ || !memcmp(header, "ftypmif1", 8) || !memcmp(header, "ftypheic", 8)) {
+ *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
+ *confidence = 0.4;
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool isCompatibleBrand(uint32_t fourcc) {
+ static const uint32_t kCompatibleBrands[] = {
+ FOURCC('i', 's', 'o', 'm'),
+ FOURCC('i', 's', 'o', '2'),
+ FOURCC('a', 'v', 'c', '1'),
+ FOURCC('h', 'v', 'c', '1'),
+ FOURCC('h', 'e', 'v', '1'),
+ FOURCC('3', 'g', 'p', '4'),
+ FOURCC('m', 'p', '4', '1'),
+ FOURCC('m', 'p', '4', '2'),
+ FOURCC('d', 'a', 's', 'h'),
+
+ // Won't promise that the following file types can be played.
+ // Just give these file types a chance.
+ FOURCC('q', 't', ' ', ' '), // Apple's QuickTime
+ FOURCC('M', 'S', 'N', 'V'), // Sony's PSP
+
+ FOURCC('3', 'g', '2', 'a'), // 3GPP2
+ FOURCC('3', 'g', '2', 'b'),
+ FOURCC('m', 'i', 'f', '1'), // HEIF image
+ FOURCC('h', 'e', 'i', 'c'), // HEIF image
+ };
+
+ for (size_t i = 0;
+ i < sizeof(kCompatibleBrands) / sizeof(kCompatibleBrands[0]);
+ ++i) {
+ if (kCompatibleBrands[i] == fourcc) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Attempt to actually parse the 'ftyp' atom and determine if a suitable
+// compatible brand is present.
+// Also try to identify where this file's metadata ends
+// (end of the 'moov' atom) and report it to the caller as part of
+// the metadata.
+static bool BetterSniffMPEG4(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *meta) {
+ // We scan up to 128 bytes to identify this file as an MP4.
+ static const off64_t kMaxScanOffset = 128ll;
+
+ off64_t offset = 0ll;
+ bool foundGoodFileType = false;
+ off64_t moovAtomEndOffset = -1ll;
+ bool done = false;
+
+ while (!done && offset < kMaxScanOffset) {
+ uint32_t hdr[2];
+ if (source->readAt(offset, hdr, 8) < 8) {
+ return false;
+ }
+
+ uint64_t chunkSize = ntohl(hdr[0]);
+ uint32_t chunkType = ntohl(hdr[1]);
+ off64_t chunkDataOffset = offset + 8;
+
+ if (chunkSize == 1) {
+ if (source->readAt(offset + 8, &chunkSize, 8) < 8) {
+ return false;
+ }
+
+ chunkSize = ntoh64(chunkSize);
+ chunkDataOffset += 8;
+
+ if (chunkSize < 16) {
+ // The smallest valid chunk is 16 bytes long in this case.
+ return false;
+ }
+
+ } else if (chunkSize < 8) {
+ // The smallest valid chunk is 8 bytes long.
+ return false;
+ }
+
+ // (data_offset - offset) is either 8 or 16
+ off64_t chunkDataSize = chunkSize - (chunkDataOffset - offset);
+ if (chunkDataSize < 0) {
+ ALOGE("b/23540914");
+ return ERROR_MALFORMED;
+ }
+
+ char chunkstring[5];
+ MakeFourCCString(chunkType, chunkstring);
+ ALOGV("saw chunk type %s, size %" PRIu64 " @ %lld", chunkstring, chunkSize, (long long)offset);
+ switch (chunkType) {
+ case FOURCC('f', 't', 'y', 'p'):
+ {
+ if (chunkDataSize < 8) {
+ return false;
+ }
+
+ uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4;
+ for (size_t i = 0; i < numCompatibleBrands + 2; ++i) {
+ if (i == 1) {
+ // Skip this index, it refers to the minorVersion,
+ // not a brand.
+ continue;
+ }
+
+ uint32_t brand;
+ if (source->readAt(
+ chunkDataOffset + 4 * i, &brand, 4) < 4) {
+ return false;
+ }
+
+ brand = ntohl(brand);
+
+ if (isCompatibleBrand(brand)) {
+ foundGoodFileType = true;
+ break;
+ }
+ }
+
+ if (!foundGoodFileType) {
+ return false;
+ }
+
+ break;
+ }
+
+ case FOURCC('m', 'o', 'o', 'v'):
+ {
+ moovAtomEndOffset = offset + chunkSize;
+
+ done = true;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ offset += chunkSize;
+ }
+
+ if (!foundGoodFileType) {
+ return false;
+ }
+
+ *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
+ *confidence = 0.4f;
+
+ if (moovAtomEndOffset >= 0) {
+ *meta = new AMessage;
+ (*meta)->setInt64("meta-data-size", moovAtomEndOffset);
+
+ ALOGV("found metadata size: %lld", (long long)moovAtomEndOffset);
+ }
+
+ return true;
+}
+
+static MediaExtractor* CreateExtractor(
+ const sp<DataSource> &source,
+ const sp<AMessage>& meta __unused) {
+ return new MPEG4Extractor(source);
+}
+
+static MediaExtractor::CreatorFunc Sniff(
+ const sp<DataSource> &source,
+ String8 *mimeType,
+ float *confidence,
+ sp<AMessage> *meta) {
+ if (BetterSniffMPEG4(source, mimeType, confidence, meta)) {
+ return CreateExtractor;
+ }
+
+ if (LegacySniffMPEG4(source, mimeType, confidence)) {
+ ALOGW("Identified supported mpeg4 through LegacySniffMPEG4.");
+ return CreateExtractor;
+ }
+
+ return NULL;
+}
+
+extern "C" {
+// This is the only symbol that needs to be exported
+__attribute__ ((visibility ("default")))
+MediaExtractor::ExtractorDef GETEXTRACTORDEF() {
+ return {
+ MediaExtractor::EXTRACTORDEF_VERSION,
+ UUID("27575c67-4417-4c54-8d3d-8e626985a164"),
+ 1, // version
+ "MP4 Extractor",
+ Sniff
+ };
+}
+
+} // extern "C"
+
+} // namespace android
diff --git a/media/extractors/mp4/MPEG4Extractor.h b/media/extractors/mp4/MPEG4Extractor.h
new file mode 100644
index 0000000..bd404d2
--- /dev/null
+++ b/media/extractors/mp4/MPEG4Extractor.h
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+#ifndef MPEG4_EXTRACTOR_H_
+
+#define MPEG4_EXTRACTOR_H_
+
+#include <arpa/inet.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/Utils.h>
+#include <utils/List.h>
+#include <utils/Vector.h>
+#include <utils/String8.h>
+
+namespace android {
+struct AMessage;
+class DataSource;
+class SampleTable;
+class String8;
+namespace heif {
+class ItemTable;
+}
+using heif::ItemTable;
+
+struct SidxEntry {
+ size_t mSize;
+ uint32_t mDurationUs;
+};
+
+struct Trex {
+ uint32_t track_ID;
+ uint32_t default_sample_description_index;
+ uint32_t default_sample_duration;
+ uint32_t default_sample_size;
+ uint32_t default_sample_flags;
+};
+
+class MPEG4Extractor : public MediaExtractor {
+public:
+ // Extractor assumes ownership of "source".
+ explicit MPEG4Extractor(const sp<DataSource> &source);
+
+ virtual size_t countTracks();
+ virtual sp<MediaSource> getTrack(size_t index);
+ virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+
+ virtual sp<MetaData> getMetaData();
+ virtual uint32_t flags() const;
+ virtual const char * name() { return "MPEG4Extractor"; }
+ virtual void release();
+
+ // for DRM
+ virtual char* getDrmTrackInfo(size_t trackID, int *len);
+
+protected:
+ virtual ~MPEG4Extractor();
+
+ virtual void populateMetrics();
+
+private:
+
+ struct PsshInfo {
+ uint8_t uuid[16];
+ uint32_t datalen;
+ uint8_t *data;
+ };
+ struct Track {
+ Track *next;
+ sp<MetaData> meta;
+ uint32_t timescale;
+ sp<SampleTable> sampleTable;
+ bool includes_expensive_metadata;
+ bool skipTrack;
+ bool has_elst;
+ int64_t elst_media_time;
+ uint64_t elst_segment_duration;
+ };
+
+ Vector<SidxEntry> mSidxEntries;
+ off64_t mMoofOffset;
+ bool mMoofFound;
+ bool mMdatFound;
+
+ Vector<PsshInfo> mPssh;
+
+ Vector<Trex> mTrex;
+
+ sp<DataSource> mDataSource;
+ status_t mInitCheck;
+ uint32_t mHeaderTimescale;
+ bool mIsQT;
+ bool mIsHEIF;
+
+ Track *mFirstTrack, *mLastTrack;
+
+ sp<MetaData> mFileMetaData;
+
+ Vector<uint32_t> mPath;
+ String8 mLastCommentMean;
+ String8 mLastCommentName;
+ String8 mLastCommentData;
+
+ KeyedVector<uint32_t, AString> mMetaKeyMap;
+
+ status_t readMetaData();
+ status_t parseChunk(off64_t *offset, int depth);
+ status_t parseITunesMetaData(off64_t offset, size_t size);
+ status_t parseColorInfo(off64_t offset, size_t size);
+ status_t parse3GPPMetaData(off64_t offset, size_t size, int depth);
+ void parseID3v2MetaData(off64_t offset);
+ status_t parseQTMetaKey(off64_t data_offset, size_t data_size);
+ status_t parseQTMetaVal(int32_t keyId, off64_t data_offset, size_t data_size);
+
+ status_t updateAudioTrackInfoFromESDS_MPEG4Audio(
+ const void *esds_data, size_t esds_size);
+
+ static status_t verifyTrack(Track *track);
+
+ struct SINF {
+ SINF *next;
+ uint16_t trackID;
+ uint8_t IPMPDescriptorID;
+ ssize_t len;
+ char *IPMPData;
+ };
+
+ SINF *mFirstSINF;
+
+ bool mIsDrm;
+ sp<ItemTable> mItemTable;
+
+ status_t parseDrmSINF(off64_t *offset, off64_t data_offset);
+
+ status_t parseTrackHeader(off64_t data_offset, off64_t data_size);
+
+ status_t parseSegmentIndex(off64_t data_offset, size_t data_size);
+
+ Track *findTrackByMimePrefix(const char *mimePrefix);
+
+ status_t parseAC3SampleEntry(off64_t offset);
+ status_t parseAC3SpecificBox(off64_t offset, uint16_t sampleRate);
+
+ MPEG4Extractor(const MPEG4Extractor &);
+ MPEG4Extractor &operator=(const MPEG4Extractor &);
+};
+
+bool SniffMPEG4(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *);
+
+} // namespace android
+
+#endif // MPEG4_EXTRACTOR_H_
diff --git a/media/extractors/mp4/NOTICE b/media/extractors/mp4/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/media/extractors/mp4/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, 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.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/media/extractors/mp4/SampleIterator.cpp b/media/extractors/mp4/SampleIterator.cpp
new file mode 100644
index 0000000..c4c6faf
--- /dev/null
+++ b/media/extractors/mp4/SampleIterator.cpp
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2010 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_TAG "SampleIterator"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "SampleIterator.h"
+
+#include <arpa/inet.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/Utils.h>
+
+#include "SampleTable.h"
+
+namespace android {
+
+SampleIterator::SampleIterator(SampleTable *table)
+ : mTable(table),
+ mInitialized(false),
+ mTimeToSampleIndex(0),
+ mTTSSampleIndex(0),
+ mTTSSampleTime(0),
+ mTTSCount(0),
+ mTTSDuration(0) {
+ reset();
+}
+
+void SampleIterator::reset() {
+ mSampleToChunkIndex = 0;
+ mFirstChunk = 0;
+ mFirstChunkSampleIndex = 0;
+ mStopChunk = 0;
+ mStopChunkSampleIndex = 0;
+ mSamplesPerChunk = 0;
+ mChunkDesc = 0;
+}
+
+status_t SampleIterator::seekTo(uint32_t sampleIndex) {
+ ALOGV("seekTo(%d)", sampleIndex);
+
+ if (sampleIndex >= mTable->mNumSampleSizes) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ if (mTable->mSampleToChunkOffset < 0
+ || mTable->mChunkOffsetOffset < 0
+ || mTable->mSampleSizeOffset < 0
+ || mTable->mTimeToSampleCount == 0) {
+
+ return ERROR_MALFORMED;
+ }
+
+ if (mInitialized && mCurrentSampleIndex == sampleIndex) {
+ return OK;
+ }
+
+ if (!mInitialized || sampleIndex < mFirstChunkSampleIndex) {
+ reset();
+ }
+
+ if (sampleIndex >= mStopChunkSampleIndex) {
+ status_t err;
+ if ((err = findChunkRange(sampleIndex)) != OK) {
+ ALOGE("findChunkRange failed");
+ return err;
+ }
+ }
+
+ CHECK(sampleIndex < mStopChunkSampleIndex);
+
+ if (mSamplesPerChunk == 0) {
+ ALOGE("b/22802344");
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t chunk =
+ (sampleIndex - mFirstChunkSampleIndex) / mSamplesPerChunk
+ + mFirstChunk;
+
+ if (!mInitialized || chunk != mCurrentChunkIndex) {
+ status_t err;
+ if ((err = getChunkOffset(chunk, &mCurrentChunkOffset)) != OK) {
+ ALOGE("getChunkOffset return error");
+ return err;
+ }
+
+ mCurrentChunkSampleSizes.clear();
+
+ uint32_t firstChunkSampleIndex =
+ mFirstChunkSampleIndex
+ + mSamplesPerChunk * (chunk - mFirstChunk);
+
+ for (uint32_t i = 0; i < mSamplesPerChunk; ++i) {
+ size_t sampleSize;
+ if ((err = getSampleSizeDirect(
+ firstChunkSampleIndex + i, &sampleSize)) != OK) {
+ ALOGE("getSampleSizeDirect return error");
+ mCurrentChunkSampleSizes.clear();
+ return err;
+ }
+
+ mCurrentChunkSampleSizes.push(sampleSize);
+ }
+
+ mCurrentChunkIndex = chunk;
+ }
+
+ uint32_t chunkRelativeSampleIndex =
+ (sampleIndex - mFirstChunkSampleIndex) % mSamplesPerChunk;
+
+ mCurrentSampleOffset = mCurrentChunkOffset;
+ for (uint32_t i = 0; i < chunkRelativeSampleIndex; ++i) {
+ mCurrentSampleOffset += mCurrentChunkSampleSizes[i];
+ }
+
+ mCurrentSampleSize = mCurrentChunkSampleSizes[chunkRelativeSampleIndex];
+ if (sampleIndex < mTTSSampleIndex) {
+ mTimeToSampleIndex = 0;
+ mTTSSampleIndex = 0;
+ mTTSSampleTime = 0;
+ mTTSCount = 0;
+ mTTSDuration = 0;
+ }
+
+ status_t err;
+ if ((err = findSampleTimeAndDuration(
+ sampleIndex, &mCurrentSampleTime, &mCurrentSampleDuration)) != OK) {
+ ALOGE("findSampleTime return error");
+ return err;
+ }
+
+ mCurrentSampleIndex = sampleIndex;
+
+ mInitialized = true;
+
+ return OK;
+}
+
+status_t SampleIterator::findChunkRange(uint32_t sampleIndex) {
+ CHECK(sampleIndex >= mFirstChunkSampleIndex);
+
+ while (sampleIndex >= mStopChunkSampleIndex) {
+ if (mSampleToChunkIndex == mTable->mNumSampleToChunkOffsets) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mFirstChunkSampleIndex = mStopChunkSampleIndex;
+
+ const SampleTable::SampleToChunkEntry *entry =
+ &mTable->mSampleToChunkEntries[mSampleToChunkIndex];
+
+ mFirstChunk = entry->startChunk;
+ mSamplesPerChunk = entry->samplesPerChunk;
+ mChunkDesc = entry->chunkDesc;
+
+ if (mSampleToChunkIndex + 1 < mTable->mNumSampleToChunkOffsets) {
+ mStopChunk = entry[1].startChunk;
+
+ if (mSamplesPerChunk == 0 || mStopChunk < mFirstChunk ||
+ (mStopChunk - mFirstChunk) > UINT32_MAX / mSamplesPerChunk ||
+ ((mStopChunk - mFirstChunk) * mSamplesPerChunk >
+ UINT32_MAX - mFirstChunkSampleIndex)) {
+
+ return ERROR_OUT_OF_RANGE;
+ }
+ mStopChunkSampleIndex =
+ mFirstChunkSampleIndex
+ + (mStopChunk - mFirstChunk) * mSamplesPerChunk;
+ } else {
+ mStopChunk = 0xffffffff;
+ mStopChunkSampleIndex = 0xffffffff;
+ }
+
+ ++mSampleToChunkIndex;
+ }
+
+ return OK;
+}
+
+status_t SampleIterator::getChunkOffset(uint32_t chunk, off64_t *offset) {
+ *offset = 0;
+
+ if (chunk >= mTable->mNumChunkOffsets) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mTable->mChunkOffsetType == SampleTable::kChunkOffsetType32) {
+ uint32_t offset32;
+
+ if (mTable->mDataSource->readAt(
+ mTable->mChunkOffsetOffset + 8 + 4 * chunk,
+ &offset32,
+ sizeof(offset32)) < (ssize_t)sizeof(offset32)) {
+ return ERROR_IO;
+ }
+
+ *offset = ntohl(offset32);
+ } else {
+ CHECK_EQ(mTable->mChunkOffsetType, SampleTable::kChunkOffsetType64);
+
+ uint64_t offset64;
+ if (mTable->mDataSource->readAt(
+ mTable->mChunkOffsetOffset + 8 + 8 * chunk,
+ &offset64,
+ sizeof(offset64)) < (ssize_t)sizeof(offset64)) {
+ return ERROR_IO;
+ }
+
+ *offset = ntoh64(offset64);
+ }
+
+ return OK;
+}
+
+status_t SampleIterator::getSampleSizeDirect(
+ uint32_t sampleIndex, size_t *size) {
+ *size = 0;
+
+ if (sampleIndex >= mTable->mNumSampleSizes) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mTable->mDefaultSampleSize > 0) {
+ *size = mTable->mDefaultSampleSize;
+ return OK;
+ }
+
+ switch (mTable->mSampleSizeFieldSize) {
+ case 32:
+ {
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + 4 * sampleIndex,
+ size, sizeof(*size)) < (ssize_t)sizeof(*size)) {
+ return ERROR_IO;
+ }
+
+ *size = ntohl(*size);
+ break;
+ }
+
+ case 16:
+ {
+ uint16_t x;
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + 2 * sampleIndex,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *size = ntohs(x);
+ break;
+ }
+
+ case 8:
+ {
+ uint8_t x;
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + sampleIndex,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *size = x;
+ break;
+ }
+
+ default:
+ {
+ CHECK_EQ(mTable->mSampleSizeFieldSize, 4u);
+
+ uint8_t x;
+ if (mTable->mDataSource->readAt(
+ mTable->mSampleSizeOffset + 12 + sampleIndex / 2,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *size = (sampleIndex & 1) ? x & 0x0f : x >> 4;
+ break;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleIterator::findSampleTimeAndDuration(
+ uint32_t sampleIndex, uint32_t *time, uint32_t *duration) {
+ if (sampleIndex >= mTable->mNumSampleSizes) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ while (true) {
+ if (mTTSSampleIndex > UINT32_MAX - mTTSCount) {
+ return ERROR_OUT_OF_RANGE;
+ }
+ if(sampleIndex < mTTSSampleIndex + mTTSCount) {
+ break;
+ }
+ if (mTimeToSampleIndex == mTable->mTimeToSampleCount ||
+ (mTTSDuration != 0 && mTTSCount > UINT32_MAX / mTTSDuration) ||
+ mTTSSampleTime > UINT32_MAX - (mTTSCount * mTTSDuration)) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mTTSSampleIndex += mTTSCount;
+ mTTSSampleTime += mTTSCount * mTTSDuration;
+
+ mTTSCount = mTable->mTimeToSample[2 * mTimeToSampleIndex];
+ mTTSDuration = mTable->mTimeToSample[2 * mTimeToSampleIndex + 1];
+
+ ++mTimeToSampleIndex;
+ }
+
+ *time = mTTSSampleTime + mTTSDuration * (sampleIndex - mTTSSampleIndex);
+
+ int32_t offset = mTable->getCompositionTimeOffset(sampleIndex);
+ if ((offset < 0 && *time < (offset == INT32_MIN ?
+ INT32_MAX : uint32_t(-offset))) ||
+ (offset > 0 && *time > UINT32_MAX - offset)) {
+ ALOGE("%u + %d would overflow", *time, offset);
+ return ERROR_OUT_OF_RANGE;
+ }
+ if (offset > 0) {
+ *time += offset;
+ } else {
+ *time -= (offset == INT32_MIN ? INT32_MAX : (-offset));
+ }
+
+ *duration = mTTSDuration;
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/extractors/mp4/SampleIterator.h b/media/extractors/mp4/SampleIterator.h
new file mode 100644
index 0000000..6a3fd3b
--- /dev/null
+++ b/media/extractors/mp4/SampleIterator.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SAMPLE_ITERATOR_H_
+
+#define SAMPLE_ITERATOR_H_
+
+#include <utils/Vector.h>
+
+namespace android {
+
+class SampleTable;
+
+struct SampleIterator {
+ explicit SampleIterator(SampleTable *table);
+
+ status_t seekTo(uint32_t sampleIndex);
+
+ uint32_t getChunkIndex() const { return mCurrentChunkIndex; }
+ uint32_t getDescIndex() const { return mChunkDesc; }
+ off64_t getSampleOffset() const { return mCurrentSampleOffset; }
+ size_t getSampleSize() const { return mCurrentSampleSize; }
+ uint32_t getSampleTime() const { return mCurrentSampleTime; }
+ uint32_t getSampleDuration() const { return mCurrentSampleDuration; }
+
+ status_t getSampleSizeDirect(
+ uint32_t sampleIndex, size_t *size);
+
+private:
+ SampleTable *mTable;
+
+ bool mInitialized;
+
+ uint32_t mSampleToChunkIndex;
+ uint32_t mFirstChunk;
+ uint32_t mFirstChunkSampleIndex;
+ uint32_t mStopChunk;
+ uint32_t mStopChunkSampleIndex;
+ uint32_t mSamplesPerChunk;
+ uint32_t mChunkDesc;
+
+ uint32_t mCurrentChunkIndex;
+ off64_t mCurrentChunkOffset;
+ Vector<size_t> mCurrentChunkSampleSizes;
+
+ uint32_t mTimeToSampleIndex;
+ uint32_t mTTSSampleIndex;
+ uint32_t mTTSSampleTime;
+ uint32_t mTTSCount;
+ uint32_t mTTSDuration;
+
+ uint32_t mCurrentSampleIndex;
+ off64_t mCurrentSampleOffset;
+ size_t mCurrentSampleSize;
+ uint32_t mCurrentSampleTime;
+ uint32_t mCurrentSampleDuration;
+
+ void reset();
+ status_t findChunkRange(uint32_t sampleIndex);
+ status_t getChunkOffset(uint32_t chunk, off64_t *offset);
+ status_t findSampleTimeAndDuration(uint32_t sampleIndex, uint32_t *time, uint32_t *duration);
+
+ SampleIterator(const SampleIterator &);
+ SampleIterator &operator=(const SampleIterator &);
+};
+
+} // namespace android
+
+#endif // SAMPLE_ITERATOR_H_
diff --git a/media/extractors/mp4/SampleTable.cpp b/media/extractors/mp4/SampleTable.cpp
new file mode 100644
index 0000000..d87d1b6
--- /dev/null
+++ b/media/extractors/mp4/SampleTable.cpp
@@ -0,0 +1,1000 @@
+/*
+ * 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_TAG "SampleTable"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <limits>
+
+#include "SampleTable.h"
+#include "SampleIterator.h"
+
+#include <arpa/inet.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/Utils.h>
+
+/* TODO: remove after being merged into other branches */
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+namespace android {
+
+// static
+const uint32_t SampleTable::kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o');
+// static
+const uint32_t SampleTable::kChunkOffsetType64 = FOURCC('c', 'o', '6', '4');
+// static
+const uint32_t SampleTable::kSampleSizeType32 = FOURCC('s', 't', 's', 'z');
+// static
+const uint32_t SampleTable::kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2');
+
+////////////////////////////////////////////////////////////////////////////////
+
+const off64_t kMaxOffset = std::numeric_limits<off64_t>::max();
+
+struct SampleTable::CompositionDeltaLookup {
+ CompositionDeltaLookup();
+
+ void setEntries(
+ const int32_t *deltaEntries, size_t numDeltaEntries);
+
+ int32_t getCompositionTimeOffset(uint32_t sampleIndex);
+
+private:
+ Mutex mLock;
+
+ const int32_t *mDeltaEntries;
+ size_t mNumDeltaEntries;
+
+ size_t mCurrentDeltaEntry;
+ size_t mCurrentEntrySampleIndex;
+
+ DISALLOW_EVIL_CONSTRUCTORS(CompositionDeltaLookup);
+};
+
+SampleTable::CompositionDeltaLookup::CompositionDeltaLookup()
+ : mDeltaEntries(NULL),
+ mNumDeltaEntries(0),
+ mCurrentDeltaEntry(0),
+ mCurrentEntrySampleIndex(0) {
+}
+
+void SampleTable::CompositionDeltaLookup::setEntries(
+ const int32_t *deltaEntries, size_t numDeltaEntries) {
+ Mutex::Autolock autolock(mLock);
+
+ mDeltaEntries = deltaEntries;
+ mNumDeltaEntries = numDeltaEntries;
+ mCurrentDeltaEntry = 0;
+ mCurrentEntrySampleIndex = 0;
+}
+
+int32_t SampleTable::CompositionDeltaLookup::getCompositionTimeOffset(
+ uint32_t sampleIndex) {
+ Mutex::Autolock autolock(mLock);
+
+ if (mDeltaEntries == NULL) {
+ return 0;
+ }
+
+ if (sampleIndex < mCurrentEntrySampleIndex) {
+ mCurrentDeltaEntry = 0;
+ mCurrentEntrySampleIndex = 0;
+ }
+
+ while (mCurrentDeltaEntry < mNumDeltaEntries) {
+ uint32_t sampleCount = mDeltaEntries[2 * mCurrentDeltaEntry];
+ if (sampleIndex < mCurrentEntrySampleIndex + sampleCount) {
+ return mDeltaEntries[2 * mCurrentDeltaEntry + 1];
+ }
+
+ mCurrentEntrySampleIndex += sampleCount;
+ ++mCurrentDeltaEntry;
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+SampleTable::SampleTable(const sp<DataSource> &source)
+ : mDataSource(source),
+ mChunkOffsetOffset(-1),
+ mChunkOffsetType(0),
+ mNumChunkOffsets(0),
+ mSampleToChunkOffset(-1),
+ mNumSampleToChunkOffsets(0),
+ mSampleSizeOffset(-1),
+ mSampleSizeFieldSize(0),
+ mDefaultSampleSize(0),
+ mNumSampleSizes(0),
+ mHasTimeToSample(false),
+ mTimeToSampleCount(0),
+ mTimeToSample(NULL),
+ mSampleTimeEntries(NULL),
+ mCompositionTimeDeltaEntries(NULL),
+ mNumCompositionTimeDeltaEntries(0),
+ mCompositionDeltaLookup(new CompositionDeltaLookup),
+ mSyncSampleOffset(-1),
+ mNumSyncSamples(0),
+ mSyncSamples(NULL),
+ mLastSyncSampleIndex(0),
+ mSampleToChunkEntries(NULL),
+ mTotalSize(0) {
+ mSampleIterator = new SampleIterator(this);
+}
+
+SampleTable::~SampleTable() {
+ delete[] mSampleToChunkEntries;
+ mSampleToChunkEntries = NULL;
+
+ delete[] mSyncSamples;
+ mSyncSamples = NULL;
+
+ delete[] mTimeToSample;
+ mTimeToSample = NULL;
+
+ delete mCompositionDeltaLookup;
+ mCompositionDeltaLookup = NULL;
+
+ delete[] mCompositionTimeDeltaEntries;
+ mCompositionTimeDeltaEntries = NULL;
+
+ delete[] mSampleTimeEntries;
+ mSampleTimeEntries = NULL;
+
+ delete mSampleIterator;
+ mSampleIterator = NULL;
+}
+
+bool SampleTable::isValid() const {
+ return mChunkOffsetOffset >= 0
+ && mSampleToChunkOffset >= 0
+ && mSampleSizeOffset >= 0
+ && mHasTimeToSample;
+}
+
+status_t SampleTable::setChunkOffsetParams(
+ uint32_t type, off64_t data_offset, size_t data_size) {
+ if (mChunkOffsetOffset >= 0) {
+ return ERROR_MALFORMED;
+ }
+
+ CHECK(type == kChunkOffsetType32 || type == kChunkOffsetType64);
+
+ mChunkOffsetOffset = data_offset;
+ mChunkOffsetType = type;
+
+ if (data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mNumChunkOffsets = U32_AT(&header[4]);
+
+ if (mChunkOffsetType == kChunkOffsetType32) {
+ if ((data_size - 8) / 4 < mNumChunkOffsets) {
+ return ERROR_MALFORMED;
+ }
+ } else {
+ if ((data_size - 8) / 8 < mNumChunkOffsets) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setSampleToChunkParams(
+ off64_t data_offset, size_t data_size) {
+ if (mSampleToChunkOffset >= 0) {
+ // already set
+ return ERROR_MALFORMED;
+ }
+
+ if (data_offset < 0) {
+ return ERROR_MALFORMED;
+ }
+
+ mSampleToChunkOffset = data_offset;
+
+ if (data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mNumSampleToChunkOffsets = U32_AT(&header[4]);
+
+ if ((data_size - 8) / sizeof(SampleToChunkEntry) < mNumSampleToChunkOffsets) {
+ return ERROR_MALFORMED;
+ }
+
+ if ((uint64_t)kMaxTotalSize / sizeof(SampleToChunkEntry) <=
+ (uint64_t)mNumSampleToChunkOffsets) {
+ ALOGE("Sample-to-chunk table size too large.");
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mTotalSize += (uint64_t)mNumSampleToChunkOffsets *
+ sizeof(SampleToChunkEntry);
+ if (mTotalSize > kMaxTotalSize) {
+ ALOGE("Sample-to-chunk table size would make sample table too large.\n"
+ " Requested sample-to-chunk table size = %llu\n"
+ " Eventual sample table size >= %llu\n"
+ " Allowed sample table size = %llu\n",
+ (unsigned long long)mNumSampleToChunkOffsets *
+ sizeof(SampleToChunkEntry),
+ (unsigned long long)mTotalSize,
+ (unsigned long long)kMaxTotalSize);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mSampleToChunkEntries =
+ new (std::nothrow) SampleToChunkEntry[mNumSampleToChunkOffsets];
+ if (!mSampleToChunkEntries) {
+ ALOGE("Cannot allocate sample-to-chunk table with %llu entries.",
+ (unsigned long long)mNumSampleToChunkOffsets);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mNumSampleToChunkOffsets == 0) {
+ return OK;
+ }
+
+ if ((off64_t)(kMaxOffset - 8 -
+ ((mNumSampleToChunkOffsets - 1) * sizeof(SampleToChunkEntry)))
+ < mSampleToChunkOffset) {
+ return ERROR_MALFORMED;
+ }
+
+ for (uint32_t i = 0; i < mNumSampleToChunkOffsets; ++i) {
+ uint8_t buffer[sizeof(SampleToChunkEntry)];
+
+ if (mDataSource->readAt(
+ mSampleToChunkOffset + 8 + i * sizeof(SampleToChunkEntry),
+ buffer,
+ sizeof(buffer))
+ != (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+ // chunk index is 1 based in the spec.
+ if (U32_AT(buffer) < 1) {
+ ALOGE("b/23534160");
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ // We want the chunk index to be 0-based.
+ mSampleToChunkEntries[i].startChunk = U32_AT(buffer) - 1;
+ mSampleToChunkEntries[i].samplesPerChunk = U32_AT(&buffer[4]);
+ mSampleToChunkEntries[i].chunkDesc = U32_AT(&buffer[8]);
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setSampleSizeParams(
+ uint32_t type, off64_t data_offset, size_t data_size) {
+ if (mSampleSizeOffset >= 0) {
+ return ERROR_MALFORMED;
+ }
+
+ CHECK(type == kSampleSizeType32 || type == kSampleSizeTypeCompact);
+
+ mSampleSizeOffset = data_offset;
+
+ if (data_size < 12) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[12];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mDefaultSampleSize = U32_AT(&header[4]);
+ mNumSampleSizes = U32_AT(&header[8]);
+ if (mNumSampleSizes > (UINT32_MAX - 12) / 16) {
+ ALOGE("b/23247055, mNumSampleSizes(%u)", mNumSampleSizes);
+ return ERROR_MALFORMED;
+ }
+
+ if (type == kSampleSizeType32) {
+ mSampleSizeFieldSize = 32;
+
+ if (mDefaultSampleSize != 0) {
+ return OK;
+ }
+
+ if (data_size < 12 + mNumSampleSizes * 4) {
+ return ERROR_MALFORMED;
+ }
+ } else {
+ if ((mDefaultSampleSize & 0xffffff00) != 0) {
+ // The high 24 bits are reserved and must be 0.
+ return ERROR_MALFORMED;
+ }
+
+ mSampleSizeFieldSize = mDefaultSampleSize & 0xff;
+ mDefaultSampleSize = 0;
+
+ if (mSampleSizeFieldSize != 4 && mSampleSizeFieldSize != 8
+ && mSampleSizeFieldSize != 16) {
+ return ERROR_MALFORMED;
+ }
+
+ if (data_size < 12 + (mNumSampleSizes * mSampleSizeFieldSize + 4) / 8) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setTimeToSampleParams(
+ off64_t data_offset, size_t data_size) {
+ if (mHasTimeToSample || data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mTimeToSampleCount = U32_AT(&header[4]);
+ if (mTimeToSampleCount > UINT32_MAX / (2 * sizeof(uint32_t))) {
+ // Choose this bound because
+ // 1) 2 * sizeof(uint32_t) is the amount of memory needed for one
+ // time-to-sample entry in the time-to-sample table.
+ // 2) mTimeToSampleCount is the number of entries of the time-to-sample
+ // table.
+ // 3) We hope that the table size does not exceed UINT32_MAX.
+ ALOGE("Time-to-sample table size too large.");
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ // Note: At this point, we know that mTimeToSampleCount * 2 will not
+ // overflow because of the above condition.
+
+ uint64_t allocSize = (uint64_t)mTimeToSampleCount * 2 * sizeof(uint32_t);
+ mTotalSize += allocSize;
+ if (mTotalSize > kMaxTotalSize) {
+ ALOGE("Time-to-sample table size would make sample table too large.\n"
+ " Requested time-to-sample table size = %llu\n"
+ " Eventual sample table size >= %llu\n"
+ " Allowed sample table size = %llu\n",
+ (unsigned long long)allocSize,
+ (unsigned long long)mTotalSize,
+ (unsigned long long)kMaxTotalSize);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mTimeToSample = new (std::nothrow) uint32_t[mTimeToSampleCount * 2];
+ if (!mTimeToSample) {
+ ALOGE("Cannot allocate time-to-sample table with %llu entries.",
+ (unsigned long long)mTimeToSampleCount);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mDataSource->readAt(data_offset + 8, mTimeToSample,
+ (size_t)allocSize) < (ssize_t)allocSize) {
+ ALOGE("Incomplete data read for time-to-sample table.");
+ return ERROR_IO;
+ }
+
+ for (size_t i = 0; i < mTimeToSampleCount * 2; ++i) {
+ mTimeToSample[i] = ntohl(mTimeToSample[i]);
+ }
+
+ mHasTimeToSample = true;
+ return OK;
+}
+
+// NOTE: per 14996-12, version 0 ctts contains unsigned values, while version 1
+// contains signed values, however some software creates version 0 files that
+// contain signed values, so we're always treating the values as signed,
+// regardless of version.
+status_t SampleTable::setCompositionTimeToSampleParams(
+ off64_t data_offset, size_t data_size) {
+ ALOGI("There are reordered frames present.");
+
+ if (mCompositionTimeDeltaEntries != NULL || data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header))
+ < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ uint32_t flags = U32_AT(header);
+ uint32_t version = flags >> 24;
+ flags &= 0xffffff;
+
+ if ((version != 0 && version != 1) || flags != 0) {
+ // Expected version = 0 or 1, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ size_t numEntries = U32_AT(&header[4]);
+
+ if (((SIZE_MAX / 8) - 1 < numEntries) || (data_size != (numEntries + 1) * 8)) {
+ return ERROR_MALFORMED;
+ }
+
+ mNumCompositionTimeDeltaEntries = numEntries;
+ uint64_t allocSize = (uint64_t)numEntries * 2 * sizeof(int32_t);
+ if (allocSize > kMaxTotalSize) {
+ ALOGE("Composition-time-to-sample table size too large.");
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mTotalSize += allocSize;
+ if (mTotalSize > kMaxTotalSize) {
+ ALOGE("Composition-time-to-sample table would make sample table too large.\n"
+ " Requested composition-time-to-sample table size = %llu\n"
+ " Eventual sample table size >= %llu\n"
+ " Allowed sample table size = %llu\n",
+ (unsigned long long)allocSize,
+ (unsigned long long)mTotalSize,
+ (unsigned long long)kMaxTotalSize);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mCompositionTimeDeltaEntries = new (std::nothrow) int32_t[2 * numEntries];
+ if (!mCompositionTimeDeltaEntries) {
+ ALOGE("Cannot allocate composition-time-to-sample table with %llu "
+ "entries.", (unsigned long long)numEntries);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mDataSource->readAt(data_offset + 8, mCompositionTimeDeltaEntries,
+ (size_t)allocSize) < (ssize_t)allocSize) {
+ delete[] mCompositionTimeDeltaEntries;
+ mCompositionTimeDeltaEntries = NULL;
+
+ return ERROR_IO;
+ }
+
+ for (size_t i = 0; i < 2 * numEntries; ++i) {
+ mCompositionTimeDeltaEntries[i] = ntohl(mCompositionTimeDeltaEntries[i]);
+ }
+
+ mCompositionDeltaLookup->setEntries(
+ mCompositionTimeDeltaEntries, mNumCompositionTimeDeltaEntries);
+
+ return OK;
+}
+
+status_t SampleTable::setSyncSampleParams(off64_t data_offset, size_t data_size) {
+ if (mSyncSampleOffset >= 0 || data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->readAt(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t numSyncSamples = U32_AT(&header[4]);
+
+ if (numSyncSamples < 2) {
+ ALOGV("Table of sync samples is empty or has only a single entry!");
+ }
+
+ uint64_t allocSize = (uint64_t)numSyncSamples * sizeof(uint32_t);
+ if (allocSize > kMaxTotalSize) {
+ ALOGE("Sync sample table size too large.");
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mTotalSize += allocSize;
+ if (mTotalSize > kMaxTotalSize) {
+ ALOGE("Sync sample table size would make sample table too large.\n"
+ " Requested sync sample table size = %llu\n"
+ " Eventual sample table size >= %llu\n"
+ " Allowed sample table size = %llu\n",
+ (unsigned long long)allocSize,
+ (unsigned long long)mTotalSize,
+ (unsigned long long)kMaxTotalSize);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ mSyncSamples = new (std::nothrow) uint32_t[numSyncSamples];
+ if (!mSyncSamples) {
+ ALOGE("Cannot allocate sync sample table with %llu entries.",
+ (unsigned long long)numSyncSamples);
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mDataSource->readAt(data_offset + 8, mSyncSamples,
+ (size_t)allocSize) != (ssize_t)allocSize) {
+ delete[] mSyncSamples;
+ mSyncSamples = NULL;
+ return ERROR_IO;
+ }
+
+ for (size_t i = 0; i < numSyncSamples; ++i) {
+ if (mSyncSamples[i] == 0) {
+ ALOGE("b/32423862, unexpected zero value in stss");
+ continue;
+ }
+ mSyncSamples[i] = ntohl(mSyncSamples[i]) - 1;
+ }
+
+ mSyncSampleOffset = data_offset;
+ mNumSyncSamples = numSyncSamples;
+
+ return OK;
+}
+
+uint32_t SampleTable::countChunkOffsets() const {
+ return mNumChunkOffsets;
+}
+
+uint32_t SampleTable::countSamples() const {
+ return mNumSampleSizes;
+}
+
+status_t SampleTable::getMaxSampleSize(size_t *max_size) {
+ Mutex::Autolock autoLock(mLock);
+
+ *max_size = 0;
+
+ for (uint32_t i = 0; i < mNumSampleSizes; ++i) {
+ size_t sample_size;
+ status_t err = getSampleSize_l(i, &sample_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (sample_size > *max_size) {
+ *max_size = sample_size;
+ }
+ }
+
+ return OK;
+}
+
+uint32_t abs_difference(uint32_t time1, uint32_t time2) {
+ return time1 > time2 ? time1 - time2 : time2 - time1;
+}
+
+// static
+int SampleTable::CompareIncreasingTime(const void *_a, const void *_b) {
+ const SampleTimeEntry *a = (const SampleTimeEntry *)_a;
+ const SampleTimeEntry *b = (const SampleTimeEntry *)_b;
+
+ if (a->mCompositionTime < b->mCompositionTime) {
+ return -1;
+ } else if (a->mCompositionTime > b->mCompositionTime) {
+ return 1;
+ }
+
+ return 0;
+}
+
+void SampleTable::buildSampleEntriesTable() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mSampleTimeEntries != NULL || mNumSampleSizes == 0) {
+ if (mNumSampleSizes == 0) {
+ ALOGE("b/23247055, mNumSampleSizes(%u)", mNumSampleSizes);
+ }
+ return;
+ }
+
+ mTotalSize += (uint64_t)mNumSampleSizes * sizeof(SampleTimeEntry);
+ if (mTotalSize > kMaxTotalSize) {
+ ALOGE("Sample entry table size would make sample table too large.\n"
+ " Requested sample entry table size = %llu\n"
+ " Eventual sample table size >= %llu\n"
+ " Allowed sample table size = %llu\n",
+ (unsigned long long)mNumSampleSizes * sizeof(SampleTimeEntry),
+ (unsigned long long)mTotalSize,
+ (unsigned long long)kMaxTotalSize);
+ return;
+ }
+
+ mSampleTimeEntries = new (std::nothrow) SampleTimeEntry[mNumSampleSizes];
+ if (!mSampleTimeEntries) {
+ ALOGE("Cannot allocate sample entry table with %llu entries.",
+ (unsigned long long)mNumSampleSizes);
+ return;
+ }
+
+ uint32_t sampleIndex = 0;
+ uint32_t sampleTime = 0;
+
+ for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {
+ uint32_t n = mTimeToSample[2 * i];
+ uint32_t delta = mTimeToSample[2 * i + 1];
+
+ for (uint32_t j = 0; j < n; ++j) {
+ if (sampleIndex < mNumSampleSizes) {
+ // Technically this should always be the case if the file
+ // is well-formed, but you know... there's (gasp) malformed
+ // content out there.
+
+ mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex;
+
+ int32_t compTimeDelta =
+ mCompositionDeltaLookup->getCompositionTimeOffset(
+ sampleIndex);
+
+ if ((compTimeDelta < 0 && sampleTime <
+ (compTimeDelta == INT32_MIN ?
+ INT32_MAX : uint32_t(-compTimeDelta)))
+ || (compTimeDelta > 0 &&
+ sampleTime > UINT32_MAX - compTimeDelta)) {
+ ALOGE("%u + %d would overflow, clamping",
+ sampleTime, compTimeDelta);
+ if (compTimeDelta < 0) {
+ sampleTime = 0;
+ } else {
+ sampleTime = UINT32_MAX;
+ }
+ compTimeDelta = 0;
+ }
+
+ mSampleTimeEntries[sampleIndex].mCompositionTime =
+ compTimeDelta > 0 ? sampleTime + compTimeDelta:
+ sampleTime - (-compTimeDelta);
+ }
+
+ ++sampleIndex;
+ if (sampleTime > UINT32_MAX - delta) {
+ ALOGE("%u + %u would overflow, clamping",
+ sampleTime, delta);
+ sampleTime = UINT32_MAX;
+ } else {
+ sampleTime += delta;
+ }
+ }
+ }
+
+ qsort(mSampleTimeEntries, mNumSampleSizes, sizeof(SampleTimeEntry),
+ CompareIncreasingTime);
+}
+
+status_t SampleTable::findSampleAtTime(
+ uint64_t req_time, uint64_t scale_num, uint64_t scale_den,
+ uint32_t *sample_index, uint32_t flags) {
+ buildSampleEntriesTable();
+
+ if (mSampleTimeEntries == NULL) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ uint32_t left = 0;
+ uint32_t right_plus_one = mNumSampleSizes;
+ while (left < right_plus_one) {
+ uint32_t center = left + (right_plus_one - left) / 2;
+ uint64_t centerTime =
+ getSampleTime(center, scale_num, scale_den);
+
+ if (req_time < centerTime) {
+ right_plus_one = center;
+ } else if (req_time > centerTime) {
+ left = center + 1;
+ } else {
+ *sample_index = mSampleTimeEntries[center].mSampleIndex;
+ return OK;
+ }
+ }
+
+ uint32_t closestIndex = left;
+
+ if (closestIndex == mNumSampleSizes) {
+ if (flags == kFlagAfter) {
+ return ERROR_OUT_OF_RANGE;
+ }
+ flags = kFlagBefore;
+ } else if (closestIndex == 0) {
+ if (flags == kFlagBefore) {
+ // normally we should return out of range, but that is
+ // treated as end-of-stream. instead return first sample
+ //
+ // return ERROR_OUT_OF_RANGE;
+ }
+ flags = kFlagAfter;
+ }
+
+ switch (flags) {
+ case kFlagBefore:
+ {
+ --closestIndex;
+ break;
+ }
+
+ case kFlagAfter:
+ {
+ // nothing to do
+ break;
+ }
+
+ default:
+ {
+ CHECK(flags == kFlagClosest);
+ // pick closest based on timestamp. use abs_difference for safety
+ if (abs_difference(
+ getSampleTime(closestIndex, scale_num, scale_den), req_time) >
+ abs_difference(
+ req_time, getSampleTime(closestIndex - 1, scale_num, scale_den))) {
+ --closestIndex;
+ }
+ break;
+ }
+ }
+
+ *sample_index = mSampleTimeEntries[closestIndex].mSampleIndex;
+ return OK;
+}
+
+status_t SampleTable::findSyncSampleNear(
+ uint32_t start_sample_index, uint32_t *sample_index, uint32_t flags) {
+ Mutex::Autolock autoLock(mLock);
+
+ *sample_index = 0;
+
+ if (mSyncSampleOffset < 0) {
+ // All samples are sync-samples.
+ *sample_index = start_sample_index;
+ return OK;
+ }
+
+ if (mNumSyncSamples == 0) {
+ *sample_index = 0;
+ return OK;
+ }
+
+ uint32_t left = 0;
+ uint32_t right_plus_one = mNumSyncSamples;
+ while (left < right_plus_one) {
+ uint32_t center = left + (right_plus_one - left) / 2;
+ uint32_t x = mSyncSamples[center];
+
+ if (start_sample_index < x) {
+ right_plus_one = center;
+ } else if (start_sample_index > x) {
+ left = center + 1;
+ } else {
+ *sample_index = x;
+ return OK;
+ }
+ }
+
+ if (left == mNumSyncSamples) {
+ if (flags == kFlagAfter) {
+ ALOGE("tried to find a sync frame after the last one: %d", left);
+ return ERROR_OUT_OF_RANGE;
+ }
+ flags = kFlagBefore;
+ }
+ else if (left == 0) {
+ if (flags == kFlagBefore) {
+ ALOGE("tried to find a sync frame before the first one: %d", left);
+
+ // normally we should return out of range, but that is
+ // treated as end-of-stream. instead seek to first sync
+ //
+ // return ERROR_OUT_OF_RANGE;
+ }
+ flags = kFlagAfter;
+ }
+
+ // Now ssi[left - 1] <(=) start_sample_index <= ssi[left]
+ switch (flags) {
+ case kFlagBefore:
+ {
+ --left;
+ break;
+ }
+ case kFlagAfter:
+ {
+ // nothing to do
+ break;
+ }
+ default:
+ {
+ // this route is not used, but implement it nonetheless
+ CHECK(flags == kFlagClosest);
+
+ status_t err = mSampleIterator->seekTo(start_sample_index);
+ if (err != OK) {
+ return err;
+ }
+ uint32_t sample_time = mSampleIterator->getSampleTime();
+
+ err = mSampleIterator->seekTo(mSyncSamples[left]);
+ if (err != OK) {
+ return err;
+ }
+ uint32_t upper_time = mSampleIterator->getSampleTime();
+
+ err = mSampleIterator->seekTo(mSyncSamples[left - 1]);
+ if (err != OK) {
+ return err;
+ }
+ uint32_t lower_time = mSampleIterator->getSampleTime();
+
+ // use abs_difference for safety
+ if (abs_difference(upper_time, sample_time) >
+ abs_difference(sample_time, lower_time)) {
+ --left;
+ }
+ break;
+ }
+ }
+
+ *sample_index = mSyncSamples[left];
+ return OK;
+}
+
+status_t SampleTable::findThumbnailSample(uint32_t *sample_index) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mSyncSampleOffset < 0) {
+ // All samples are sync-samples.
+ *sample_index = 0;
+ return OK;
+ }
+
+ uint32_t bestSampleIndex = 0;
+ size_t maxSampleSize = 0;
+
+ static const size_t kMaxNumSyncSamplesToScan = 20;
+
+ // Consider the first kMaxNumSyncSamplesToScan sync samples and
+ // pick the one with the largest (compressed) size as the thumbnail.
+
+ size_t numSamplesToScan = mNumSyncSamples;
+ if (numSamplesToScan > kMaxNumSyncSamplesToScan) {
+ numSamplesToScan = kMaxNumSyncSamplesToScan;
+ }
+
+ for (size_t i = 0; i < numSamplesToScan; ++i) {
+ uint32_t x = mSyncSamples[i];
+
+ // Now x is a sample index.
+ size_t sampleSize;
+ status_t err = getSampleSize_l(x, &sampleSize);
+ if (err != OK) {
+ return err;
+ }
+
+ if (i == 0 || sampleSize > maxSampleSize) {
+ bestSampleIndex = x;
+ maxSampleSize = sampleSize;
+ }
+ }
+
+ *sample_index = bestSampleIndex;
+
+ return OK;
+}
+
+status_t SampleTable::getSampleSize_l(
+ uint32_t sampleIndex, size_t *sampleSize) {
+ return mSampleIterator->getSampleSizeDirect(
+ sampleIndex, sampleSize);
+}
+
+status_t SampleTable::getMetaDataForSample(
+ uint32_t sampleIndex,
+ off64_t *offset,
+ size_t *size,
+ uint32_t *compositionTime,
+ bool *isSyncSample,
+ uint32_t *sampleDuration) {
+ Mutex::Autolock autoLock(mLock);
+
+ status_t err;
+ if ((err = mSampleIterator->seekTo(sampleIndex)) != OK) {
+ return err;
+ }
+
+ if (offset) {
+ *offset = mSampleIterator->getSampleOffset();
+ }
+
+ if (size) {
+ *size = mSampleIterator->getSampleSize();
+ }
+
+ if (compositionTime) {
+ *compositionTime = mSampleIterator->getSampleTime();
+ }
+
+ if (isSyncSample) {
+ *isSyncSample = false;
+ if (mSyncSampleOffset < 0) {
+ // Every sample is a sync sample.
+ *isSyncSample = true;
+ } else {
+ size_t i = (mLastSyncSampleIndex < mNumSyncSamples)
+ && (mSyncSamples[mLastSyncSampleIndex] <= sampleIndex)
+ ? mLastSyncSampleIndex : 0;
+
+ while (i < mNumSyncSamples && mSyncSamples[i] < sampleIndex) {
+ ++i;
+ }
+
+ if (i < mNumSyncSamples && mSyncSamples[i] == sampleIndex) {
+ *isSyncSample = true;
+ }
+
+ mLastSyncSampleIndex = i;
+ }
+ }
+
+ if (sampleDuration) {
+ *sampleDuration = mSampleIterator->getSampleDuration();
+ }
+
+ return OK;
+}
+
+int32_t SampleTable::getCompositionTimeOffset(uint32_t sampleIndex) {
+ return mCompositionDeltaLookup->getCompositionTimeOffset(sampleIndex);
+}
+
+} // namespace android
diff --git a/media/extractors/mp4/SampleTable.h b/media/extractors/mp4/SampleTable.h
new file mode 100644
index 0000000..eb1a674
--- /dev/null
+++ b/media/extractors/mp4/SampleTable.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+#ifndef SAMPLE_TABLE_H_
+
+#define SAMPLE_TABLE_H_
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <media/stagefright/MediaErrors.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class DataSource;
+struct SampleIterator;
+
+class SampleTable : public RefBase {
+public:
+ explicit SampleTable(const sp<DataSource> &source);
+
+ bool isValid() const;
+
+ // type can be 'stco' or 'co64'.
+ status_t setChunkOffsetParams(
+ uint32_t type, off64_t data_offset, size_t data_size);
+
+ status_t setSampleToChunkParams(off64_t data_offset, size_t data_size);
+
+ // type can be 'stsz' or 'stz2'.
+ status_t setSampleSizeParams(
+ uint32_t type, off64_t data_offset, size_t data_size);
+
+ status_t setTimeToSampleParams(off64_t data_offset, size_t data_size);
+
+ status_t setCompositionTimeToSampleParams(
+ off64_t data_offset, size_t data_size);
+
+ status_t setSyncSampleParams(off64_t data_offset, size_t data_size);
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ uint32_t countChunkOffsets() const;
+
+ uint32_t countSamples() const;
+
+ status_t getMaxSampleSize(size_t *size);
+
+ status_t getMetaDataForSample(
+ uint32_t sampleIndex,
+ off64_t *offset,
+ size_t *size,
+ uint32_t *compositionTime,
+ bool *isSyncSample = NULL,
+ uint32_t *sampleDuration = NULL);
+
+ enum {
+ kFlagBefore,
+ kFlagAfter,
+ kFlagClosest
+ };
+ status_t findSampleAtTime(
+ uint64_t req_time, uint64_t scale_num, uint64_t scale_den,
+ uint32_t *sample_index, uint32_t flags);
+
+ status_t findSyncSampleNear(
+ uint32_t start_sample_index, uint32_t *sample_index,
+ uint32_t flags);
+
+ status_t findThumbnailSample(uint32_t *sample_index);
+
+protected:
+ ~SampleTable();
+
+private:
+ struct CompositionDeltaLookup;
+
+ static const uint32_t kChunkOffsetType32;
+ static const uint32_t kChunkOffsetType64;
+ static const uint32_t kSampleSizeType32;
+ static const uint32_t kSampleSizeTypeCompact;
+
+ // Limit the total size of all internal tables to 200MiB.
+ static const size_t kMaxTotalSize = 200 * (1 << 20);
+
+ sp<DataSource> mDataSource;
+ Mutex mLock;
+
+ off64_t mChunkOffsetOffset;
+ uint32_t mChunkOffsetType;
+ uint32_t mNumChunkOffsets;
+
+ off64_t mSampleToChunkOffset;
+ uint32_t mNumSampleToChunkOffsets;
+
+ off64_t mSampleSizeOffset;
+ uint32_t mSampleSizeFieldSize;
+ uint32_t mDefaultSampleSize;
+ uint32_t mNumSampleSizes;
+
+ bool mHasTimeToSample;
+ uint32_t mTimeToSampleCount;
+ uint32_t* mTimeToSample;
+
+ struct SampleTimeEntry {
+ uint32_t mSampleIndex;
+ uint32_t mCompositionTime;
+ };
+ SampleTimeEntry *mSampleTimeEntries;
+
+ int32_t *mCompositionTimeDeltaEntries;
+ size_t mNumCompositionTimeDeltaEntries;
+ CompositionDeltaLookup *mCompositionDeltaLookup;
+
+ off64_t mSyncSampleOffset;
+ uint32_t mNumSyncSamples;
+ uint32_t *mSyncSamples;
+ size_t mLastSyncSampleIndex;
+
+ SampleIterator *mSampleIterator;
+
+ struct SampleToChunkEntry {
+ uint32_t startChunk;
+ uint32_t samplesPerChunk;
+ uint32_t chunkDesc;
+ };
+ SampleToChunkEntry *mSampleToChunkEntries;
+
+ // Approximate size of all tables combined.
+ uint64_t mTotalSize;
+
+ friend struct SampleIterator;
+
+ // normally we don't round
+ inline uint64_t getSampleTime(
+ size_t sample_index, uint64_t scale_num, uint64_t scale_den) const {
+ return (sample_index < (size_t)mNumSampleSizes && mSampleTimeEntries != NULL
+ && scale_den != 0)
+ ? (mSampleTimeEntries[sample_index].mCompositionTime * scale_num) / scale_den : 0;
+ }
+
+ status_t getSampleSize_l(uint32_t sample_index, size_t *sample_size);
+ int32_t getCompositionTimeOffset(uint32_t sampleIndex);
+
+ static int CompareIncreasingTime(const void *, const void *);
+
+ void buildSampleEntriesTable();
+
+ SampleTable(const SampleTable &);
+ SampleTable &operator=(const SampleTable &);
+};
+
+} // namespace android
+
+#endif // SAMPLE_TABLE_H_