summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/httplive
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/httplive')
-rw-r--r--media/libstagefright/httplive/Android.mk2
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp303
-rw-r--r--media/libstagefright/httplive/LiveSession.h51
-rw-r--r--media/libstagefright/httplive/M3UParser.cpp178
-rw-r--r--media/libstagefright/httplive/M3UParser.h10
-rw-r--r--media/libstagefright/httplive/PlaylistFetcher.cpp232
-rw-r--r--media/libstagefright/httplive/PlaylistFetcher.h24
7 files changed, 531 insertions, 269 deletions
diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk
index f3529f9..e8d558c 100644
--- a/media/libstagefright/httplive/Android.mk
+++ b/media/libstagefright/httplive/Android.mk
@@ -13,6 +13,8 @@ LOCAL_C_INCLUDES:= \
$(TOP)/frameworks/native/include/media/openmax \
$(TOP)/external/openssl/include
+LOCAL_CFLAGS += -Werror
+
LOCAL_SHARED_LIBRARIES := \
libbinder \
libcrypto \
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index fc1353a..95779c4 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -27,6 +27,8 @@
#include "mpeg2ts/AnotherPacketSource.h"
#include <cutils/properties.h>
+#include <media/IMediaHTTPConnection.h>
+#include <media/IMediaHTTPService.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -34,6 +36,7 @@
#include <media/stagefright/DataSource.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaHTTP.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
@@ -44,17 +47,13 @@
namespace android {
LiveSession::LiveSession(
- const sp<AMessage> &notify, uint32_t flags, bool uidValid, uid_t uid)
+ const sp<AMessage> &notify, uint32_t flags,
+ const sp<IMediaHTTPService> &httpService)
: mNotify(notify),
mFlags(flags),
- mUIDValid(uidValid),
- mUID(uid),
+ mHTTPService(httpService),
mInPreparationPhase(true),
- mHTTPDataSource(
- HTTPBase::Create(
- (mFlags & kFlagIncognito)
- ? HTTPBase::kFlagIncognito
- : 0)),
+ mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())),
mPrevBandwidthIndex(-1),
mStreamMask(0),
mCheckBandwidthGeneration(0),
@@ -62,18 +61,14 @@ LiveSession::LiveSession(
mRealTimeBaseUs(0ll),
mReconfigurationInProgress(false),
mDisconnectReplyID(0) {
- if (mUIDValid) {
- mHTTPDataSource->setUID(mUID);
- }
-
- mPacketSources.add(
- STREAMTYPE_AUDIO, new AnotherPacketSource(NULL /* meta */));
- mPacketSources.add(
- STREAMTYPE_VIDEO, new AnotherPacketSource(NULL /* meta */));
+ mStreams[kAudioIndex] = StreamItem("audio");
+ mStreams[kVideoIndex] = StreamItem("video");
+ mStreams[kSubtitleIndex] = StreamItem("subtitle");
- mPacketSources.add(
- STREAMTYPE_SUBTITLES, new AnotherPacketSource(NULL /* meta */));
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
+ }
}
LiveSession::~LiveSession() {
@@ -374,6 +369,12 @@ int LiveSession::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b)
return 1;
}
+// static
+LiveSession::StreamType LiveSession::indexToType(int idx) {
+ CHECK(idx >= 0 && idx < kMaxStreams);
+ return (StreamType)(1 << idx);
+}
+
void LiveSession::onConnect(const sp<AMessage> &msg) {
AString url;
CHECK(msg->findString("url", &url));
@@ -512,54 +513,81 @@ sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) {
return info.mFetcher;
}
+/*
+ * Illustration of parameters:
+ *
+ * 0 `range_offset`
+ * +------------+-------------------------------------------------------+--+--+
+ * | | | next block to fetch | | |
+ * | | `source` handle => `out` buffer | | | |
+ * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | |
+ * | |<----------- `range_length` / buffer capacity ----------->| |
+ * |<------------------------------ file_size ------------------------------->|
+ *
+ * Special parameter values:
+ * - range_length == -1 means entire file
+ * - block_size == 0 means entire range
+ *
+ */
status_t LiveSession::fetchFile(
const char *url, sp<ABuffer> *out,
int64_t range_offset, int64_t range_length,
+ uint32_t block_size, /* download block size */
+ sp<DataSource> *source, /* to return and reuse source */
String8 *actualUrl) {
- *out = NULL;
+ off64_t size;
+ sp<DataSource> temp_source;
+ if (source == NULL) {
+ source = &temp_source;
+ }
- sp<DataSource> source;
+ if (*source == NULL) {
+ if (!strncasecmp(url, "file://", 7)) {
+ *source = new FileSource(url + 7);
+ } else if (strncasecmp(url, "http://", 7)
+ && strncasecmp(url, "https://", 8)) {
+ return ERROR_UNSUPPORTED;
+ } else {
+ KeyedVector<String8, String8> headers = mExtraHeaders;
+ if (range_offset > 0 || range_length >= 0) {
+ headers.add(
+ String8("Range"),
+ String8(
+ StringPrintf(
+ "bytes=%lld-%s",
+ range_offset,
+ range_length < 0
+ ? "" : StringPrintf("%lld",
+ range_offset + range_length - 1).c_str()).c_str()));
+ }
+ status_t err = mHTTPDataSource->connect(url, &headers);
- if (!strncasecmp(url, "file://", 7)) {
- source = new FileSource(url + 7);
- } else if (strncasecmp(url, "http://", 7)
- && strncasecmp(url, "https://", 8)) {
- return ERROR_UNSUPPORTED;
- } else {
- KeyedVector<String8, String8> headers = mExtraHeaders;
- if (range_offset > 0 || range_length >= 0) {
- headers.add(
- String8("Range"),
- String8(
- StringPrintf(
- "bytes=%lld-%s",
- range_offset,
- range_length < 0
- ? "" : StringPrintf("%lld", range_offset + range_length - 1).c_str()).c_str()));
- }
- status_t err = mHTTPDataSource->connect(url, &headers);
+ if (err != OK) {
+ return err;
+ }
- if (err != OK) {
- return err;
+ *source = mHTTPDataSource;
}
-
- source = mHTTPDataSource;
}
- off64_t size;
- status_t err = source->getSize(&size);
-
- if (err != OK) {
+ status_t getSizeErr = (*source)->getSize(&size);
+ if (getSizeErr != OK) {
size = 65536;
}
- sp<ABuffer> buffer = new ABuffer(size);
- buffer->setRange(0, 0);
+ sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
+ if (*out == NULL) {
+ buffer->setRange(0, 0);
+ }
+ // adjust range_length if only reading partial block
+ if (block_size > 0 && (range_length == -1 || buffer->size() + block_size < range_length)) {
+ range_length = buffer->size() + block_size;
+ }
for (;;) {
+ // Only resize when we don't know the size.
size_t bufferRemaining = buffer->capacity() - buffer->size();
-
- if (bufferRemaining == 0) {
+ if (bufferRemaining == 0 && getSizeErr != OK) {
bufferRemaining = 32768;
ALOGV("increasing download buffer to %d bytes",
@@ -584,7 +612,9 @@ status_t LiveSession::fetchFile(
}
}
- ssize_t n = source->readAt(
+ // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
+ // to help us break out of the loop.
+ ssize_t n = (*source)->readAt(
buffer->size(), buffer->data() + buffer->size(),
maxBytesToRead);
@@ -601,7 +631,7 @@ status_t LiveSession::fetchFile(
*out = buffer;
if (actualUrl != NULL) {
- *actualUrl = source->getUri();
+ *actualUrl = (*source)->getUri();
if (actualUrl->isEmpty()) {
*actualUrl = url;
}
@@ -618,7 +648,7 @@ sp<M3UParser> LiveSession::fetchPlaylist(
sp<ABuffer> buffer;
String8 actualUrl;
- status_t err = fetchFile(url, &buffer, 0, -1, &actualUrl);
+ status_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl);
if (err != OK) {
return NULL;
@@ -834,19 +864,11 @@ void LiveSession::changeConfiguration(
uint32_t streamMask = 0;
- AString audioURI;
- if (mPlaylist->getAudioURI(item.mPlaylistIndex, &audioURI)) {
- streamMask |= STREAMTYPE_AUDIO;
- }
-
- AString videoURI;
- if (mPlaylist->getVideoURI(item.mPlaylistIndex, &videoURI)) {
- streamMask |= STREAMTYPE_VIDEO;
- }
-
- AString subtitleURI;
- if (mPlaylist->getSubtitleURI(item.mPlaylistIndex, &subtitleURI)) {
- streamMask |= STREAMTYPE_SUBTITLES;
+ AString URIs[kMaxStreams];
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (mPlaylist->getTypeURI(item.mPlaylistIndex, mStreams[i].mType, &URIs[i])) {
+ streamMask |= indexToType(i);
+ }
}
// Step 1, stop and discard fetchers that are no longer needed.
@@ -858,10 +880,10 @@ void LiveSession::changeConfiguration(
// If we're seeking all current fetchers are discarded.
if (timeUs < 0ll) {
- if (((streamMask & STREAMTYPE_AUDIO) && uri == audioURI)
- || ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI)
- || ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI)) {
- discardFetcher = false;
+ for (size_t j = 0; j < kMaxStreams; ++j) {
+ if ((streamMask & indexToType(j)) && uri == URIs[j]) {
+ discardFetcher = false;
+ }
}
}
@@ -875,14 +897,10 @@ void LiveSession::changeConfiguration(
sp<AMessage> msg = new AMessage(kWhatChangeConfiguration2, id());
msg->setInt32("streamMask", streamMask);
msg->setInt64("timeUs", timeUs);
- if (streamMask & STREAMTYPE_AUDIO) {
- msg->setString("audioURI", audioURI.c_str());
- }
- if (streamMask & STREAMTYPE_VIDEO) {
- msg->setString("videoURI", videoURI.c_str());
- }
- if (streamMask & STREAMTYPE_SUBTITLES) {
- msg->setString("subtitleURI", subtitleURI.c_str());
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (streamMask & indexToType(i)) {
+ msg->setString(mStreams[i].uriKey().c_str(), URIs[i].c_str());
+ }
}
// Every time a fetcher acknowledges the stopAsync or pauseAsync request
@@ -913,18 +931,13 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
uint32_t streamMask;
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
- AString audioURI, videoURI, subtitleURI;
- if (streamMask & STREAMTYPE_AUDIO) {
- CHECK(msg->findString("audioURI", &audioURI));
- ALOGV("audioURI = '%s'", audioURI.c_str());
- }
- if (streamMask & STREAMTYPE_VIDEO) {
- CHECK(msg->findString("videoURI", &videoURI));
- ALOGV("videoURI = '%s'", videoURI.c_str());
- }
- if (streamMask & STREAMTYPE_SUBTITLES) {
- CHECK(msg->findString("subtitleURI", &subtitleURI));
- ALOGV("subtitleURI = '%s'", subtitleURI.c_str());
+ AString URIs[kMaxStreams];
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (streamMask & indexToType(i)) {
+ const AString &uriKey = mStreams[i].uriKey();
+ CHECK(msg->findString(uriKey.c_str(), &URIs[i]));
+ ALOGV("%s = '%s'", uriKey.c_str(), URIs[i].c_str());
+ }
}
// Determine which decoders to shutdown on the player side,
@@ -934,15 +947,12 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
// 2) its streamtype was already active and still is but the URI
// has changed.
uint32_t changedMask = 0;
- if (((mStreamMask & streamMask & STREAMTYPE_AUDIO)
- && !(audioURI == mAudioURI))
- || (mStreamMask & ~streamMask & STREAMTYPE_AUDIO)) {
- changedMask |= STREAMTYPE_AUDIO;
- }
- if (((mStreamMask & streamMask & STREAMTYPE_VIDEO)
- && !(videoURI == mVideoURI))
- || (mStreamMask & ~streamMask & STREAMTYPE_VIDEO)) {
- changedMask |= STREAMTYPE_VIDEO;
+ for (size_t i = 0; i < kMaxStreams && i != kSubtitleIndex; ++i) {
+ if (((mStreamMask & streamMask & indexToType(i))
+ && !(URIs[i] == mStreams[i].mUri))
+ || (mStreamMask & ~streamMask & indexToType(i))) {
+ changedMask |= indexToType(i);
+ }
}
if (changedMask == 0) {
@@ -974,15 +984,10 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
uint32_t streamMask;
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
- AString audioURI, videoURI, subtitleURI;
- if (streamMask & STREAMTYPE_AUDIO) {
- CHECK(msg->findString("audioURI", &audioURI));
- }
- if (streamMask & STREAMTYPE_VIDEO) {
- CHECK(msg->findString("videoURI", &videoURI));
- }
- if (streamMask & STREAMTYPE_SUBTITLES) {
- CHECK(msg->findString("subtitleURI", &subtitleURI));
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (streamMask & indexToType(i)) {
+ CHECK(msg->findString(mStreams[i].uriKey().c_str(), &mStreams[i].mUri));
+ }
}
int64_t timeUs;
@@ -994,9 +999,6 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
mRealTimeBaseUs = ALooper::GetNowUs() - timeUs;
mStreamMask = streamMask;
- mAudioURI = audioURI;
- mVideoURI = videoURI;
- mSubtitleURI = subtitleURI;
// Resume all existing fetchers and assign them packet sources.
for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
@@ -1004,22 +1006,12 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
uint32_t resumeMask = 0;
- sp<AnotherPacketSource> audioSource;
- if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
- audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
- resumeMask |= STREAMTYPE_AUDIO;
- }
-
- sp<AnotherPacketSource> videoSource;
- if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
- videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
- resumeMask |= STREAMTYPE_VIDEO;
- }
-
- sp<AnotherPacketSource> subtitleSource;
- if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
- subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
- resumeMask |= STREAMTYPE_SUBTITLES;
+ sp<AnotherPacketSource> sources[kMaxStreams];
+ for (size_t j = 0; j < kMaxStreams; ++j) {
+ if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+ sources[j] = mPacketSources.valueFor(indexToType(j));
+ resumeMask |= indexToType(j);
+ }
}
CHECK_NE(resumeMask, 0u);
@@ -1029,7 +1021,7 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
streamMask &= ~resumeMask;
mFetcherInfos.valueAt(i).mFetcher->startAsync(
- audioSource, videoSource, subtitleSource);
+ sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex]);
}
// streamMask now only contains the types that need a new fetcher created.
@@ -1038,52 +1030,33 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
ALOGV("creating new fetchers for mask 0x%08x", streamMask);
}
- while (streamMask != 0) {
- StreamType streamType = (StreamType)(streamMask & ~(streamMask - 1));
+ for (size_t i = 0; i < kMaxStreams; i++) {
+ if (!(indexToType(i) & streamMask)) {
+ continue;
+ }
AString uri;
- switch (streamType) {
- case STREAMTYPE_AUDIO:
- uri = audioURI;
- break;
- case STREAMTYPE_VIDEO:
- uri = videoURI;
- break;
- case STREAMTYPE_SUBTITLES:
- uri = subtitleURI;
- break;
- default:
- TRESPASS();
- }
+ uri = mStreams[i].mUri;
sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
CHECK(fetcher != NULL);
- sp<AnotherPacketSource> audioSource;
- if ((streamMask & STREAMTYPE_AUDIO) && uri == audioURI) {
- audioSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
- audioSource->clear();
+ sp<AnotherPacketSource> sources[kMaxStreams];
+ // TRICKY: looping from i as earlier streams are already removed from streamMask
+ for (size_t j = i; j < kMaxStreams; ++j) {
+ if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) {
+ sources[j] = mPacketSources.valueFor(indexToType(j));
+ sources[j]->clear();
- streamMask &= ~STREAMTYPE_AUDIO;
- }
-
- sp<AnotherPacketSource> videoSource;
- if ((streamMask & STREAMTYPE_VIDEO) && uri == videoURI) {
- videoSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
- videoSource->clear();
-
- streamMask &= ~STREAMTYPE_VIDEO;
- }
-
- sp<AnotherPacketSource> subtitleSource;
- if ((streamMask & STREAMTYPE_SUBTITLES) && uri == subtitleURI) {
- subtitleSource = mPacketSources.valueFor(STREAMTYPE_SUBTITLES);
- subtitleSource->clear();
-
- streamMask &= ~STREAMTYPE_SUBTITLES;
+ streamMask &= ~indexToType(j);
+ }
}
- fetcher->startAsync(audioSource, videoSource, subtitleSource, timeUs);
+ fetcher->startAsync(
+ sources[kAudioIndex],
+ sources[kVideoIndex],
+ sources[kSubtitleIndex],
+ timeUs);
}
// All fetchers have now been started, the configuration change
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index 8f6a4ea..c4d125c 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -28,6 +28,7 @@ struct ABuffer;
struct AnotherPacketSource;
struct DataSource;
struct HTTPBase;
+struct IMediaHTTPService;
struct LiveDataSource;
struct M3UParser;
struct PlaylistFetcher;
@@ -40,12 +41,20 @@ struct LiveSession : public AHandler {
};
LiveSession(
const sp<AMessage> &notify,
- uint32_t flags = 0, bool uidValid = false, uid_t uid = 0);
+ uint32_t flags,
+ const sp<IMediaHTTPService> &httpService);
+
+ enum StreamIndex {
+ kAudioIndex = 0,
+ kVideoIndex = 1,
+ kSubtitleIndex = 2,
+ kMaxStreams = 3,
+ };
enum StreamType {
- STREAMTYPE_AUDIO = 1,
- STREAMTYPE_VIDEO = 2,
- STREAMTYPE_SUBTITLES = 4,
+ STREAMTYPE_AUDIO = 1 << kAudioIndex,
+ STREAMTYPE_VIDEO = 1 << kVideoIndex,
+ STREAMTYPE_SUBTITLES = 1 << kSubtitleIndex,
};
status_t dequeueAccessUnit(StreamType stream, sp<ABuffer> *accessUnit);
@@ -105,10 +114,22 @@ private:
bool mIsPrepared;
};
+ struct StreamItem {
+ const char *mType;
+ AString mUri;
+ StreamItem() : mType("") {}
+ StreamItem(const char *type) : mType(type) {}
+ AString uriKey() {
+ AString key(mType);
+ key.append("URI");
+ return key;
+ }
+ };
+ StreamItem mStreams[kMaxStreams];
+
sp<AMessage> mNotify;
uint32_t mFlags;
- bool mUIDValid;
- uid_t mUID;
+ sp<IMediaHTTPService> mHTTPService;
bool mInPreparationPhase;
@@ -123,7 +144,6 @@ private:
sp<M3UParser> mPlaylist;
KeyedVector<AString, FetcherInfo> mFetcherInfos;
- AString mAudioURI, mVideoURI, mSubtitleURI;
uint32_t mStreamMask;
KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources;
@@ -145,9 +165,25 @@ private:
status_t onSeek(const sp<AMessage> &msg);
void onFinishDisconnect2();
+ // If given a non-zero block_size (default 0), it is used to cap the number of
+ // bytes read in from the DataSource. If given a non-NULL buffer, new content
+ // is read into the end.
+ //
+ // The DataSource we read from is responsible for signaling error or EOF to help us
+ // break out of the read loop. The DataSource can be returned to the caller, so
+ // that the caller can reuse it for subsequent fetches (within the initially
+ // requested range).
+ //
+ // For reused HTTP sources, the caller must download a file sequentially without
+ // any overlaps or gaps to prevent reconnection.
status_t fetchFile(
const char *url, sp<ABuffer> *out,
+ /* request/open a file starting at range_offset for range_length bytes */
int64_t range_offset = 0, int64_t range_length = -1,
+ /* download block size */
+ uint32_t block_size = 0,
+ /* reuse DataSource if doing partial fetch */
+ sp<DataSource> *source = NULL,
String8 *actualUrl = NULL);
sp<M3UParser> fetchPlaylist(
@@ -156,6 +192,7 @@ private:
size_t getBandwidthIndex();
static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
+ static StreamType indexToType(int idx);
void changeConfiguration(
int64_t timeUs, size_t bandwidthIndex, bool pickTrack = false);
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index 5ef7c0f..587a6d5 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -24,6 +24,7 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
#include <media/mediaplayer.h>
namespace android {
@@ -125,7 +126,7 @@ void M3UParser::MediaGroup::pickRandomMediaItems() {
mSelectedIndex = strtoul(value, &end, 10);
CHECK(end > value && *end == '\0');
- if (mSelectedIndex >= mMediaItems.size()) {
+ if (mSelectedIndex >= (ssize_t)mMediaItems.size()) {
mSelectedIndex = mMediaItems.size() - 1;
}
} else {
@@ -165,14 +166,14 @@ status_t M3UParser::MediaGroup::selectTrack(size_t index, bool select) {
ALOGE("track %d does not exist", index);
return INVALID_OPERATION;
}
- if (mSelectedIndex == index) {
+ if (mSelectedIndex == (ssize_t)index) {
ALOGE("track %d already selected", index);
return BAD_VALUE;
}
ALOGV("selected track %d", index);
mSelectedIndex = index;
} else {
- if (mSelectedIndex != index) {
+ if (mSelectedIndex != (ssize_t)index) {
ALOGE("track %d is not selected", index);
return BAD_VALUE;
}
@@ -352,9 +353,27 @@ bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
if (!meta->findString(key, &groupID)) {
*uri = mItems.itemAt(index).mURI;
- // Assume media without any more specific attribute contains
- // audio and video, but no subtitles.
- return !strcmp("audio", key) || !strcmp("video", key);
+ AString codecs;
+ if (!meta->findString("codecs", &codecs)) {
+ // Assume media without any more specific attribute contains
+ // audio and video, but no subtitles.
+ return !strcmp("audio", key) || !strcmp("video", key);
+ } else {
+ // Split the comma separated list of codecs.
+ size_t offset = 0;
+ ssize_t commaPos = -1;
+ codecs.append(',');
+ while ((commaPos = codecs.find(",", offset)) >= 0) {
+ AString codec(codecs, offset, commaPos - offset);
+ // return true only if a codec of type `key` ("audio"/"video")
+ // is found.
+ if (codecIsType(codec, key)) {
+ return true;
+ }
+ offset = commaPos + 1;
+ }
+ return false;
+ }
}
sp<MediaGroup> group = mMediaGroups.valueFor(groupID);
@@ -369,18 +388,6 @@ bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const {
return true;
}
-bool M3UParser::getAudioURI(size_t index, AString *uri) const {
- return getTypeURI(index, "audio", uri);
-}
-
-bool M3UParser::getVideoURI(size_t index, AString *uri) const {
- return getTypeURI(index, "video", uri);
-}
-
-bool M3UParser::getSubtitleURI(size_t index, AString *uri) const {
- return getTypeURI(index, "subtitles", uri);
-}
-
static bool MakeURL(const char *baseURL, const char *url, AString *out) {
out->clear();
@@ -694,12 +701,22 @@ status_t M3UParser::parseStreamInf(
*meta = new AMessage;
}
(*meta)->setInt32("bandwidth", x);
+ } else if (!strcasecmp("codecs", key.c_str())) {
+ if (!isQuotedString(val)) {
+ ALOGE("Expected quoted string for %s attribute, "
+ "got '%s' instead.",
+ key.c_str(), val.c_str());;
+
+ return ERROR_MALFORMED;
+ }
+
+ key.tolower();
+ const AString &codecs = unquoteString(val);
+ (*meta)->setString(key.c_str(), codecs.c_str());
} else if (!strcasecmp("audio", key.c_str())
|| !strcasecmp("video", key.c_str())
|| !strcasecmp("subtitles", key.c_str())) {
- if (val.size() < 2
- || val.c_str()[0] != '"'
- || val.c_str()[val.size() - 1] != '"') {
+ if (!isQuotedString(val)) {
ALOGE("Expected quoted string for %s attribute, "
"got '%s' instead.",
key.c_str(), val.c_str());
@@ -707,7 +724,7 @@ status_t M3UParser::parseStreamInf(
return ERROR_MALFORMED;
}
- AString groupID(val, 1, val.size() - 2);
+ const AString &groupID = unquoteString(val);
ssize_t groupIndex = mMediaGroups.indexOfKey(groupID);
if (groupIndex < 0) {
@@ -1096,4 +1113,121 @@ status_t M3UParser::ParseDouble(const char *s, double *x) {
return OK;
}
+// static
+bool M3UParser::isQuotedString(const AString &str) {
+ if (str.size() < 2
+ || str.c_str()[0] != '"'
+ || str.c_str()[str.size() - 1] != '"') {
+ return false;
+ }
+ return true;
+}
+
+// static
+AString M3UParser::unquoteString(const AString &str) {
+ if (!isQuotedString(str)) {
+ return str;
+ }
+ return AString(str, 1, str.size() - 2);
+}
+
+// static
+bool M3UParser::codecIsType(const AString &codec, const char *type) {
+ if (codec.size() < 4) {
+ return false;
+ }
+ const char *c = codec.c_str();
+ switch (FOURCC(c[0], c[1], c[2], c[3])) {
+ // List extracted from http://www.mp4ra.org/codecs.html
+ case 'ac-3':
+ case 'alac':
+ case 'dra1':
+ case 'dtsc':
+ case 'dtse':
+ case 'dtsh':
+ case 'dtsl':
+ case 'ec-3':
+ case 'enca':
+ case 'g719':
+ case 'g726':
+ case 'm4ae':
+ case 'mlpa':
+ case 'mp4a':
+ case 'raw ':
+ case 'samr':
+ case 'sawb':
+ case 'sawp':
+ case 'sevc':
+ case 'sqcp':
+ case 'ssmv':
+ case 'twos':
+ case 'agsm':
+ case 'alaw':
+ case 'dvi ':
+ case 'fl32':
+ case 'fl64':
+ case 'ima4':
+ case 'in24':
+ case 'in32':
+ case 'lpcm':
+ case 'Qclp':
+ case 'QDM2':
+ case 'QDMC':
+ case 'ulaw':
+ case 'vdva':
+ return !strcmp("audio", type);
+
+ case 'avc1':
+ case 'avc2':
+ case 'avcp':
+ case 'drac':
+ case 'encv':
+ case 'mjp2':
+ case 'mp4v':
+ case 'mvc1':
+ case 'mvc2':
+ case 'resv':
+ case 's263':
+ case 'svc1':
+ case 'vc-1':
+ case 'CFHD':
+ case 'civd':
+ case 'DV10':
+ case 'dvh5':
+ case 'dvh6':
+ case 'dvhp':
+ case 'DVOO':
+ case 'DVOR':
+ case 'DVTV':
+ case 'DVVT':
+ case 'flic':
+ case 'gif ':
+ case 'h261':
+ case 'h263':
+ case 'HD10':
+ case 'jpeg':
+ case 'M105':
+ case 'mjpa':
+ case 'mjpb':
+ case 'png ':
+ case 'PNTG':
+ case 'rle ':
+ case 'rpza':
+ case 'Shr0':
+ case 'Shr1':
+ case 'Shr2':
+ case 'Shr3':
+ case 'Shr4':
+ case 'SVQ1':
+ case 'SVQ3':
+ case 'tga ':
+ case 'tiff':
+ case 'WRLE':
+ return !strcmp("video", type);
+
+ default:
+ return false;
+ }
+}
+
} // namespace android
diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index 5248004..ccd6556 100644
--- a/media/libstagefright/httplive/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -45,9 +45,7 @@ struct M3UParser : public RefBase {
status_t getTrackInfo(Parcel* reply) const;
ssize_t getSelectedIndex() const;
- bool getAudioURI(size_t index, AString *uri) const;
- bool getVideoURI(size_t index, AString *uri) const;
- bool getSubtitleURI(size_t index, AString *uri) const;
+ bool getTypeURI(size_t index, const char *key, AString *uri) const;
protected:
virtual ~M3UParser();
@@ -95,11 +93,13 @@ private:
status_t parseMedia(const AString &line);
- bool getTypeURI(size_t index, const char *key, AString *uri) const;
-
static status_t ParseInt32(const char *s, int32_t *x);
static status_t ParseDouble(const char *s, double *x);
+ static bool isQuotedString(const AString &str);
+ static AString unquoteString(const AString &str);
+ static bool codecIsType(const AString &codec, const char *type);
+
DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
};
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index 973b779..030cbde 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -47,6 +47,7 @@ namespace android {
// static
const int64_t PlaylistFetcher::kMinBufferedDurationUs = 10000000ll;
+const int64_t PlaylistFetcher::kMaxMonitorDelayUs = 3000000ll;
PlaylistFetcher::PlaylistFetcher(
const sp<AMessage> &notify,
@@ -61,6 +62,7 @@ PlaylistFetcher::PlaylistFetcher(
mSeqNumber(-1),
mNumRetries(0),
mStartup(true),
+ mPrepared(false),
mNextPTSTimeUs(-1ll),
mMonitorQueueGeneration(0),
mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY),
@@ -103,10 +105,16 @@ int64_t PlaylistFetcher::getSegmentStartTimeUs(int32_t seqNumber) const {
return segmentStartUs;
}
-bool PlaylistFetcher::timeToRefreshPlaylist(int64_t nowUs) const {
- if (mPlaylist == NULL) {
+int64_t PlaylistFetcher::delayUsToRefreshPlaylist() const {
+ int64_t nowUs = ALooper::GetNowUs();
+
+ if (mPlaylist == NULL || mLastPlaylistFetchTimeUs < 0ll) {
CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY);
- return true;
+ return 0ll;
+ }
+
+ if (mPlaylist->isComplete()) {
+ return (~0llu >> 1);
}
int32_t targetDurationSecs;
@@ -157,11 +165,13 @@ bool PlaylistFetcher::timeToRefreshPlaylist(int64_t nowUs) const {
break;
}
- return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs;
+ int64_t delayUs = mLastPlaylistFetchTimeUs + minPlaylistAgeUs - nowUs;
+ return delayUs > 0ll ? delayUs : 0ll;
}
status_t PlaylistFetcher::decryptBuffer(
- size_t playlistIndex, const sp<ABuffer> &buffer) {
+ size_t playlistIndex, const sp<ABuffer> &buffer,
+ bool first) {
sp<AMessage> itemMeta;
bool found = false;
AString method;
@@ -179,6 +189,7 @@ status_t PlaylistFetcher::decryptBuffer(
if (!found) {
method = "NONE";
}
+ buffer->meta()->setString("cipher-method", method.c_str());
if (method == "NONE") {
return OK;
@@ -218,63 +229,89 @@ status_t PlaylistFetcher::decryptBuffer(
return UNKNOWN_ERROR;
}
- unsigned char aes_ivec[16];
+ size_t n = buffer->size();
+ if (!n) {
+ return OK;
+ }
+ CHECK(n % 16 == 0);
- AString iv;
- if (itemMeta->findString("cipher-iv", &iv)) {
- if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
- || iv.size() != 16 * 2 + 2) {
- ALOGE("malformed cipher IV '%s'.", iv.c_str());
- return ERROR_MALFORMED;
- }
+ if (first) {
+ // If decrypting the first block in a file, read the iv from the manifest
+ // or derive the iv from the file's sequence number.
- 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)) {
+ AString iv;
+ if (itemMeta->findString("cipher-iv", &iv)) {
+ if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
+ || iv.size() != 16 * 2 + 2) {
ALOGE("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;
+ memset(mAESInitVec, 0, sizeof(mAESInitVec));
+ 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)) {
+ ALOGE("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;
+
+ mAESInitVec[i] = nibble1 << 4 | nibble2;
+ }
+ } else {
+ memset(mAESInitVec, 0, sizeof(mAESInitVec));
+ mAESInitVec[15] = mSeqNumber & 0xff;
+ mAESInitVec[14] = (mSeqNumber >> 8) & 0xff;
+ mAESInitVec[13] = (mSeqNumber >> 16) & 0xff;
+ mAESInitVec[12] = (mSeqNumber >> 24) & 0xff;
}
- } 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());
+ &aes_key, mAESInitVec, AES_DECRYPT);
- size_t n = buffer->size();
- CHECK_GT(n, 0u);
+ return OK;
+}
- size_t pad = buffer->data()[n - 1];
+status_t PlaylistFetcher::checkDecryptPadding(const sp<ABuffer> &buffer) {
+ status_t err;
+ AString method;
+ CHECK(buffer->meta()->findString("cipher-method", &method));
+ if (method == "NONE") {
+ return OK;
+ }
- 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);
+ uint8_t padding = 0;
+ if (buffer->size() > 0) {
+ padding = buffer->data()[buffer->size() - 1];
}
- n -= pad;
+ if (padding > 16) {
+ return ERROR_MALFORMED;
+ }
- buffer->setRange(buffer->offset(), n);
+ for (size_t i = buffer->size() - padding; i < padding; i++) {
+ if (buffer->data()[i] != padding) {
+ return ERROR_MALFORMED;
+ }
+ }
+ buffer->setRange(buffer->offset(), buffer->size() - padding);
return OK;
}
-void PlaylistFetcher::postMonitorQueue(int64_t delayUs) {
+void PlaylistFetcher::postMonitorQueue(int64_t delayUs, int64_t minDelayUs) {
+ int64_t maxDelayUs = delayUsToRefreshPlaylist();
+ if (maxDelayUs < minDelayUs) {
+ maxDelayUs = minDelayUs;
+ }
+ if (delayUs > maxDelayUs) {
+ ALOGV("Need to refresh playlist in %lld", maxDelayUs);
+ delayUs = maxDelayUs;
+ }
sp<AMessage> msg = new AMessage(kWhatMonitorQueue, id());
msg->setInt32("generation", mMonitorQueueGeneration);
msg->post(delayUs);
@@ -415,6 +452,7 @@ status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
if (mStartTimeUs >= 0ll) {
mSeqNumber = -1;
mStartup = true;
+ mPrepared = false;
}
postMonitorQueue();
@@ -456,40 +494,62 @@ void PlaylistFetcher::queueDiscontinuity(
void PlaylistFetcher::onMonitorQueue() {
bool downloadMore = false;
+ refreshPlaylist();
- status_t finalResult;
+ int32_t targetDurationSecs;
+ int64_t targetDurationUs = kMinBufferedDurationUs;
+ if (mPlaylist != NULL) {
+ CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
+ targetDurationUs = targetDurationSecs * 1000000ll;
+ }
+
+ // buffer at least 3 times the target duration, or up to 10 seconds
+ int64_t durationToBufferUs = targetDurationUs * 3;
+ if (durationToBufferUs > kMinBufferedDurationUs) {
+ durationToBufferUs = kMinBufferedDurationUs;
+ }
+
+ int64_t bufferedDurationUs = 0ll;
+ status_t finalResult = NOT_ENOUGH_DATA;
if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
sp<AnotherPacketSource> packetSource =
mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
- int64_t bufferedDurationUs =
+ bufferedDurationUs =
packetSource->getBufferedDurationUs(&finalResult);
-
- downloadMore = (bufferedDurationUs < kMinBufferedDurationUs);
finalResult = OK;
} else {
bool first = true;
- int64_t minBufferedDurationUs = 0ll;
for (size_t i = 0; i < mPacketSources.size(); ++i) {
if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) {
continue;
}
- int64_t bufferedDurationUs =
+ int64_t bufferedStreamDurationUs =
mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
-
- if (first || bufferedDurationUs < minBufferedDurationUs) {
- minBufferedDurationUs = bufferedDurationUs;
+ if (first || bufferedStreamDurationUs < bufferedDurationUs) {
+ bufferedDurationUs = bufferedStreamDurationUs;
first = false;
}
}
+ }
+ downloadMore = (bufferedDurationUs < durationToBufferUs);
+
+ // signal start if buffered up at least the target size
+ if (!mPrepared && bufferedDurationUs > targetDurationUs && downloadMore) {
+ mPrepared = true;
- downloadMore =
- !first && (minBufferedDurationUs < kMinBufferedDurationUs);
+ ALOGV("prepared, buffered=%lld > %lld",
+ bufferedDurationUs, targetDurationUs);
+ sp<AMessage> msg = mNotify->dup();
+ msg->setInt32("what", kWhatTemporarilyDoneFetching);
+ msg->post();
}
if (finalResult == OK && downloadMore) {
+ ALOGV("monitoring, buffered=%lld < %lld",
+ bufferedDurationUs, durationToBufferUs);
onDownloadNext();
} else {
// Nothing to do yet, try again in a second.
@@ -498,15 +558,17 @@ void PlaylistFetcher::onMonitorQueue() {
msg->setInt32("what", kWhatTemporarilyDoneFetching);
msg->post();
- postMonitorQueue(1000000ll);
+ int64_t delayUs = mPrepared ? kMaxMonitorDelayUs : targetDurationUs / 2;
+ ALOGV("pausing for %lld, buffered=%lld > %lld",
+ delayUs, bufferedDurationUs, durationToBufferUs);
+ // :TRICKY: need to enforce minimum delay because the delay to
+ // refresh the playlist will become 0
+ postMonitorQueue(delayUs, mPrepared ? targetDurationUs * 2 : 0);
}
}
-void PlaylistFetcher::onDownloadNext() {
- int64_t nowUs = ALooper::GetNowUs();
-
- if (mLastPlaylistFetchTimeUs < 0ll
- || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
+status_t PlaylistFetcher::refreshPlaylist() {
+ if (delayUsToRefreshPlaylist() <= 0) {
bool unchanged;
sp<M3UParser> playlist = mSession->fetchPlaylist(
mURI.c_str(), mPlaylistHash, &unchanged);
@@ -522,7 +584,7 @@ void PlaylistFetcher::onDownloadNext() {
} else {
ALOGE("failed to load playlist at url '%s'", mURI.c_str());
notifyError(ERROR_IO);
- return;
+ return ERROR_IO;
}
} else {
mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
@@ -535,6 +597,13 @@ void PlaylistFetcher::onDownloadNext() {
mLastPlaylistFetchTimeUs = ALooper::GetNowUs();
}
+ return OK;
+}
+
+void PlaylistFetcher::onDownloadNext() {
+ if (refreshPlaylist() != OK) {
+ return;
+ }
int32_t firstSeqNumberInPlaylist;
if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
@@ -553,12 +622,18 @@ void PlaylistFetcher::onDownloadNext() {
if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
mSeqNumber = getSeqNumberForTime(mStartTimeUs);
+ ALOGV("Initial sequence number for time %lld is %d from (%d .. %d)",
+ mStartTimeUs, mSeqNumber, firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist);
} else {
// If this is a live session, start 3 segments from the end.
mSeqNumber = lastSeqNumberInPlaylist - 3;
if (mSeqNumber < firstSeqNumberInPlaylist) {
mSeqNumber = firstSeqNumberInPlaylist;
}
+ ALOGV("Initial sequence number for live event %d from (%d .. %d)",
+ mSeqNumber, firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist);
}
mStartTimeUs = -1ll;
@@ -570,16 +645,35 @@ void PlaylistFetcher::onDownloadNext() {
++mNumRetries;
if (mSeqNumber > lastSeqNumberInPlaylist) {
- mLastPlaylistFetchTimeUs = -1;
- postMonitorQueue(3000000ll);
+ // refresh in increasing fraction (1/2, 1/3, ...) of the
+ // playlist's target duration or 3 seconds, whichever is less
+ int32_t targetDurationSecs;
+ CHECK(mPlaylist->meta()->findInt32(
+ "target-duration", &targetDurationSecs));
+ int64_t delayUs = mPlaylist->size() * targetDurationSecs *
+ 1000000ll / (1 + mNumRetries);
+ if (delayUs > kMaxMonitorDelayUs) {
+ delayUs = kMaxMonitorDelayUs;
+ }
+ ALOGV("sequence number high: %d from (%d .. %d), "
+ "monitor in %lld (retry=%d)",
+ mSeqNumber, firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist, delayUs, mNumRetries);
+ postMonitorQueue(delayUs);
return;
}
// we've missed the boat, let's start from the lowest sequence
// number available and signal a discontinuity.
- ALOGI("We've missed the boat, restarting playback.");
- mSeqNumber = lastSeqNumberInPlaylist;
+ ALOGI("We've missed the boat, restarting playback."
+ " mStartup=%d, was looking for %d in %d-%d",
+ mStartup, mSeqNumber, firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist);
+ mSeqNumber = lastSeqNumberInPlaylist - 3;
+ if (mSeqNumber < firstSeqNumberInPlaylist) {
+ mSeqNumber = firstSeqNumberInPlaylist;
+ }
explicitDiscontinuity = true;
// fall through
@@ -633,6 +727,9 @@ void PlaylistFetcher::onDownloadNext() {
CHECK(buffer != NULL);
err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, buffer);
+ if (err == OK) {
+ err = checkDecryptPadding(buffer);
+ }
if (err != OK) {
ALOGE("decryptBuffer failed w/ error %d", err);
@@ -788,12 +885,13 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
&& source->dequeueAccessUnit(&accessUnit) == OK) {
// Note that we do NOT dequeue any discontinuities.
+ // for simplicity, store a reference to the format in each unit
+ sp<MetaData> format = source->getFormat();
+ if (format != NULL) {
+ accessUnit->meta()->setObject("format", format);
+ }
packetSource->queueAccessUnit(accessUnit);
}
-
- if (packetSource->getFormat() == NULL) {
- packetSource->setFormat(source->getFormat());
- }
}
return OK;
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
index 1648e02..ac04a77 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.h
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -79,6 +79,7 @@ private:
};
static const int64_t kMinBufferedDurationUs;
+ static const int64_t kMaxMonitorDelayUs;
sp<AMessage> mNotify;
sp<LiveSession> mSession;
@@ -97,6 +98,7 @@ private:
int32_t mSeqNumber;
int32_t mNumRetries;
bool mStartup;
+ bool mPrepared;
int64_t mNextPTSTimeUs;
int32_t mMonitorQueueGeneration;
@@ -117,13 +119,29 @@ private:
uint64_t mFirstPTS;
int64_t mAbsoluteTimeAnchorUs;
+ // Stores the initialization vector to decrypt the next block of cipher text, which can
+ // either be derived from the sequence number, read from the manifest, or copied from
+ // the last block of cipher text (cipher-block chaining).
+ unsigned char mAESInitVec[16];
+
+ // Set first to true if decrypting the first segment of a playlist segment. When
+ // first is true, reset the initialization vector based on the available
+ // information in the manifest; otherwise, use the initialization vector as
+ // updated by the last call to AES_cbc_encrypt.
+ //
+ // For the input to decrypt correctly, decryptBuffer must be called on
+ // consecutive byte ranges on block boundaries, e.g. 0..15, 16..47, 48..63,
+ // and so on.
status_t decryptBuffer(
- size_t playlistIndex, const sp<ABuffer> &buffer);
+ size_t playlistIndex, const sp<ABuffer> &buffer,
+ bool first = true);
+ status_t checkDecryptPadding(const sp<ABuffer> &buffer);
- void postMonitorQueue(int64_t delayUs = 0);
+ void postMonitorQueue(int64_t delayUs = 0, int64_t minDelayUs = 0);
void cancelMonitorQueue();
- bool timeToRefreshPlaylist(int64_t nowUs) const;
+ int64_t delayUsToRefreshPlaylist() const;
+ status_t refreshPlaylist();
// Returns the media time in us of the segment specified by seqNumber.
// This is computed by summing the durations of all segments before it.