Support for Ogg Vorbis decoding in stagefright.

Set the magic property media.stagefright.enable-vorbis to true to use the new implementation instead of the standalon vorbis player for file-based playback. HTTP streaming of vorbis content will always go through stagefright.

Change-Id: Ie3843a99fadb22372f89540d0f8d65196e0c2af8
related-to-bug: 2654400
diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h
index 3f8bc51..4edfb88 100644
--- a/include/media/stagefright/MediaDefs.h
+++ b/include/media/stagefright/MediaDefs.h
@@ -36,6 +36,7 @@
 
 extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4;
 extern const char *MEDIA_MIMETYPE_CONTAINER_WAV;
+extern const char *MEDIA_MIMETYPE_CONTAINER_VORBIS;
 
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index a6d8d2c..3e1f4a5 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -678,6 +678,26 @@
     return PV_PLAYER;
 }
 
+// By default we use the VORBIS_PLAYER for vorbis playback (duh!),
+// but if the magic property is set we will use our new experimental
+// stagefright code instead.
+static player_type OverrideStagefrightForVorbis(player_type player) {
+    if (player != VORBIS_PLAYER) {
+        return player;
+    }
+
+#if BUILD_WITH_FULL_STAGEFRIGHT
+    char value[PROPERTY_VALUE_MAX];
+    if (property_get("media.stagefright.enable-vorbis", value, NULL)
+        && (!strcmp(value, "1") || !strcmp(value, "true"))) {
+        return STAGEFRIGHT_PLAYER;
+    }
+#endif
+
+    return VORBIS_PLAYER;
+}
+
+
 player_type getPlayerType(int fd, int64_t offset, int64_t length)
 {
     char buf[20];
@@ -689,7 +709,7 @@
 
     // Ogg vorbis?
     if (ident == 0x5367674f) // 'OggS'
-        return VORBIS_PLAYER;
+        return OverrideStagefrightForVorbis(VORBIS_PLAYER);
 
 #ifndef NO_OPENCORE
     if (ident == 0x75b22630) {
@@ -725,6 +745,13 @@
         return TEST_PLAYER;
     }
 
+    bool useStagefrightForHTTP = false;
+    char value[PROPERTY_VALUE_MAX];
+    if (property_get("media.stagefright.enable-http", value, NULL)
+        && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
+        useStagefrightForHTTP = true;
+    }
+
     // use MidiFile for MIDI extensions
     int lenURL = strlen(url);
     for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -732,17 +759,18 @@
         int start = lenURL - len;
         if (start > 0) {
             if (!strncmp(url + start, FILE_EXTS[i].extension, len)) {
-                return FILE_EXTS[i].playertype;
+                if (FILE_EXTS[i].playertype == VORBIS_PLAYER
+                    && !strncasecmp(url, "http://", 7)
+                    && useStagefrightForHTTP) {
+                    return STAGEFRIGHT_PLAYER;
+                }
+                return OverrideStagefrightForVorbis(FILE_EXTS[i].playertype);
             }
         }
     }
 
     if (!strncasecmp(url, "http://", 7)) {
-        char value[PROPERTY_VALUE_MAX];
-        if (!property_get("media.stagefright.enable-http", value, NULL)
-            || (strcmp(value, "1") && strcasecmp(value, "true"))) {
-            // For now, we're going to use PV for http-based playback
-            // by default until we can clear up a few more issues.
+        if (!useStagefrightForHTTP) {
             return PV_PLAYER;
         }
     }
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 2a65766..8191cc7 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -39,6 +39,7 @@
         StagefrightMetadataRetriever.cpp \
         TimeSource.cpp            \
         TimedEventQueue.cpp       \
+        VorbisExtractor.cpp       \
         WAVExtractor.cpp          \
         string.cpp
 
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index 86e4bfe..5db3201 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -18,6 +18,7 @@
 #include "include/MP3Extractor.h"
 #include "include/MPEG4Extractor.h"
 #include "include/WAVExtractor.h"
+#include "include/VorbisExtractor.h"
 
 #include <media/stagefright/CachingDataSource.h>
 #include <media/stagefright/DataSource.h>
@@ -92,6 +93,7 @@
     RegisterSniffer(SniffMPEG4);
     RegisterSniffer(SniffAMR);
     RegisterSniffer(SniffWAV);
+    RegisterSniffer(SniffVorbis);
 }
 
 // static
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 3a89170..db18ab6 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -34,5 +34,6 @@
 
 const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
 const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
+const char *MEDIA_MIMETYPE_CONTAINER_VORBIS = "application/ogg";
 
 }  // namespace android
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 738e18a..832db04 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -22,6 +22,7 @@
 #include "include/MP3Extractor.h"
 #include "include/MPEG4Extractor.h"
 #include "include/WAVExtractor.h"
+#include "include/VorbisExtractor.h"
 
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaDefs.h>
@@ -62,6 +63,8 @@
         return new AMRExtractor(source);
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
         return new WAVExtractor(source);
+    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_VORBIS)) {
+        return new VorbisExtractor(source);
     }
 
     return NULL;
diff --git a/media/libstagefright/VorbisExtractor.cpp b/media/libstagefright/VorbisExtractor.cpp
new file mode 100644
index 0000000..96b05c0
--- /dev/null
+++ b/media/libstagefright/VorbisExtractor.cpp
@@ -0,0 +1,311 @@
+/*
+ * 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 "VorbisExtractor"
+#include <utils/Log.h>
+
+#include "include/VorbisExtractor.h"
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/String8.h>
+
+#include <ivorbisfile.h>
+
+namespace android {
+
+struct VorbisDataSource {
+    sp<DataSource> mDataSource;
+    off_t mOffset;
+};
+
+static size_t VorbisRead(
+        void *ptr, size_t size, size_t nmemb, void *datasource) {
+    VorbisDataSource *vds = (VorbisDataSource *)datasource;
+
+    ssize_t n = vds->mDataSource->readAt(vds->mOffset, ptr, size * nmemb);
+
+    if (n < 0) {
+        return n;
+    }
+
+    vds->mOffset += n;
+
+    return n / size;
+}
+
+static int VorbisSeek(
+        void *datasource, ogg_int64_t offset, int whence) {
+    VorbisDataSource *vds = (VorbisDataSource *)datasource;
+
+    switch (whence) {
+        case SEEK_SET:
+            vds->mOffset = offset;
+            break;
+        case SEEK_END:
+        {
+            off_t size;
+            if (vds->mDataSource->getSize(&size) != OK) {
+                errno = ESPIPE;
+                return -1;
+            }
+
+            vds->mOffset = offset + size;
+            break;
+        }
+
+        case SEEK_CUR:
+        {
+            vds->mOffset += offset;
+            break;
+        }
+
+        default:
+        {
+            errno = EINVAL;
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+static int VorbisClose(void *datasource) {
+    return 0;
+}
+
+static long VorbisTell(void *datasource) {
+    VorbisDataSource *vds = (VorbisDataSource *)datasource;
+
+    return vds->mOffset;
+}
+
+static const ov_callbacks gVorbisCallbacks = {
+    &VorbisRead,
+    &VorbisSeek,
+    &VorbisClose,
+    &VorbisTell
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct VorbisSource : public MediaSource {
+    VorbisSource(const sp<VorbisExtractor> &extractor,
+                 const sp<MetaData> &meta, OggVorbis_File *file);
+
+    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 ~VorbisSource();
+
+private:
+    enum {
+        kMaxBufferSize = 8192
+    };
+
+    sp<VorbisExtractor> mExtractor;
+    sp<MetaData> mMeta;
+    OggVorbis_File *mFile;
+    MediaBufferGroup *mGroup;
+
+    VorbisSource(const VorbisSource &);
+    VorbisSource &operator=(const VorbisSource &);
+};
+
+VorbisSource::VorbisSource(
+        const sp<VorbisExtractor> &extractor,
+        const sp<MetaData> &meta, OggVorbis_File *file)
+    : mExtractor(extractor),
+      mMeta(meta),
+      mFile(file),
+      mGroup(NULL) {
+}
+
+VorbisSource::~VorbisSource() {
+    if (mGroup) {
+        stop();
+    }
+}
+
+sp<MetaData> VorbisSource::getFormat() {
+    return mMeta;
+}
+
+status_t VorbisSource::start(MetaData *params) {
+    if (mGroup != NULL) {
+        return INVALID_OPERATION;
+    }
+
+    mGroup = new MediaBufferGroup;
+    mGroup->add_buffer(new MediaBuffer(kMaxBufferSize));
+
+    return OK;
+}
+
+status_t VorbisSource::stop() {
+    delete mGroup;
+    mGroup = NULL;
+
+    return OK;
+}
+
+status_t VorbisSource::read(
+        MediaBuffer **out, const ReadOptions *options) {
+    *out = NULL;
+
+    int64_t seekTimeUs;
+    if (options && options->getSeekTo(&seekTimeUs)) {
+        ov_time_seek(mFile, seekTimeUs / 1000ll);
+    }
+
+    MediaBuffer *buffer;
+    CHECK_EQ(OK, mGroup->acquire_buffer(&buffer));
+
+    ogg_int64_t positionMs = ov_time_tell(mFile);
+
+    int bitstream;
+    long n = ov_read(mFile, buffer->data(), buffer->size(), &bitstream);
+
+    if (n <= 0) {
+        LOGE("ov_read returned %ld", n);
+
+        buffer->release();
+        buffer = NULL;
+
+        return n < 0 ? ERROR_MALFORMED : ERROR_END_OF_STREAM;
+    }
+
+    buffer->set_range(0, n);
+    buffer->meta_data()->setInt64(kKeyTime, positionMs * 1000ll);
+
+    *out = buffer;
+
+    return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+VorbisExtractor::VorbisExtractor(const sp<DataSource> &source)
+    : mDataSource(source),
+      mFile(new OggVorbis_File),
+      mVorbisDataSource(new VorbisDataSource),
+      mInitCheck(NO_INIT) {
+    mVorbisDataSource->mDataSource = mDataSource;
+    mVorbisDataSource->mOffset = 0;
+
+    int res = ov_open_callbacks(
+            mVorbisDataSource, mFile, NULL, 0, gVorbisCallbacks);
+
+    if (res != 0) {
+        return;
+    }
+
+    mMeta = new MetaData;
+    mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+
+    vorbis_info *vi = ov_info(mFile, -1);
+    mMeta->setInt32(kKeySampleRate, vi->rate);
+    mMeta->setInt32(kKeyChannelCount, vi->channels);
+
+    ogg_int64_t durationMs = ov_time_total(mFile, -1);
+    mMeta->setInt64(kKeyDuration, durationMs * 1000ll);
+
+    LOGI("Successfully initialized.");
+
+    mInitCheck = OK;
+}
+
+VorbisExtractor::~VorbisExtractor() {
+    CHECK_EQ(0, ov_clear(mFile));
+
+    delete mVorbisDataSource;
+    mVorbisDataSource = NULL;
+
+    delete mFile;
+    mFile = NULL;
+}
+
+size_t VorbisExtractor::countTracks() {
+    return mInitCheck != OK ? 0 : 1;
+}
+
+sp<MediaSource> VorbisExtractor::getTrack(size_t index) {
+    if (index >= 1) {
+        return NULL;
+    }
+
+    return new VorbisSource(this, mMeta, mFile);
+}
+
+sp<MetaData> VorbisExtractor::getTrackMetaData(
+        size_t index, uint32_t flags) {
+    if (index >= 1) {
+        return NULL;
+    }
+
+    return mMeta;
+}
+
+sp<MetaData> VorbisExtractor::getMetaData() {
+    sp<MetaData> meta = new MetaData;
+
+    if (mInitCheck != OK) {
+        return meta;
+    }
+
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_VORBIS);
+
+    return meta;
+}
+
+bool SniffVorbis(
+        const sp<DataSource> &source, String8 *mimeType, float *confidence) {
+    OggVorbis_File file;
+
+    VorbisDataSource vds;
+    vds.mDataSource = source;
+    vds.mOffset = 0;
+
+    int res = ov_test_callbacks(&vds, &file, NULL, 0, gVorbisCallbacks);
+
+    CHECK_EQ(0, ov_clear(&file));
+
+    if (res != 0) {
+        return false;
+    }
+
+    *mimeType = MEDIA_MIMETYPE_CONTAINER_VORBIS;
+    *confidence = 0.4f;
+
+    LOGV("This looks like an Ogg file.");
+
+    return true;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/include/VorbisExtractor.h b/media/libstagefright/include/VorbisExtractor.h
new file mode 100644
index 0000000..8e38a93
--- /dev/null
+++ b/media/libstagefright/include/VorbisExtractor.h
@@ -0,0 +1,60 @@
+/*
+ * 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 VORBIS_EXTRACTOR_H_
+
+#define VORBIS_EXTRACTOR_H_
+
+#include <media/stagefright/MediaExtractor.h>
+
+struct OggVorbis_File;
+
+namespace android {
+
+class DataSource;
+class String8;
+
+struct VorbisDataSource;
+
+struct VorbisExtractor : public MediaExtractor {
+    VorbisExtractor(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();
+
+protected:
+    virtual ~VorbisExtractor();
+
+private:
+    sp<DataSource> mDataSource;
+    struct OggVorbis_File *mFile;
+    struct VorbisDataSource *mVorbisDataSource;
+    status_t mInitCheck;
+    sp<MetaData> mMeta;
+
+    VorbisExtractor(const VorbisExtractor &);
+    VorbisExtractor &operator=(const VorbisExtractor &);
+};
+
+bool SniffVorbis(
+        const sp<DataSource> &source, String8 *mimeType, float *confidence);
+
+}  // namespace android
+
+#endif  // VORBIS_EXTRACTOR_H_