| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "OggExtractor" |
| #include <utils/Log.h> |
| |
| #include "OggExtractor.h" |
| |
| #include <cutils/properties.h> |
| #include <utils/Vector.h> |
| #include <media/DataSourceBase.h> |
| #include <media/ExtractorUtils.h> |
| #include <media/stagefright/foundation/ABuffer.h> |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/foundation/base64.h> |
| #include <media/stagefright/foundation/ByteUtils.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MediaErrors.h> |
| #include <media/stagefright/MetaDataUtils.h> |
| #include <system/audio.h> |
| #include <utils/String8.h> |
| |
| extern "C" { |
| #include <Tremolo/codec_internal.h> |
| |
| int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb); |
| int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb); |
| int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb); |
| long vorbis_packet_blocksize(vorbis_info *vi,ogg_packet *op); |
| } |
| |
| namespace android { |
| |
| struct OggSource : public MediaTrackHelper { |
| explicit OggSource(OggExtractor *extractor); |
| |
| virtual media_status_t getFormat(AMediaFormat *); |
| |
| virtual media_status_t start(); |
| virtual media_status_t stop(); |
| |
| virtual media_status_t read( |
| MediaBufferHelper **buffer, const ReadOptions *options = NULL); |
| |
| protected: |
| virtual ~OggSource(); |
| |
| private: |
| OggExtractor *mExtractor; |
| bool mStarted; |
| |
| OggSource(const OggSource &); |
| OggSource &operator=(const OggSource &); |
| }; |
| |
| struct MyOggExtractor { |
| MyOggExtractor( |
| DataSourceHelper *source, |
| const char *mimeType, |
| size_t numHeaders, |
| int64_t seekPreRollUs); |
| virtual ~MyOggExtractor(); |
| |
| media_status_t getFormat(AMediaFormat *) const; |
| |
| // Returns an approximate bitrate in bits per second. |
| virtual uint64_t approxBitrate() const = 0; |
| |
| status_t seekToTime(int64_t timeUs); |
| status_t seekToOffset(off64_t offset); |
| virtual media_status_t readNextPacket(MediaBufferHelper **buffer) = 0; |
| |
| status_t init(); |
| |
| media_status_t getFileMetaData(AMediaFormat *meta) { |
| return AMediaFormat_copy(meta, mFileMeta); |
| } |
| |
| void setBufferGroup(MediaBufferGroupHelper *group) { |
| mBufferGroup = group; |
| } |
| protected: |
| struct Page { |
| uint64_t mGranulePosition; |
| int32_t mPrevPacketSize; |
| uint64_t mPrevPacketPos; |
| uint32_t mSerialNo; |
| uint32_t mPageNo; |
| uint8_t mFlags; |
| uint8_t mNumSegments; |
| uint8_t mLace[255]; |
| }; |
| |
| struct TOCEntry { |
| off64_t mPageOffset; |
| int64_t mTimeUs; |
| }; |
| |
| MediaBufferGroupHelper *mBufferGroup; |
| DataSourceHelper *mSource; |
| off64_t mOffset; |
| Page mCurrentPage; |
| uint64_t mCurGranulePosition; |
| uint64_t mPrevGranulePosition; |
| size_t mCurrentPageSize; |
| bool mFirstPacketInPage; |
| uint64_t mCurrentPageSamples; |
| size_t mNextLaceIndex; |
| |
| const char *mMimeType; |
| size_t mNumHeaders; |
| int64_t mSeekPreRollUs; |
| |
| off64_t mFirstDataOffset; |
| |
| vorbis_info mVi; |
| vorbis_comment mVc; |
| |
| AMediaFormat *mMeta; |
| AMediaFormat *mFileMeta; |
| |
| Vector<TOCEntry> mTableOfContents; |
| |
| int32_t mHapticChannelCount; |
| |
| ssize_t readPage(off64_t offset, Page *page); |
| status_t findNextPage(off64_t startOffset, off64_t *pageOffset); |
| |
| virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const = 0; |
| |
| // Extract codec format, metadata tags, and various codec specific data; |
| // the format and CSD's are required to setup the decoders for the enclosed media content. |
| // |
| // Valid values for `type` are: |
| // 1 - bitstream identification header |
| // 3 - comment header |
| // 5 - codec setup header (Vorbis only) |
| virtual media_status_t verifyHeader(MediaBufferHelper *buffer, uint8_t type) = 0; |
| |
| // Read the next ogg packet from the underlying data source; optionally |
| // calculate the timestamp for the output packet whilst pretending |
| // that we are parsing an Ogg Vorbis stream. |
| // |
| // *buffer is NULL'ed out immediately upon entry, and if successful a new buffer is allocated; |
| // clients are responsible for releasing the original buffer. |
| media_status_t _readNextPacket(MediaBufferHelper **buffer, bool calcVorbisTimestamp); |
| |
| int32_t getPacketBlockSize(MediaBufferHelper *buffer); |
| |
| void parseFileMetaData(); |
| |
| status_t findPrevGranulePosition(off64_t pageOffset, uint64_t *granulePos); |
| |
| void buildTableOfContents(); |
| |
| void setChannelMask(int channelCount); |
| |
| MyOggExtractor(const MyOggExtractor &); |
| MyOggExtractor &operator=(const MyOggExtractor &); |
| }; |
| |
| struct MyVorbisExtractor : public MyOggExtractor { |
| explicit MyVorbisExtractor(DataSourceHelper *source) |
| : MyOggExtractor(source, |
| MEDIA_MIMETYPE_AUDIO_VORBIS, |
| /* numHeaders */ 3, |
| /* seekPreRollUs */ 0) { |
| } |
| |
| virtual uint64_t approxBitrate() const; |
| |
| virtual media_status_t readNextPacket(MediaBufferHelper **buffer) { |
| return _readNextPacket(buffer, /* calcVorbisTimestamp = */ true); |
| } |
| |
| protected: |
| virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const { |
| if (granulePos > INT64_MAX / 1000000ll) { |
| return INT64_MAX; |
| } |
| return granulePos * 1000000ll / mVi.rate; |
| } |
| |
| virtual media_status_t verifyHeader(MediaBufferHelper *buffer, uint8_t type); |
| }; |
| |
| struct MyOpusExtractor : public MyOggExtractor { |
| static const int32_t kOpusSampleRate = 48000; |
| static const int64_t kOpusSeekPreRollUs = 80000; // 80 ms |
| |
| explicit MyOpusExtractor(DataSourceHelper *source) |
| : MyOggExtractor(source, MEDIA_MIMETYPE_AUDIO_OPUS, /*numHeaders*/ 2, kOpusSeekPreRollUs), |
| mChannelCount(0), |
| mCodecDelay(0), |
| mStartGranulePosition(-1) { |
| } |
| |
| virtual uint64_t approxBitrate() const { |
| return 0; |
| } |
| |
| virtual media_status_t readNextPacket(MediaBufferHelper **buffer); |
| |
| protected: |
| virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const; |
| virtual media_status_t verifyHeader(MediaBufferHelper *buffer, uint8_t type); |
| |
| private: |
| media_status_t verifyOpusHeader(MediaBufferHelper *buffer); |
| media_status_t verifyOpusComments(MediaBufferHelper *buffer); |
| uint32_t getNumSamplesInPacket(MediaBufferHelper *buffer) const; |
| |
| uint8_t mChannelCount; |
| uint16_t mCodecDelay; |
| int64_t mStartGranulePosition; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| OggSource::OggSource(OggExtractor *extractor) |
| : mExtractor(extractor), |
| mStarted(false) { |
| } |
| |
| OggSource::~OggSource() { |
| if (mStarted) { |
| stop(); |
| } |
| } |
| |
| media_status_t OggSource::getFormat(AMediaFormat *meta) { |
| return mExtractor->mImpl->getFormat(meta); |
| } |
| |
| media_status_t OggSource::start() { |
| if (mStarted) { |
| return AMEDIA_ERROR_INVALID_OPERATION; |
| } |
| // initialize buffer group with a single small buffer, but a generous upper limit |
| mBufferGroup->init(1 /* number of buffers */, 128 /* size */, 64 /* max number of buffers */); |
| mExtractor->mImpl->setBufferGroup(mBufferGroup); |
| mStarted = true; |
| |
| return AMEDIA_OK; |
| } |
| |
| media_status_t OggSource::stop() { |
| mStarted = false; |
| |
| return AMEDIA_OK; |
| } |
| |
| media_status_t OggSource::read( |
| MediaBufferHelper **out, const ReadOptions *options) { |
| *out = NULL; |
| |
| int64_t seekTimeUs; |
| ReadOptions::SeekMode mode; |
| if (options && options->getSeekTo(&seekTimeUs, &mode)) { |
| status_t err = mExtractor->mImpl->seekToTime(seekTimeUs); |
| if (err != OK) { |
| return AMEDIA_ERROR_UNKNOWN; |
| } |
| } |
| |
| MediaBufferHelper *packet; |
| media_status_t err = mExtractor->mImpl->readNextPacket(&packet); |
| |
| if (err != AMEDIA_OK) { |
| return err; |
| } |
| |
| AMediaFormat *meta = packet->meta_data(); |
| #if 0 |
| int64_t timeUs; |
| if (AMediaFormat_findInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeStampUs)) { |
| ALOGI("found time = %lld us", timeUs); |
| } else { |
| ALOGI("NO time"); |
| } |
| #endif |
| |
| AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, 1); |
| |
| *out = packet; |
| ALOGV("returning buffer %p", packet); |
| return AMEDIA_OK; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| MyOggExtractor::MyOggExtractor( |
| DataSourceHelper *source, |
| const char *mimeType, |
| size_t numHeaders, |
| int64_t seekPreRollUs) |
| : mBufferGroup(NULL), |
| mSource(source), |
| mOffset(0), |
| mCurGranulePosition(0), |
| mPrevGranulePosition(0), |
| mCurrentPageSize(0), |
| mFirstPacketInPage(true), |
| mCurrentPageSamples(0), |
| mNextLaceIndex(0), |
| mMimeType(mimeType), |
| mNumHeaders(numHeaders), |
| mSeekPreRollUs(seekPreRollUs), |
| mFirstDataOffset(-1), |
| mHapticChannelCount(0) { |
| mCurrentPage.mNumSegments = 0; |
| mCurrentPage.mFlags = 0; |
| |
| vorbis_info_init(&mVi); |
| vorbis_comment_init(&mVc); |
| mMeta = AMediaFormat_new(); |
| mFileMeta = AMediaFormat_new(); |
| } |
| |
| MyOggExtractor::~MyOggExtractor() { |
| AMediaFormat_delete(mFileMeta); |
| AMediaFormat_delete(mMeta); |
| vorbis_comment_clear(&mVc); |
| vorbis_info_clear(&mVi); |
| } |
| |
| media_status_t MyOggExtractor::getFormat(AMediaFormat *meta) const { |
| return AMediaFormat_copy(meta, mMeta); |
| } |
| |
| status_t MyOggExtractor::findNextPage( |
| off64_t startOffset, off64_t *pageOffset) { |
| *pageOffset = startOffset; |
| |
| for (;;) { |
| char signature[4]; |
| ssize_t n = mSource->readAt(*pageOffset, &signature, 4); |
| |
| if (n < 4) { |
| *pageOffset = 0; |
| |
| return (n < 0) ? n : (status_t)ERROR_END_OF_STREAM; |
| } |
| |
| if (!memcmp(signature, "OggS", 4)) { |
| if (*pageOffset > startOffset) { |
| ALOGV("skipped %lld bytes of junk to reach next frame", |
| (long long)(*pageOffset - startOffset)); |
| } |
| |
| return OK; |
| } |
| |
| // see how far ahead to skip; avoid some fruitless comparisons |
| unsigned int i; |
| for (i = 1; i < 4 ; i++) { |
| if (signature[i] == 'O') |
| break; |
| } |
| *pageOffset += i; |
| } |
| } |
| |
| // Given the offset of the "current" page, find the page immediately preceding |
| // it (if any) and return its granule position. |
| // To do this we back up from the "current" page's offset until we find any |
| // page preceding it and then scan forward to just before the current page. |
| status_t MyOggExtractor::findPrevGranulePosition( |
| off64_t pageOffset, uint64_t *granulePos) { |
| *granulePos = 0; |
| |
| off64_t prevPageOffset = 0; |
| off64_t prevGuess = pageOffset; |
| for (;;) { |
| if (prevGuess >= 5000) { |
| prevGuess -= 5000; |
| } else { |
| prevGuess = 0; |
| } |
| |
| ALOGV("backing up %lld bytes", (long long)(pageOffset - prevGuess)); |
| |
| status_t err = findNextPage(prevGuess, &prevPageOffset); |
| if (err == ERROR_END_OF_STREAM) { |
| // We are at the last page and didn't back off enough; |
| // back off 5000 bytes more and try again. |
| continue; |
| } else if (err != OK) { |
| return err; |
| } |
| |
| if (prevPageOffset < pageOffset || prevGuess == 0) { |
| break; |
| } |
| } |
| |
| if (prevPageOffset == pageOffset) { |
| // We did not find a page preceding this one. |
| return UNKNOWN_ERROR; |
| } |
| |
| ALOGV("prevPageOffset at %lld, pageOffset at %lld", |
| (long long)prevPageOffset, (long long)pageOffset); |
| uint8_t flag = 0; |
| for (;;) { |
| Page prevPage; |
| ssize_t n = readPage(prevPageOffset, &prevPage); |
| |
| if (n <= 0) { |
| return (flag & 0x4) ? OK : (status_t)n; |
| } |
| flag = prevPage.mFlags; |
| prevPageOffset += n; |
| *granulePos = prevPage.mGranulePosition; |
| if (prevPageOffset == pageOffset) { |
| return OK; |
| } |
| } |
| } |
| |
| status_t MyOggExtractor::seekToTime(int64_t timeUs) { |
| timeUs -= mSeekPreRollUs; |
| if (timeUs < 0) { |
| timeUs = 0; |
| } |
| |
| if (mTableOfContents.isEmpty()) { |
| // Perform approximate seeking based on avg. bitrate. |
| uint64_t bps = approxBitrate(); |
| if (bps <= 0) { |
| return INVALID_OPERATION; |
| } |
| |
| off64_t pos = timeUs * bps / 8000000ll; |
| |
| ALOGV("seeking to offset %lld", (long long)pos); |
| return seekToOffset(pos); |
| } |
| |
| size_t left = 0; |
| size_t right_plus_one = mTableOfContents.size(); |
| while (left < right_plus_one) { |
| size_t center = left + (right_plus_one - left) / 2; |
| |
| const TOCEntry &entry = mTableOfContents.itemAt(center); |
| |
| if (timeUs < entry.mTimeUs) { |
| right_plus_one = center; |
| } else if (timeUs > entry.mTimeUs) { |
| left = center + 1; |
| } else { |
| left = center; |
| break; |
| } |
| } |
| |
| if (left == mTableOfContents.size()) { |
| --left; |
| } |
| |
| const TOCEntry &entry = mTableOfContents.itemAt(left); |
| |
| ALOGV("seeking to entry %zu / %zu at offset %lld", |
| left, mTableOfContents.size(), (long long)entry.mPageOffset); |
| |
| return seekToOffset(entry.mPageOffset); |
| } |
| |
| status_t MyOggExtractor::seekToOffset(off64_t offset) { |
| if (mFirstDataOffset >= 0 && offset < mFirstDataOffset) { |
| // Once we know where the actual audio data starts (past the headers) |
| // don't ever seek to anywhere before that. |
| offset = mFirstDataOffset; |
| } |
| |
| off64_t pageOffset; |
| status_t err = findNextPage(offset, &pageOffset); |
| |
| if (err != OK) { |
| return err; |
| } |
| |
| // We found the page we wanted to seek to, but we'll also need |
| // the page preceding it to determine how many valid samples are on |
| // this page. |
| findPrevGranulePosition(pageOffset, &mPrevGranulePosition); |
| |
| mOffset = pageOffset; |
| |
| mCurrentPageSize = 0; |
| mFirstPacketInPage = true; |
| mCurrentPageSamples = 0; |
| mCurrentPage.mNumSegments = 0; |
| mCurrentPage.mPrevPacketSize = -1; |
| mNextLaceIndex = 0; |
| |
| // XXX what if new page continues packet from last??? |
| |
| return OK; |
| } |
| |
| ssize_t MyOggExtractor::readPage(off64_t offset, Page *page) { |
| uint8_t header[27]; |
| ssize_t n; |
| if ((n = mSource->readAt(offset, header, sizeof(header))) |
| < (ssize_t)sizeof(header)) { |
| ALOGV("failed to read %zu bytes at offset %#016llx, got %zd bytes", |
| sizeof(header), (long long)offset, n); |
| |
| if (n == 0 || n == ERROR_END_OF_STREAM) { |
| return AMEDIA_ERROR_END_OF_STREAM; |
| } else if (n < 0) { |
| return AMEDIA_ERROR_UNKNOWN; |
| } else { |
| return AMEDIA_ERROR_IO; |
| } |
| } |
| |
| if (memcmp(header, "OggS", 4)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| if (header[4] != 0) { |
| // Wrong version. |
| |
| return AMEDIA_ERROR_UNSUPPORTED; |
| } |
| |
| page->mFlags = header[5]; |
| |
| if (page->mFlags & ~7) { |
| // Only bits 0-2 are defined in version 0. |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| page->mGranulePosition = U64LE_AT(&header[6]); |
| |
| #if 0 |
| printf("granulePosition = %llu (0x%llx)\n", |
| page->mGranulePosition, page->mGranulePosition); |
| #endif |
| |
| page->mSerialNo = U32LE_AT(&header[14]); |
| page->mPageNo = U32LE_AT(&header[18]); |
| |
| page->mNumSegments = header[26]; |
| if (mSource->readAt( |
| offset + sizeof(header), page->mLace, page->mNumSegments) |
| < (ssize_t)page->mNumSegments) { |
| return AMEDIA_ERROR_IO; |
| } |
| |
| size_t totalSize = 0;; |
| for (size_t i = 0; i < page->mNumSegments; ++i) { |
| totalSize += page->mLace[i]; |
| } |
| |
| #if 0 |
| String8 tmp; |
| for (size_t i = 0; i < page->mNumSegments; ++i) { |
| char x[32]; |
| sprintf(x, "%s%u", i > 0 ? ", " : "", (unsigned)page->mLace[i]); |
| |
| tmp.append(x); |
| } |
| |
| ALOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string()); |
| #endif |
| |
| return sizeof(header) + page->mNumSegments + totalSize; |
| } |
| |
| media_status_t MyOpusExtractor::readNextPacket(MediaBufferHelper **out) { |
| if (mOffset <= mFirstDataOffset && mStartGranulePosition < 0) { |
| // The first sample might not start at time 0; find out where by subtracting |
| // the number of samples on the first page from the granule position |
| // (position of last complete sample) of the first page. This happens |
| // the first time before we attempt to read a packet from the first page. |
| MediaBufferHelper *mBuf; |
| uint32_t numSamples = 0; |
| uint64_t curGranulePosition = 0; |
| while (true) { |
| media_status_t err = _readNextPacket(&mBuf, /* calcVorbisTimestamp = */false); |
| if (err != AMEDIA_OK && err != AMEDIA_ERROR_END_OF_STREAM) { |
| return err; |
| } |
| // First two pages are header pages. |
| if (err == AMEDIA_ERROR_END_OF_STREAM || mCurrentPage.mPageNo > 2) { |
| if (mBuf != NULL) { |
| mBuf->release(); |
| mBuf = NULL; |
| } |
| break; |
| } |
| curGranulePosition = mCurrentPage.mGranulePosition; |
| numSamples += getNumSamplesInPacket(mBuf); |
| mBuf->release(); |
| mBuf = NULL; |
| } |
| |
| if (curGranulePosition > numSamples) { |
| mStartGranulePosition = curGranulePosition - numSamples; |
| } else { |
| mStartGranulePosition = 0; |
| } |
| seekToOffset(0); |
| } |
| |
| media_status_t err = _readNextPacket(out, /* calcVorbisTimestamp = */false); |
| if (err != AMEDIA_OK) { |
| return err; |
| } |
| |
| int32_t currentPageSamples; |
| // Calculate timestamps by accumulating durations starting from the first sample of a page; |
| // We assume that we only seek to page boundaries. |
| AMediaFormat *meta = (*out)->meta_data(); |
| if (AMediaFormat_getInt32(meta, AMEDIAFORMAT_KEY_VALID_SAMPLES, ¤tPageSamples)) { |
| // first packet in page |
| if (mOffset == mFirstDataOffset) { |
| currentPageSamples -= mStartGranulePosition; |
| AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_VALID_SAMPLES, currentPageSamples); |
| } |
| (void) __builtin_sub_overflow(mCurrentPage.mGranulePosition, currentPageSamples, |
| &mCurGranulePosition); |
| } |
| |
| int64_t timeUs = getTimeUsOfGranule(mCurGranulePosition); |
| AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs); |
| |
| uint32_t frames = getNumSamplesInPacket(*out); |
| mCurGranulePosition += frames; |
| return AMEDIA_OK; |
| } |
| |
| uint32_t MyOpusExtractor::getNumSamplesInPacket(MediaBufferHelper *buffer) const { |
| if (buffer == NULL || buffer->range_length() < 1) { |
| return 0; |
| } |
| |
| uint8_t *data = (uint8_t *)buffer->data() + buffer->range_offset(); |
| uint8_t toc = data[0]; |
| uint8_t config = (toc >> 3) & 0x1f; |
| uint32_t frameSizesUs[] = { |
| 10000, 20000, 40000, 60000, // 0...3 |
| 10000, 20000, 40000, 60000, // 4...7 |
| 10000, 20000, 40000, 60000, // 8...11 |
| 10000, 20000, // 12...13 |
| 10000, 20000, // 14...15 |
| 2500, 5000, 10000, 20000, // 16...19 |
| 2500, 5000, 10000, 20000, // 20...23 |
| 2500, 5000, 10000, 20000, // 24...27 |
| 2500, 5000, 10000, 20000 // 28...31 |
| }; |
| uint32_t frameSizeUs = frameSizesUs[config]; |
| |
| uint32_t numFrames; |
| uint8_t c = toc & 3; |
| switch (c) { |
| case 0: |
| numFrames = 1; |
| break; |
| case 1: |
| case 2: |
| numFrames = 2; |
| break; |
| case 3: |
| if (buffer->range_length() < 3) { |
| numFrames = 0; |
| } else { |
| numFrames = data[2] & 0x3f; |
| } |
| break; |
| default: |
| TRESPASS(); |
| } |
| |
| uint32_t numSamples = (uint32_t)((uint64_t)frameSizeUs * numFrames * kOpusSampleRate) / 1000000; |
| return numSamples; |
| } |
| |
| /* |
| * basic mediabuffer implementation used during initial parsing of the |
| * header packets, which happens before we have a buffer group |
| */ |
| class StandAloneMediaBuffer : public MediaBufferHelper { |
| private: |
| void *mData; |
| size_t mSize; |
| size_t mOffset; |
| size_t mLength; |
| AMediaFormat *mFormat; |
| public: |
| StandAloneMediaBuffer(size_t size) : MediaBufferHelper(NULL) { |
| mSize = size; |
| mData = malloc(mSize); |
| mOffset = 0; |
| mLength = mSize; |
| mFormat = AMediaFormat_new(); |
| ALOGV("created standalone media buffer %p of size %zu", this, mSize); |
| } |
| |
| ~StandAloneMediaBuffer() override { |
| free(mData); |
| AMediaFormat_delete(mFormat); |
| ALOGV("deleted standalone media buffer %p of size %zu", this, mSize); |
| } |
| |
| void release() override { |
| delete this; |
| } |
| |
| void* data() override { |
| return mData; |
| } |
| |
| size_t size() override { |
| return mSize; |
| } |
| |
| size_t range_offset() override { |
| return mOffset; |
| } |
| |
| size_t range_length() override { |
| return mLength; |
| } |
| |
| void set_range(size_t offset, size_t length) override { |
| mOffset = offset; |
| mLength = length; |
| } |
| AMediaFormat *meta_data() override { |
| return mFormat; |
| } |
| }; |
| |
| media_status_t MyOggExtractor::_readNextPacket(MediaBufferHelper **out, bool calcVorbisTimestamp) { |
| *out = NULL; |
| |
| MediaBufferHelper *buffer = NULL; |
| int64_t timeUs = -1; |
| |
| for (;;) { |
| size_t i; |
| size_t packetSize = 0; |
| bool gotFullPacket = false; |
| for (i = mNextLaceIndex; i < mCurrentPage.mNumSegments; ++i) { |
| uint8_t lace = mCurrentPage.mLace[i]; |
| |
| packetSize += lace; |
| |
| if (lace < 255) { |
| gotFullPacket = true; |
| ++i; |
| break; |
| } |
| } |
| |
| if (mNextLaceIndex < mCurrentPage.mNumSegments) { |
| off64_t dataOffset = mOffset + 27 + mCurrentPage.mNumSegments; |
| for (size_t j = 0; j < mNextLaceIndex; ++j) { |
| dataOffset += mCurrentPage.mLace[j]; |
| } |
| |
| size_t fullSize = packetSize; |
| if (buffer != NULL) { |
| fullSize += buffer->range_length(); |
| } |
| if (fullSize > 16 * 1024 * 1024) { // arbitrary limit of 16 MB packet size |
| if (buffer != NULL) { |
| buffer->release(); |
| } |
| ALOGE("b/36592202"); |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| MediaBufferHelper *tmp; |
| if (mBufferGroup) { |
| mBufferGroup->acquire_buffer(&tmp, false, fullSize); |
| ALOGV("acquired buffer %p from group", tmp); |
| } else { |
| tmp = new StandAloneMediaBuffer(fullSize); |
| } |
| if (tmp == NULL) { |
| if (buffer != NULL) { |
| buffer->release(); |
| } |
| ALOGE("b/36592202"); |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| AMediaFormat_clear(tmp->meta_data()); |
| if (buffer != NULL) { |
| memcpy(tmp->data(), buffer->data(), buffer->range_length()); |
| tmp->set_range(0, buffer->range_length()); |
| buffer->release(); |
| } else { |
| tmp->set_range(0, 0); |
| } |
| buffer = tmp; |
| |
| ssize_t n = mSource->readAt( |
| dataOffset, |
| (uint8_t *)buffer->data() + buffer->range_length(), |
| packetSize); |
| |
| if (n < (ssize_t)packetSize) { |
| buffer->release(); |
| ALOGV("failed to read %zu bytes at %#016llx, got %zd bytes", |
| packetSize, (long long)dataOffset, n); |
| return AMEDIA_ERROR_IO; |
| } |
| |
| buffer->set_range(0, fullSize); |
| |
| mNextLaceIndex = i; |
| |
| if (gotFullPacket) { |
| // We've just read the entire packet. |
| |
| if (mFirstPacketInPage) { |
| AMediaFormat *meta = buffer->meta_data(); |
| AMediaFormat_setInt32( |
| meta, AMEDIAFORMAT_KEY_VALID_SAMPLES, mCurrentPageSamples); |
| mFirstPacketInPage = false; |
| } |
| |
| if (calcVorbisTimestamp) { |
| int32_t curBlockSize = getPacketBlockSize(buffer); |
| if (mCurrentPage.mPrevPacketSize < 0) { |
| mCurrentPage.mPrevPacketSize = curBlockSize; |
| mCurrentPage.mPrevPacketPos = |
| mCurrentPage.mGranulePosition - mCurrentPageSamples; |
| timeUs = mCurrentPage.mPrevPacketPos * 1000000ll / mVi.rate; |
| } else { |
| // The effective block size is the average of the two overlapped blocks |
| int32_t actualBlockSize = |
| (curBlockSize + mCurrentPage.mPrevPacketSize) / 2; |
| timeUs = mCurrentPage.mPrevPacketPos * 1000000ll / mVi.rate; |
| // The actual size output by the decoder will be half the effective |
| // size, due to the overlap |
| mCurrentPage.mPrevPacketPos += actualBlockSize / 2; |
| mCurrentPage.mPrevPacketSize = curBlockSize; |
| } |
| AMediaFormat *meta = buffer->meta_data(); |
| AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs); |
| } |
| *out = buffer; |
| |
| return AMEDIA_OK; |
| } |
| |
| // fall through, the buffer now contains the start of the packet. |
| } |
| |
| CHECK_EQ(mNextLaceIndex, mCurrentPage.mNumSegments); |
| |
| mOffset += mCurrentPageSize; |
| uint8_t flag = mCurrentPage.mFlags; |
| ssize_t n = readPage(mOffset, &mCurrentPage); |
| |
| if (n <= 0) { |
| if (buffer) { |
| buffer->release(); |
| buffer = NULL; |
| } |
| |
| ALOGV("readPage returned %zd", n); |
| |
| if (flag & 0x04) return AMEDIA_ERROR_END_OF_STREAM; |
| return (media_status_t) n; |
| } |
| |
| // Prevent a harmless unsigned integer overflow by clamping to 0 |
| if (mCurrentPage.mGranulePosition >= mPrevGranulePosition) { |
| mCurrentPageSamples = |
| mCurrentPage.mGranulePosition - mPrevGranulePosition; |
| } else { |
| mCurrentPageSamples = 0; |
| } |
| mFirstPacketInPage = true; |
| |
| mPrevGranulePosition = mCurrentPage.mGranulePosition; |
| |
| mCurrentPageSize = n; |
| mNextLaceIndex = 0; |
| |
| if (buffer != NULL) { |
| if ((mCurrentPage.mFlags & 1) == 0) { |
| // This page does not continue the packet, i.e. the packet |
| // is already complete. |
| |
| if (timeUs >= 0) { |
| AMediaFormat *meta = buffer->meta_data(); |
| AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs); |
| } |
| |
| AMediaFormat *meta = buffer->meta_data(); |
| AMediaFormat_setInt32( |
| meta, AMEDIAFORMAT_KEY_VALID_SAMPLES, mCurrentPageSamples); |
| mFirstPacketInPage = false; |
| |
| *out = buffer; |
| |
| return AMEDIA_OK; |
| } |
| } |
| } |
| } |
| |
| status_t MyOggExtractor::init() { |
| AMediaFormat_setString(mMeta, AMEDIAFORMAT_KEY_MIME, mMimeType); |
| |
| media_status_t err; |
| MediaBufferHelper *packet; |
| for (size_t i = 0; i < mNumHeaders; ++i) { |
| // ignore timestamp for configuration packets |
| if ((err = _readNextPacket(&packet, /* calcVorbisTimestamp = */ false)) != AMEDIA_OK) { |
| return err; |
| } |
| ALOGV("read packet of size %zu\n", packet->range_length()); |
| err = verifyHeader(packet, /* type = */ i * 2 + 1); |
| packet->release(); |
| packet = NULL; |
| if (err != AMEDIA_OK) { |
| return err; |
| } |
| } |
| |
| mFirstDataOffset = mOffset + mCurrentPageSize; |
| |
| off64_t size; |
| uint64_t lastGranulePosition; |
| if (!(mSource->flags() & DataSourceBase::kIsCachingDataSource) |
| && mSource->getSize(&size) == OK |
| && findPrevGranulePosition(size, &lastGranulePosition) == OK) { |
| // Let's assume it's cheap to seek to the end. |
| // The granule position of the final page in the stream will |
| // give us the exact duration of the content, something that |
| // we can only approximate using avg. bitrate if seeking to |
| // the end is too expensive or impossible (live streaming). |
| |
| int64_t durationUs = getTimeUsOfGranule(lastGranulePosition); |
| |
| AMediaFormat_setInt64(mMeta, AMEDIAFORMAT_KEY_DURATION, durationUs); |
| |
| buildTableOfContents(); |
| } |
| |
| return AMEDIA_OK; |
| } |
| |
| void MyOggExtractor::buildTableOfContents() { |
| off64_t offset = mFirstDataOffset; |
| Page page; |
| ssize_t pageSize; |
| while ((pageSize = readPage(offset, &page)) > 0) { |
| mTableOfContents.push(); |
| |
| TOCEntry &entry = |
| mTableOfContents.editItemAt(mTableOfContents.size() - 1); |
| |
| entry.mPageOffset = offset; |
| entry.mTimeUs = getTimeUsOfGranule(page.mGranulePosition); |
| |
| offset += (size_t)pageSize; |
| } |
| |
| // Limit the maximum amount of RAM we spend on the table of contents, |
| // if necessary thin out the table evenly to trim it down to maximum |
| // size. |
| |
| static const size_t kMaxTOCSize = 8192; |
| static const size_t kMaxNumTOCEntries = kMaxTOCSize / sizeof(TOCEntry); |
| |
| size_t numerator = mTableOfContents.size(); |
| |
| if (numerator > kMaxNumTOCEntries) { |
| size_t denom = numerator - kMaxNumTOCEntries; |
| |
| size_t accum = 0; |
| for (ssize_t i = mTableOfContents.size(); i > 0; --i) { |
| accum += denom; |
| if (accum >= numerator) { |
| mTableOfContents.removeAt(i); |
| accum -= numerator; |
| } |
| } |
| } |
| } |
| |
| int32_t MyOggExtractor::getPacketBlockSize(MediaBufferHelper *buffer) { |
| const uint8_t *data = |
| (const uint8_t *)buffer->data() + buffer->range_offset(); |
| |
| size_t size = buffer->range_length(); |
| |
| ogg_buffer buf; |
| buf.data = (uint8_t *)data; |
| buf.size = size; |
| buf.refcount = 1; |
| buf.ptr.owner = NULL; |
| |
| ogg_reference ref; |
| ref.buffer = &buf; |
| ref.begin = 0; |
| ref.length = size; |
| ref.next = NULL; |
| |
| ogg_packet pack; |
| pack.packet = &ref; |
| pack.bytes = ref.length; |
| pack.b_o_s = 0; |
| pack.e_o_s = 0; |
| pack.granulepos = 0; |
| pack.packetno = 0; |
| |
| return vorbis_packet_blocksize(&mVi, &pack); |
| } |
| |
| int64_t MyOpusExtractor::getTimeUsOfGranule(uint64_t granulePos) const { |
| uint64_t pcmSamplePosition = 0; |
| if (granulePos > mCodecDelay) { |
| pcmSamplePosition = granulePos - mCodecDelay; |
| } |
| if (pcmSamplePosition > INT64_MAX / 1000000ll) { |
| return INT64_MAX; |
| } |
| return pcmSamplePosition * 1000000ll / kOpusSampleRate; |
| } |
| |
| media_status_t MyOpusExtractor::verifyHeader(MediaBufferHelper *buffer, uint8_t type) { |
| switch (type) { |
| // there are actually no header types defined in the Opus spec; we choose 1 and 3 to mean |
| // header and comments such that we can share code with MyVorbisExtractor. |
| case 1: |
| return verifyOpusHeader(buffer); |
| case 3: |
| return verifyOpusComments(buffer); |
| default: |
| return AMEDIA_ERROR_INVALID_OPERATION; |
| } |
| } |
| |
| media_status_t MyOpusExtractor::verifyOpusHeader(MediaBufferHelper *buffer) { |
| const size_t kOpusHeaderSize = 19; |
| const uint8_t *data = |
| (const uint8_t *)buffer->data() + buffer->range_offset(); |
| |
| size_t size = buffer->range_length(); |
| |
| if (size < kOpusHeaderSize |
| || memcmp(data, "OpusHead", 8)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| // allow both version 0 and 1. Per the opus specification: |
| // An earlier draft of the specification described a version 0, but the only difference |
| // between version 1 and version 0 is that version 0 did not specify the semantics for |
| // handling the version field |
| if ( /* version = */ data[8] > 1) { |
| ALOGW("no support for opus version %d", data[8]); |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| mChannelCount = data[9]; |
| mCodecDelay = U16LE_AT(&data[10]); |
| |
| // kKeyOpusHeader is csd-0 |
| AMediaFormat_setBuffer(mMeta, AMEDIAFORMAT_KEY_CSD_0, data, size); |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_SAMPLE_RATE, kOpusSampleRate); |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mChannelCount); |
| int64_t codecdelay = mCodecDelay /* sample/s */ * 1000000000ll / kOpusSampleRate; |
| AMediaFormat_setBuffer(mMeta, AMEDIAFORMAT_KEY_CSD_1, &codecdelay, sizeof(codecdelay)); |
| int64_t preroll = kOpusSeekPreRollUs * 1000 /* = 80 ms*/; |
| AMediaFormat_setBuffer(mMeta, AMEDIAFORMAT_KEY_CSD_2, &preroll, sizeof(preroll)); |
| |
| return AMEDIA_OK; |
| } |
| |
| media_status_t MyOpusExtractor::verifyOpusComments(MediaBufferHelper *buffer) { |
| // add artificial framing bit so we can reuse _vorbis_unpack_comment |
| int32_t commentSize = buffer->range_length() + 1; |
| auto tmp = heapbuffer<uint8_t>(commentSize); |
| uint8_t *commentData = tmp.get(); |
| if (commentData == nullptr) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| memcpy(commentData, |
| (uint8_t *)buffer->data() + buffer->range_offset(), |
| buffer->range_length()); |
| |
| ogg_buffer buf; |
| buf.data = commentData; |
| buf.size = commentSize; |
| buf.refcount = 1; |
| buf.ptr.owner = NULL; |
| |
| ogg_reference ref; |
| ref.buffer = &buf; |
| ref.begin = 0; |
| ref.length = commentSize; |
| ref.next = NULL; |
| |
| oggpack_buffer bits; |
| oggpack_readinit(&bits, &ref); |
| |
| // skip 'OpusTags' |
| const char *OpusTags = "OpusTags"; |
| const int32_t headerLen = strlen(OpusTags); |
| int32_t framingBitOffset = headerLen; |
| for (int i = 0; i < headerLen; ++i) { |
| char chr = oggpack_read(&bits, 8); |
| if (chr != OpusTags[i]) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| } |
| |
| int32_t vendorLen = oggpack_read(&bits, 32); |
| framingBitOffset += 4; |
| if (vendorLen < 0 || vendorLen > commentSize - 8) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| // skip vendor string |
| framingBitOffset += vendorLen; |
| for (int i = 0; i < vendorLen; ++i) { |
| oggpack_read(&bits, 8); |
| } |
| |
| int32_t n = oggpack_read(&bits, 32); |
| framingBitOffset += 4; |
| if (n < 0 || n > ((commentSize - oggpack_bytes(&bits)) >> 2)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| for (int i = 0; i < n; ++i) { |
| int32_t len = oggpack_read(&bits, 32); |
| framingBitOffset += 4; |
| if (len < 0 || len > (commentSize - oggpack_bytes(&bits))) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| framingBitOffset += len; |
| for (int j = 0; j < len; ++j) { |
| oggpack_read(&bits, 8); |
| } |
| } |
| if (framingBitOffset < 0 || framingBitOffset >= commentSize) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| commentData[framingBitOffset] = 1; |
| |
| buf.data = commentData + headerLen; |
| buf.size = commentSize - headerLen; |
| buf.refcount = 1; |
| buf.ptr.owner = NULL; |
| |
| ref.buffer = &buf; |
| ref.begin = 0; |
| ref.length = commentSize - headerLen; |
| ref.next = NULL; |
| |
| oggpack_readinit(&bits, &ref); |
| int err = _vorbis_unpack_comment(&mVc, &bits); |
| if (0 != err) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| parseFileMetaData(); |
| setChannelMask(mChannelCount); |
| return AMEDIA_OK; |
| } |
| |
| media_status_t MyVorbisExtractor::verifyHeader( |
| MediaBufferHelper *buffer, uint8_t type) { |
| const uint8_t *data = |
| (const uint8_t *)buffer->data() + buffer->range_offset(); |
| |
| size_t size = buffer->range_length(); |
| |
| if (size < 7 || data[0] != type || memcmp(&data[1], "vorbis", 6)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| ogg_buffer buf; |
| buf.data = (uint8_t *)data; |
| buf.size = size; |
| buf.refcount = 1; |
| buf.ptr.owner = NULL; |
| |
| ogg_reference ref; |
| ref.buffer = &buf; |
| ref.begin = 0; |
| ref.length = size; |
| ref.next = NULL; |
| |
| oggpack_buffer bits; |
| oggpack_readinit(&bits, &ref); |
| |
| if (oggpack_read(&bits, 8) != type) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| for (size_t i = 0; i < 6; ++i) { |
| oggpack_read(&bits, 8); // skip 'vorbis' |
| } |
| |
| switch (type) { |
| case 1: |
| { |
| if (0 != _vorbis_unpack_info(&mVi, &bits)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| AMediaFormat_setBuffer(mMeta, AMEDIAFORMAT_KEY_CSD_0, data, size); |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_SAMPLE_RATE, mVi.rate); |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_CHANNEL_COUNT, mVi.channels); |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_BIT_RATE, mVi.bitrate_nominal); |
| |
| ALOGV("lower-bitrate = %ld", mVi.bitrate_lower); |
| ALOGV("upper-bitrate = %ld", mVi.bitrate_upper); |
| ALOGV("nominal-bitrate = %ld", mVi.bitrate_nominal); |
| ALOGV("window-bitrate = %ld", mVi.bitrate_window); |
| ALOGV("blocksizes: %d/%d", |
| vorbis_info_blocksize(&mVi, 0), |
| vorbis_info_blocksize(&mVi, 1) |
| ); |
| |
| off64_t size; |
| if (mSource->getSize(&size) == OK) { |
| uint64_t bps = approxBitrate(); |
| if (bps != 0) { |
| AMediaFormat_setInt64(mMeta, AMEDIAFORMAT_KEY_DURATION, size * 8000000ll / bps); |
| } |
| } |
| break; |
| } |
| |
| case 3: |
| { |
| if (0 != _vorbis_unpack_comment(&mVc, &bits)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| parseFileMetaData(); |
| setChannelMask(mVi.channels); |
| break; |
| } |
| |
| case 5: |
| { |
| if (0 != _vorbis_unpack_books(&mVi, &bits)) { |
| return AMEDIA_ERROR_MALFORMED; |
| } |
| |
| AMediaFormat_setBuffer(mMeta, AMEDIAFORMAT_KEY_CSD_1, data, size); |
| break; |
| } |
| } |
| |
| return AMEDIA_OK; |
| } |
| |
| uint64_t MyVorbisExtractor::approxBitrate() const { |
| if (mVi.bitrate_nominal != 0) { |
| return mVi.bitrate_nominal; |
| } |
| |
| return (mVi.bitrate_lower + mVi.bitrate_upper) / 2; |
| } |
| |
| |
| void MyOggExtractor::parseFileMetaData() { |
| AMediaFormat_setString(mFileMeta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_CONTAINER_OGG); |
| |
| for (int i = 0; i < mVc.comments; ++i) { |
| const char *comment = mVc.user_comments[i]; |
| size_t commentLength = mVc.comment_lengths[i]; |
| parseVorbisComment(mFileMeta, comment, commentLength); |
| //ALOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]); |
| } |
| |
| AMediaFormat_getInt32(mFileMeta, AMEDIAFORMAT_KEY_HAPTIC_CHANNEL_COUNT, &mHapticChannelCount); |
| } |
| |
| void MyOggExtractor::setChannelMask(int channelCount) { |
| // Set channel mask according to channel count. When haptic channel count is found in |
| // file meta, set haptic channel mask to try haptic playback. |
| if (mHapticChannelCount > 0) { |
| const audio_channel_mask_t hapticChannelMask = |
| haptic_channel_mask_from_count(mHapticChannelCount); |
| const int32_t audioChannelCount = channelCount - mHapticChannelCount; |
| if (hapticChannelMask == AUDIO_CHANNEL_INVALID |
| || audioChannelCount <= 0 || audioChannelCount > FCC_8) { |
| ALOGE("Invalid haptic channel count found in metadata: %d", mHapticChannelCount); |
| } else { |
| const audio_channel_mask_t channelMask = audio_channel_out_mask_from_count( |
| audioChannelCount) | hapticChannelMask; |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_CHANNEL_MASK, channelMask); |
| AMediaFormat_setInt32( |
| mMeta, AMEDIAFORMAT_KEY_HAPTIC_CHANNEL_COUNT, mHapticChannelCount); |
| } |
| } else { |
| AMediaFormat_setInt32(mMeta, AMEDIAFORMAT_KEY_CHANNEL_MASK, |
| audio_channel_out_mask_from_count(channelCount)); |
| } |
| } |
| |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| OggExtractor::OggExtractor(DataSourceHelper *source) |
| : mDataSource(source), |
| mInitCheck(NO_INIT), |
| mImpl(NULL) { |
| for (int i = 0; i < 2; ++i) { |
| if (mImpl != NULL) { |
| delete mImpl; |
| } |
| if (i == 0) { |
| mImpl = new MyVorbisExtractor(mDataSource); |
| } else { |
| mImpl = new MyOpusExtractor(mDataSource); |
| } |
| mInitCheck = mImpl->seekToOffset(0); |
| |
| if (mInitCheck == OK) { |
| mInitCheck = mImpl->init(); |
| if (mInitCheck == OK) { |
| break; |
| } |
| } |
| } |
| } |
| |
| OggExtractor::~OggExtractor() { |
| delete mImpl; |
| mImpl = NULL; |
| delete mDataSource; |
| } |
| |
| size_t OggExtractor::countTracks() { |
| return mInitCheck != OK ? 0 : 1; |
| } |
| |
| MediaTrackHelper *OggExtractor::getTrack(size_t index) { |
| if (index >= 1) { |
| return NULL; |
| } |
| |
| return new OggSource(this); |
| } |
| |
| media_status_t OggExtractor::getTrackMetaData( |
| AMediaFormat *meta, |
| size_t index, uint32_t /* flags */) { |
| if (index >= 1) { |
| return AMEDIA_ERROR_UNKNOWN; |
| } |
| |
| return mImpl->getFormat(meta); |
| } |
| |
| media_status_t OggExtractor::getMetaData(AMediaFormat *meta) { |
| return mImpl->getFileMetaData(meta); |
| } |
| |
| static CMediaExtractor* CreateExtractor( |
| CDataSource *source, |
| void *) { |
| return wrap(new OggExtractor(new DataSourceHelper(source))); |
| } |
| |
| static CreatorFunc Sniff( |
| CDataSource *source, |
| float *confidence, |
| void **, |
| FreeMetaFunc *) { |
| DataSourceHelper helper(source); |
| char tmp[4]; |
| if (helper.readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) { |
| return NULL; |
| } |
| |
| *confidence = 0.5f; |
| |
| return CreateExtractor; |
| } |
| |
| static const char *extensions[] = { |
| "oga", |
| "ogg", |
| "opus", |
| NULL |
| }; |
| |
| extern "C" { |
| // This is the only symbol that needs to be exported |
| __attribute__ ((visibility ("default"))) |
| ExtractorDef GETEXTRACTORDEF() { |
| return { |
| EXTRACTORDEF_VERSION, |
| UUID("8cc5cd06-f772-495e-8a62-cba9649374e9"), |
| 1, // version |
| "Ogg Extractor", |
| { .v3 = {Sniff, extensions} }, |
| }; |
| } |
| |
| } // extern "C" |
| |
| } // namespace android |