Fix DASH Live edge calculation.

Also added clamping to getSegmentNum in one case where
it was not already implemented, and defined this behavior
property in the getSegmentNum javadoc.

Issue: #262
This commit is contained in:
Oliver Woodman 2015-01-26 14:49:33 +00:00
parent a64df69f85
commit f1a7784eb1
5 changed files with 79 additions and 6 deletions

View File

@ -349,7 +349,7 @@ public class DashChunkSource implements ChunkSource {
int segmentNum;
if (queue.isEmpty()) {
if (currentManifest.dynamic) {
seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded);
seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded, segmentIndex.isExplicit());
}
segmentNum = segmentIndex.getSegmentNum(seekPositionUs);
} else {
@ -476,9 +476,10 @@ public class DashChunkSource implements ChunkSource {
*
* @param nowUs An estimate of the current server time, in microseconds.
* @param indexUnbounded True if the segment index for this source is unbounded. False otherwise.
* @param indexExplicit True if the segment index is explicit. False otherwise.
* @return The seek position in microseconds.
*/
private long getLiveSeekPosition(long nowUs, boolean indexUnbounded) {
private long getLiveSeekPosition(long nowUs, boolean indexUnbounded, boolean indexExplicit) {
long liveEdgeTimestampUs;
if (indexUnbounded) {
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
@ -491,6 +492,12 @@ public class DashChunkSource implements ChunkSource {
+ segmentIndex.getDurationUs(lastSegmentNum);
liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, indexLiveEdgeTimestampUs);
}
if (!indexExplicit) {
// Some segments defined by the index may not be available yet. Bound the calculated live
// edge based on the elapsed time since the manifest became available.
liveEdgeTimestampUs = Math.min(liveEdgeTimestampUs,
nowUs - currentManifest.availabilityStartTime * 1000);
}
}
return liveEdgeTimestampUs - liveEdgeLatencyUs;
}

View File

@ -28,6 +28,11 @@ public interface DashSegmentIndex {
/**
* Returns the segment number of the segment containing a given media time.
* <p>
* If the given media time is outside the range of the index, then the returned segment number is
* clamped to {@link #getFirstSegmentNum()} (if the given media time is earlier the start of the
* first segment) or {@link #getLastSegmentNum()} (if the given media time is later then the end
* of the last segment).
*
* @param timeUs The time in microseconds.
* @return The segment number of the corresponding segment.
@ -78,4 +83,18 @@ public interface DashSegmentIndex {
*/
int getLastSegmentNum();
/**
* Returns true if segments are defined explicitly by the index.
* <p>
* If true is returned, each segment is defined explicitly by the index data, and all of the
* listed segments are guaranteed to be available at the time when the index was obtained.
* <p>
* If false is returned then segment information was derived from properties such as a fixed
* segment duration. If the presentation is dynamic, it's possible that only a subset of the
* segments are available.
*
* @return True if segments are defined explicitly by the index. False otherwise.
*/
boolean isExplicit();
}

View File

@ -74,4 +74,9 @@ public class DashWrappingSegmentIndex implements DashSegmentIndex {
return Util.binarySearchFloor(segmentIndex.timesUs, timeUs, true, true);
}
@Override
public boolean isExplicit() {
return true;
}
}

View File

@ -277,6 +277,11 @@ public abstract class Representation {
return segmentBase.getLastSegmentNum();
}
@Override
public boolean isExplicit() {
return segmentBase.isExplicit();
}
}
}

View File

@ -128,15 +128,22 @@ public abstract class SegmentBase {
this.segmentTimeline = segmentTimeline;
}
/**
* @see DashSegmentIndex#getSegmentNum(long)
*/
public int getSegmentNum(long timeUs) {
int lowIndex = getFirstSegmentNum();
int highIndex = getLastSegmentNum();
if (segmentTimeline == null) {
// All segments are of equal duration (with the possible exception of the last one).
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
return startNumber + (int) (timeUs / durationUs);
int segmentNum = startNumber + (int) (timeUs / durationUs);
// Ensure we stay within bounds.
return segmentNum < lowIndex ? lowIndex
: highIndex != DashSegmentIndex.INDEX_UNBOUNDED && segmentNum > highIndex ? highIndex
: segmentNum;
} else {
// Identify the segment using binary search.
int lowIndex = getFirstSegmentNum();
int highIndex = getLastSegmentNum();
// The high index cannot be unbounded. Identify the segment using binary search.
while (lowIndex <= highIndex) {
int midIndex = (lowIndex + highIndex) / 2;
long midTimeUs = getSegmentTimeUs(midIndex);
@ -152,6 +159,9 @@ public abstract class SegmentBase {
}
}
/**
* @see DashSegmentIndex#getDurationUs(int)
*/
public final long getSegmentDurationUs(int sequenceNumber) {
if (segmentTimeline != null) {
long duration = segmentTimeline.get(sequenceNumber - startNumber).duration;
@ -163,6 +173,9 @@ public abstract class SegmentBase {
}
}
/**
* @see DashSegmentIndex#getTimeUs(int)
*/
public final long getSegmentTimeUs(int sequenceNumber) {
long unscaledSegmentTime;
if (segmentTimeline != null) {
@ -174,14 +187,33 @@ public abstract class SegmentBase {
return Util.scaleLargeTimestamp(unscaledSegmentTime, C.MICROS_PER_SECOND, timescale);
}
/**
* Returns a {@link RangedUri} defining the location of a segment for the given index in the
* given representation.
*
* @see DashSegmentIndex#getSegmentUrl(int)
*/
public abstract RangedUri getSegmentUrl(Representation representation, int index);
/**
* @see DashSegmentIndex#getFirstSegmentNum()
*/
public int getFirstSegmentNum() {
return startNumber;
}
/**
* @see DashSegmentIndex#getLastSegmentNum()
*/
public abstract int getLastSegmentNum();
/**
* @see DashSegmentIndex#isExplicit()
*/
public boolean isExplicit() {
return segmentTimeline != null;
}
}
/**
@ -225,6 +257,11 @@ public abstract class SegmentBase {
return startNumber + mediaSegments.size() - 1;
}
@Override
public boolean isExplicit() {
return true;
}
}
/**