From cabb7da7125a80c55233bd23959327424e9646cc Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 24 Mar 2011 14:18:02 -0700 Subject: Support for divx files, i.e. mpeg 4 video + mpeg audio layer III in .avi files Change-Id: I6e1e989c43300ddb0d341e3e9183cdccd69eeae6 related-to-bug: 4192562 --- media/java/android/media/MediaFile.java | 4 +- media/libstagefright/AVIExtractor.cpp | 922 +++++++++++++++++++++++ media/libstagefright/Android.mk | 1 + media/libstagefright/DataSource.cpp | 2 + media/libstagefright/MediaDefs.cpp | 1 + media/libstagefright/MediaExtractor.cpp | 3 + media/libstagefright/StagefrightMediaScanner.cpp | 1 + media/libstagefright/include/AVIExtractor.h | 111 +++ 8 files changed, 1044 insertions(+), 1 deletion(-) create mode 100644 media/libstagefright/AVIExtractor.cpp create mode 100644 media/libstagefright/include/AVIExtractor.h (limited to 'media') diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index c41e0ad..352579a 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -66,8 +66,9 @@ public class MediaFile { public static final int FILE_TYPE_ASF = 26; public static final int FILE_TYPE_MKV = 27; public static final int FILE_TYPE_MP2TS = 28; + public static final int FILE_TYPE_AVI = 29; private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4; - private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_MP2TS; + private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_AVI; // Image file types public static final int FILE_TYPE_JPEG = 31; @@ -198,6 +199,7 @@ public class MediaFile { addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); + addFileType("AVI", FILE_TYPE_AVI, "video/avi"); if (isWMVEnabled()) { addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", MtpConstants.FORMAT_WMV); 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 + +#include "include/AVIExtractor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +struct AVIExtractor::AVISource : public MediaSource { + AVISource(const sp &extractor, size_t trackIndex); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + + virtual sp getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + +protected: + virtual ~AVISource(); + +private: + sp mExtractor; + size_t mTrackIndex; + const AVIExtractor::Track &mTrack; + MediaBufferGroup *mBufferGroup; + size_t mSampleIndex; + + DISALLOW_EVIL_CONSTRUCTORS(AVISource); +}; + +//////////////////////////////////////////////////////////////////////////////// + +AVIExtractor::AVISource::AVISource( + const sp &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 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) + : mDataSource(dataSource) { + mInitCheck = parseHeaders(); + + if (mInitCheck != OK) { + mTracks.clear(); + } +} + +AVIExtractor::~AVIExtractor() { +} + +size_t AVIExtractor::countTracks() { + return mTracks.size(); +} + +sp AVIExtractor::getTrack(size_t index) { + return index < mTracks.size() ? new AVISource(this, index) : NULL; +} + +sp AVIExtractor::getTrackMetaData( + size_t index, uint32_t flags) { + return index < mTracks.size() ? mTracks.editItemAt(index).mMeta : NULL; +} + +sp AVIExtractor::getMetaData() { + sp 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 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 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 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 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 MakeMPEG4VideoCodecSpecificData(const sp &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 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 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 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 &source, String8 *mimeType, float *confidence, + sp *) { + 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 #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::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 +#include +#include +#include + +namespace android { + +struct AVIExtractor : public MediaExtractor { + AVIExtractor(const sp &dataSource); + + virtual size_t countTracks(); + + virtual sp getTrack(size_t index); + + virtual sp getTrackMetaData( + size_t index, uint32_t flags); + + virtual sp getMetaData(); + +protected: + virtual ~AVIExtractor(); + +private: + struct AVISource; + + struct SampleInfo { + uint32_t mOffset; + bool mIsKey; + }; + + struct Track { + sp mMeta; + Vector 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 mDataSource; + status_t mInitCheck; + Vector 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 &source, String8 *mimeType, float *confidence, + sp *); + +} // namespace android + +#endif // AVI_EXTRACTOR_H_ -- cgit v1.1