Merge "Initial checkin of preliminary support for "http live" streaming in stagefright." into kraken
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 6286046..f67826e 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -81,6 +81,7 @@
         libstagefright_vpxdec \
         libvpx \
         libstagefright_mpeg2ts \
+        libstagefright_httplive \
 
 LOCAL_SHARED_LIBRARIES += \
         libstagefright_amrnb_common \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index 274dad9..88c8ee4 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -39,6 +39,8 @@
 
 #include <surfaceflinger/ISurface.h>
 
+#include "include/LiveSource.h"
+
 namespace android {
 
 struct AwesomeEvent : public TimedEventQueue::Event {
@@ -261,6 +263,16 @@
 
 status_t AwesomePlayer::setDataSource(
         int fd, int64_t offset, int64_t length) {
+#if 0
+    // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_96.m3u8");
+    // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_1500.m3u8");
+    return setDataSource("httplive://iphone.video.hsn.com/iPhone_high.m3u8");
+    // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/iphonewebcast/webcast090209_all/webcast090209_all.m3u8");
+    // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_062209_iphone/hi/prog_index.m3u8");
+    // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_googmaps/hi/prog_index.m3u8");
+    // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/mtv/ni_spo_25a_rt74137_clip_syn/hi/prog_index.m3u8");
+#endif
+
     Mutex::Autolock autoLock(mLock);
 
     reset_l();
@@ -438,11 +450,11 @@
         durationUs = mDurationUs;
     }
 
+    int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();
+
+    LOGI("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6);
+
     if (durationUs >= 0) {
-        int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();
-
-        LOGV("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6);
-
         int64_t positionUs;
         getPosition(&positionUs);
 
@@ -453,7 +465,8 @@
 
         postBufferingEvent_l();
     } else {
-        LOGE("Not sending buffering status because duration is unknown.");
+        // LOGE("Not sending buffering status because duration is unknown.");
+        postBufferingEvent_l();
     }
 }
 
@@ -1123,6 +1136,20 @@
                 mConnectingDataSource, 64 * 1024, 10);
 
         mConnectingDataSource.clear();
+    } else if (!strncasecmp(mUri.string(), "httplive://", 11)) {
+        String8 uri("http://");
+        uri.append(mUri.string() + 11);
+
+        dataSource = new LiveSource(uri.string());
+
+        if (dataSource->flags() & DataSource::kWantsPrefetching) {
+            mPrefetcher = new Prefetcher;
+        }
+
+        sp<MediaExtractor> extractor =
+            MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
+
+        return setDataSource_l(extractor);
     } else {
         dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
     }
diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk
new file mode 100644
index 0000000..3de2c80
--- /dev/null
+++ b/media/libstagefright/httplive/Android.mk
@@ -0,0 +1,20 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:=       \
+        LiveSource.cpp  \
+        M3UParser.cpp   \
+
+LOCAL_C_INCLUDES:= \
+	$(JNI_H_INCLUDE) \
+	$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
+        $(TOP)/frameworks/base/media/libstagefright
+
+LOCAL_MODULE:= libstagefright_httplive
+
+ifeq ($(TARGET_ARCH),arm)
+    LOCAL_CFLAGS += -Wno-psabi
+endif
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
new file mode 100644
index 0000000..9e3aa7b
--- /dev/null
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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_TAG "LiveSource"
+#include <utils/Log.h>
+
+#include "include/LiveSource.h"
+
+#include "include/HTTPStream.h"
+#include "include/M3UParser.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/HTTPDataSource.h>
+#include <media/stagefright/MediaDebug.h>
+
+namespace android {
+
+LiveSource::LiveSource(const char *url)
+    : mURL(url),
+      mInitCheck(NO_INIT),
+      mPlaylistIndex(0),
+      mLastFetchTimeUs(-1),
+      mSourceSize(0),
+      mOffsetBias(0) {
+    if (switchToNext()) {
+        mInitCheck = OK;
+    }
+}
+
+LiveSource::~LiveSource() {
+}
+
+status_t LiveSource::initCheck() const {
+    return mInitCheck;
+}
+
+bool LiveSource::loadPlaylist() {
+    mPlaylist.clear();
+    mPlaylistIndex = 0;
+
+    sp<ABuffer> buffer;
+    status_t err = fetchM3U(mURL.c_str(), &buffer);
+
+    if (err != OK) {
+        return false;
+    }
+
+    mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+
+    if (mPlaylist->initCheck() != OK) {
+        return false;
+    }
+
+    if (!mPlaylist->meta()->findInt32(
+                "media-sequence", &mFirstItemSequenceNumber)) {
+        mFirstItemSequenceNumber = 0;
+    }
+
+    return true;
+}
+
+static int64_t getNowUs() {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+
+    return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll;
+}
+
+bool LiveSource::switchToNext() {
+    mOffsetBias += mSourceSize;
+    mSourceSize = 0;
+
+    if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll
+        || mPlaylistIndex == mPlaylist->size()) {
+        int32_t nextSequenceNumber =
+            mPlaylistIndex + mFirstItemSequenceNumber;
+
+        if (!loadPlaylist()) {
+            LOGE("failed to reload playlist");
+            return false;
+        }
+
+        if (mLastFetchTimeUs < 0) {
+            mPlaylistIndex = mPlaylist->size() / 2;
+        } else {
+            if (nextSequenceNumber < mFirstItemSequenceNumber
+                    || nextSequenceNumber
+                            >= mFirstItemSequenceNumber + (int32_t)mPlaylist->size()) {
+                LOGE("Cannot find sequence number %d in new playlist",
+                     nextSequenceNumber);
+
+                return false;
+            }
+
+            mPlaylistIndex = nextSequenceNumber - mFirstItemSequenceNumber;
+        }
+
+        mLastFetchTimeUs = getNowUs();
+    }
+
+    AString uri;
+    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
+    LOGI("switching to %s", uri.c_str());
+
+    mSource = new HTTPDataSource(uri.c_str());
+    if (mSource->connect() != OK
+            || mSource->getSize(&mSourceSize) != OK) {
+        return false;
+    }
+
+    mPlaylistIndex++;
+    return true;
+}
+
+ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
+    CHECK(offset >= mOffsetBias);
+    offset -= mOffsetBias;
+
+    if (offset >= mSourceSize) {
+        CHECK_EQ(offset, mSourceSize);
+
+        offset -= mSourceSize;
+        if (!switchToNext()) {
+            return ERROR_END_OF_STREAM;
+        }
+    }
+
+    size_t numRead = 0;
+    while (numRead < size) {
+        ssize_t n = mSource->readAt(
+                offset + numRead, (uint8_t *)data + numRead, size - numRead);
+
+        if (n <= 0) {
+            break;
+        }
+
+        numRead += n;
+    }
+
+    return numRead;
+}
+
+status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
+    *out = NULL;
+
+    mSource = new HTTPDataSource(url);
+    status_t err = mSource->connect();
+
+    if (err != OK) {
+        return err;
+    }
+
+    off_t size;
+    err = mSource->getSize(&size);
+
+    if (err != OK) {
+        return err;
+    }
+
+    sp<ABuffer> buffer = new ABuffer(size);
+    size_t offset = 0;
+    while (offset < (size_t)size) {
+        ssize_t n = mSource->readAt(
+                offset, buffer->data() + offset, size - offset);
+
+        if (n <= 0) {
+            return ERROR_IO;
+        }
+
+        offset += n;
+    }
+
+    *out = buffer;
+
+    return OK;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
new file mode 100644
index 0000000..edd8648
--- /dev/null
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+#include "include/M3UParser.h"
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+M3UParser::M3UParser(
+        const char *baseURI, const void *data, size_t size)
+    : mInitCheck(NO_INIT),
+      mBaseURI(baseURI),
+      mIsExtM3U(false),
+      mIsVariantPlaylist(false) {
+    mInitCheck = parse(data, size);
+}
+
+M3UParser::~M3UParser() {
+}
+
+status_t M3UParser::initCheck() const {
+    return mInitCheck;
+}
+
+bool M3UParser::isExtM3U() const {
+    return mIsExtM3U;
+}
+
+bool M3UParser::isVariantPlaylist() const {
+    return mIsVariantPlaylist;
+}
+
+sp<AMessage> M3UParser::meta() {
+    return mMeta;
+}
+
+size_t M3UParser::size() {
+    return mItems.size();
+}
+
+bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) {
+    uri->clear();
+    if (meta) { *meta = NULL; }
+
+    if (index >= mItems.size()) {
+        return false;
+    }
+
+    *uri = mItems.itemAt(index).mURI;
+
+    if (meta) {
+        *meta = mItems.itemAt(index).mMeta;
+    }
+
+    return true;
+}
+
+static bool MakeURL(const char *baseURL, const char *url, AString *out) {
+    out->clear();
+
+    if (strncasecmp("http://", baseURL, 7)) {
+        // Base URL must be absolute
+        return false;
+    }
+
+    if (!strncasecmp("http://", url, 7)) {
+        // "url" is already an absolute URL, ignore base URL.
+        out->setTo(url);
+        return true;
+    }
+
+    size_t n = strlen(baseURL);
+    if (baseURL[n - 1] == '/') {
+        out->setTo(baseURL);
+        out->append(url);
+    } else {
+        char *slashPos = strrchr(baseURL, '/');
+
+        if (slashPos > &baseURL[6]) {
+            out->setTo(baseURL, slashPos - baseURL);
+        } else {
+            out->setTo(baseURL);
+        }
+
+        out->append("/");
+        out->append(url);
+    }
+
+    return true;
+}
+
+status_t M3UParser::parse(const void *_data, size_t size) {
+    int32_t lineNo = 0;
+
+    sp<AMessage> itemMeta;
+
+    const char *data = (const char *)_data;
+    size_t offset = 0;
+    while (offset < size) {
+        size_t offsetLF = offset;
+        while (offsetLF < size && data[offsetLF] != '\n') {
+            ++offsetLF;
+        }
+        if (offsetLF >= size) {
+            break;
+        }
+
+        AString line;
+        if (offsetLF > offset && data[offsetLF - 1] == '\r') {
+            line.setTo(&data[offset], offsetLF - offset - 1);
+        } else {
+            line.setTo(&data[offset], offsetLF - offset);
+        }
+
+        LOGI("#%s#", line.c_str());
+
+        if (lineNo == 0 && line == "#EXTM3U") {
+            mIsExtM3U = true;
+        }
+
+        if (mIsExtM3U) {
+            status_t err = OK;
+
+            if (line.startsWith("#EXT-X-TARGETDURATION")) {
+                if (mIsVariantPlaylist) {
+                    return ERROR_MALFORMED;
+                }
+                err = parseMetaData(line, &mMeta, "target-duration");
+            } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) {
+                if (mIsVariantPlaylist) {
+                    return ERROR_MALFORMED;
+                }
+                err = parseMetaData(line, &mMeta, "media-sequence");
+            } else if (line.startsWith("#EXTINF")) {
+                if (mIsVariantPlaylist) {
+                    return ERROR_MALFORMED;
+                }
+                err = parseMetaData(line, &itemMeta, "duration");
+            } else if (line.startsWith("#EXT-X-STREAM-INF")) {
+                if (mMeta != NULL) {
+                    return ERROR_MALFORMED;
+                }
+                mIsVariantPlaylist = true;
+            }
+
+            if (err != OK) {
+                return err;
+            }
+        }
+
+        if (!line.startsWith("#")) {
+            if (!mIsVariantPlaylist) {
+                int32_t durationSecs;
+                if (itemMeta == NULL
+                        || !itemMeta->findInt32("duration", &durationSecs)) {
+                    return ERROR_MALFORMED;
+                }
+            }
+
+            mItems.push();
+            Item *item = &mItems.editItemAt(mItems.size() - 1);
+
+            CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI));
+
+            item->mMeta = itemMeta;
+
+            itemMeta.clear();
+        }
+
+        offset = offsetLF + 1;
+        ++lineNo;
+    }
+
+    return OK;
+}
+
+// static
+status_t M3UParser::parseMetaData(
+        const AString &line, sp<AMessage> *meta, const char *key) {
+    ssize_t colonPos = line.find(":");
+
+    if (colonPos < 0) {
+        return ERROR_MALFORMED;
+    }
+
+    int32_t x;
+    status_t err = ParseInt32(line.c_str() + colonPos + 1, &x);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (meta->get() == NULL) {
+        *meta = new AMessage;
+    }
+    (*meta)->setInt32(key, x);
+
+    return OK;
+}
+
+// static
+status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
+    char *end;
+    long lval = strtol(s, &end, 10);
+
+    if (end == s || (*end != '\0' && *end != ',')) {
+        return ERROR_MALFORMED;
+    }
+
+    *x = (int32_t)lval;
+
+    return OK;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/include/LiveSource.h b/media/libstagefright/include/LiveSource.h
new file mode 100644
index 0000000..3218633
--- /dev/null
+++ b/media/libstagefright/include/LiveSource.h
@@ -0,0 +1,69 @@
+/*
+ * 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 LIVE_SOURCE_H_
+
+#define LIVE_SOURCE_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <media/stagefright/DataSource.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct ABuffer;
+struct HTTPDataSource;
+struct M3UParser;
+
+struct LiveSource : public DataSource {
+    LiveSource(const char *url);
+
+    virtual status_t initCheck() const;
+
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+    virtual uint32_t flags() {
+        return kWantsPrefetching;
+    }
+
+protected:
+    virtual ~LiveSource();
+
+private:
+    AString mURL;
+    status_t mInitCheck;
+
+    sp<M3UParser> mPlaylist;
+    int32_t mFirstItemSequenceNumber;
+    size_t mPlaylistIndex;
+    int64_t mLastFetchTimeUs;
+
+    sp<HTTPDataSource> mSource;
+    off_t mSourceSize;
+    off_t mOffsetBias;
+
+    status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
+
+    bool switchToNext();
+    bool loadPlaylist();
+
+    DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
+};
+
+}  // namespace android
+
+#endif  // LIVE_SOURCE_H_
diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/include/M3UParser.h
new file mode 100644
index 0000000..36553de
--- /dev/null
+++ b/media/libstagefright/include/M3UParser.h
@@ -0,0 +1,71 @@
+/*
+ * 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 M3U_PARSER_H_
+
+#define M3U_PARSER_H_
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct M3UParser : public RefBase {
+    M3UParser(const char *baseURI, const void *data, size_t size);
+
+    status_t initCheck() const;
+
+    bool isExtM3U() const;
+    bool isVariantPlaylist() const;
+
+    sp<AMessage> meta();
+
+    size_t size();
+    bool itemAt(size_t index, AString *uri, sp<AMessage> *meta = NULL);
+
+protected:
+    virtual ~M3UParser();
+
+private:
+    struct Item {
+        AString mURI;
+        sp<AMessage> mMeta;
+    };
+
+    status_t mInitCheck;
+
+    AString mBaseURI;
+    bool mIsExtM3U;
+    bool mIsVariantPlaylist;
+
+    sp<AMessage> mMeta;
+    Vector<Item> mItems;
+
+    status_t parse(const void *data, size_t size);
+
+    static status_t parseMetaData(
+            const AString &line, sp<AMessage> *meta, const char *key);
+
+    static status_t ParseInt32(const char *s, int32_t *x);
+
+    DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
+};
+
+}  // namespace android
+
+#endif  // M3U_PARSER_H_
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index b798273..b287c95 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -175,6 +175,7 @@
 
 bool SniffMPEG2TS(
         const sp<DataSource> &source, String8 *mimeType, float *confidence) {
+#if 0
     char header;
     if (source->readAt(0, &header, 1) != 1 || header != 0x47) {
         return false;
@@ -184,6 +185,13 @@
     mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
 
     return true;
+#else
+    // For now we're going to never identify this type of stream, since we'd
+    // just base our decision on a single byte...
+    // Instead you can instantiate an MPEG2TSExtractor by explicitly stating
+    // its proper mime type in the call to MediaExtractor::Create(...).
+    return false;
+#endif
 }
 
 }  // namespace android