diff options
author | Andreas Huber <andih@google.com> | 2010-12-03 16:12:25 -0800 |
---|---|---|
committer | Andreas Huber <andih@google.com> | 2010-12-06 08:28:36 -0800 |
commit | b5590846c035a28399818451201aaf1256913ec3 (patch) | |
tree | 94841822da5b896e8c05e581bb040b87041f7fe0 /media/libstagefright | |
parent | 768ec6f2be1ca7bd7ac5ae34498bdfaa1fbe19dc (diff) | |
download | frameworks_base-b5590846c035a28399818451201aaf1256913ec3.zip frameworks_base-b5590846c035a28399818451201aaf1256913ec3.tar.gz frameworks_base-b5590846c035a28399818451201aaf1256913ec3.tar.bz2 |
Squashed commit of the following:
commit 9254c845d7c82976fd4b8be406ce4b17eeb0e119
Author: Andreas Huber <andih@google.com>
Date: Fri Dec 3 15:26:12 2010 -0800
Remove obsolete code from the cached data source.
Change-Id: I794b986ac8977cbc834dff189221a636ba564e36
commit 2ee33711064c58c53ba65ed9e63dd4b01ec2380e
Author: Andreas Huber <andih@google.com>
Date: Fri Dec 3 15:23:13 2010 -0800
LiveSource is dead, long live LiveSession.
Change-Id: Ibcd0731ecf9c94f0b3e5db3d53d012d9da2a1c66
commit 9eabb2c3cd8571ab859bdeae0aa7f655c414d8fa
Author: Andreas Huber <andih@google.com>
Date: Fri Dec 3 12:49:31 2010 -0800
Respect explicitly signalled discontinuities.
Change-Id: I3c0c16a2de7a99742d25db7d1b2ff0258de52271
commit 7f7f7b6b906b6ece6e4d43af7fd5f494e805c5e5
Author: Andreas Huber <andih@google.com>
Date: Fri Dec 3 11:45:57 2010 -0800
Better protection against syncword emulation in AAC ADTS content.
Change-Id: I867e80a4556dd46d24ab3e781177c248a5221719
commit fe765766582efcc350aed01135ea603576adccf6
Author: Andreas Huber <andih@google.com>
Date: Fri Dec 3 09:15:59 2010 -0800
New implementation of http live driving code.
Change-Id: I31ddf3d6a0d5929b121be704a2b9c3d6775f7737
Change-Id: Id8d1829c8fcb173756965013f848c1d426ef1048
Diffstat (limited to 'media/libstagefright')
-rw-r--r-- | media/libstagefright/AwesomePlayer.cpp | 22 | ||||
-rw-r--r-- | media/libstagefright/NuCachedSource2.cpp | 46 | ||||
-rw-r--r-- | media/libstagefright/NuHTTPDataSource.cpp | 2 | ||||
-rw-r--r-- | media/libstagefright/httplive/Android.mk | 7 | ||||
-rw-r--r-- | media/libstagefright/httplive/LiveDataSource.cpp | 142 | ||||
-rw-r--r-- | media/libstagefright/httplive/LiveDataSource.h | 61 | ||||
-rw-r--r-- | media/libstagefright/httplive/LiveSession.cpp | 646 | ||||
-rw-r--r-- | media/libstagefright/httplive/LiveSource.cpp | 655 | ||||
-rw-r--r-- | media/libstagefright/include/AwesomePlayer.h | 4 | ||||
-rw-r--r-- | media/libstagefright/include/LiveSession.h | 115 | ||||
-rw-r--r-- | media/libstagefright/include/LiveSource.h | 98 | ||||
-rw-r--r-- | media/libstagefright/include/MPEG2TSExtractor.h | 6 | ||||
-rw-r--r-- | media/libstagefright/include/NuCachedSource2.h | 6 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/ESQueue.cpp | 31 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp | 28 |
15 files changed, 1032 insertions, 837 deletions
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 8ebbe6c..2743a3a 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -22,7 +22,6 @@ #include "include/ARTSPController.h" #include "include/AwesomePlayer.h" -#include "include/LiveSource.h" #include "include/SoftwareRenderer.h" #include "include/NuCachedSource2.h" #include "include/ThrottledSource.h" @@ -51,6 +50,7 @@ #include <surfaceflinger/Surface.h> #include <media/stagefright/foundation/ALooper.h> +#include "include/LiveSession.h" #define USE_SURFACE_ALLOC 1 @@ -634,6 +634,11 @@ void AwesomePlayer::reset_l() { mRTSPController.clear(); } + if (mLiveSession != NULL) { + mLiveSession->disconnect(); + mLiveSession.clear(); + } + mRTPPusher.clear(); mRTCPPusher.clear(); mRTPSession.clear(); @@ -1659,16 +1664,23 @@ status_t AwesomePlayer::finishSetDataSource_l() { String8 uri("http://"); uri.append(mUri.string() + 11); - sp<LiveSource> liveSource = new LiveSource(uri.string()); + if (mLooper == NULL) { + mLooper = new ALooper; + mLooper->setName("httplive"); + mLooper->start(); + } + + mLiveSession = new LiveSession; + mLooper->registerHandler(mLiveSession); - mCachedSource = new NuCachedSource2(liveSource); - dataSource = mCachedSource; + mLiveSession->connect(uri.string()); + dataSource = mLiveSession->getDataSource(); sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); static_cast<MPEG2TSExtractor *>(extractor.get()) - ->setLiveSource(liveSource); + ->setLiveSession(mLiveSession); return setDataSource_l(extractor); } else if (!strncmp("rtsp://gtalk/", mUri.string(), 13)) { diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp index 829ab20..110fb03 100644 --- a/media/libstagefright/NuCachedSource2.cpp +++ b/media/libstagefright/NuCachedSource2.cpp @@ -179,8 +179,7 @@ NuCachedSource2::NuCachedSource2(const sp<DataSource> &source) mFinalStatus(OK), mLastAccessPos(0), mFetching(true), - mLastFetchTimeUs(-1), - mSuspended(false) { + mLastFetchTimeUs(-1) { mLooper->setName("NuCachedSource2"); mLooper->registerHandler(mReflector); mLooper->start(); @@ -223,12 +222,6 @@ void NuCachedSource2::onMessageReceived(const sp<AMessage> &msg) { break; } - case kWhatSuspend: - { - onSuspend(); - break; - } - default: TRESPASS(); } @@ -270,7 +263,6 @@ void NuCachedSource2::onFetch() { bool keepAlive = !mFetching - && !mSuspended && mFinalStatus == OK && ALooper::GetNowUs() >= mLastFetchTimeUs + kKeepAliveIntervalUs; @@ -287,7 +279,7 @@ void NuCachedSource2::onFetch() { LOGI("Cache full, done prefetching for now"); mFetching = false; } - } else if (!mSuspended) { + } else { Mutex::Autolock autoLock(mLock); restartPrefetcherIfNecessary_l(); } @@ -478,40 +470,6 @@ status_t NuCachedSource2::seekInternal_l(off64_t offset) { return OK; } -void NuCachedSource2::clearCacheAndResume() { - LOGV("clearCacheAndResume"); - - Mutex::Autolock autoLock(mLock); - - CHECK(mSuspended); - - mCacheOffset = 0; - mFinalStatus = OK; - mLastAccessPos = 0; - mLastFetchTimeUs = -1; - - size_t totalSize = mCache->totalSize(); - CHECK_EQ(mCache->releaseFromStart(totalSize), totalSize); - - mFetching = true; - mSuspended = false; -} - -void NuCachedSource2::suspend() { - (new AMessage(kWhatSuspend, mReflector->id()))->post(); - - while (!mSuspended) { - usleep(10000); - } -} - -void NuCachedSource2::onSuspend() { - Mutex::Autolock autoLock(mLock); - - mFetching = false; - mSuspended = true; -} - void NuCachedSource2::resumeFetchingIfNecessary() { Mutex::Autolock autoLock(mLock); diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index 269b233..4ce7265 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -456,7 +456,7 @@ void NuHTTPDataSource::applyTimeoutResponse() { bool NuHTTPDataSource::estimateBandwidth(int32_t *bandwidth_bps) { Mutex::Autolock autoLock(mLock); - if (mNumBandwidthHistoryItems < 10) { + if (mNumBandwidthHistoryItems < 2) { return false; } diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk index 3aabf5f..9225e41 100644 --- a/media/libstagefright/httplive/Android.mk +++ b/media/libstagefright/httplive/Android.mk @@ -2,9 +2,10 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - LiveSource.cpp \ - M3UParser.cpp \ +LOCAL_SRC_FILES:= \ + LiveDataSource.cpp \ + LiveSession.cpp \ + M3UParser.cpp \ LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ diff --git a/media/libstagefright/httplive/LiveDataSource.cpp b/media/libstagefright/httplive/LiveDataSource.cpp new file mode 100644 index 0000000..25e2902 --- /dev/null +++ b/media/libstagefright/httplive/LiveDataSource.cpp @@ -0,0 +1,142 @@ +/* + * 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 "LiveDataSource" +#include <utils/Log.h> + +#include "LiveDataSource.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> + +#define SAVE_BACKUP 0 + +namespace android { + +LiveDataSource::LiveDataSource() + : mOffset(0), + mFinalResult(OK), + mBackupFile(NULL) { +#if SAVE_BACKUP + mBackupFile = fopen("/data/misc/backup.ts", "wb"); + CHECK(mBackupFile != NULL); +#endif +} + +LiveDataSource::~LiveDataSource() { + if (mBackupFile != NULL) { + fclose(mBackupFile); + mBackupFile = NULL; + } +} + +status_t LiveDataSource::initCheck() const { + return OK; +} + +size_t LiveDataSource::countQueuedBuffers() { + Mutex::Autolock autoLock(mLock); + + return mBufferQueue.size(); +} + +ssize_t LiveDataSource::readAt(off64_t offset, void *data, size_t size) { + Mutex::Autolock autoLock(mLock); + + if (offset != mOffset) { + LOGE("Attempt at reading non-sequentially from LiveDataSource."); + return -EPIPE; + } + + size_t sizeDone = 0; + + while (sizeDone < size) { + while (mBufferQueue.empty() && mFinalResult == OK) { + mCondition.wait(mLock); + } + + if (mBufferQueue.empty()) { + if (sizeDone > 0) { + mOffset += sizeDone; + return sizeDone; + } + + return mFinalResult; + } + + sp<ABuffer> buffer = *mBufferQueue.begin(); + + size_t copy = size - sizeDone; + + if (copy > buffer->size()) { + copy = buffer->size(); + } + + memcpy((uint8_t *)data + sizeDone, buffer->data(), copy); + + sizeDone += copy; + + buffer->setRange(buffer->offset() + copy, buffer->size() - copy); + + if (buffer->size() == 0) { + mBufferQueue.erase(mBufferQueue.begin()); + } + } + + mOffset += sizeDone; + + return sizeDone; +} + +void LiveDataSource::queueBuffer(const sp<ABuffer> &buffer) { + Mutex::Autolock autoLock(mLock); + + if (mFinalResult != OK) { + return; + } + +#if SAVE_BACKUP + if (mBackupFile != NULL) { + CHECK_EQ(fwrite(buffer->data(), 1, buffer->size(), mBackupFile), + buffer->size()); + } +#endif + + mBufferQueue.push_back(buffer); + mCondition.broadcast(); +} + +void LiveDataSource::queueEOS(status_t finalResult) { + CHECK_NE(finalResult, (status_t)OK); + + Mutex::Autolock autoLock(mLock); + + mFinalResult = finalResult; + mCondition.broadcast(); +} + +void LiveDataSource::reset() { + Mutex::Autolock autoLock(mLock); + + // XXX FIXME: If we've done a partial read and waiting for more buffers, + // we'll mix old and new data... + + mFinalResult = OK; + mBufferQueue.clear(); +} + +} // namespace android diff --git a/media/libstagefright/httplive/LiveDataSource.h b/media/libstagefright/httplive/LiveDataSource.h new file mode 100644 index 0000000..a489ec6 --- /dev/null +++ b/media/libstagefright/httplive/LiveDataSource.h @@ -0,0 +1,61 @@ +/* + * 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_DATA_SOURCE_H_ + +#define LIVE_DATA_SOURCE_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/DataSource.h> +#include <utils/threads.h> +#include <utils/List.h> + +namespace android { + +struct ABuffer; + +struct LiveDataSource : public DataSource { + LiveDataSource(); + + virtual status_t initCheck() const; + + virtual ssize_t readAt(off64_t offset, void *data, size_t size); + + void queueBuffer(const sp<ABuffer> &buffer); + void queueEOS(status_t finalResult); + void reset(); + + size_t countQueuedBuffers(); + +protected: + virtual ~LiveDataSource(); + +private: + Mutex mLock; + Condition mCondition; + + off64_t mOffset; + List<sp<ABuffer> > mBufferQueue; + status_t mFinalResult; + + FILE *mBackupFile; + + DISALLOW_EVIL_CONSTRUCTORS(LiveDataSource); +}; + +} // namespace android + +#endif // LIVE_DATA_SOURCE_H_ diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp new file mode 100644 index 0000000..62567be --- /dev/null +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -0,0 +1,646 @@ +/* + * 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 "LiveSession" +#include <utils/Log.h> + +#include "include/LiveSession.h" + +#include "LiveDataSource.h" + +#include "include/M3UParser.h" +#include "include/NuHTTPDataSource.h" + +#include <cutils/properties.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaErrors.h> + +#include <ctype.h> +#include <openssl/aes.h> + +namespace android { + +const int64_t LiveSession::kMaxPlaylistAgeUs = 15000000ll; + +LiveSession::LiveSession() + : mDataSource(new LiveDataSource), + mHTTPDataSource(new NuHTTPDataSource), + mPrevBandwidthIndex(-1), + mLastPlaylistFetchTimeUs(-1), + mSeqNumber(-1), + mSeekTimeUs(-1), + mNumRetries(0), + mDurationUs(-1), + mSeekDone(false), + mMonitorQueueGeneration(0) { +} + +LiveSession::~LiveSession() { +} + +sp<DataSource> LiveSession::getDataSource() { + return mDataSource; +} + +void LiveSession::connect(const char *url) { + sp<AMessage> msg = new AMessage(kWhatConnect, id()); + msg->setString("url", url); + msg->post(); +} + +void LiveSession::disconnect() { + (new AMessage(kWhatDisconnect, id()))->post(); +} + +void LiveSession::seekTo(int64_t timeUs) { + Mutex::Autolock autoLock(mLock); + mSeekDone = false; + + sp<AMessage> msg = new AMessage(kWhatSeek, id()); + msg->setInt64("timeUs", timeUs); + msg->post(); + + while (!mSeekDone) { + mCondition.wait(mLock); + } +} + +void LiveSession::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatConnect: + onConnect(msg); + break; + + case kWhatDisconnect: + onDisconnect(); + break; + + case kWhatMonitorQueue: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mMonitorQueueGeneration) { + // Stale event + break; + } + + onMonitorQueue(); + break; + } + + case kWhatSeek: + onSeek(msg); + break; + + default: + TRESPASS(); + break; + } +} + +// static +int LiveSession::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) { + if (a->mBandwidth < b->mBandwidth) { + return -1; + } else if (a->mBandwidth == b->mBandwidth) { + return 0; + } + + return 1; +} + +void LiveSession::onConnect(const sp<AMessage> &msg) { + AString url; + CHECK(msg->findString("url", &url)); + + LOGI("onConnect '%s'", url.c_str()); + + mMasterURL = url; + + sp<M3UParser> playlist = fetchPlaylist(url.c_str()); + CHECK(playlist != NULL); + + if (playlist->isVariantPlaylist()) { + for (size_t i = 0; i < playlist->size(); ++i) { + BandwidthItem item; + + sp<AMessage> meta; + playlist->itemAt(i, &item.mURI, &meta); + + unsigned long bandwidth; + CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth)); + + mBandwidthItems.push(item); + } + + CHECK_GT(mBandwidthItems.size(), 0u); + + mBandwidthItems.sort(SortByBandwidth); + + if (mBandwidthItems.size() > 1) { + // XXX Remove the lowest bitrate stream for now... + mBandwidthItems.removeAt(0); + } + } + + postMonitorQueue(); +} + +void LiveSession::onDisconnect() { + LOGI("onDisconnect"); + + mDataSource->queueEOS(ERROR_END_OF_STREAM); +} + +status_t LiveSession::fetchFile(const char *url, sp<ABuffer> *out) { + *out = NULL; + + sp<DataSource> source; + + if (!strncasecmp(url, "file://", 7)) { + source = new FileSource(url + 7); + } else { + CHECK(!strncasecmp(url, "http://", 7)); + + status_t err = mHTTPDataSource->connect(url); + + if (err != OK) { + return err; + } + + source = mHTTPDataSource; + } + + off64_t size; + status_t err = source->getSize(&size); + + if (err != OK) { + size = 65536; + } + + sp<ABuffer> buffer = new ABuffer(size); + buffer->setRange(0, 0); + + for (;;) { + size_t bufferRemaining = buffer->capacity() - buffer->size(); + + if (bufferRemaining == 0) { + bufferRemaining = 32768; + + LOGV("increasing download buffer to %d bytes", + buffer->size() + bufferRemaining); + + sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); + memcpy(copy->data(), buffer->data(), buffer->size()); + copy->setRange(0, buffer->size()); + + buffer = copy; + } + + ssize_t n = source->readAt( + buffer->size(), buffer->data() + buffer->size(), + bufferRemaining); + + if (n < 0) { + return err; + } + + if (n == 0) { + break; + } + + buffer->setRange(0, buffer->size() + (size_t)n); + } + + *out = buffer; + + return OK; +} + +sp<M3UParser> LiveSession::fetchPlaylist(const char *url) { + sp<ABuffer> buffer; + status_t err = fetchFile(url, &buffer); + + if (err != OK) { + return NULL; + } + + sp<M3UParser> playlist = + new M3UParser(url, buffer->data(), buffer->size()); + + if (playlist->initCheck() != OK) { + return NULL; + } + + return playlist; +} + +static double uniformRand() { + return (double)rand() / RAND_MAX; +} + +size_t LiveSession::getBandwidthIndex() { + if (mBandwidthItems.size() == 0) { + return 0; + } + +#if 1 + int32_t bandwidthBps; + if (mHTTPDataSource != NULL + && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) { + LOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f); + } else { + LOGV("no bandwidth estimate."); + return 0; // Pick the lowest bandwidth stream by default. + } + + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.httplive.max-bw", value, NULL)) { + char *end; + long maxBw = strtoul(value, &end, 10); + if (end > value && *end == '\0') { + if (maxBw > 0 && bandwidthBps > maxBw) { + LOGV("bandwidth capped to %ld bps", maxBw); + bandwidthBps = maxBw; + } + } + } + + // Consider only 80% of the available bandwidth usable. + bandwidthBps = (bandwidthBps * 8) / 10; + + // Pick the highest bandwidth stream below or equal to estimated bandwidth. + + size_t index = mBandwidthItems.size() - 1; + while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth + > (size_t)bandwidthBps) { + --index; + } +#elif 0 + // Change bandwidth at random() + size_t index = uniformRand() * mBandwidthItems.size(); +#elif 0 + // There's a 50% chance to stay on the current bandwidth and + // a 50% chance to switch to the next higher bandwidth (wrapping around + // to lowest) + const size_t kMinIndex = 0; + + size_t index; + if (mPrevBandwidthIndex < 0) { + index = kMinIndex; + } else if (uniformRand() < 0.5) { + index = (size_t)mPrevBandwidthIndex; + } else { + index = mPrevBandwidthIndex + 1; + if (index == mBandwidthItems.size()) { + index = kMinIndex; + } + } +#elif 0 + // Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec + + size_t index = mBandwidthItems.size() - 1; + while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth > 1200000) { + --index; + } +#else + size_t index = mBandwidthItems.size() - 1; // Highest bandwidth stream +#endif + + return index; +} + +void LiveSession::onDownloadNext() { + size_t bandwidthIndex = getBandwidthIndex(); + + int64_t nowUs = ALooper::GetNowUs(); + + if (mLastPlaylistFetchTimeUs < 0 + || (ssize_t)bandwidthIndex != mPrevBandwidthIndex + || (!mPlaylist->isComplete() + && mLastPlaylistFetchTimeUs + kMaxPlaylistAgeUs <= nowUs)) { + AString url; + if (mBandwidthItems.size() > 0) { + url = mBandwidthItems.editItemAt(bandwidthIndex).mURI; + } else { + url = mMasterURL; + } + + bool firstTime = (mPlaylist == NULL); + + mPlaylist = fetchPlaylist(url.c_str()); + CHECK(mPlaylist != NULL); + + if (firstTime) { + Mutex::Autolock autoLock(mLock); + + int32_t targetDuration; + if (!mPlaylist->isComplete() + || !mPlaylist->meta()->findInt32( + "target-duration", &targetDuration)) { + mDurationUs = -1; + } else { + mDurationUs = 1000000ll * targetDuration * mPlaylist->size(); + } + } + + mLastPlaylistFetchTimeUs = ALooper::GetNowUs(); + } + + int32_t firstSeqNumberInPlaylist; + if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( + "media-sequence", &firstSeqNumberInPlaylist)) { + firstSeqNumberInPlaylist = 0; + } + + bool explicitDiscontinuity = false; + + if (mSeekTimeUs >= 0) { + int32_t targetDuration; + if (mPlaylist->isComplete() && + mPlaylist->meta()->findInt32( + "target-duration", &targetDuration)) { + int64_t seekTimeSecs = (mSeekTimeUs + 500000ll) / 1000000ll; + int64_t index = seekTimeSecs / targetDuration; + + if (index >= 0 && index < mPlaylist->size()) { + mSeqNumber = firstSeqNumberInPlaylist + index; + mDataSource->reset(); + + explicitDiscontinuity = true; + } + } + + mSeekTimeUs = -1; + + Mutex::Autolock autoLock(mLock); + mSeekDone = true; + mCondition.broadcast(); + } + + if (mSeqNumber < 0) { + if (mPlaylist->isComplete()) { + mSeqNumber = firstSeqNumberInPlaylist; + } else { + mSeqNumber = firstSeqNumberInPlaylist + mPlaylist->size() / 2; + } + } + + int32_t lastSeqNumberInPlaylist = + firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1; + + if (mSeqNumber < firstSeqNumberInPlaylist + || mSeqNumber > lastSeqNumberInPlaylist) { + if (!mPlaylist->isComplete() + && mSeqNumber > lastSeqNumberInPlaylist + && mNumRetries < kMaxNumRetries) { + ++mNumRetries; + + mLastPlaylistFetchTimeUs = -1; + postMonitorQueue(1000000ll); + return; + } + + LOGE("Cannot find sequence number %d in playlist " + "(contains %d - %d)", + mSeqNumber, firstSeqNumberInPlaylist, + firstSeqNumberInPlaylist + mPlaylist->size() - 1); + + mDataSource->queueEOS(ERROR_END_OF_STREAM); + return; + } + + mNumRetries = 0; + + AString uri; + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( + mSeqNumber - firstSeqNumberInPlaylist, + &uri, + &itemMeta)); + + int32_t val; + if (itemMeta->findInt32("discontinuity", &val) && val != 0) { + explicitDiscontinuity = true; + } + + sp<ABuffer> buffer; + status_t err = fetchFile(uri.c_str(), &buffer); + CHECK_EQ(err, (status_t)OK); + + CHECK_EQ((status_t)OK, + decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer)); + + if (buffer->size() == 0 || buffer->data()[0] != 0x47) { + // Not a transport stream??? + + LOGE("This doesn't look like a transport stream..."); + + mDataSource->queueEOS(ERROR_UNSUPPORTED); + return; + } + + if (explicitDiscontinuity + || (mPrevBandwidthIndex >= 0 + && (size_t)mPrevBandwidthIndex != bandwidthIndex)) { + // Signal discontinuity. + + sp<ABuffer> tmp = new ABuffer(188); + memset(tmp->data(), 0, tmp->size()); + + mDataSource->queueBuffer(tmp); + } + + mDataSource->queueBuffer(buffer); + + mPrevBandwidthIndex = bandwidthIndex; + ++mSeqNumber; + + postMonitorQueue(); +} + +void LiveSession::onMonitorQueue() { + if (mSeekTimeUs >= 0 + || mDataSource->countQueuedBuffers() < kMaxNumQueuedFragments) { + onDownloadNext(); + } else { + postMonitorQueue(1000000ll); + } +} + +status_t LiveSession::decryptBuffer( + size_t playlistIndex, const sp<ABuffer> &buffer) { + sp<AMessage> itemMeta; + bool found = false; + AString method; + + for (ssize_t i = playlistIndex; i >= 0; --i) { + AString uri; + CHECK(mPlaylist->itemAt(i, &uri, &itemMeta)); + + if (itemMeta->findString("cipher-method", &method)) { + found = true; + break; + } + } + + if (!found) { + method = "NONE"; + } + + if (method == "NONE") { + return OK; + } else if (!(method == "AES-128")) { + LOGE("Unsupported cipher method '%s'", method.c_str()); + return ERROR_UNSUPPORTED; + } + + AString keyURI; + if (!itemMeta->findString("cipher-uri", &keyURI)) { + LOGE("Missing key uri"); + return ERROR_MALFORMED; + } + + ssize_t index = mAESKeyForURI.indexOfKey(keyURI); + + sp<ABuffer> key; + if (index >= 0) { + key = mAESKeyForURI.valueAt(index); + } else { + key = new ABuffer(16); + + sp<NuHTTPDataSource> keySource = new NuHTTPDataSource; + status_t err = keySource->connect(keyURI.c_str()); + + if (err == OK) { + size_t offset = 0; + while (offset < 16) { + ssize_t n = keySource->readAt( + offset, key->data() + offset, 16 - offset); + if (n <= 0) { + err = ERROR_IO; + break; + } + + offset += n; + } + } + + if (err != OK) { + LOGE("failed to fetch cipher key from '%s'.", keyURI.c_str()); + return ERROR_IO; + } + + mAESKeyForURI.add(keyURI, key); + } + + AES_KEY aes_key; + if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) { + LOGE("failed to set AES decryption key."); + return UNKNOWN_ERROR; + } + + unsigned char aes_ivec[16]; + + AString iv; + if (itemMeta->findString("cipher-iv", &iv)) { + if ((!iv.startsWith("0x") && !iv.startsWith("0X")) + || iv.size() != 16 * 2 + 2) { + LOGE("malformed cipher IV '%s'.", iv.c_str()); + return ERROR_MALFORMED; + } + + memset(aes_ivec, 0, sizeof(aes_ivec)); + for (size_t i = 0; i < 16; ++i) { + char c1 = tolower(iv.c_str()[2 + 2 * i]); + char c2 = tolower(iv.c_str()[3 + 2 * i]); + if (!isxdigit(c1) || !isxdigit(c2)) { + LOGE("malformed cipher IV '%s'.", iv.c_str()); + return ERROR_MALFORMED; + } + uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10; + uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10; + + aes_ivec[i] = nibble1 << 4 | nibble2; + } + } else { + memset(aes_ivec, 0, sizeof(aes_ivec)); + aes_ivec[15] = mSeqNumber & 0xff; + aes_ivec[14] = (mSeqNumber >> 8) & 0xff; + aes_ivec[13] = (mSeqNumber >> 16) & 0xff; + aes_ivec[12] = (mSeqNumber >> 24) & 0xff; + } + + AES_cbc_encrypt( + buffer->data(), buffer->data(), buffer->size(), + &aes_key, aes_ivec, AES_DECRYPT); + + // hexdump(buffer->data(), buffer->size()); + + size_t n = buffer->size(); + CHECK_GT(n, 0u); + + size_t pad = buffer->data()[n - 1]; + + CHECK_GT(pad, 0u); + CHECK_LE(pad, 16u); + CHECK_GE((size_t)n, pad); + for (size_t i = 0; i < pad; ++i) { + CHECK_EQ((unsigned)buffer->data()[n - 1 - i], pad); + } + + n -= pad; + + buffer->setRange(buffer->offset(), n); + + return OK; +} + +void LiveSession::postMonitorQueue(int64_t delayUs) { + sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id()); + msg->setInt32("generation", ++mMonitorQueueGeneration); + msg->post(delayUs); +} + +void LiveSession::onSeek(const sp<AMessage> &msg) { + int64_t timeUs; + CHECK(msg->findInt64("timeUs", &timeUs)); + + mSeekTimeUs = timeUs; + postMonitorQueue(); +} + +status_t LiveSession::getDuration(int64_t *durationUs) { + Mutex::Autolock autoLock(mLock); + *durationUs = mDurationUs; + + return OK; +} + +bool LiveSession::isSeekable() { + int64_t durationUs; + return getDuration(&durationUs) == OK && durationUs >= 0; +} + +} // namespace android + diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp deleted file mode 100644 index 4451bd5..0000000 --- a/media/libstagefright/httplive/LiveSource.cpp +++ /dev/null @@ -1,655 +0,0 @@ -/* - * 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 "LiveSource" -#include <utils/Log.h> - -#include "include/LiveSource.h" -#include "include/M3UParser.h" -#include "include/NuHTTPDataSource.h" - -#include <cutils/properties.h> -#include <media/stagefright/foundation/hexdump.h> -#include <media/stagefright/foundation/ABuffer.h> -#include <media/stagefright/foundation/ADebug.h> -#include <media/stagefright/FileSource.h> - -#include <ctype.h> -#include <openssl/aes.h> - -namespace android { - -LiveSource::LiveSource(const char *url) - : mMasterURL(url), - mInitCheck(NO_INIT), - mDurationUs(-1), - mPlaylistIndex(0), - mLastFetchTimeUs(-1), - mSource(new NuHTTPDataSource), - mSourceSize(0), - mOffsetBias(0), - mSignalDiscontinuity(false), - mPrevBandwidthIndex(-1), - mAESKey((AES_KEY *)malloc(sizeof(AES_KEY))), - mStreamEncrypted(false) { - if (switchToNext()) { - mInitCheck = OK; - - determineSeekability(); - } -} - -LiveSource::~LiveSource() { - free(mAESKey); - mAESKey = NULL; -} - -status_t LiveSource::initCheck() const { - return mInitCheck; -} - -// static -int LiveSource::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) { - if (a->mBandwidth < b->mBandwidth) { - return -1; - } else if (a->mBandwidth == b->mBandwidth) { - return 0; - } - - return 1; -} - -static double uniformRand() { - return (double)rand() / RAND_MAX; -} - -size_t LiveSource::getBandwidthIndex() { - if (mBandwidthItems.size() == 0) { - return 0; - } - -#if 1 - int32_t bandwidthBps; - if (mSource != NULL && mSource->estimateBandwidth(&bandwidthBps)) { - LOGI("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f); - } else { - LOGI("no bandwidth estimate."); - return 0; // Pick the lowest bandwidth stream by default. - } - - char value[PROPERTY_VALUE_MAX]; - if (property_get("media.httplive.max-bw", value, NULL)) { - char *end; - long maxBw = strtoul(value, &end, 10); - if (end > value && *end == '\0') { - if (maxBw > 0 && bandwidthBps > maxBw) { - LOGV("bandwidth capped to %ld bps", maxBw); - bandwidthBps = maxBw; - } - } - } - - // Consider only 80% of the available bandwidth usable. - bandwidthBps = (bandwidthBps * 8) / 10; - - // Pick the highest bandwidth stream below or equal to estimated bandwidth. - - size_t index = mBandwidthItems.size() - 1; - while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth - > (size_t)bandwidthBps) { - --index; - } -#elif 0 - // Change bandwidth at random() - size_t index = uniformRand() * mBandwidthItems.size(); -#elif 0 - // There's a 50% chance to stay on the current bandwidth and - // a 50% chance to switch to the next higher bandwidth (wrapping around - // to lowest) - const size_t kMinIndex = 0; - - size_t index; - if (mPrevBandwidthIndex < 0) { - index = kMinIndex; - } else if (uniformRand() < 0.5) { - index = (size_t)mPrevBandwidthIndex; - } else { - index = mPrevBandwidthIndex + 1; - if (index == mBandwidthItems.size()) { - index = kMinIndex; - } - } -#elif 0 - // Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec - - size_t index = mBandwidthItems.size() - 1; - while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth > 1200000) { - --index; - } -#else - size_t index = mBandwidthItems.size() - 1; // Highest bandwidth stream -#endif - - return index; -} - -bool LiveSource::loadPlaylist(bool fetchMaster, size_t bandwidthIndex) { - mSignalDiscontinuity = false; - - mPlaylist.clear(); - mPlaylistIndex = 0; - - if (fetchMaster) { - mPrevBandwidthIndex = -1; - - sp<ABuffer> buffer; - status_t err = fetchM3U(mMasterURL.c_str(), &buffer); - - if (err != OK) { - return false; - } - - mPlaylist = new M3UParser( - mMasterURL.c_str(), buffer->data(), buffer->size()); - - if (mPlaylist->initCheck() != OK) { - return false; - } - - if (mPlaylist->isVariantPlaylist()) { - for (size_t i = 0; i < mPlaylist->size(); ++i) { - BandwidthItem item; - - sp<AMessage> meta; - mPlaylist->itemAt(i, &item.mURI, &meta); - - unsigned long bandwidth; - CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth)); - - mBandwidthItems.push(item); - } - mPlaylist.clear(); - - // fall through - if (mBandwidthItems.size() == 0) { - return false; - } - - mBandwidthItems.sort(SortByBandwidth); - -#if 1 // XXX - if (mBandwidthItems.size() > 1) { - // Remove the lowest bandwidth stream, this is sometimes - // an AAC program stream, which we don't support at this point. - mBandwidthItems.removeItemsAt(0); - } -#endif - - for (size_t i = 0; i < mBandwidthItems.size(); ++i) { - const BandwidthItem &item = mBandwidthItems.itemAt(i); - LOGV("item #%d: %s", i, item.mURI.c_str()); - } - - bandwidthIndex = getBandwidthIndex(); - } - } - - if (mBandwidthItems.size() > 0) { - mURL = mBandwidthItems.editItemAt(bandwidthIndex).mURI; - - if (mPrevBandwidthIndex >= 0 - && (size_t)mPrevBandwidthIndex != bandwidthIndex) { - // If we switched streams because of bandwidth changes, - // we'll signal this discontinuity by inserting a - // special transport stream packet into the stream. - mSignalDiscontinuity = true; - } - - mPrevBandwidthIndex = bandwidthIndex; - } else { - mURL = mMasterURL; - } - - if (mPlaylist == NULL) { - 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->isVariantPlaylist()) { - 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() { - mSignalDiscontinuity = false; - - mOffsetBias += mSourceSize; - mSourceSize = 0; - - size_t bandwidthIndex = getBandwidthIndex(); - - if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll - || mPlaylistIndex == mPlaylist->size() - || (ssize_t)bandwidthIndex != mPrevBandwidthIndex) { - int32_t nextSequenceNumber = - mPlaylistIndex + mFirstItemSequenceNumber; - - if (!loadPlaylist(mLastFetchTimeUs < 0, bandwidthIndex)) { - LOGE("failed to reload playlist"); - return false; - } - - if (mLastFetchTimeUs < 0) { - if (isSeekable()) { - mPlaylistIndex = 0; - } else { - // This is live streamed content, the first seqnum in the - // various bandwidth' streams may be slightly off, so don't - // start at the very first entry. - // With a segment duration of 6-10secs, this really only - // delays playback up to 30secs compared to real time. - mPlaylistIndex = 3; - if (mPlaylistIndex >= mPlaylist->size()) { - mPlaylistIndex = mPlaylist->size() - 1; - } - } - } 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(); - } - - if (!setupCipher()) { - return false; - } - - AString uri; - sp<AMessage> itemMeta; - CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta)); - LOGV("switching to %s", uri.c_str()); - - if (mSource->connect(uri.c_str()) != OK - || mSource->getSize(&mSourceSize) != OK) { - return false; - } - - int32_t val; - if (itemMeta->findInt32("discontinuity", &val) && val != 0) { - mSignalDiscontinuity = true; - } - - mPlaylistIndex++; - - return true; -} - -bool LiveSource::setupCipher() { - sp<AMessage> itemMeta; - bool found = false; - AString method; - - for (ssize_t i = mPlaylistIndex; i >= 0; --i) { - AString uri; - CHECK(mPlaylist->itemAt(i, &uri, &itemMeta)); - - if (itemMeta->findString("cipher-method", &method)) { - found = true; - break; - } - } - - if (!found) { - method = "NONE"; - } - - mStreamEncrypted = false; - - if (method == "AES-128") { - AString keyURI; - if (!itemMeta->findString("cipher-uri", &keyURI)) { - LOGE("Missing key uri"); - return false; - } - - ssize_t index = mAESKeyForURI.indexOfKey(keyURI); - - sp<ABuffer> key; - if (index >= 0) { - key = mAESKeyForURI.valueAt(index); - } else { - key = new ABuffer(16); - - sp<NuHTTPDataSource> keySource = new NuHTTPDataSource; - status_t err = keySource->connect(keyURI.c_str()); - - if (err == OK) { - size_t offset = 0; - while (offset < 16) { - ssize_t n = keySource->readAt( - offset, key->data() + offset, 16 - offset); - if (n <= 0) { - err = ERROR_IO; - break; - } - - offset += n; - } - } - - if (err != OK) { - LOGE("failed to fetch cipher key from '%s'.", keyURI.c_str()); - return false; - } - - mAESKeyForURI.add(keyURI, key); - } - - if (AES_set_decrypt_key(key->data(), 128, (AES_KEY *)mAESKey) != 0) { - LOGE("failed to set AES decryption key."); - return false; - } - - AString iv; - if (itemMeta->findString("cipher-iv", &iv)) { - if ((!iv.startsWith("0x") && !iv.startsWith("0X")) - || iv.size() != 16 * 2 + 2) { - LOGE("malformed cipher IV '%s'.", iv.c_str()); - return false; - } - - memset(mAESIVec, 0, sizeof(mAESIVec)); - for (size_t i = 0; i < 16; ++i) { - char c1 = tolower(iv.c_str()[2 + 2 * i]); - char c2 = tolower(iv.c_str()[3 + 2 * i]); - if (!isxdigit(c1) || !isxdigit(c2)) { - LOGE("malformed cipher IV '%s'.", iv.c_str()); - return false; - } - uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10; - uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10; - - mAESIVec[i] = nibble1 << 4 | nibble2; - } - } else { - size_t seqNum = mPlaylistIndex + mFirstItemSequenceNumber; - - memset(mAESIVec, 0, sizeof(mAESIVec)); - mAESIVec[15] = seqNum & 0xff; - mAESIVec[14] = (seqNum >> 8) & 0xff; - mAESIVec[13] = (seqNum >> 16) & 0xff; - mAESIVec[12] = (seqNum >> 24) & 0xff; - } - - mStreamEncrypted = true; - } else if (!(method == "NONE")) { - LOGE("Unsupported cipher method '%s'", method.c_str()); - return false; - } - - return true; -} - -static const ssize_t kHeaderSize = 188; - -ssize_t LiveSource::readAt(off64_t offset, void *data, size_t size) { - CHECK(offset >= mOffsetBias); - offset -= mOffsetBias; - - off64_t delta = mSignalDiscontinuity ? kHeaderSize : 0; - - if (offset >= mSourceSize + delta) { - CHECK_EQ(offset, mSourceSize + delta); - - offset -= mSourceSize + delta; - if (!switchToNext()) { - return ERROR_END_OF_STREAM; - } - - if (mSignalDiscontinuity) { - LOGV("switchToNext changed streams"); - } else { - LOGV("switchToNext stayed within the same stream"); - } - - mOffsetBias += delta; - - delta = mSignalDiscontinuity ? kHeaderSize : 0; - } - - if (offset < delta) { - size_t avail = delta - offset; - memset(data, 0, avail); - return avail; - } - - bool done = false; - size_t numRead = 0; - while (numRead < size) { - ssize_t n = mSource->readAt( - offset + numRead - delta, - (uint8_t *)data + numRead, size - numRead); - - if (n <= 0) { - break; - } - - if (mStreamEncrypted) { - size_t nmod = n % 16; - CHECK(nmod == 0); - - sp<ABuffer> tmp = new ABuffer(n); - - AES_cbc_encrypt((const unsigned char *)data + numRead, - tmp->data(), - n, - (const AES_KEY *)mAESKey, - mAESIVec, - AES_DECRYPT); - - if (mSourceSize == (off64_t)(offset + numRead - delta + n)) { - // check for padding at the end of the file. - - size_t pad = tmp->data()[n - 1]; - CHECK_GT(pad, 0u); - CHECK_LE(pad, 16u); - CHECK_GE((size_t)n, pad); - for (size_t i = 0; i < pad; ++i) { - CHECK_EQ((unsigned)tmp->data()[n - 1 - i], pad); - } - - n -= pad; - mSourceSize -= pad; - - done = true; - } - - memcpy((uint8_t *)data + numRead, tmp->data(), n); - } - - numRead += n; - - if (done) { - break; - } - } - - return numRead; -} - -status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) { - *out = NULL; - - sp<DataSource> source; - - if (!strncasecmp(url, "file://", 7)) { - source = new FileSource(url + 7); - } else { - CHECK(!strncasecmp(url, "http://", 7)); - - status_t err = mSource->connect(url); - - if (err != OK) { - return err; - } - - source = mSource; - } - - off64_t size; - status_t err = source->getSize(&size); - - if (err != OK) { - size = 65536; - } - - sp<ABuffer> buffer = new ABuffer(size); - buffer->setRange(0, 0); - - for (;;) { - size_t bufferRemaining = buffer->capacity() - buffer->size(); - - if (bufferRemaining == 0) { - bufferRemaining = 32768; - - LOGV("increasing download buffer to %d bytes", - buffer->size() + bufferRemaining); - - sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); - memcpy(copy->data(), buffer->data(), buffer->size()); - copy->setRange(0, buffer->size()); - - buffer = copy; - } - - ssize_t n = source->readAt( - buffer->size(), buffer->data() + buffer->size(), - bufferRemaining); - - if (n < 0) { - return err; - } - - if (n == 0) { - break; - } - - buffer->setRange(0, buffer->size() + (size_t)n); - } - - *out = buffer; - - return OK; -} - -bool LiveSource::seekTo(int64_t seekTimeUs) { - LOGV("seek to %lld us", seekTimeUs); - - if (!mPlaylist->isComplete()) { - return false; - } - - int32_t targetDuration; - if (!mPlaylist->meta()->findInt32("target-duration", &targetDuration)) { - return false; - } - - int64_t seekTimeSecs = (seekTimeUs + 500000ll) / 1000000ll; - - int64_t index = seekTimeSecs / targetDuration; - - if (index < 0 || index >= mPlaylist->size()) { - return false; - } - - if (index == mPlaylistIndex) { - return false; - } - - mPlaylistIndex = index; - - LOGV("seeking to index %lld", index); - - switchToNext(); - mOffsetBias = 0; - - return true; -} - -bool LiveSource::getDuration(int64_t *durationUs) const { - if (mDurationUs >= 0) { - *durationUs = mDurationUs; - return true; - } - - *durationUs = 0; - return false; -} - -bool LiveSource::isSeekable() const { - return mDurationUs >= 0; -} - -void LiveSource::determineSeekability() { - mDurationUs = -1; - - if (!mPlaylist->isComplete()) { - return; - } - - int32_t targetDuration; - if (!mPlaylist->meta()->findInt32("target-duration", &targetDuration)) { - return; - } - - mDurationUs = targetDuration * 1000000ll * mPlaylist->size(); -} - -} // namespace android diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index 46f4a35..0c67432 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -45,6 +45,8 @@ struct UDPPusher; class DrmManagerClinet; class DecryptHandle; +struct LiveSession; + struct AwesomeRenderer : public RefBase { AwesomeRenderer() {} @@ -193,6 +195,8 @@ private: sp<ARTPSession> mRTPSession; sp<UDPPusher> mRTPPusher, mRTCPPusher; + sp<LiveSession> mLiveSession; + DrmManagerClient *mDrmManagerClient; DecryptHandle *mDecryptHandle; diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h new file mode 100644 index 0000000..50c0a99 --- /dev/null +++ b/media/libstagefright/include/LiveSession.h @@ -0,0 +1,115 @@ +/* + * 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_SESSION_H_ + +#define LIVE_SESSION_H_ + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct ABuffer; +struct DataSource; +struct LiveDataSource; +struct M3UParser; +struct NuHTTPDataSource; + +struct LiveSession : public AHandler { + LiveSession(); + + sp<DataSource> getDataSource(); + + void connect(const char *url); + void disconnect(); + + // Blocks until seek is complete. + void seekTo(int64_t timeUs); + + status_t getDuration(int64_t *durationUs); + bool isSeekable(); + +protected: + virtual ~LiveSession(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kMaxNumQueuedFragments = 2, + kMaxNumRetries = 3, + }; + + static const int64_t kMaxPlaylistAgeUs; + + enum { + kWhatConnect = 'conn', + kWhatDisconnect = 'disc', + kWhatMonitorQueue = 'moni', + kWhatSeek = 'seek', + }; + + struct BandwidthItem { + AString mURI; + unsigned long mBandwidth; + }; + + sp<LiveDataSource> mDataSource; + + sp<NuHTTPDataSource> mHTTPDataSource; + + AString mMasterURL; + Vector<BandwidthItem> mBandwidthItems; + + KeyedVector<AString, sp<ABuffer> > mAESKeyForURI; + + ssize_t mPrevBandwidthIndex; + int64_t mLastPlaylistFetchTimeUs; + sp<M3UParser> mPlaylist; + int32_t mSeqNumber; + int64_t mSeekTimeUs; + int32_t mNumRetries; + + Mutex mLock; + Condition mCondition; + int64_t mDurationUs; + bool mSeekDone; + + int32_t mMonitorQueueGeneration; + + void onConnect(const sp<AMessage> &msg); + void onDisconnect(); + void onDownloadNext(); + void onMonitorQueue(); + void onSeek(const sp<AMessage> &msg); + + status_t fetchFile(const char *url, sp<ABuffer> *out); + sp<M3UParser> fetchPlaylist(const char *url); + size_t getBandwidthIndex(); + + status_t decryptBuffer( + size_t playlistIndex, const sp<ABuffer> &buffer); + + void postMonitorQueue(int64_t delayUs = 0); + + static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *); + + DISALLOW_EVIL_CONSTRUCTORS(LiveSession); +}; + +} // namespace android + +#endif // LIVE_SESSION_H_ diff --git a/media/libstagefright/include/LiveSource.h b/media/libstagefright/include/LiveSource.h deleted file mode 100644 index 38fe328..0000000 --- a/media/libstagefright/include/LiveSource.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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/KeyedVector.h> -#include <utils/Vector.h> - -namespace android { - -struct ABuffer; -struct NuHTTPDataSource; -struct M3UParser; - -struct LiveSource : public DataSource { - LiveSource(const char *url); - - virtual status_t initCheck() const; - - virtual ssize_t readAt(off64_t offset, void *data, size_t size); - - virtual uint32_t flags() { - return kWantsPrefetching; - } - - bool getDuration(int64_t *durationUs) const; - - bool isSeekable() const; - bool seekTo(int64_t seekTimeUs); - -protected: - virtual ~LiveSource(); - -private: - struct BandwidthItem { - AString mURI; - unsigned long mBandwidth; - }; - Vector<BandwidthItem> mBandwidthItems; - - AString mMasterURL; - AString mURL; - status_t mInitCheck; - int64_t mDurationUs; - - sp<M3UParser> mPlaylist; - int32_t mFirstItemSequenceNumber; - size_t mPlaylistIndex; - int64_t mLastFetchTimeUs; - - sp<NuHTTPDataSource> mSource; - off64_t mSourceSize; - off64_t mOffsetBias; - - bool mSignalDiscontinuity; - ssize_t mPrevBandwidthIndex; - - void *mAESKey; - unsigned char mAESIVec[16]; - bool mStreamEncrypted; - - KeyedVector<AString, sp<ABuffer> > mAESKeyForURI; - - status_t fetchM3U(const char *url, sp<ABuffer> *buffer); - - static int SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b); - - bool switchToNext(); - bool loadPlaylist(bool fetchMaster, size_t bandwidthIndex); - void determineSeekability(); - - size_t getBandwidthIndex(); - bool setupCipher(); - - DISALLOW_EVIL_CONSTRUCTORS(LiveSource); -}; - -} // namespace android - -#endif // LIVE_SOURCE_H_ diff --git a/media/libstagefright/include/MPEG2TSExtractor.h b/media/libstagefright/include/MPEG2TSExtractor.h index 58401d4..efe7496 100644 --- a/media/libstagefright/include/MPEG2TSExtractor.h +++ b/media/libstagefright/include/MPEG2TSExtractor.h @@ -15,7 +15,7 @@ struct ATSParser; struct DataSource; struct MPEG2TSSource; struct String8; -struct LiveSource; +struct LiveSession; struct MPEG2TSExtractor : public MediaExtractor { MPEG2TSExtractor(const sp<DataSource> &source); @@ -28,7 +28,7 @@ struct MPEG2TSExtractor : public MediaExtractor { virtual uint32_t flags() const; - void setLiveSource(const sp<LiveSource> &liveSource); + void setLiveSession(const sp<LiveSession> &liveSession); void seekTo(int64_t seekTimeUs); private: @@ -37,7 +37,7 @@ private: mutable Mutex mLock; sp<DataSource> mDataSource; - sp<LiveSource> mLiveSource; + sp<LiveSession> mLiveSession; sp<ATSParser> mParser; diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h index f0f7daf..78719c1 100644 --- a/media/libstagefright/include/NuCachedSource2.h +++ b/media/libstagefright/include/NuCachedSource2.h @@ -45,9 +45,6 @@ struct NuCachedSource2 : public DataSource { size_t cachedSize(); size_t approxDataRemaining(bool *eos); - void suspend(); - void clearCacheAndResume(); - void resumeFetchingIfNecessary(); protected: @@ -69,7 +66,6 @@ private: enum { kWhatFetchMore = 'fetc', kWhatRead = 'read', - kWhatSuspend = 'susp', }; sp<DataSource> mSource; @@ -87,12 +83,10 @@ private: sp<AMessage> mAsyncResult; bool mFetching; int64_t mLastFetchTimeUs; - bool mSuspended; void onMessageReceived(const sp<AMessage> &msg); void onFetch(); void onRead(const sp<AMessage> &msg); - void onSuspend(); void fetchInternal(); ssize_t readInternal(off64_t offset, void *data, size_t size); diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp index 37bcb23..1fb7c39 100644 --- a/media/libstagefright/mpeg2ts/ESQueue.cpp +++ b/media/libstagefright/mpeg2ts/ESQueue.cpp @@ -49,6 +49,33 @@ void ElementaryStreamQueue::clear() { mFormat.clear(); } +static bool IsSeeminglyValidADTSHeader(const uint8_t *ptr, size_t size) { + if (size < 3) { + // Not enough data to verify header. + return false; + } + + if (ptr[0] != 0xff || (ptr[1] >> 4) != 0x0f) { + return false; + } + + unsigned layer = (ptr[1] >> 1) & 3; + + if (layer != 0) { + return false; + } + + unsigned ID = (ptr[1] >> 3) & 1; + unsigned profile_ObjectType = ptr[2] >> 6; + + if (ID == 1 && profile_ObjectType == 3) { + // MPEG-2 profile 3 is reserved. + return false; + } + + return true; +} + status_t ElementaryStreamQueue::appendData( const void *data, size_t size, int64_t timeUs) { if (mBuffer == NULL || mBuffer->size() == 0) { @@ -96,8 +123,8 @@ status_t ElementaryStreamQueue::appendData( } #else ssize_t startOffset = -1; - for (size_t i = 0; i + 1 < size; ++i) { - if (ptr[i] == 0xff && (ptr[i + 1] >> 4) == 0x0f) { + for (size_t i = 0; i < size; ++i) { + if (IsSeeminglyValidADTSHeader(&ptr[i], size - i)) { startOffset = i; break; } diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp index 3176810..600116e 100644 --- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp +++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp @@ -19,7 +19,7 @@ #include <utils/Log.h> #include "include/MPEG2TSExtractor.h" -#include "include/LiveSource.h" +#include "include/LiveSession.h" #include "include/NuCachedSource2.h" #include <media/stagefright/DataSource.h> @@ -82,8 +82,8 @@ sp<MetaData> MPEG2TSSource::getFormat() { sp<MetaData> meta = mImpl->getFormat(); int64_t durationUs; - if (mExtractor->mLiveSource != NULL - && mExtractor->mLiveSource->getDuration(&durationUs)) { + if (mExtractor->mLiveSession != NULL + && mExtractor->mLiveSession->getDuration(&durationUs) == OK) { meta->setInt64(kKeyDuration, durationUs); } @@ -226,32 +226,20 @@ status_t MPEG2TSExtractor::feedMore() { return OK; } -void MPEG2TSExtractor::setLiveSource(const sp<LiveSource> &liveSource) { +void MPEG2TSExtractor::setLiveSession(const sp<LiveSession> &liveSession) { Mutex::Autolock autoLock(mLock); - mLiveSource = liveSource; + mLiveSession = liveSession; } void MPEG2TSExtractor::seekTo(int64_t seekTimeUs) { Mutex::Autolock autoLock(mLock); - if (mLiveSource == NULL) { + if (mLiveSession == NULL) { return; } - if (mDataSource->flags() & DataSource::kIsCachingDataSource) { - static_cast<NuCachedSource2 *>(mDataSource.get())->suspend(); - } - - if (mLiveSource->seekTo(seekTimeUs)) { - mParser->signalDiscontinuity(true /* isSeek */); - mOffset = 0; - } - - if (mDataSource->flags() & DataSource::kIsCachingDataSource) { - static_cast<NuCachedSource2 *>(mDataSource.get()) - ->clearCacheAndResume(); - } + mLiveSession->seekTo(seekTimeUs); } uint32_t MPEG2TSExtractor::flags() const { @@ -259,7 +247,7 @@ uint32_t MPEG2TSExtractor::flags() const { uint32_t flags = CAN_PAUSE; - if (mLiveSource != NULL && mLiveSource->isSeekable()) { + if (mLiveSession != NULL && mLiveSession->isSeekable()) { flags |= CAN_SEEK_FORWARD | CAN_SEEK_BACKWARD | CAN_SEEK; } |