diff options
-rw-r--r-- | media/libstagefright/Android.mk | 1 | ||||
-rw-r--r-- | media/libstagefright/AwesomePlayer.cpp | 35 | ||||
-rw-r--r-- | media/libstagefright/httplive/Android.mk | 20 | ||||
-rw-r--r-- | media/libstagefright/httplive/LiveSource.cpp | 191 | ||||
-rw-r--r-- | media/libstagefright/httplive/M3UParser.cpp | 231 | ||||
-rw-r--r-- | media/libstagefright/include/LiveSource.h | 69 | ||||
-rw-r--r-- | media/libstagefright/include/M3UParser.h | 71 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp | 8 |
8 files changed, 622 insertions, 4 deletions
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 @@ LOCAL_STATIC_LIBRARIES := \ 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_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(); + 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); } 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 @@ status_t MPEG2TSExtractor::feedMore() { 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 @@ bool SniffMPEG2TS( 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 |