Make extractors separate libraries

instead of being integrated into libstagefright

Test: thoroughly
Change-Id: I11cdfde6d2daf248c8e0f99237db1c74be260eb0
diff --git a/media/extractors/ogg/Android.bp b/media/extractors/ogg/Android.bp
new file mode 100644
index 0000000..17b939a
--- /dev/null
+++ b/media/extractors/ogg/Android.bp
@@ -0,0 +1,41 @@
+cc_library_shared {
+
+    srcs: ["OggExtractor.cpp"],
+
+    include_dirs: [
+        "frameworks/av/media/libstagefright/include",
+        "external/tremolo",
+    ],
+
+    shared_libs: [
+        "libstagefright",
+        "libmedia",
+        "libstagefright_foundation",
+        "libutils",
+        "liblog",
+        "libvorbisidec",
+    ],
+
+    name: "liboggextractor",
+    relative_install_path: "extractors",
+
+    compile_multilib: "first",
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-fvisibility=hidden",
+    ],
+
+    sanitize: {
+        cfi: true,
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        diag: {
+            cfi: true,
+        },
+    },
+
+}
diff --git a/media/extractors/ogg/MODULE_LICENSE_APACHE2 b/media/extractors/ogg/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/extractors/ogg/MODULE_LICENSE_APACHE2
diff --git a/media/extractors/ogg/NOTICE b/media/extractors/ogg/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/media/extractors/ogg/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
new file mode 100644
index 0000000..db4fc4d
--- /dev/null
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -0,0 +1,1409 @@
+/*
+ * 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 <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/base64.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/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.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 MediaSource {
+    explicit OggSource(const sp<OggExtractor> &extractor);
+
+    virtual sp<MetaData> 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<OggExtractor> mExtractor;
+    bool mStarted;
+
+    OggSource(const OggSource &);
+    OggSource &operator=(const OggSource &);
+};
+
+struct MyOggExtractor {
+    MyOggExtractor(
+            const sp<DataSource> &source,
+            const char *mimeType,
+            size_t numHeaders,
+            int64_t seekPreRollUs);
+    virtual ~MyOggExtractor();
+
+    sp<MetaData> getFormat() 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 status_t readNextPacket(MediaBuffer **buffer) = 0;
+
+    status_t init();
+
+    sp<MetaData> getFileMetaData() { return mFileMeta; }
+
+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;
+    };
+
+    sp<DataSource> 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;
+
+    sp<MetaData> mMeta;
+    sp<MetaData> mFileMeta;
+
+    Vector<TOCEntry> mTableOfContents;
+
+    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 status_t verifyHeader(MediaBuffer *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.
+    status_t _readNextPacket(MediaBuffer **buffer, bool calcVorbisTimestamp);
+
+    int32_t getPacketBlockSize(MediaBuffer *buffer);
+
+    void parseFileMetaData();
+
+    status_t findPrevGranulePosition(off64_t pageOffset, uint64_t *granulePos);
+
+    void buildTableOfContents();
+
+    MyOggExtractor(const MyOggExtractor &);
+    MyOggExtractor &operator=(const MyOggExtractor &);
+};
+
+struct MyVorbisExtractor : public MyOggExtractor {
+    explicit MyVorbisExtractor(const sp<DataSource> &source)
+        : MyOggExtractor(source,
+                MEDIA_MIMETYPE_AUDIO_VORBIS,
+                /* numHeaders */ 3,
+                /* seekPreRollUs */ 0) {
+    }
+
+    virtual uint64_t approxBitrate() const;
+
+    virtual status_t readNextPacket(MediaBuffer **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 status_t verifyHeader(MediaBuffer *buffer, uint8_t type);
+};
+
+struct MyOpusExtractor : public MyOggExtractor {
+    static const int32_t kOpusSampleRate = 48000;
+    static const int64_t kOpusSeekPreRollUs = 80000; // 80 ms
+
+    explicit MyOpusExtractor(const sp<DataSource> &source)
+        : MyOggExtractor(source, MEDIA_MIMETYPE_AUDIO_OPUS, /*numHeaders*/ 2, kOpusSeekPreRollUs),
+          mChannelCount(0),
+          mCodecDelay(0),
+          mStartGranulePosition(-1) {
+    }
+
+    virtual uint64_t approxBitrate() const {
+        return 0;
+    }
+
+    virtual status_t readNextPacket(MediaBuffer **buffer);
+
+protected:
+    virtual int64_t getTimeUsOfGranule(uint64_t granulePos) const;
+    virtual status_t verifyHeader(MediaBuffer *buffer, uint8_t type);
+
+private:
+    status_t verifyOpusHeader(MediaBuffer *buffer);
+    status_t verifyOpusComments(MediaBuffer *buffer);
+    uint32_t getNumSamplesInPacket(MediaBuffer *buffer) const;
+
+    uint8_t mChannelCount;
+    uint16_t mCodecDelay;
+    int64_t mStartGranulePosition;
+};
+
+static void extractAlbumArt(
+        const sp<MetaData> &fileMeta, const void *data, size_t size);
+
+////////////////////////////////////////////////////////////////////////////////
+
+OggSource::OggSource(const sp<OggExtractor> &extractor)
+    : mExtractor(extractor),
+      mStarted(false) {
+}
+
+OggSource::~OggSource() {
+    if (mStarted) {
+        stop();
+    }
+}
+
+sp<MetaData> 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;
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+        status_t err = mExtractor->mImpl->seekToTime(seekTimeUs);
+        if (err != OK) {
+            return err;
+        }
+    }
+
+    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)) {
+        ALOGI("found time = %lld us", timeUs);
+    } else {
+        ALOGI("NO time");
+    }
+#endif
+
+    packet->meta_data()->setInt32(kKeyIsSyncFrame, 1);
+
+    *out = packet;
+
+    return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MyOggExtractor::MyOggExtractor(
+        const sp<DataSource> &source,
+        const char *mimeType,
+        size_t numHeaders,
+        int64_t seekPreRollUs)
+    : mSource(source),
+      mOffset(0),
+      mCurGranulePosition(0),
+      mPrevGranulePosition(0),
+      mCurrentPageSize(0),
+      mFirstPacketInPage(true),
+      mCurrentPageSamples(0),
+      mNextLaceIndex(0),
+      mMimeType(mimeType),
+      mNumHeaders(numHeaders),
+      mSeekPreRollUs(seekPreRollUs),
+      mFirstDataOffset(-1) {
+    mCurrentPage.mNumSegments = 0;
+
+    vorbis_info_init(&mVi);
+    vorbis_comment_init(&mVc);
+}
+
+MyOggExtractor::~MyOggExtractor() {
+    vorbis_comment_clear(&mVc);
+    vorbis_info_clear(&mVi);
+}
+
+sp<MetaData> MyOggExtractor::getFormat() const {
+    return 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;
+        }
+
+        ++*pageOffset;
+    }
+}
+
+// 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);
+
+    for (;;) {
+        Page prevPage;
+        ssize_t n = readPage(prevPageOffset, &prevPage);
+
+        if (n <= 0) {
+            return (status_t)n;
+        }
+
+        prevPageOffset += n;
+
+        if (prevPageOffset == pageOffset) {
+            *granulePos = prevPage.mGranulePosition;
+            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) {
+            return n;
+        } else if (n == 0) {
+            return ERROR_END_OF_STREAM;
+        } else {
+            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];
+    }
+
+#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;
+}
+
+status_t MyOpusExtractor::readNextPacket(MediaBuffer **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.
+        MediaBuffer *mBuf;
+        uint32_t numSamples = 0;
+        uint64_t curGranulePosition = 0;
+        while (true) {
+            status_t err = _readNextPacket(&mBuf, /* calcVorbisTimestamp = */false);
+            if (err != OK && err != ERROR_END_OF_STREAM) {
+                return err;
+            }
+            // First two pages are header pages.
+            if (err == 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);
+    }
+
+    status_t err = _readNextPacket(out, /* calcVorbisTimestamp = */false);
+    if (err != 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.
+    if ((*out)->meta_data()->findInt32(kKeyValidSamples, &currentPageSamples)) {
+        // first packet in page
+        if (mOffset == mFirstDataOffset) {
+            currentPageSamples -= mStartGranulePosition;
+            (*out)->meta_data()->setInt32(kKeyValidSamples, currentPageSamples);
+        }
+        mCurGranulePosition = mCurrentPage.mGranulePosition - currentPageSamples;
+    }
+
+    int64_t timeUs = getTimeUsOfGranule(mCurGranulePosition);
+    (*out)->meta_data()->setInt64(kKeyTime, timeUs);
+
+    uint32_t frames = getNumSamplesInPacket(*out);
+    mCurGranulePosition += frames;
+    return OK;
+}
+
+uint32_t MyOpusExtractor::getNumSamplesInPacket(MediaBuffer *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 = frameSizeUs * numFrames * kOpusSampleRate / 1000000;
+    return numSamples;
+}
+
+status_t MyOggExtractor::_readNextPacket(MediaBuffer **out, bool calcVorbisTimestamp) {
+    *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) {
+            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 ERROR_MALFORMED;
+            }
+            MediaBuffer *tmp = new (std::nothrow) MediaBuffer(fullSize);
+            if (tmp == NULL) {
+                if (buffer != NULL) {
+                    buffer->release();
+                }
+                ALOGE("b/36592202");
+                return ERROR_MALFORMED;
+            }
+            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 ERROR_IO;
+            }
+
+            buffer->set_range(0, fullSize);
+
+            mNextLaceIndex = i;
+
+            if (gotFullPacket) {
+                // We've just read the entire packet.
+
+                if (mFirstPacketInPage) {
+                    buffer->meta_data()->setInt32(
+                            kKeyValidSamples, 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;
+                    }
+                    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;
+            }
+
+            ALOGV("readPage returned %zd", n);
+
+            return n < 0 ? n : (status_t)ERROR_END_OF_STREAM;
+        }
+
+        // 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) {
+                    buffer->meta_data()->setInt64(kKeyTime, timeUs);
+                }
+
+                buffer->meta_data()->setInt32(
+                        kKeyValidSamples, mCurrentPageSamples);
+                mFirstPacketInPage = false;
+
+                *out = buffer;
+
+                return OK;
+            }
+        }
+    }
+}
+
+status_t MyOggExtractor::init() {
+    mMeta = new MetaData;
+    mMeta->setCString(kKeyMIMEType, mMimeType);
+
+    status_t err;
+    MediaBuffer *packet;
+    for (size_t i = 0; i < mNumHeaders; ++i) {
+        // ignore timestamp for configuration packets
+        if ((err = _readNextPacket(&packet, /* calcVorbisTimestamp = */ false)) != 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 != OK) {
+            return err;
+        }
+    }
+
+    mFirstDataOffset = mOffset + mCurrentPageSize;
+
+    off64_t size;
+    uint64_t lastGranulePosition;
+    if (!(mSource->flags() & DataSource::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);
+
+        mMeta->setInt64(kKeyDuration, durationUs);
+
+        buildTableOfContents();
+    }
+
+    return 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() - 1; i >= 0; --i) {
+            accum += denom;
+            if (accum >= numerator) {
+                mTableOfContents.removeAt(i);
+                accum -= numerator;
+            }
+        }
+    }
+}
+
+int32_t MyOggExtractor::getPacketBlockSize(MediaBuffer *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;
+}
+
+status_t MyOpusExtractor::verifyHeader(MediaBuffer *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 INVALID_OPERATION;
+    }
+}
+
+status_t MyOpusExtractor::verifyOpusHeader(MediaBuffer *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)
+            || /* version = */ data[8] != 1) {
+        return ERROR_MALFORMED;
+    }
+
+    mChannelCount = data[9];
+    mCodecDelay = U16LE_AT(&data[10]);
+
+    mMeta->setData(kKeyOpusHeader, 0, data, size);
+    mMeta->setInt32(kKeySampleRate, kOpusSampleRate);
+    mMeta->setInt32(kKeyChannelCount, mChannelCount);
+    mMeta->setInt64(kKeyOpusSeekPreRoll /* ns */, kOpusSeekPreRollUs * 1000 /* = 80 ms*/);
+    mMeta->setInt64(kKeyOpusCodecDelay /* ns */,
+            mCodecDelay /* sample/s */ * 1000000000ll / kOpusSampleRate);
+
+    return OK;
+}
+
+status_t MyOpusExtractor::verifyOpusComments(MediaBuffer *buffer) {
+    // add artificial framing bit so we can reuse _vorbis_unpack_comment
+    int32_t commentSize = buffer->range_length() + 1;
+    sp<ABuffer> aBuf = new ABuffer(commentSize);
+    if (aBuf->capacity() <= buffer->range_length()) {
+        return ERROR_MALFORMED;
+    }
+
+    uint8_t* commentData = aBuf->data();
+    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 ERROR_MALFORMED;
+        }
+    }
+
+    int32_t vendorLen = oggpack_read(&bits, 32);
+    framingBitOffset += 4;
+    if (vendorLen < 0 || vendorLen > commentSize - 8) {
+        return 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 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 ERROR_MALFORMED;
+        }
+        framingBitOffset += len;
+        for (int j = 0; j < len; ++j) {
+            oggpack_read(&bits, 8);
+        }
+    }
+    if (framingBitOffset < 0 || framingBitOffset >= commentSize) {
+        return 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 ERROR_MALFORMED;
+    }
+
+    parseFileMetaData();
+    return OK;
+}
+
+status_t 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();
+
+    if (size < 7 || data[0] != type || memcmp(&data[1], "vorbis", 6)) {
+        return 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 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 ERROR_MALFORMED;
+            }
+
+            mMeta->setData(kKeyVorbisInfo, 0, data, size);
+            mMeta->setInt32(kKeySampleRate, mVi.rate);
+            mMeta->setInt32(kKeyChannelCount, mVi.channels);
+            mMeta->setInt32(kKeyBitRate, 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) {
+                    mMeta->setInt64(kKeyDuration, size * 8000000ll / bps);
+                }
+            }
+            break;
+        }
+
+        case 3:
+        {
+            if (0 != _vorbis_unpack_comment(&mVc, &bits)) {
+                return ERROR_MALFORMED;
+            }
+
+            parseFileMetaData();
+            break;
+        }
+
+        case 5:
+        {
+            if (0 != _vorbis_unpack_books(&mVi, &bits)) {
+                return ERROR_MALFORMED;
+            }
+
+            mMeta->setData(kKeyVorbisBooks, 0, data, size);
+            break;
+        }
+    }
+
+    return OK;
+}
+
+uint64_t MyVorbisExtractor::approxBitrate() const {
+    if (mVi.bitrate_nominal != 0) {
+        return mVi.bitrate_nominal;
+    }
+
+    return (mVi.bitrate_lower + mVi.bitrate_upper) / 2;
+}
+
+// also exists in FLACExtractor, candidate for moving to utility/support library?
+static void parseVorbisComment(
+        const sp<MetaData> &fileMeta, const char *comment, size_t commentLength)
+{
+    struct {
+        const char *const mTag;
+        uint32_t mKey;
+    } kMap[] = {
+        { "TITLE", kKeyTitle },
+        { "ARTIST", kKeyArtist },
+        { "ALBUMARTIST", kKeyAlbumArtist },
+        { "ALBUM ARTIST", kKeyAlbumArtist },
+        { "COMPILATION", kKeyCompilation },
+        { "ALBUM", kKeyAlbum },
+        { "COMPOSER", kKeyComposer },
+        { "GENRE", kKeyGenre },
+        { "AUTHOR", kKeyAuthor },
+        { "TRACKNUMBER", kKeyCDTrackNumber },
+        { "DISCNUMBER", kKeyDiscNumber },
+        { "DATE", kKeyDate },
+        { "YEAR", kKeyYear },
+        { "LYRICIST", kKeyWriter },
+        { "METADATA_BLOCK_PICTURE", kKeyAlbumArt },
+        { "ANDROID_LOOP", kKeyAutoLoop },
+    };
+
+        for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) {
+            size_t tagLen = strlen(kMap[j].mTag);
+            if (!strncasecmp(kMap[j].mTag, comment, tagLen)
+                    && comment[tagLen] == '=') {
+                if (kMap[j].mKey == kKeyAlbumArt) {
+                    extractAlbumArt(
+                            fileMeta,
+                            &comment[tagLen + 1],
+                            commentLength - tagLen - 1);
+                } else if (kMap[j].mKey == kKeyAutoLoop) {
+                    if (!strcasecmp(&comment[tagLen + 1], "true")) {
+                        fileMeta->setInt32(kKeyAutoLoop, true);
+                    }
+                } else {
+                    fileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
+                }
+            }
+        }
+
+}
+
+// also exists in FLACExtractor, candidate for moving to utility/support library?
+static void extractAlbumArt(
+        const sp<MetaData> &fileMeta, const void *data, size_t size) {
+    ALOGV("extractAlbumArt from '%s'", (const char *)data);
+
+    sp<ABuffer> flacBuffer = decodeBase64(AString((const char *)data, size));
+    if (flacBuffer == NULL) {
+        ALOGE("malformed base64 encoded data.");
+        return;
+    }
+
+    size_t flacSize = flacBuffer->size();
+    uint8_t *flac = flacBuffer->data();
+    ALOGV("got flac of size %zu", flacSize);
+
+    uint32_t picType;
+    uint32_t typeLen;
+    uint32_t descLen;
+    uint32_t dataLen;
+    char type[128];
+
+    if (flacSize < 8) {
+        return;
+    }
+
+    picType = U32_AT(flac);
+
+    if (picType != 3) {
+        // This is not a front cover.
+        return;
+    }
+
+    typeLen = U32_AT(&flac[4]);
+    if (typeLen > sizeof(type) - 1) {
+        return;
+    }
+
+    // we've already checked above that flacSize >= 8
+    if (flacSize - 8 < typeLen) {
+        return;
+    }
+
+    memcpy(type, &flac[8], typeLen);
+    type[typeLen] = '\0';
+
+    ALOGV("picType = %d, type = '%s'", picType, type);
+
+    if (!strcmp(type, "-->")) {
+        // This is not inline cover art, but an external url instead.
+        return;
+    }
+
+    if (flacSize < 32 || flacSize - 32 < typeLen) {
+        return;
+    }
+
+    descLen = U32_AT(&flac[8 + typeLen]);
+    if (flacSize - 32 - typeLen < descLen) {
+        return;
+    }
+
+    dataLen = U32_AT(&flac[8 + typeLen + 4 + descLen + 16]);
+
+    // we've already checked above that (flacSize - 32 - typeLen - descLen) >= 0
+    if (flacSize - 32 - typeLen - descLen < dataLen) {
+        return;
+    }
+
+    ALOGV("got image data, %zu trailing bytes",
+         flacSize - 32 - typeLen - descLen - dataLen);
+
+    fileMeta->setData(
+            kKeyAlbumArt, 0, &flac[8 + typeLen + 4 + descLen + 20], dataLen);
+
+    fileMeta->setCString(kKeyAlbumArtMIME, type);
+}
+
+void MyOggExtractor::parseFileMetaData() {
+    mFileMeta = new MetaData;
+    mFileMeta->setCString(kKeyMIMEType, 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]);
+    }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+OggExtractor::OggExtractor(const sp<DataSource> &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;
+}
+
+size_t OggExtractor::countTracks() {
+    return mInitCheck != OK ? 0 : 1;
+}
+
+sp<MediaSource> OggExtractor::getTrack(size_t index) {
+    if (index >= 1) {
+        return NULL;
+    }
+
+    return new OggSource(this);
+}
+
+sp<MetaData> OggExtractor::getTrackMetaData(
+        size_t index, uint32_t /* flags */) {
+    if (index >= 1) {
+        return NULL;
+    }
+
+    return mImpl->getFormat();
+}
+
+sp<MetaData> OggExtractor::getMetaData() {
+    return mImpl->getFileMetaData();
+}
+
+static MediaExtractor* CreateExtractor(
+        const sp<DataSource> &source,
+        const sp<AMessage>& meta __unused) {
+    return new OggExtractor(source);
+}
+
+static MediaExtractor::CreatorFunc Sniff(
+        const sp<DataSource> &source,
+        String8 *mimeType,
+        float *confidence,
+        sp<AMessage> *) {
+    char tmp[4];
+    if (source->readAt(0, tmp, 4) < 4 || memcmp(tmp, "OggS", 4)) {
+        return NULL;
+    }
+
+    mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_OGG);
+    *confidence = 0.2f;
+
+    return CreateExtractor;
+}
+
+extern "C" {
+// This is the only symbol that needs to be exported
+__attribute__ ((visibility ("default")))
+MediaExtractor::ExtractorDef GETEXTRACTORDEF() {
+    return {
+        MediaExtractor::EXTRACTORDEF_VERSION,
+        UUID("8cc5cd06-f772-495e-8a62-cba9649374e9"),
+        1, // version
+        "Ogg Extractor",
+        Sniff
+    };
+}
+
+} // extern "C"
+
+}  // namespace android
diff --git a/media/extractors/ogg/OggExtractor.h b/media/extractors/ogg/OggExtractor.h
new file mode 100644
index 0000000..f42c105
--- /dev/null
+++ b/media/extractors/ogg/OggExtractor.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#ifndef OGG_EXTRACTOR_H_
+
+#define OGG_EXTRACTOR_H_
+
+#include <utils/Errors.h>
+#include <media/stagefright/MediaExtractor.h>
+
+namespace android {
+
+struct AMessage;
+class DataSource;
+class String8;
+
+struct MyOggExtractor;
+struct OggSource;
+
+struct OggExtractor : public MediaExtractor {
+    explicit OggExtractor(const sp<DataSource> &source);
+
+    virtual size_t countTracks();
+    virtual sp<MediaSource> getTrack(size_t index);
+    virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
+
+    virtual sp<MetaData> getMetaData();
+    virtual const char * name() { return "OggExtractor"; }
+
+protected:
+    virtual ~OggExtractor();
+
+private:
+    friend struct OggSource;
+
+    sp<DataSource> mDataSource;
+    status_t mInitCheck;
+
+    MyOggExtractor *mImpl;
+
+    OggExtractor(const OggExtractor &);
+    OggExtractor &operator=(const OggExtractor &);
+};
+
+bool SniffOgg(
+        const sp<DataSource> &source, String8 *mimeType, float *confidence,
+        sp<AMessage> *);
+
+}  // namespace android
+
+#endif  // OGG_EXTRACTOR_H_