From ee7ff20e69498ebd53dd9717a0f984188341a75e Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 7 May 2010 10:35:13 -0700 Subject: A new OggExtractor/VorbisDecoder combo to support approximate seeking. Change-Id: Id5d0c1c8b1adc62896bb5ed951f7b5cfda811e95 related-to-bug: 2654400 --- media/libstagefright/OggExtractor.cpp | 586 ++++++++++++++++++++++++++++++++++ 1 file changed, 586 insertions(+) create mode 100644 media/libstagefright/OggExtractor.cpp (limited to 'media/libstagefright/OggExtractor.cpp') diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp new file mode 100644 index 0000000..699b955 --- /dev/null +++ b/media/libstagefright/OggExtractor.cpp @@ -0,0 +1,586 @@ +/* + * 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 + +#include "include/OggExtractor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { + #include + + 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); +} + +namespace android { + +struct OggSource : public MediaSource { + OggSource(const sp &extractor); + + virtual sp getFormat(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~OggSource(); + +private: + sp mExtractor; + bool mStarted; + + OggSource(const OggSource &); + OggSource &operator=(const OggSource &); +}; + +struct MyVorbisExtractor { + MyVorbisExtractor(const sp &source); + virtual ~MyVorbisExtractor(); + + sp getFormat() const; + + // Returns an approximate bitrate in bits per second. + uint64_t approxBitrate(); + + status_t seekToOffset(off_t offset); + status_t readNextPacket(MediaBuffer **buffer); + + void init(); + +private: + struct Page { + uint64_t mGranulePosition; + uint32_t mSerialNo; + uint32_t mPageNo; + uint8_t mFlags; + uint8_t mNumSegments; + uint8_t mLace[255]; + }; + + sp mSource; + off_t mOffset; + Page mCurrentPage; + size_t mCurrentPageSize; + size_t mNextLaceIndex; + + vorbis_info mVi; + vorbis_comment mVc; + + sp mMeta; + + ssize_t readPage(off_t offset, Page *page); + status_t findNextPage(off_t startOffset, off_t *pageOffset); + + void verifyHeader( + MediaBuffer *buffer, uint8_t type); + + MyVorbisExtractor(const MyVorbisExtractor &); + MyVorbisExtractor &operator=(const MyVorbisExtractor &); +}; + +//////////////////////////////////////////////////////////////////////////////// + +OggSource::OggSource(const sp &extractor) + : mExtractor(extractor), + mStarted(false) { +} + +OggSource::~OggSource() { + if (mStarted) { + stop(); + } +} + +sp OggSource::getFormat() { + return mExtractor->mImpl->getFormat(); +} + +status_t OggSource::start(MetaData *params) { + if (mStarted) { + return INVALID_OPERATION; + } + + mStarted = true; + + return OK; +} + +status_t OggSource::stop() { + mStarted = false; + + return OK; +} + +status_t OggSource::read( + MediaBuffer **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + if (options && options->getSeekTo(&seekTimeUs)) { + off_t pos = seekTimeUs * mExtractor->mImpl->approxBitrate() / 8000000ll; + LOGI("seeking to offset %ld", pos); + + if (mExtractor->mImpl->seekToOffset(pos) != OK) { + return ERROR_END_OF_STREAM; + } + } + + MediaBuffer *packet; + status_t err = mExtractor->mImpl->readNextPacket(&packet); + + if (err != OK) { + return err; + } + +#if 0 + int64_t timeUs; + if (packet->meta_data()->findInt64(kKeyTime, &timeUs)) { + LOGI("found time = %lld us", timeUs); + } else { + LOGI("NO time"); + } +#endif + + *out = packet; + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MyVorbisExtractor::MyVorbisExtractor(const sp &source) + : mSource(source), + mOffset(0), + mCurrentPageSize(0), + mNextLaceIndex(0) { + mCurrentPage.mNumSegments = 0; +} + +MyVorbisExtractor::~MyVorbisExtractor() { +} + +sp MyVorbisExtractor::getFormat() const { + return mMeta; +} + +status_t MyVorbisExtractor::findNextPage( + off_t startOffset, off_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) { + LOGV("skipped %ld bytes of junk to reach next frame", + *pageOffset - startOffset); + } + + return OK; + } + + ++*pageOffset; + } +} + +status_t MyVorbisExtractor::seekToOffset(off_t offset) { + off_t pageOffset; + status_t err = findNextPage(offset, &pageOffset); + + if (err != OK) { + return err; + } + + mOffset = pageOffset; + + mCurrentPageSize = 0; + mCurrentPage.mNumSegments = 0; + mNextLaceIndex = 0; + + // XXX what if new page continues packet from last??? + + return OK; +} + +ssize_t MyVorbisExtractor::readPage(off_t offset, Page *page) { + uint8_t header[27]; + if (mSource->readAt(offset, header, sizeof(header)) + < (ssize_t)sizeof(header)) { + LOGE("failed to read %d bytes at offset 0x%08lx", sizeof(header), offset); + + return ERROR_IO; + } + + if (memcmp(header, "OggS", 4)) { + return ERROR_MALFORMED; + } + + if (header[4] != 0) { + // Wrong version. + + return ERROR_UNSUPPORTED; + } + + page->mFlags = header[5]; + + if (page->mFlags & ~7) { + // Only bits 0-2 are defined in version 0. + return 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 ERROR_IO; + } + + size_t totalSize = 0;; + for (size_t i = 0; i < page->mNumSegments; ++i) { + totalSize += page->mLace[i]; + } + + 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); + } + + LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string()); + + return sizeof(header) + page->mNumSegments + totalSize; +} + +status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) { + *out = NULL; + + MediaBuffer *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) { + off_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(); + } + MediaBuffer *tmp = new MediaBuffer(fullSize); + if (buffer != NULL) { + memcpy(tmp->data(), buffer->data(), buffer->range_length()); + tmp->set_range(0, buffer->range_length()); + buffer->release(); + } else { + // XXX Not only is this not technically the correct time for + // this packet, we also stamp every packet in this page + // with the same time. This needs fixing later. + timeUs = mCurrentPage.mGranulePosition * 1000000ll / mVi.rate; + 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) { + LOGE("failed to read %d bytes at 0x%08lx", packetSize, dataOffset); + return ERROR_IO; + } + + buffer->set_range(0, fullSize); + + mNextLaceIndex = i; + + if (gotFullPacket) { + // We've just read the entire packet. + + if (timeUs >= 0) { + buffer->meta_data()->setInt64(kKeyTime, timeUs); + } + + *out = buffer; + + return OK; + } + + // fall through, the buffer now contains the start of the packet. + } + + CHECK_EQ(mNextLaceIndex, mCurrentPage.mNumSegments); + + mOffset += mCurrentPageSize; + ssize_t n = readPage(mOffset, &mCurrentPage); + + if (n <= 0) { + if (buffer) { + buffer->release(); + buffer = NULL; + } + + LOGE("readPage returned %ld", n); + + return n < 0 ? n : (status_t)ERROR_END_OF_STREAM; + } + + 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) { + buffer->meta_data()->setInt64(kKeyTime, timeUs); + } + + *out = buffer; + + return OK; + } + } + } +} + +void MyVorbisExtractor::init() { + vorbis_info_init(&mVi); + + vorbis_comment mVc; + + mMeta = new MetaData; + mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS); + + MediaBuffer *packet; + CHECK_EQ(readNextPacket(&packet), OK); + LOGV("read packet of size %d\n", packet->range_length()); + verifyHeader(packet, 1); + packet->release(); + packet = NULL; + + CHECK_EQ(readNextPacket(&packet), OK); + LOGV("read packet of size %d\n", packet->range_length()); + verifyHeader(packet, 3); + packet->release(); + packet = NULL; + + CHECK_EQ(readNextPacket(&packet), OK); + LOGV("read packet of size %d\n", packet->range_length()); + verifyHeader(packet, 5); + packet->release(); + packet = NULL; +} + +void MyVorbisExtractor::verifyHeader( + MediaBuffer *buffer, uint8_t type) { + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + size_t size = buffer->range_length(); + + CHECK(size >= 7); + + CHECK_EQ(data[0], type); + CHECK(!memcmp(&data[1], "vorbis", 6)); + + 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); + + CHECK_EQ(oggpack_read(&bits, 8), type); + for (size_t i = 0; i < 6; ++i) { + oggpack_read(&bits, 8); // skip 'vorbis' + } + + switch (type) { + case 1: + { + CHECK_EQ(0, _vorbis_unpack_info(&mVi, &bits)); + + mMeta->setData(kKeyVorbisInfo, 0, data, size); + mMeta->setInt32(kKeySampleRate, mVi.rate); + mMeta->setInt32(kKeyChannelCount, mVi.channels); + + LOGV("lower-bitrate = %ld", mVi.bitrate_lower); + LOGV("upper-bitrate = %ld", mVi.bitrate_upper); + LOGV("nominal-bitrate = %ld", mVi.bitrate_nominal); + LOGV("window-bitrate = %ld", mVi.bitrate_window); + + off_t size; + if (mSource->getSize(&size) == OK) { + uint64_t bps = approxBitrate(); + + mMeta->setInt64(kKeyDuration, size * 8000000ll / bps); + } + break; + } + + case 3: + { + CHECK_EQ(0, _vorbis_unpack_comment(&mVc, &bits)); + break; + } + + case 5: + { + CHECK_EQ(0, _vorbis_unpack_books(&mVi, &bits)); + + mMeta->setData(kKeyVorbisBooks, 0, data, size); + break; + } + } +} + +uint64_t MyVorbisExtractor::approxBitrate() { + if (mVi.bitrate_nominal != 0) { + return mVi.bitrate_nominal; + } + + return (mVi.bitrate_lower + mVi.bitrate_upper) / 2; +} + +//////////////////////////////////////////////////////////////////////////////// + +OggExtractor::OggExtractor(const sp &source) + : mDataSource(source), + mInitCheck(NO_INIT), + mImpl(NULL) { + mImpl = new MyVorbisExtractor(mDataSource); + CHECK_EQ(mImpl->seekToOffset(0), OK); + mImpl->init(); + + mInitCheck = OK; +} + +OggExtractor::~OggExtractor() { + delete mImpl; + mImpl = NULL; +} + +size_t OggExtractor::countTracks() { + return mInitCheck != OK ? 0 : 1; +} + +sp OggExtractor::getTrack(size_t index) { + if (index >= 1) { + return NULL; + } + + return new OggSource(this); +} + +sp OggExtractor::getTrackMetaData( + size_t index, uint32_t flags) { + if (index >= 1) { + return NULL; + } + + return mImpl->getFormat(); +} + +sp OggExtractor::getMetaData() { + sp meta = new MetaData; + + if (mInitCheck != OK) { + return meta; + } + + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG); + + return meta; +} + +bool SniffOgg( + const sp &source, String8 *mimeType, float *confidence) { + char tmp[4]; + if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) { + return false; + } + + mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_OGG); + *confidence = 0.2f; + + return true; +} + +} // namespace android -- cgit v1.1