summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/httplive/LiveSession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/httplive/LiveSession.cpp')
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp275
1 files changed, 121 insertions, 154 deletions
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 118c174..2d93152 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -52,9 +52,10 @@ namespace android {
// static
// Bandwidth Switch Mark Defaults
-const int64_t LiveSession::kUpSwitchMarkUs = 25000000ll;
-const int64_t LiveSession::kDownSwitchMarkUs = 18000000ll;
+const int64_t LiveSession::kUpSwitchMarkUs = 15000000ll;
+const int64_t LiveSession::kDownSwitchMarkUs = 20000000ll;
const int64_t LiveSession::kUpSwitchMarginUs = 5000000ll;
+const int64_t LiveSession::kResumeThresholdUs = 100000ll;
// Buffer Prepare/Ready/Underflow Marks
const int64_t LiveSession::kReadyMarkUs = 5000000ll;
@@ -70,7 +71,7 @@ struct LiveSession::BandwidthEstimator : public RefBase {
private:
// Bandwidth estimation parameters
static const int32_t kMaxBandwidthHistoryItems = 20;
- static const int64_t kMaxBandwidthHistoryWindowUs = 3000000ll; // 3 sec
+ static const int64_t kMaxBandwidthHistoryWindowUs = 5000000ll; // 5 sec
struct BandwidthEntry {
int64_t mDelayUs;
@@ -405,26 +406,30 @@ bool LiveSession::checkSwitchProgress(
sp<AMessage> lastDequeueMeta, lastEnqueueMeta;
if (delayUs > 0) {
lastDequeueMeta = source->getMetaAfterLastDequeued(delayUs);
+ if (lastDequeueMeta == NULL) {
+ // this means we don't have enough cushion, try again later
+ ALOGV("[%s] up switching failed due to insufficient buffer",
+ stream == STREAMTYPE_AUDIO ? "audio" : "video");
+ return false;
+ }
} else {
+ // It's okay for lastDequeueMeta to be NULL here, it means the
+ // decoder hasn't even started dequeueing
lastDequeueMeta = source->getLatestDequeuedMeta();
}
// Then, trim off packets at beginning of mPacketSources2 that's before
// the latest dequeued time. These samples are definitely too late.
- int64_t lastTimeUs, startTimeUs;
- int32_t lastSeq, startSeq;
- if (lastDequeueMeta != NULL) {
- CHECK(lastDequeueMeta->findInt64("timeUs", &lastTimeUs));
- CHECK(lastDequeueMeta->findInt32("discontinuitySeq", &lastSeq));
- firstNewMeta[i] = mPacketSources2.editValueAt(i)
- ->trimBuffersBeforeTimeUs(lastSeq, lastTimeUs);
- }
+ firstNewMeta[i] = mPacketSources2.editValueAt(i)
+ ->trimBuffersBeforeMeta(lastDequeueMeta);
+
// Now firstNewMeta[i] is the first sample after the trim.
// If it's NULL, we failed because dequeue already past all samples
// in mPacketSource2, we have to try again.
if (firstNewMeta[i] == NULL) {
+ HLSTime dequeueTime(lastDequeueMeta);
ALOGV("[%s] dequeue time (%d, %lld) past start time",
stream == STREAMTYPE_AUDIO ? "audio" : "video",
- lastSeq, (long long) lastTimeUs);
+ dequeueTime.mSeq, (long long) dequeueTime.mTimeUs);
return false;
}
@@ -434,20 +439,16 @@ bool LiveSession::checkSwitchProgress(
// lastEnqueueMeta == NULL means old fetcher stopped at a discontinuity
// boundary, no need to resume as the content will look different anyways
if (lastEnqueueMeta != NULL) {
- CHECK(lastEnqueueMeta->findInt64("timeUs", &lastTimeUs));
- CHECK(lastEnqueueMeta->findInt32("discontinuitySeq", &lastSeq));
- CHECK(firstNewMeta[i]->findInt64("timeUs", &startTimeUs));
- CHECK(firstNewMeta[i]->findInt32("discontinuitySeq", &startSeq));
+ HLSTime lastTime(lastEnqueueMeta), startTime(firstNewMeta[i]);
// no need to resume old fetcher if new fetcher started in different
// discontinuity sequence, as the content will look different.
- *needResumeUntil |=
- (startSeq == lastSeq
- && startTimeUs - lastTimeUs > 100000ll);
+ *needResumeUntil |= (startTime.mSeq == lastTime.mSeq
+ && startTime.mTimeUs - lastTime.mTimeUs > kResumeThresholdUs);
- // update the stopTime for resumeUntil, as we might have removed some
- // packets from the head in mPacketSource2
- stopParams->setInt64(getKeyForStream(stream), startTimeUs);
+ // update the stopTime for resumeUntil
+ stopParams->setInt32("discontinuitySeq", startTime.mSeq);
+ stopParams->setInt64(getKeyForStream(stream), startTime.mTimeUs);
}
}
@@ -457,18 +458,11 @@ bool LiveSession::checkSwitchProgress(
for (size_t i = 0; i < kMaxStreams; ++i) {
StreamType stream = indexToType(i);
if (!(mSwapMask & mNewStreamMask & stream)
- || (newUri != mStreams[i].mNewUri)) {
- continue;
- }
- if (stream == STREAMTYPE_SUBTITLES) {
+ || (newUri != mStreams[i].mNewUri)
+ || stream == STREAMTYPE_SUBTITLES) {
continue;
}
- int64_t startTimeUs;
- int32_t startSeq;
- CHECK(firstNewMeta[i] != NULL);
- CHECK(firstNewMeta[i]->findInt64("timeUs", &startTimeUs));
- CHECK(firstNewMeta[i]->findInt32("discontinuitySeq", &startSeq));
- mPacketSources.valueFor(stream)->trimBuffersAfterTimeUs(startSeq, startTimeUs);
+ mPacketSources.valueFor(stream)->trimBuffersAfterMeta(firstNewMeta[i]);
}
// no resumeUntil if already underflow
@@ -574,7 +568,7 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
{
int64_t targetDurationUs;
CHECK(msg->findInt64("targetDurationUs", &targetDurationUs));
- mUpSwitchMark = min(kUpSwitchMarkUs, targetDurationUs * 3);
+ mUpSwitchMark = min(kUpSwitchMarkUs, targetDurationUs * 7 / 4);
mDownSwitchMark = min(kDownSwitchMarkUs, targetDurationUs * 9 / 4);
mUpSwitchMargin = min(kUpSwitchMarginUs, targetDurationUs);
break;
@@ -625,15 +619,15 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
{
ALOGV("kWhatStopReached");
- AString uri;
- CHECK(msg->findString("uri", &uri));
+ AString oldUri;
+ CHECK(msg->findString("uri", &oldUri));
- ssize_t index = mFetcherInfos.indexOfKey(uri);
+ ssize_t index = mFetcherInfos.indexOfKey(oldUri);
if (index < 0) {
break;
}
- tryToFinishBandwidthSwitch(uri);
+ tryToFinishBandwidthSwitch(oldUri);
break;
}
@@ -837,6 +831,7 @@ void LiveSession::onConnect(const sp<AMessage> &msg) {
size_t initialBandwidthIndex = 0;
if (mPlaylist->isVariantPlaylist()) {
+ Vector<BandwidthItem> itemsWithVideo;
for (size_t i = 0; i < mPlaylist->size(); ++i) {
BandwidthItem item;
@@ -848,14 +843,22 @@ void LiveSession::onConnect(const sp<AMessage> &msg) {
CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
- if (initialBandwidth == 0) {
- initialBandwidth = item.mBandwidth;
- }
-
mBandwidthItems.push(item);
+ if (mPlaylist->hasType(i, "video")) {
+ itemsWithVideo.push(item);
+ }
+ }
+ // remove the audio-only variants if we have at least one with video
+ if (!itemsWithVideo.empty()
+ && itemsWithVideo.size() < mBandwidthItems.size()) {
+ mBandwidthItems.clear();
+ for (size_t i = 0; i < itemsWithVideo.size(); ++i) {
+ mBandwidthItems.push(itemsWithVideo[i]);
+ }
}
CHECK_GT(mBandwidthItems.size(), 0u);
+ initialBandwidth = mBandwidthItems[0].mBandwidth;
mBandwidthItems.sort(SortByBandwidth);
@@ -1090,6 +1093,9 @@ sp<M3UParser> LiveSession::fetchPlaylist(
String8 actualUrl;
ssize_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl);
+ // close off the connection after use
+ mHTTPDataSource->disconnect();
+
if (err <= 0) {
return NULL;
}
@@ -1333,22 +1339,14 @@ size_t LiveSession::getBandwidthIndex(int32_t bandwidthBps) {
return index;
}
-int64_t LiveSession::latestMediaSegmentStartTimeUs() {
- sp<AMessage> audioMeta = mPacketSources.valueFor(STREAMTYPE_AUDIO)->getLatestDequeuedMeta();
- int64_t minSegmentStartTimeUs = -1, videoSegmentStartTimeUs = -1;
- if (audioMeta != NULL) {
- audioMeta->findInt64("segmentStartTimeUs", &minSegmentStartTimeUs);
- }
+HLSTime LiveSession::latestMediaSegmentStartTime() const {
+ HLSTime audioTime(mPacketSources.valueFor(
+ STREAMTYPE_AUDIO)->getLatestDequeuedMeta());
- sp<AMessage> videoMeta = mPacketSources.valueFor(STREAMTYPE_VIDEO)->getLatestDequeuedMeta();
- if (videoMeta != NULL
- && videoMeta->findInt64("segmentStartTimeUs", &videoSegmentStartTimeUs)) {
- if (minSegmentStartTimeUs < 0 || videoSegmentStartTimeUs < minSegmentStartTimeUs) {
- minSegmentStartTimeUs = videoSegmentStartTimeUs;
- }
+ HLSTime videoTime(mPacketSources.valueFor(
+ STREAMTYPE_VIDEO)->getLatestDequeuedMeta());
- }
- return minSegmentStartTimeUs;
+ return audioTime < videoTime ? videoTime : audioTime;
}
status_t LiveSession::onSeek(const sp<AMessage> &msg) {
@@ -1459,14 +1457,9 @@ void LiveSession::changeConfiguration(
}
const AString &uri = mFetcherInfos.keyAt(i);
+ sp<PlaylistFetcher> &fetcher = mFetcherInfos.editValueAt(i).mFetcher;
- bool discardFetcher = true;
-
- if (timeUs < 0ll) {
- // delay fetcher removal if not picking tracks
- discardFetcher = pickTrack;
- }
-
+ bool discardFetcher = true, delayRemoval = false;
for (size_t j = 0; j < kMaxStreams; ++j) {
StreamType type = indexToType(j);
if ((streamMask & type) && uri == URIs[j]) {
@@ -1475,15 +1468,23 @@ void LiveSession::changeConfiguration(
discardFetcher = false;
}
}
+ // Delay fetcher removal if not picking tracks, AND old fetcher
+ // has stream mask that overlaps new variant. (Okay to discard
+ // old fetcher now, if completely no overlap.)
+ if (discardFetcher && timeUs < 0ll && !pickTrack
+ && (fetcher->getStreamTypeMask() & streamMask)) {
+ discardFetcher = false;
+ delayRemoval = true;
+ }
if (discardFetcher) {
- mFetcherInfos.valueAt(i).mFetcher->stopAsync();
+ fetcher->stopAsync();
} else {
float threshold = -1.0f; // always finish fetching by default
if (timeUs >= 0ll) {
// seeking, no need to finish fetching
threshold = 0.0f;
- } else if (!pickTrack) {
+ } else if (delayRemoval) {
// adapting, abort if remaining of current segment is over threshold
threshold = getAbortThreshold(
mOrigBandwidthIndex, mCurBandwidthIndex);
@@ -1491,7 +1492,7 @@ void LiveSession::changeConfiguration(
ALOGV("Pausing with threshold %.3f", threshold);
- mFetcherInfos.valueAt(i).mFetcher->pauseAsync(threshold);
+ fetcher->pauseAsync(threshold);
}
}
@@ -1640,17 +1641,29 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
CHECK(msg->findInt32("resumeMask", (int32_t *)&resumeMask));
+ mNewStreamMask = streamMask | resumeMask;
+
int64_t timeUs;
int32_t pickTrack;
bool switching = false;
- bool finishSwitching = false;
CHECK(msg->findInt64("timeUs", &timeUs));
CHECK(msg->findInt32("pickTrack", &pickTrack));
if (timeUs < 0ll) {
if (!pickTrack) {
- switching = true;
- finishSwitching = (streamMask == 0);
+ // mSwapMask contains streams that are in both old and new variant,
+ // (in mNewStreamMask & mStreamMask) but with different URIs
+ // (not in resumeMask).
+ // For example, old variant has video and audio in two separate
+ // URIs, and new variant has only audio with unchanged URI. mSwapMask
+ // should be 0 as there is nothing to swap. We only need to stop video,
+ // and resume audio.
+ mSwapMask = mNewStreamMask & mStreamMask & ~resumeMask;
+ switching = (mSwapMask != 0);
+ if (!switching) {
+ ALOGV("#### Finishing Bandwidth Switch Early: %zd => %zd",
+ mOrigBandwidthIndex, mCurBandwidthIndex);
+ }
}
mRealTimeBaseUs = ALooper::GetNowUs() - mLastDequeuedTimeUs;
} else {
@@ -1667,11 +1680,6 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
}
}
- mNewStreamMask = streamMask | resumeMask;
- if (switching) {
- mSwapMask = mStreamMask & ~resumeMask;
- }
-
// Of all existing fetchers:
// * Resume fetchers that are still needed and assign them original packet sources.
// * Mark otherwise unneeded fetchers for removal.
@@ -1701,14 +1709,12 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
sp<PlaylistFetcher> fetcher = addFetcher(uri.c_str());
CHECK(fetcher != NULL);
- int64_t startTimeUs = -1;
- int64_t segmentStartTimeUs = -1ll;
- int32_t discontinuitySeq = -1;
+ HLSTime startTime;
SeekMode seekMode = kSeekModeExactPosition;
sp<AnotherPacketSource> sources[kMaxStreams];
- if (i == kSubtitleIndex) {
- segmentStartTimeUs = latestMediaSegmentStartTimeUs();
+ if (i == kSubtitleIndex || (!pickTrack && !switching)) {
+ startTime = latestMediaSegmentStartTime();
}
// TRICKY: looping from i as earlier streams are already removed from streamMask
@@ -1718,25 +1724,15 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
sources[j] = mPacketSources.valueFor(indexToType(j));
if (timeUs >= 0) {
- startTimeUs = timeUs;
+ startTime.mTimeUs = timeUs;
} else {
int32_t type;
sp<AMessage> meta;
- if (pickTrack) {
- // selecting
-
- // FIXME:
- // This should only apply to the track that's being picked, we
- // need a bitmask to indicate that.
- //
- // It's possible that selectTrack() gets called during a bandwidth
- // switch, and we needed to fetch a new variant. The new fetcher
- // should start from where old fetcher left off, not where decoder
- // is dequeueing at.
-
+ if (!switching) {
+ // selecting, or adapting but no swap required
meta = sources[j]->getLatestDequeuedMeta();
} else {
- // adapting
+ // adapting and swap required
meta = sources[j]->getLatestEnqueuedMeta();
if (meta != NULL && mCurBandwidthIndex > mOrigBandwidthIndex) {
// switching up
@@ -1744,60 +1740,28 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
}
}
- if (j != kSubtitleIndex
- && meta != NULL
+ if (j != kSubtitleIndex && meta != NULL
&& !meta->findInt32("discontinuity", &type)) {
- int64_t tmpUs;
- int64_t tmpSegmentUs;
- int32_t seq;
-
- CHECK(meta->findInt64("timeUs", &tmpUs));
- CHECK(meta->findInt64("segmentStartTimeUs", &tmpSegmentUs));
- CHECK(meta->findInt32("discontinuitySeq", &seq));
- // If we're switching and looking for next sample or segment, set the target
- // segment start time to tmpSegmentUs + tmpDurationUs / 2, which is
- // the middle point of the segment where the last sample was.
- // This is needed if segments of the two variants are not perfectly
- // aligned. (If the corresponding segment in new variant starts slightly
- // later than that in the old variant, we still want the switching to
- // start in the next one, not the current one)
- if (mStreams[j].mSeekMode == kSeekModeNextSample
- || mStreams[j].mSeekMode == kSeekModeNextSegment) {
- int64_t tmpDurationUs;
- CHECK(meta->findInt64("segmentDurationUs", &tmpDurationUs));
- tmpSegmentUs += tmpDurationUs / 2;
- }
- if (startTimeUs < 0 || seq > discontinuitySeq
- || (seq == discontinuitySeq
- && (tmpSegmentUs > segmentStartTimeUs
- || (tmpSegmentUs == segmentStartTimeUs
- && tmpUs > startTimeUs)))) {
- startTimeUs = tmpUs;
- segmentStartTimeUs = tmpSegmentUs;
- discontinuitySeq = seq;
+ HLSTime tmpTime(meta);
+ if (startTime < tmpTime) {
+ startTime = tmpTime;
}
}
- if (pickTrack) {
- // selecting track, queue discontinuities before content
+ if (!switching) {
+ // selecting, or adapting but no swap required
sources[j]->clear();
if (j == kSubtitleIndex) {
break;
}
ALOGV("stream[%zu]: queue format change", j);
-
sources[j]->queueDiscontinuity(
ATSParser::DISCONTINUITY_FORMAT_ONLY, NULL, true);
} else {
- // adapting, queue discontinuities after resume
+ // switching, queue discontinuities after resume
sources[j] = mPacketSources2.valueFor(indexToType(j));
sources[j]->clear();
- uint32_t extraStreams = mNewStreamMask & (~mStreamMask);
- if (extraStreams & indexToType(j)) {
- sources[j]->queueDiscontinuity(
- ATSParser::DISCONTINUITY_FORMAT_ONLY, NULL, true);
- }
// the new fetcher might be providing streams that used to be
// provided by two different fetchers, if one of the fetcher
// paused in the middle while the other somehow paused in next
@@ -1812,13 +1776,19 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
}
}
+ // Set the target segment start time to the middle point of the
+ // segment where the last sample was.
+ // This gives a better guess if segments of the two variants are not
+ // perfectly aligned. (If the corresponding segment in new variant
+ // starts slightly later than that in the old variant, we still want
+ // to pick that segment, not the one before)
fetcher->startAsync(
sources[kAudioIndex],
sources[kVideoIndex],
sources[kSubtitleIndex],
- startTimeUs < 0 ? mLastSeekTimeUs : startTimeUs,
- segmentStartTimeUs,
- discontinuitySeq,
+ startTime.mTimeUs < 0 ? mLastSeekTimeUs : startTime.mTimeUs,
+ startTime.getSegmentTimeUs(true /* midpoint */),
+ startTime.mSeq,
seekMode);
}
@@ -1829,18 +1799,6 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
mReconfigurationInProgress = false;
if (switching) {
mSwitchInProgress = true;
-
- if (finishSwitching) {
- // Switch is finished now, no new fetchers are created.
- // This path is hit when old variant had video and audio from
- // two separate fetchers, while new variant has audio only,
- // which reuses the previous audio fetcher.
- for (size_t i = 0; i < kMaxStreams; ++i) {
- if (mSwapMask & indexToType(i)) {
- tryToFinishBandwidthSwitch(mStreams[i].mUri);
- }
- }
- }
} else {
mStreamMask = mNewStreamMask;
mOrigBandwidthIndex = mCurBandwidthIndex;
@@ -1940,7 +1898,6 @@ void LiveSession::tryToFinishBandwidthSwitch(const AString &oldUri) {
mSwitchInProgress = false;
mOrigBandwidthIndex = mCurBandwidthIndex;
-
restartPollBuffering();
}
@@ -2090,7 +2047,8 @@ bool LiveSession::checkBuffering(
}
if (bufferedDurationUs > mUpSwitchMark) {
++upCount;
- } else if (bufferedDurationUs < mDownSwitchMark) {
+ }
+ if (bufferedDurationUs < mDownSwitchMark) {
++downCount;
}
}
@@ -2169,15 +2127,24 @@ void LiveSession::switchBandwidthIfNeeded(bool bufferHigh, bool bufferLow) {
}
int32_t curBandwidth = mBandwidthItems.itemAt(mCurBandwidthIndex).mBandwidth;
- bool bandwidthLow = bandwidthBps < (int32_t)curBandwidth * 8 / 10;
- bool bandwidthHigh = bandwidthBps > (int32_t)curBandwidth * 12 / 10;
-
- if ((bufferHigh && bandwidthHigh) || (bufferLow && bandwidthLow)) {
+ // canSwithDown and canSwitchUp can't both be true.
+ // we only want to switch up when measured bw is 120% higher than current variant,
+ // and we only want to switch down when measured bw is below current variant.
+ bool canSwithDown = bufferLow
+ && (bandwidthBps < (int32_t)curBandwidth);
+ bool canSwitchUp = bufferHigh
+ && (bandwidthBps > (int32_t)curBandwidth * 12 / 10);
+
+ if (canSwithDown || canSwitchUp) {
ssize_t bandwidthIndex = getBandwidthIndex(bandwidthBps);
+ // it's possible that we're checking for canSwitchUp case, but the returned
+ // bandwidthIndex is < mCurBandwidthIndex, as getBandwidthIndex() only uses 70%
+ // of measured bw. In that case we don't want to do anything, since we have
+ // both enough buffer and enough bw.
if (bandwidthIndex == mCurBandwidthIndex
- || (bufferHigh && bandwidthIndex < mCurBandwidthIndex)
- || (bufferLow && bandwidthIndex > mCurBandwidthIndex)) {
+ || (canSwitchUp && bandwidthIndex < mCurBandwidthIndex)
+ || (canSwithDown && bandwidthIndex > mCurBandwidthIndex)) {
return;
}