summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2011-03-24 14:18:02 -0700
committerAndreas Huber <andih@google.com>2011-03-30 13:13:25 -0700
commitf8374dec590223ebdd6959b26d9ba90749dd8328 (patch)
tree4d93c723dcba33bbad56b3f55a664c6a3144481c /media
parent72bbbff2dfb120ed1ebc9a50e8509364508bfa70 (diff)
downloadframeworks_av-f8374dec590223ebdd6959b26d9ba90749dd8328.zip
frameworks_av-f8374dec590223ebdd6959b26d9ba90749dd8328.tar.gz
frameworks_av-f8374dec590223ebdd6959b26d9ba90749dd8328.tar.bz2
Support for divx files, i.e. mpeg 4 video + mpeg audio layer III in .avi files
Change-Id: I6e1e989c43300ddb0d341e3e9183cdccd69eeae6 related-to-bug: 4192562
Diffstat (limited to 'media')
-rw-r--r--media/libstagefright/AVIExtractor.cpp922
-rw-r--r--media/libstagefright/Android.mk1
-rw-r--r--media/libstagefright/DataSource.cpp2
-rw-r--r--media/libstagefright/MediaDefs.cpp1
-rw-r--r--media/libstagefright/MediaExtractor.cpp3
-rw-r--r--media/libstagefright/StagefrightMediaScanner.cpp1
-rw-r--r--media/libstagefright/include/AVIExtractor.h111
7 files changed, 1041 insertions, 0 deletions
diff --git a/media/libstagefright/AVIExtractor.cpp b/media/libstagefright/AVIExtractor.cpp
new file mode 100644
index 0000000..6313ca3
--- /dev/null
+++ b/media/libstagefright/AVIExtractor.cpp
@@ -0,0 +1,922 @@
+/*
+ * Copyright (C) 2011 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 "AVIExtractor"
+#include <utils/Log.h>
+
+#include "include/AVIExtractor.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct AVIExtractor::AVISource : public MediaSource {
+ AVISource(const sp<AVIExtractor> &extractor, size_t trackIndex);
+
+ virtual status_t start(MetaData *params);
+ virtual status_t stop();
+
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options);
+
+protected:
+ virtual ~AVISource();
+
+private:
+ sp<AVIExtractor> mExtractor;
+ size_t mTrackIndex;
+ const AVIExtractor::Track &mTrack;
+ MediaBufferGroup *mBufferGroup;
+ size_t mSampleIndex;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AVISource);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+AVIExtractor::AVISource::AVISource(
+ const sp<AVIExtractor> &extractor, size_t trackIndex)
+ : mExtractor(extractor),
+ mTrackIndex(trackIndex),
+ mTrack(mExtractor->mTracks.itemAt(trackIndex)),
+ mBufferGroup(NULL) {
+}
+
+AVIExtractor::AVISource::~AVISource() {
+ if (mBufferGroup) {
+ stop();
+ }
+}
+
+status_t AVIExtractor::AVISource::start(MetaData *params) {
+ CHECK(!mBufferGroup);
+
+ mBufferGroup = new MediaBufferGroup;
+
+ mBufferGroup->add_buffer(new MediaBuffer(mTrack.mMaxSampleSize));
+ mBufferGroup->add_buffer(new MediaBuffer(mTrack.mMaxSampleSize));
+ mSampleIndex = 0;
+
+ return OK;
+}
+
+status_t AVIExtractor::AVISource::stop() {
+ CHECK(mBufferGroup);
+
+ delete mBufferGroup;
+ mBufferGroup = NULL;
+
+ return OK;
+}
+
+sp<MetaData> AVIExtractor::AVISource::getFormat() {
+ return mTrack.mMeta;
+}
+
+status_t AVIExtractor::AVISource::read(
+ MediaBuffer **buffer, const ReadOptions *options) {
+ CHECK(mBufferGroup);
+
+ *buffer = NULL;
+
+ int64_t seekTimeUs;
+ ReadOptions::SeekMode seekMode;
+ if (options && options->getSeekTo(&seekTimeUs, &seekMode)) {
+ status_t err =
+ mExtractor->getSampleIndexAtTime(
+ mTrackIndex, seekTimeUs, seekMode, &mSampleIndex);
+
+ if (err != OK) {
+ return ERROR_END_OF_STREAM;
+ }
+ }
+
+ int64_t timeUs =
+ (mSampleIndex * 1000000ll * mTrack.mRate) / mTrack.mScale;
+
+ off64_t offset;
+ size_t size;
+ bool isKey;
+ status_t err = mExtractor->getSampleInfo(
+ mTrackIndex, mSampleIndex, &offset, &size, &isKey);
+
+ ++mSampleIndex;
+
+ if (err != OK) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ MediaBuffer *out;
+ CHECK_EQ(mBufferGroup->acquire_buffer(&out), (status_t)OK);
+
+ ssize_t n = mExtractor->mDataSource->readAt(offset, out->data(), size);
+
+ if (n < (ssize_t)size) {
+ return n < 0 ? (status_t)n : (status_t)ERROR_MALFORMED;
+ }
+
+ out->set_range(0, size);
+
+ out->meta_data()->setInt64(kKeyTime, timeUs);
+
+ if (isKey) {
+ out->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+ }
+
+ *buffer = out;
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+AVIExtractor::AVIExtractor(const sp<DataSource> &dataSource)
+ : mDataSource(dataSource) {
+ mInitCheck = parseHeaders();
+
+ if (mInitCheck != OK) {
+ mTracks.clear();
+ }
+}
+
+AVIExtractor::~AVIExtractor() {
+}
+
+size_t AVIExtractor::countTracks() {
+ return mTracks.size();
+}
+
+sp<MediaSource> AVIExtractor::getTrack(size_t index) {
+ return index < mTracks.size() ? new AVISource(this, index) : NULL;
+}
+
+sp<MetaData> AVIExtractor::getTrackMetaData(
+ size_t index, uint32_t flags) {
+ return index < mTracks.size() ? mTracks.editItemAt(index).mMeta : NULL;
+}
+
+sp<MetaData> AVIExtractor::getMetaData() {
+ sp<MetaData> meta = new MetaData;
+
+ if (mInitCheck == OK) {
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_AVI);
+ }
+
+ return meta;
+}
+
+status_t AVIExtractor::parseHeaders() {
+ mTracks.clear();
+ mMovieOffset = 0;
+ mFoundIndex = false;
+ mOffsetsAreAbsolute = false;
+
+ ssize_t res = parseChunk(0ll, -1ll);
+
+ if (res < 0) {
+ return (status_t)res;
+ }
+
+ if (mMovieOffset == 0ll || !mFoundIndex) {
+ return ERROR_MALFORMED;
+ }
+
+ return OK;
+}
+
+ssize_t AVIExtractor::parseChunk(off64_t offset, off64_t size, int depth) {
+ if (size >= 0 && size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t tmp[12];
+ ssize_t n = mDataSource->readAt(offset, tmp, 8);
+
+ if (n < 8) {
+ return (n < 0) ? n : (ssize_t)ERROR_MALFORMED;
+ }
+
+ uint32_t fourcc = U32_AT(tmp);
+ uint32_t chunkSize = U32LE_AT(&tmp[4]);
+
+ if (size >= 0 && chunkSize + 8 > size) {
+ return ERROR_MALFORMED;
+ }
+
+ static const char kPrefix[] = " ";
+ const char *prefix = &kPrefix[strlen(kPrefix) - 2 * depth];
+
+ if (fourcc == FOURCC('L', 'I', 'S', 'T')
+ || fourcc == FOURCC('R', 'I', 'F', 'F')) {
+ // It's a list of chunks
+
+ if (size >= 0 && size < 12) {
+ return ERROR_MALFORMED;
+ }
+
+ n = mDataSource->readAt(offset + 8, &tmp[8], 4);
+
+ if (n < 4) {
+ return (n < 0) ? n : (ssize_t)ERROR_MALFORMED;
+ }
+
+ uint32_t subFourcc = U32_AT(&tmp[8]);
+
+ LOGV("%s offset 0x%08llx LIST of '%c%c%c%c', size %d",
+ prefix,
+ offset,
+ (char)(subFourcc >> 24),
+ (char)((subFourcc >> 16) & 0xff),
+ (char)((subFourcc >> 8) & 0xff),
+ (char)(subFourcc & 0xff),
+ chunkSize - 4);
+
+ if (subFourcc == FOURCC('m', 'o', 'v', 'i')) {
+ // We're not going to parse this, but will take note of the
+ // offset.
+
+ mMovieOffset = offset;
+ } else {
+ off64_t subOffset = offset + 12;
+ off64_t subOffsetLimit = subOffset + chunkSize - 4;
+ while (subOffset < subOffsetLimit) {
+ ssize_t res =
+ parseChunk(subOffset, subOffsetLimit - subOffset, depth + 1);
+
+ if (res < 0) {
+ return res;
+ }
+
+ subOffset += res;
+ }
+ }
+ } else {
+ LOGV("%s offset 0x%08llx CHUNK '%c%c%c%c'",
+ prefix,
+ offset,
+ (char)(fourcc >> 24),
+ (char)((fourcc >> 16) & 0xff),
+ (char)((fourcc >> 8) & 0xff),
+ (char)(fourcc & 0xff));
+
+ status_t err = OK;
+
+ switch (fourcc) {
+ case FOURCC('s', 't', 'r', 'h'):
+ {
+ err = parseStreamHeader(offset + 8, chunkSize);
+ break;
+ }
+
+ case FOURCC('s', 't', 'r', 'f'):
+ {
+ err = parseStreamFormat(offset + 8, chunkSize);
+ break;
+ }
+
+ case FOURCC('i', 'd', 'x', '1'):
+ {
+ err = parseIndex(offset + 8, chunkSize);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ if (chunkSize & 1) {
+ ++chunkSize;
+ }
+
+ return chunkSize + 8;
+}
+
+static const char *GetMIMETypeForHandler(uint32_t handler) {
+ switch (handler) {
+ // Wow... shamelessly copied from
+ // http://wiki.multimedia.cx/index.php?title=ISO_MPEG-4
+
+ case FOURCC('3', 'I', 'V', '2'):
+ case FOURCC('3', 'i', 'v', '2'):
+ case FOURCC('B', 'L', 'Z', '0'):
+ case FOURCC('D', 'I', 'G', 'I'):
+ case FOURCC('D', 'I', 'V', '1'):
+ case FOURCC('d', 'i', 'v', '1'):
+ case FOURCC('D', 'I', 'V', 'X'):
+ case FOURCC('d', 'i', 'v', 'x'):
+ case FOURCC('D', 'X', '5', '0'):
+ case FOURCC('d', 'x', '5', '0'):
+ case FOURCC('D', 'X', 'G', 'M'):
+ case FOURCC('E', 'M', '4', 'A'):
+ case FOURCC('E', 'P', 'H', 'V'):
+ case FOURCC('F', 'M', 'P', '4'):
+ case FOURCC('f', 'm', 'p', '4'):
+ case FOURCC('F', 'V', 'F', 'W'):
+ case FOURCC('H', 'D', 'X', '4'):
+ case FOURCC('h', 'd', 'x', '4'):
+ case FOURCC('M', '4', 'C', 'C'):
+ case FOURCC('M', '4', 'S', '2'):
+ case FOURCC('m', '4', 's', '2'):
+ case FOURCC('M', 'P', '4', 'S'):
+ case FOURCC('m', 'p', '4', 's'):
+ case FOURCC('M', 'P', '4', 'V'):
+ case FOURCC('m', 'p', '4', 'v'):
+ case FOURCC('M', 'V', 'X', 'M'):
+ case FOURCC('R', 'M', 'P', '4'):
+ case FOURCC('S', 'E', 'D', 'G'):
+ case FOURCC('S', 'M', 'P', '4'):
+ case FOURCC('U', 'M', 'P', '4'):
+ case FOURCC('W', 'V', '1', 'F'):
+ case FOURCC('X', 'V', 'I', 'D'):
+ case FOURCC('X', 'v', 'i', 'D'):
+ case FOURCC('x', 'v', 'i', 'd'):
+ case FOURCC('X', 'V', 'I', 'X'):
+ return MEDIA_MIMETYPE_VIDEO_MPEG4;
+
+ default:
+ return NULL;
+ }
+}
+
+status_t AVIExtractor::parseStreamHeader(off64_t offset, size_t size) {
+ if (size != 56) {
+ return ERROR_MALFORMED;
+ }
+
+ if (mTracks.size() > 99) {
+ return -ERANGE;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(size);
+ ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+ if (n < (ssize_t)size) {
+ return n < 0 ? (status_t)n : ERROR_MALFORMED;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ uint32_t type = U32_AT(data);
+ uint32_t handler = U32_AT(&data[4]);
+ uint32_t flags = U32LE_AT(&data[8]);
+
+ sp<MetaData> meta = new MetaData;
+
+ uint32_t rate = U32LE_AT(&data[20]);
+ uint32_t scale = U32LE_AT(&data[24]);
+
+ const char *mime = NULL;
+ Track::Kind kind = Track::OTHER;
+
+ if (type == FOURCC('v', 'i', 'd', 's')) {
+ mime = GetMIMETypeForHandler(handler);
+
+ if (mime && strncasecmp(mime, "video/", 6)) {
+ return ERROR_MALFORMED;
+ }
+
+ kind = Track::VIDEO;
+ } else if (type == FOURCC('a', 'u', 'd', 's')) {
+ if (mime && strncasecmp(mime, "audio/", 6)) {
+ return ERROR_MALFORMED;
+ }
+
+ kind = Track::AUDIO;
+ }
+
+ if (!mime) {
+ mime = "application/octet-stream";
+ }
+
+ meta->setCString(kKeyMIMEType, mime);
+
+ mTracks.push();
+ Track *track = &mTracks.editItemAt(mTracks.size() - 1);
+
+ track->mMeta = meta;
+ track->mRate = rate;
+ track->mScale = scale;
+ track->mKind = kind;
+ track->mNumSyncSamples = 0;
+ track->mThumbnailSampleSize = 0;
+ track->mThumbnailSampleIndex = -1;
+ track->mMaxSampleSize = 0;
+
+ return OK;
+}
+
+status_t AVIExtractor::parseStreamFormat(off64_t offset, size_t size) {
+ if (mTracks.isEmpty()) {
+ return ERROR_MALFORMED;
+ }
+
+ Track *track = &mTracks.editItemAt(mTracks.size() - 1);
+
+ if (track->mKind == Track::OTHER) {
+ // We don't support this content, but that's not a parsing error.
+ return OK;
+ }
+
+ bool isVideo = (track->mKind == Track::VIDEO);
+
+ if ((isVideo && size < 40) || (!isVideo && size < 18)) {
+ // Expected a BITMAPINFO or WAVEFORMATEX structure, respectively.
+ return ERROR_MALFORMED;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(size);
+ ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+ if (n < (ssize_t)size) {
+ return n < 0 ? (status_t)n : ERROR_MALFORMED;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ if (isVideo) {
+ uint32_t width = U32LE_AT(&data[4]);
+ uint32_t height = U32LE_AT(&data[8]);
+
+ track->mMeta->setInt32(kKeyWidth, width);
+ track->mMeta->setInt32(kKeyHeight, height);
+ } else {
+ uint32_t format = U16LE_AT(data);
+ if (format == 0x55) {
+ track->mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ }
+
+ uint32_t numChannels = U16LE_AT(&data[2]);
+ uint32_t sampleRate = U32LE_AT(&data[4]);
+
+ track->mMeta->setInt32(kKeyChannelCount, numChannels);
+ track->mMeta->setInt32(kKeySampleRate, sampleRate);
+ }
+
+ return OK;
+}
+
+// static
+bool AVIExtractor::IsCorrectChunkType(
+ ssize_t trackIndex, Track::Kind kind, uint32_t chunkType) {
+ uint32_t chunkBase = chunkType & 0xffff;
+
+ switch (kind) {
+ case Track::VIDEO:
+ {
+ if (chunkBase != FOURCC(0, 0, 'd', 'c')
+ && chunkBase != FOURCC(0, 0, 'd', 'b')) {
+ return false;
+ }
+ break;
+ }
+
+ case Track::AUDIO:
+ {
+ if (chunkBase != FOURCC(0, 0, 'w', 'b')) {
+ return false;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (trackIndex < 0) {
+ return true;
+ }
+
+ uint8_t hi = chunkType >> 24;
+ uint8_t lo = (chunkType >> 16) & 0xff;
+
+ if (hi < '0' || hi > '9' || lo < '0' || lo > '9') {
+ return false;
+ }
+
+ if (trackIndex != (10 * (hi - '0') + (lo - '0'))) {
+ return false;
+ }
+
+ return true;
+}
+
+status_t AVIExtractor::parseIndex(off64_t offset, size_t size) {
+ if ((size % 16) != 0) {
+ return ERROR_MALFORMED;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(size);
+ ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+ if (n < (ssize_t)size) {
+ return n < 0 ? (status_t)n : ERROR_MALFORMED;
+ }
+
+ const uint8_t *data = buffer->data();
+
+ while (size > 0) {
+ uint32_t chunkType = U32_AT(data);
+
+ uint8_t hi = chunkType >> 24;
+ uint8_t lo = (chunkType >> 16) & 0xff;
+
+ if (hi < '0' || hi > '9' || lo < '0' || lo > '9') {
+ return ERROR_MALFORMED;
+ }
+
+ size_t trackIndex = 10 * (hi - '0') + (lo - '0');
+
+ if (trackIndex >= mTracks.size()) {
+ return ERROR_MALFORMED;
+ }
+
+ Track *track = &mTracks.editItemAt(trackIndex);
+
+ if (!IsCorrectChunkType(-1, track->mKind, chunkType)) {
+ return ERROR_MALFORMED;
+ }
+
+ if (track->mKind == Track::OTHER) {
+ data += 16;
+ size -= 16;
+ continue;
+ }
+
+ uint32_t flags = U32LE_AT(&data[4]);
+ uint32_t offset = U32LE_AT(&data[8]);
+ uint32_t chunkSize = U32LE_AT(&data[12]);
+
+ if (chunkSize > track->mMaxSampleSize) {
+ track->mMaxSampleSize = chunkSize;
+ }
+
+ track->mSamples.push();
+
+ SampleInfo *info =
+ &track->mSamples.editItemAt(track->mSamples.size() - 1);
+
+ info->mOffset = offset;
+ info->mIsKey = (flags & 0x10) != 0;
+
+ if (info->mIsKey) {
+ static const size_t kMaxNumSyncSamplesToScan = 20;
+
+ if (track->mNumSyncSamples < kMaxNumSyncSamplesToScan) {
+ if (chunkSize > track->mThumbnailSampleSize) {
+ track->mThumbnailSampleSize = chunkSize;
+
+ track->mThumbnailSampleIndex =
+ track->mSamples.size() - 1;
+ }
+ }
+
+ ++track->mNumSyncSamples;
+ }
+
+ data += 16;
+ size -= 16;
+ }
+
+ if (!mTracks.isEmpty()) {
+ off64_t offset;
+ size_t size;
+ bool isKey;
+ status_t err = getSampleInfo(0, 0, &offset, &size, &isKey);
+
+ if (err != OK) {
+ mOffsetsAreAbsolute = !mOffsetsAreAbsolute;
+ err = getSampleInfo(0, 0, &offset, &size, &isKey);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+
+ LOGV("Chunk offsets are %s",
+ mOffsetsAreAbsolute ? "absolute" : "movie-chunk relative");
+ }
+
+ for (size_t i = 0; i < mTracks.size(); ++i) {
+ Track *track = &mTracks.editItemAt(i);
+
+ int64_t durationUs =
+ (track->mSamples.size() * 1000000ll * track->mRate) / track->mScale;
+
+ LOGV("track %d duration = %.2f secs", i, durationUs / 1E6);
+
+ track->mMeta->setInt64(kKeyDuration, durationUs);
+ track->mMeta->setInt32(kKeyMaxInputSize, track->mMaxSampleSize);
+
+ const char *tmp;
+ CHECK(track->mMeta->findCString(kKeyMIMEType, &tmp));
+
+ AString mime = tmp;
+
+ if (!strncasecmp("video/", mime.c_str(), 6)
+ && track->mThumbnailSampleIndex >= 0) {
+ int64_t thumbnailTimeUs =
+ (track->mThumbnailSampleIndex * 1000000ll * track->mRate)
+ / track->mScale;
+
+ track->mMeta->setInt64(kKeyThumbnailTime, thumbnailTimeUs);
+
+ if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_MPEG4)) {
+ status_t err = addMPEG4CodecSpecificData(i);
+
+ if (err != OK) {
+ return err;
+ }
+ }
+ }
+ }
+
+ mFoundIndex = true;
+
+ return OK;
+}
+
+static size_t GetSizeWidth(size_t x) {
+ size_t n = 1;
+ while (x > 127) {
+ ++n;
+ x >>= 7;
+ }
+ return n;
+}
+
+static uint8_t *EncodeSize(uint8_t *dst, size_t x) {
+ while (x > 127) {
+ *dst++ = (x & 0x7f) | 0x80;
+ x >>= 7;
+ }
+ *dst++ = x;
+ return dst;
+}
+
+sp<ABuffer> MakeMPEG4VideoCodecSpecificData(const sp<ABuffer> &config) {
+ size_t len1 = config->size() + GetSizeWidth(config->size()) + 1;
+ size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13;
+ size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3;
+
+ sp<ABuffer> csd = new ABuffer(len3);
+ uint8_t *dst = csd->data();
+ *dst++ = 0x03;
+ dst = EncodeSize(dst, len2 + 3);
+ *dst++ = 0x00; // ES_ID
+ *dst++ = 0x00;
+ *dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag
+
+ *dst++ = 0x04;
+ dst = EncodeSize(dst, len1 + 13);
+ *dst++ = 0x01; // Video ISO/IEC 14496-2 Simple Profile
+ for (size_t i = 0; i < 12; ++i) {
+ *dst++ = 0x00;
+ }
+
+ *dst++ = 0x05;
+ dst = EncodeSize(dst, config->size());
+ memcpy(dst, config->data(), config->size());
+ dst += config->size();
+
+ // hexdump(csd->data(), csd->size());
+
+ return csd;
+}
+
+status_t AVIExtractor::addMPEG4CodecSpecificData(size_t trackIndex) {
+ Track *track = &mTracks.editItemAt(trackIndex);
+
+ off64_t offset;
+ size_t size;
+ bool isKey;
+ status_t err = getSampleInfo(trackIndex, 0, &offset, &size, &isKey);
+
+ if (err != OK) {
+ return err;
+ }
+
+ sp<ABuffer> buffer = new ABuffer(size);
+ ssize_t n = mDataSource->readAt(offset, buffer->data(), buffer->size());
+
+ if (n < (ssize_t)size) {
+ return n < 0 ? (status_t)n : ERROR_MALFORMED;
+ }
+
+ // Extract everything up to the first VOP start code from the first
+ // frame's encoded data and use it to construct an ESDS with the
+ // codec specific data.
+
+ size_t i = 0;
+ bool found = false;
+ while (i + 3 < buffer->size()) {
+ if (!memcmp("\x00\x00\x01\xb6", &buffer->data()[i], 4)) {
+ found = true;
+ break;
+ }
+
+ ++i;
+ }
+
+ if (!found) {
+ return ERROR_MALFORMED;
+ }
+
+ buffer->setRange(0, i);
+
+ sp<ABuffer> csd = MakeMPEG4VideoCodecSpecificData(buffer);
+ track->mMeta->setData(kKeyESDS, kTypeESDS, csd->data(), csd->size());
+
+ return OK;
+}
+
+status_t AVIExtractor::getSampleInfo(
+ size_t trackIndex, size_t sampleIndex,
+ off64_t *offset, size_t *size, bool *isKey) {
+ if (trackIndex >= mTracks.size()) {
+ return -ERANGE;
+ }
+
+ const Track &track = mTracks.itemAt(trackIndex);
+
+ if (sampleIndex >= track.mSamples.size()) {
+ return -ERANGE;
+ }
+
+ const SampleInfo &info = track.mSamples.itemAt(sampleIndex);
+
+ if (!mOffsetsAreAbsolute) {
+ *offset = info.mOffset + mMovieOffset + 8;
+ } else {
+ *offset = info.mOffset;
+ }
+
+ *size = 0;
+
+ uint8_t tmp[8];
+ ssize_t n = mDataSource->readAt(*offset, tmp, 8);
+
+ if (n < 8) {
+ return n < 0 ? (status_t)n : (status_t)ERROR_MALFORMED;
+ }
+
+ uint32_t chunkType = U32_AT(tmp);
+
+ if (!IsCorrectChunkType(trackIndex, track.mKind, chunkType)) {
+ return ERROR_MALFORMED;
+ }
+
+ *offset += 8;
+ *size = U32LE_AT(&tmp[4]);
+
+ *isKey = info.mIsKey;
+
+ return OK;
+}
+
+status_t AVIExtractor::getSampleIndexAtTime(
+ size_t trackIndex,
+ int64_t timeUs, MediaSource::ReadOptions::SeekMode mode,
+ size_t *sampleIndex) const {
+ if (trackIndex >= mTracks.size()) {
+ return -ERANGE;
+ }
+
+ const Track &track = mTracks.itemAt(trackIndex);
+
+ ssize_t closestSampleIndex =
+ timeUs / track.mRate * track.mScale / 1000000ll;
+
+ ssize_t numSamples = track.mSamples.size();
+
+ if (closestSampleIndex < 0) {
+ closestSampleIndex = 0;
+ } else if (closestSampleIndex >= numSamples) {
+ closestSampleIndex = numSamples - 1;
+ }
+
+ if (mode == MediaSource::ReadOptions::SEEK_CLOSEST) {
+ *sampleIndex = closestSampleIndex;
+
+ return OK;
+ }
+
+ ssize_t prevSyncSampleIndex = closestSampleIndex;
+ while (prevSyncSampleIndex >= 0) {
+ const SampleInfo &info =
+ track.mSamples.itemAt(prevSyncSampleIndex);
+
+ if (info.mIsKey) {
+ break;
+ }
+
+ --prevSyncSampleIndex;
+ }
+
+ ssize_t nextSyncSampleIndex = closestSampleIndex;
+ while (nextSyncSampleIndex < numSamples) {
+ const SampleInfo &info =
+ track.mSamples.itemAt(nextSyncSampleIndex);
+
+ if (info.mIsKey) {
+ break;
+ }
+
+ ++nextSyncSampleIndex;
+ }
+
+ switch (mode) {
+ case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC:
+ {
+ *sampleIndex = prevSyncSampleIndex;
+
+ return prevSyncSampleIndex >= 0 ? OK : UNKNOWN_ERROR;
+ }
+
+ case MediaSource::ReadOptions::SEEK_NEXT_SYNC:
+ {
+ *sampleIndex = nextSyncSampleIndex;
+
+ return nextSyncSampleIndex < numSamples ? OK : UNKNOWN_ERROR;
+ }
+
+ case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC:
+ {
+ if (prevSyncSampleIndex < 0 && nextSyncSampleIndex >= numSamples) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (prevSyncSampleIndex < 0) {
+ *sampleIndex = nextSyncSampleIndex;
+ return OK;
+ }
+
+ if (nextSyncSampleIndex >= numSamples) {
+ *sampleIndex = prevSyncSampleIndex;
+ return OK;
+ }
+
+ size_t dist1 = closestSampleIndex - prevSyncSampleIndex;
+ size_t dist2 = nextSyncSampleIndex - closestSampleIndex;
+
+ *sampleIndex =
+ (dist1 < dist2) ? prevSyncSampleIndex : nextSyncSampleIndex;
+
+ return OK;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+bool SniffAVI(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *) {
+ char tmp[12];
+ if (source->readAt(0, tmp, 12) < 12) {
+ return false;
+ }
+
+ if (!memcmp(tmp, "RIFF", 4) && !memcmp(&tmp[8], "AVI ", 4)) {
+ mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_AVI);
+ *confidence = 0.2;
+
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace android
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index d2e8b46..2f3e141 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \
AACExtractor.cpp \
AMRExtractor.cpp \
AMRWriter.cpp \
+ AVIExtractor.cpp \
AudioPlayer.cpp \
AudioSource.cpp \
AwesomePlayer.cpp \
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index 7c2096e..c16b3b5 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -15,6 +15,7 @@
*/
#include "include/AMRExtractor.h"
+#include "include/AVIExtractor.h"
#include "include/MP3Extractor.h"
#include "include/MPEG4Extractor.h"
#include "include/WAVExtractor.h"
@@ -111,6 +112,7 @@ void DataSource::RegisterDefaultSniffers() {
RegisterSniffer(SniffMPEG2TS);
RegisterSniffer(SniffMP3);
RegisterSniffer(SniffAAC);
+ RegisterSniffer(SniffAVI);
char value[PROPERTY_VALUE_MAX];
if (property_get("drm.service.enabled", value, NULL)
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 0be7261..8ca6ee8 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -43,6 +43,7 @@ const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg";
const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska";
const char *MEDIA_MIMETYPE_CONTAINER_MPEG2TS = "video/mp2ts";
+const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi";
const char *MEDIA_MIMETYPE_CONTAINER_WVM = "video/wvm";
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 23bad5b..af0131e 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -19,6 +19,7 @@
#include <utils/Log.h>
#include "include/AMRExtractor.h"
+#include "include/AVIExtractor.h"
#include "include/MP3Extractor.h"
#include "include/MPEG4Extractor.h"
#include "include/WAVExtractor.h"
@@ -108,6 +109,8 @@ sp<MediaExtractor> MediaExtractor::Create(
ret = new MatroskaExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
ret = new MPEG2TSExtractor(source);
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) {
+ ret = new AVIExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {
ret = new WVMExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp
index 8b86e53..f82ff32 100644
--- a/media/libstagefright/StagefrightMediaScanner.cpp
+++ b/media/libstagefright/StagefrightMediaScanner.cpp
@@ -38,6 +38,7 @@ static bool FileHasAcceptableExtension(const char *extension) {
".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota",
".mkv", ".mka", ".webm", ".ts", ".fl", ".flac", ".mxmf",
+ ".avi",
};
static const size_t kNumValidExtensions =
sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
diff --git a/media/libstagefright/include/AVIExtractor.h b/media/libstagefright/include/AVIExtractor.h
new file mode 100644
index 0000000..375a94d
--- /dev/null
+++ b/media/libstagefright/include/AVIExtractor.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 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 AVI_EXTRACTOR_H_
+
+#define AVI_EXTRACTOR_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct AVIExtractor : public MediaExtractor {
+ AVIExtractor(const sp<DataSource> &dataSource);
+
+ 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();
+
+protected:
+ virtual ~AVIExtractor();
+
+private:
+ struct AVISource;
+
+ struct SampleInfo {
+ uint32_t mOffset;
+ bool mIsKey;
+ };
+
+ struct Track {
+ sp<MetaData> mMeta;
+ Vector<SampleInfo> mSamples;
+ uint32_t mRate;
+ uint32_t mScale;
+
+ enum Kind {
+ AUDIO,
+ VIDEO,
+ OTHER
+
+ } mKind;
+
+ size_t mNumSyncSamples;
+ size_t mThumbnailSampleSize;
+ ssize_t mThumbnailSampleIndex;
+ size_t mMaxSampleSize;
+ };
+
+ sp<DataSource> mDataSource;
+ status_t mInitCheck;
+ Vector<Track> mTracks;
+
+ off64_t mMovieOffset;
+ bool mFoundIndex;
+ bool mOffsetsAreAbsolute;
+
+ ssize_t parseChunk(off64_t offset, off64_t size, int depth = 0);
+ status_t parseStreamHeader(off64_t offset, size_t size);
+ status_t parseStreamFormat(off64_t offset, size_t size);
+ status_t parseIndex(off64_t offset, size_t size);
+
+ status_t parseHeaders();
+
+ status_t getSampleInfo(
+ size_t trackIndex, size_t sampleIndex,
+ off64_t *offset, size_t *size, bool *isKey);
+
+ status_t getSampleIndexAtTime(
+ size_t trackIndex,
+ int64_t timeUs, MediaSource::ReadOptions::SeekMode mode,
+ size_t *sampleIndex) const;
+
+ status_t addMPEG4CodecSpecificData(size_t trackIndex);
+
+ static bool IsCorrectChunkType(
+ ssize_t trackIndex, Track::Kind kind, uint32_t chunkType);
+
+ DISALLOW_EVIL_CONSTRUCTORS(AVIExtractor);
+};
+
+class String8;
+struct AMessage;
+
+bool SniffAVI(
+ const sp<DataSource> &source, String8 *mimeType, float *confidence,
+ sp<AMessage> *);
+
+} // namespace android
+
+#endif // AVI_EXTRACTOR_H_