summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2010-12-03 16:12:25 -0800
committerAndreas Huber <andih@google.com>2010-12-06 08:28:36 -0800
commita44153c1a57202fb538659eb50706e60454d6273 (patch)
tree4ba43d350d44fb4bd052ce3c562c4a2ad6658270
parent16afe2fb439cab6125bb46a07a8078d4ce1c1ea5 (diff)
downloadframeworks_av-a44153c1a57202fb538659eb50706e60454d6273.zip
frameworks_av-a44153c1a57202fb538659eb50706e60454d6273.tar.gz
frameworks_av-a44153c1a57202fb538659eb50706e60454d6273.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
-rw-r--r--cmds/stagefright/stagefright.cpp14
-rw-r--r--media/libstagefright/AwesomePlayer.cpp22
-rw-r--r--media/libstagefright/NuCachedSource2.cpp46
-rw-r--r--media/libstagefright/NuHTTPDataSource.cpp2
-rw-r--r--media/libstagefright/httplive/Android.mk7
-rw-r--r--media/libstagefright/httplive/LiveDataSource.cpp142
-rw-r--r--media/libstagefright/httplive/LiveDataSource.h61
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp646
-rw-r--r--media/libstagefright/httplive/LiveSource.cpp655
-rw-r--r--media/libstagefright/include/AwesomePlayer.h4
-rw-r--r--media/libstagefright/include/LiveSession.h115
-rw-r--r--media/libstagefright/include/LiveSource.h98
-rw-r--r--media/libstagefright/include/MPEG2TSExtractor.h6
-rw-r--r--media/libstagefright/include/NuCachedSource2.h6
-rw-r--r--media/libstagefright/mpeg2ts/ESQueue.cpp31
-rw-r--r--media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp28
16 files changed, 1043 insertions, 840 deletions
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 07e506a..7ba5291 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -31,7 +31,7 @@
#include <media/IMediaPlayerService.h>
#include <media/stagefright/foundation/ALooper.h>
#include "include/ARTSPController.h"
-#include "include/LiveSource.h"
+#include "include/LiveSession.h"
#include "include/NuCachedSource2.h"
#include <media/stagefright/AudioPlayer.h>
#include <media/stagefright/DataSource.h>
@@ -560,6 +560,7 @@ int main(int argc, char **argv) {
sp<ALooper> looper;
sp<ARTSPController> rtspController;
+ sp<LiveSession> liveSession;
int res;
while ((res = getopt(argc, argv, "han:lm:b:ptsow:kx")) >= 0) {
@@ -852,8 +853,15 @@ int main(int argc, char **argv) {
String8 uri("http://");
uri.append(filename + 11);
- dataSource = new LiveSource(uri.string());
- dataSource = new NuCachedSource2(dataSource);
+ if (looper == NULL) {
+ looper = new ALooper;
+ looper->start();
+ }
+ liveSession = new LiveSession;
+ looper->registerHandler(liveSession);
+
+ liveSession->connect(uri.string());
+ dataSource = liveSession->getDataSource();
extractor =
MediaExtractor::Create(
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;
}