diff options
Diffstat (limited to 'media/libstagefright/AVIExtractor.cpp')
-rw-r--r-- | media/libstagefright/AVIExtractor.cpp | 1307 |
1 files changed, 1307 insertions, 0 deletions
diff --git a/media/libstagefright/AVIExtractor.cpp b/media/libstagefright/AVIExtractor.cpp new file mode 100644 index 0000000..5a6211e --- /dev/null +++ b/media/libstagefright/AVIExtractor.cpp @@ -0,0 +1,1307 @@ +/* + * 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/avc_utils.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; + + sp<MP3Splitter> mSplitter; + + DISALLOW_EVIL_CONSTRUCTORS(AVISource); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct AVIExtractor::MP3Splitter : public RefBase { + MP3Splitter(); + + void clear(); + void append(MediaBuffer *buffer); + status_t read(MediaBuffer **buffer); + +protected: + virtual ~MP3Splitter(); + +private: + bool mFindSync; + int64_t mBaseTimeUs; + int64_t mNumSamplesRead; + sp<ABuffer> mBuffer; + + bool resync(); + + DISALLOW_EVIL_CONSTRUCTORS(MP3Splitter); +}; + +//////////////////////////////////////////////////////////////////////////////// + +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; + + const char *mime; + CHECK(mTrack.mMeta->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) { + mSplitter = new MP3Splitter; + } else { + mSplitter.clear(); + } + + return OK; +} + +status_t AVIExtractor::AVISource::stop() { + CHECK(mBufferGroup); + + delete mBufferGroup; + mBufferGroup = NULL; + + mSplitter.clear(); + + 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; + } + + if (mSplitter != NULL) { + mSplitter->clear(); + } + } + + for (;;) { + if (mSplitter != NULL) { + status_t err = mSplitter->read(buffer); + + if (err == OK) { + break; + } else if (err != -EAGAIN) { + return err; + } + } + + off64_t offset; + size_t size; + bool isKey; + int64_t timeUs; + status_t err = mExtractor->getSampleInfo( + mTrackIndex, mSampleIndex, &offset, &size, &isKey, &timeUs); + + ++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); + } + + if (mSplitter == NULL) { + *buffer = out; + break; + } + + mSplitter->append(out); + out->release(); + out = NULL; + } + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +AVIExtractor::MP3Splitter::MP3Splitter() + : mFindSync(true), + mBaseTimeUs(-1ll), + mNumSamplesRead(0) { +} + +AVIExtractor::MP3Splitter::~MP3Splitter() { +} + +void AVIExtractor::MP3Splitter::clear() { + mFindSync = true; + mBaseTimeUs = -1ll; + mNumSamplesRead = 0; + + if (mBuffer != NULL) { + mBuffer->setRange(0, 0); + } +} + +void AVIExtractor::MP3Splitter::append(MediaBuffer *buffer) { + size_t prevCapacity = (mBuffer != NULL) ? mBuffer->capacity() : 0; + + if (mBaseTimeUs < 0) { + CHECK(mBuffer == NULL || mBuffer->size() == 0); + CHECK(buffer->meta_data()->findInt64(kKeyTime, &mBaseTimeUs)); + mNumSamplesRead = 0; + } + + if (mBuffer != NULL && mBuffer->offset() > 0) { + memmove(mBuffer->base(), mBuffer->data(), mBuffer->size()); + mBuffer->setRange(0, mBuffer->size()); + } + + if (mBuffer == NULL + || mBuffer->size() + buffer->range_length() > prevCapacity) { + size_t newCapacity = + (prevCapacity + buffer->range_length() + 1023) & ~1023; + + sp<ABuffer> newBuffer = new ABuffer(newCapacity); + if (mBuffer != NULL) { + memcpy(newBuffer->data(), mBuffer->data(), mBuffer->size()); + newBuffer->setRange(0, mBuffer->size()); + } else { + newBuffer->setRange(0, 0); + } + mBuffer = newBuffer; + } + + memcpy(mBuffer->data() + mBuffer->size(), + (const uint8_t *)buffer->data() + buffer->range_offset(), + buffer->range_length()); + + mBuffer->setRange(0, mBuffer->size() + buffer->range_length()); +} + +bool AVIExtractor::MP3Splitter::resync() { + if (mBuffer == NULL) { + return false; + } + + bool foundSync = false; + for (size_t offset = 0; offset + 3 < mBuffer->size(); ++offset) { + uint32_t firstHeader = U32_AT(mBuffer->data() + offset); + + size_t frameSize; + if (!GetMPEGAudioFrameSize(firstHeader, &frameSize)) { + continue; + } + + size_t subsequentOffset = offset + frameSize; + size_t i = 3; + while (i > 0) { + if (subsequentOffset + 3 >= mBuffer->size()) { + break; + } + + static const uint32_t kMask = 0xfffe0c00; + + uint32_t header = U32_AT(mBuffer->data() + subsequentOffset); + if ((header & kMask) != (firstHeader & kMask)) { + break; + } + + if (!GetMPEGAudioFrameSize(header, &frameSize)) { + break; + } + + subsequentOffset += frameSize; + --i; + } + + if (i == 0) { + foundSync = true; + memmove(mBuffer->data(), + mBuffer->data() + offset, + mBuffer->size() - offset); + + mBuffer->setRange(0, mBuffer->size() - offset); + break; + } + } + + return foundSync; +} + +status_t AVIExtractor::MP3Splitter::read(MediaBuffer **out) { + *out = NULL; + + if (mFindSync) { + if (!resync()) { + return -EAGAIN; + } + + mFindSync = false; + } + + if (mBuffer->size() < 4) { + return -EAGAIN; + } + + uint32_t header = U32_AT(mBuffer->data()); + size_t frameSize; + int sampleRate; + int numSamples; + if (!GetMPEGAudioFrameSize( + header, &frameSize, &sampleRate, NULL, NULL, &numSamples)) { + return ERROR_MALFORMED; + } + + if (mBuffer->size() < frameSize) { + return -EAGAIN; + } + + MediaBuffer *mbuf = new MediaBuffer(frameSize); + memcpy(mbuf->data(), mBuffer->data(), frameSize); + + int64_t timeUs = mBaseTimeUs + (mNumSamplesRead * 1000000ll) / sampleRate; + mNumSamplesRead += numSamples; + + mbuf->meta_data()->setInt64(kKeyTime, timeUs); + + mBuffer->setRange( + mBuffer->offset() + frameSize, mBuffer->size() - frameSize); + + *out = mbuf; + + 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]); + + ALOGV("%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 { + ALOGV("%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; + + // from http://wiki.multimedia.cx/index.php?title=H264 + case FOURCC('a', 'v', 'c', '1'): + case FOURCC('d', 'a', 'v', 'c'): + case FOURCC('x', '2', '6', '4'): + case FOURCC('H', '2', '6', '4'): + case FOURCC('v', 's', 's', 'h'): + return MEDIA_MIMETYPE_VIDEO_AVC; + + 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]); + + uint32_t sampleSize = U32LE_AT(&data[44]); + + 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; + } + + if (mime == NULL) { + ALOGW("Unsupported video format '%c%c%c%c'", + (char)(handler >> 24), + (char)((handler >> 16) & 0xff), + (char)((handler >> 8) & 0xff), + (char)(handler & 0xff)); + } + + 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->mBytesPerSample = sampleSize; + track->mKind = kind; + track->mNumSyncSamples = 0; + track->mThumbnailSampleSize = 0; + track->mThumbnailSampleIndex = -1; + track->mMaxSampleSize = 0; + track->mAvgChunkSize = 1.0; + track->mFirstChunkSize = 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 < 16)) { + // Expected a BITMAPINFO or WAVEFORMAT(EX) 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); + } else { + ALOGW("Unsupported audio format = 0x%04x", format); + } + + 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; + int64_t timeUs; + status_t err = getSampleInfo(0, 0, &offset, &size, &isKey, &timeUs); + + if (err != OK) { + mOffsetsAreAbsolute = !mOffsetsAreAbsolute; + err = getSampleInfo(0, 0, &offset, &size, &isKey, &timeUs); + + if (err != OK) { + return err; + } + } + + ALOGV("Chunk offsets are %s", + mOffsetsAreAbsolute ? "absolute" : "movie-chunk relative"); + } + + for (size_t i = 0; i < mTracks.size(); ++i) { + Track *track = &mTracks.editItemAt(i); + + if (track->mBytesPerSample > 0) { + // Assume all chunks are roughly the same size for now. + + // Compute the avg. size of the first 128 chunks (if there are + // that many), but exclude the size of the first one, since + // it may be an outlier. + size_t numSamplesToAverage = track->mSamples.size(); + if (numSamplesToAverage > 256) { + numSamplesToAverage = 256; + } + + double avgChunkSize = 0; + size_t j; + for (j = 0; j <= numSamplesToAverage; ++j) { + off64_t offset; + size_t size; + bool isKey; + int64_t dummy; + + status_t err = + getSampleInfo( + i, j, + &offset, &size, &isKey, &dummy); + + if (err != OK) { + return err; + } + + if (j == 0) { + track->mFirstChunkSize = size; + continue; + } + + avgChunkSize += size; + } + + avgChunkSize /= numSamplesToAverage; + + track->mAvgChunkSize = avgChunkSize; + } + + int64_t durationUs; + CHECK_EQ((status_t)OK, + getSampleTime(i, track->mSamples.size() - 1, &durationUs)); + + ALOGV("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)) { + if (track->mThumbnailSampleIndex >= 0) { + int64_t thumbnailTimeUs; + CHECK_EQ((status_t)OK, + getSampleTime(i, track->mThumbnailSampleIndex, + &thumbnailTimeUs)); + + track->mMeta->setInt64(kKeyThumbnailTime, thumbnailTimeUs); + } + + status_t err = OK; + + if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_MPEG4)) { + err = addMPEG4CodecSpecificData(i); + } else if (!strcasecmp(mime.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)) { + err = addH264CodecSpecificData(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; + int64_t timeUs; + status_t err = + getSampleInfo(trackIndex, 0, &offset, &size, &isKey, &timeUs); + + 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::addH264CodecSpecificData(size_t trackIndex) { + Track *track = &mTracks.editItemAt(trackIndex); + + off64_t offset; + size_t size; + bool isKey; + int64_t timeUs; + + // Extract codec specific data from the first non-empty sample. + + size_t sampleIndex = 0; + for (;;) { + status_t err = + getSampleInfo( + trackIndex, sampleIndex, &offset, &size, &isKey, &timeUs); + + if (err != OK) { + return err; + } + + if (size > 0) { + break; + } + + ++sampleIndex; + } + + 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; + } + + sp<MetaData> meta = MakeAVCCodecSpecificData(buffer); + + if (meta == NULL) { + ALOGE("Unable to extract AVC codec specific data"); + return ERROR_MALFORMED; + } + + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + uint32_t type; + const void *csd; + size_t csdSize; + CHECK(meta->findData(kKeyAVCC, &type, &csd, &csdSize)); + + track->mMeta->setInt32(kKeyWidth, width); + track->mMeta->setInt32(kKeyHeight, height); + track->mMeta->setData(kKeyAVCC, type, csd, csdSize); + + return OK; +} + +status_t AVIExtractor::getSampleInfo( + size_t trackIndex, size_t sampleIndex, + off64_t *offset, size_t *size, bool *isKey, + int64_t *sampleTimeUs) { + 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; + + if (track.mBytesPerSample > 0) { + size_t sampleStartInBytes; + if (sampleIndex == 0) { + sampleStartInBytes = 0; + } else { + sampleStartInBytes = + track.mFirstChunkSize + track.mAvgChunkSize * (sampleIndex - 1); + } + + sampleIndex = sampleStartInBytes / track.mBytesPerSample; + } + + *sampleTimeUs = (sampleIndex * 1000000ll * track.mRate) / track.mScale; + + return OK; +} + +status_t AVIExtractor::getSampleTime( + size_t trackIndex, size_t sampleIndex, int64_t *sampleTimeUs) { + off64_t offset; + size_t size; + bool isKey; + return getSampleInfo( + trackIndex, sampleIndex, &offset, &size, &isKey, sampleTimeUs); +} + +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; + + if (track.mBytesPerSample > 0) { + size_t closestByteOffset = + (timeUs * track.mBytesPerSample) + / track.mRate * track.mScale / 1000000ll; + + if (closestByteOffset <= track.mFirstChunkSize) { + closestSampleIndex = 0; + } else { + closestSampleIndex = + (closestByteOffset - track.mFirstChunkSize) + / track.mAvgChunkSize; + } + } else { + // Each chunk contains a single sample. + 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); + + // Just a tad over the mp3 extractor's confidence, since + // these .avi files may contain .mp3 content that otherwise would + // mistakenly lead to us identifying the entire file as a .mp3 file. + *confidence = 0.21; + + return true; + } + + return false; +} + +} // namespace android |