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

Commit 202348e0 authored by Andreas Huber's avatar Andreas Huber
Browse files

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

Change-Id: I20399f63d63af86a3ba22641c0e43385a108fb3f
parent a557b24a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ LOCAL_STATIC_LIBRARIES := \
        libstagefright_vpxdec \
        libvpx \
        libstagefright_mpeg2ts \
        libstagefright_httplive \

LOCAL_SHARED_LIBRARIES += \
        libstagefright_amrnb_common \
+31 −4
Original line number Diff line number Diff line
@@ -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_l(

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 @@ void AwesomePlayer::onBufferingUpdate() {
        durationUs = mDurationUs;
    }

    if (durationUs >= 0) {
    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;
        getPosition(&positionUs);

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

        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 @@ status_t AwesomePlayer::finishSetDataSource_l() {
                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);
    }
+20 −0
Original line number 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 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 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