Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2cbff0ed authored by Andreas Huber's avatar Andreas Huber Committed by Android (Google) Code Review
Browse files

Merge "Initial checkin of preliminary support for "http live" streaming in...

Merge "Initial checkin of preliminary support for "http live" streaming in stagefright." into kraken
parents 526f2ff1 202348e0
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -81,6 +81,7 @@ LOCAL_STATIC_LIBRARIES := \
        libstagefright_vpxdec \
        libstagefright_vpxdec \
        libvpx \
        libvpx \
        libstagefright_mpeg2ts \
        libstagefright_mpeg2ts \
        libstagefright_httplive \


LOCAL_SHARED_LIBRARIES += \
LOCAL_SHARED_LIBRARIES += \
        libstagefright_amrnb_common \
        libstagefright_amrnb_common \
+31 −4
Original line number Original line Diff line number Diff line
@@ -39,6 +39,8 @@


#include <surfaceflinger/ISurface.h>
#include <surfaceflinger/ISurface.h>


#include "include/LiveSource.h"

namespace android {
namespace android {


struct AwesomeEvent : public TimedEventQueue::Event {
struct AwesomeEvent : public TimedEventQueue::Event {
@@ -261,6 +263,16 @@ status_t AwesomePlayer::setDataSource_l(


status_t AwesomePlayer::setDataSource(
status_t AwesomePlayer::setDataSource(
        int fd, int64_t offset, int64_t length) {
        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);
    Mutex::Autolock autoLock(mLock);


    reset_l();
    reset_l();
@@ -438,11 +450,11 @@ void AwesomePlayer::onBufferingUpdate() {
        durationUs = mDurationUs;
        durationUs = mDurationUs;
    }
    }


    if (durationUs >= 0) {
    int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();
    int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs();


        LOGV("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6);
    LOGI("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6);


    if (durationUs >= 0) {
        int64_t positionUs;
        int64_t positionUs;
        getPosition(&positionUs);
        getPosition(&positionUs);


@@ -453,7 +465,8 @@ void AwesomePlayer::onBufferingUpdate() {


        postBufferingEvent_l();
        postBufferingEvent_l();
    } else {
    } 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 @@ status_t AwesomePlayer::finishSetDataSource_l() {
                mConnectingDataSource, 64 * 1024, 10);
                mConnectingDataSource, 64 * 1024, 10);


        mConnectingDataSource.clear();
        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 {
    } else {
        dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
        dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
    }
    }
+20 −0
Original line number Original line Diff line number Diff line
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)
+191 −0
Original line number Original line Diff line number Diff line
/*
 * 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
+231 −0
Original line number Original line Diff line number Diff line
/*
 * 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
Loading