/* * 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/avc_utils.h" #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; sp 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 mBuffer; bool resync(); DISALLOW_EVIL_CONSTRUCTORS(MP3Splitter); }; //////////////////////////////////////////////////////////////////////////////// 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; 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 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 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) : 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]); 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 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]); 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 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 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 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; int64_t timeUs; status_t err = getSampleInfo(trackIndex, 0, &offset, &size, &isKey, &timeUs); 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::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 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 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 &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); // 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