Skip to content

Commit

Permalink
DASH: Avoid rounding error in getSegmentCount
Browse files Browse the repository at this point in the history
Issue: #8804
PiperOrigin-RevId: 369484117
  • Loading branch information
ojw28 committed Apr 21, 2021
1 parent 7a2eaa9 commit 9a36783
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 24 deletions.
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@
* DASH:
* Parse `forced_subtitle` role from DASH manifests
([#8781](https://github.com/google/ExoPlayer/issues/8781)).
* DASH:
* Fix rounding error that could cause `SegmentTemplate.getSegmentCount()`
to return incorrect values
([#8804](https://github.com/google/ExoPlayer/issues/8804)).
* HLS:
* Fix bug of ignoring `EXT-X-START` when setting the live target offset
([#8764](https://github.com/google/ExoPlayer/pull/8764)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,7 @@ private static long getAvailableStartTimeInManifestUs(
if (index == null) {
return periodStartTimeInManifestUs;
}
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
long availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) {
return periodStartTimeInManifestUs;
}
Expand Down Expand Up @@ -1171,7 +1171,7 @@ private static long getAvailableEndTimeInManifestUs(
if (index == null) {
return periodStartTimeInManifestUs + periodDurationUs;
}
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
long availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) {
return periodStartTimeInManifestUs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public interface DashSegmentIndex {
* C#TIME_UNSET} if the period's duration is not yet known.
* @return The number of segments in the index, or {@link #INDEX_UNBOUNDED}.
*/
int getSegmentCount(long periodDurationUs);
long getSegmentCount(long periodDurationUs);

/**
* Returns the number of available segments in the index.
Expand All @@ -99,7 +99,7 @@ public interface DashSegmentIndex {
* @param nowUnixTimeUs The current time in milliseconds since the Unix epoch.
* @return The number of available segments in the index.
*/
int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs);
long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs);

/**
* Returns the time, in microseconds, at which a new segment becomes available, or {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeU
}

@Override
public int getSegmentCount(long periodDurationUs) {
public long getSegmentCount(long periodDurationUs) {
return chunkIndex.length;
}

@Override
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
return chunkIndex.length;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParame
if (representationHolder.segmentIndex != null) {
long segmentNum = representationHolder.getSegmentNum(positionUs);
long firstSyncUs = representationHolder.getSegmentStartTimeUs(segmentNum);
int segmentCount = representationHolder.getSegmentCount();
long segmentCount = representationHolder.getSegmentCount();
long secondSyncUs =
firstSyncUs < positionUs
&& (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED
Expand Down Expand Up @@ -465,7 +465,7 @@ public boolean onChunkLoadError(
&& ((InvalidResponseCodeException) e).responseCode == 404) {
RepresentationHolder representationHolder =
representationHolders[trackSelection.indexOf(chunk.trackFormat)];
int segmentCount = representationHolder.getSegmentCount();
long segmentCount = representationHolder.getSegmentCount();
if (segmentCount != DashSegmentIndex.INDEX_UNBOUNDED && segmentCount != 0) {
long lastAvailableSegmentNum = representationHolder.getFirstSegmentNum() + segmentCount - 1;
if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) {
Expand Down Expand Up @@ -723,7 +723,7 @@ protected static final class RepresentationHolder {
newPeriodDurationUs, newRepresentation, chunkExtractor, segmentNumShift, newIndex);
}

int oldIndexSegmentCount = oldIndex.getSegmentCount(newPeriodDurationUs);
long oldIndexSegmentCount = oldIndex.getSegmentCount(newPeriodDurationUs);
if (oldIndexSegmentCount == 0) {
// Segment numbers cannot shift if the old index was empty.
return new RepresentationHolder(
Expand Down Expand Up @@ -777,7 +777,7 @@ public long getFirstAvailableSegmentNum(long nowUnixTimeUs) {
+ segmentNumShift;
}

public int getSegmentCount() {
public long getSegmentCount() {
return segmentIndex.getSegmentCount(periodDurationUs);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,12 @@ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeU
}

@Override
public int getSegmentCount(long periodDurationUs) {
public long getSegmentCount(long periodDurationUs) {
return segmentBase.getSegmentCount(periodDurationUs);
}

@Override
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
return segmentBase.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
import com.google.android.exoplayer2.util.Util;
import com.google.common.math.BigIntegerMath;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.List;

/**
Expand Down Expand Up @@ -214,7 +217,7 @@ public final long getSegmentDurationUs(long sequenceNumber, long periodDurationU
long duration = segmentTimeline.get((int) (sequenceNumber - startNumber)).duration;
return (duration * C.MICROS_PER_SECOND) / timescale;
} else {
int segmentCount = getSegmentCount(periodDurationUs);
long segmentCount = getSegmentCount(periodDurationUs);
return segmentCount != INDEX_UNBOUNDED
&& sequenceNumber == (getFirstSegmentNum() + segmentCount - 1)
? (periodDurationUs - getSegmentTimeUs(sequenceNumber))
Expand Down Expand Up @@ -264,8 +267,8 @@ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeU
}

/** See {@link DashSegmentIndex#getAvailableSegmentCount(long, long)}. */
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
int segmentCount = getSegmentCount(periodDurationUs);
public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
long segmentCount = getSegmentCount(periodDurationUs);
if (segmentCount != INDEX_UNBOUNDED) {
return segmentCount;
}
Expand Down Expand Up @@ -298,7 +301,7 @@ public boolean isExplicit() {
}

/** See {@link DashSegmentIndex#getSegmentCount(long)}. */
public abstract int getSegmentCount(long periodDurationUs);
public abstract long getSegmentCount(long periodDurationUs);
}

/** A {@link MultiSegmentBase} that uses a SegmentList to define its segments. */
Expand Down Expand Up @@ -356,7 +359,7 @@ public RangedUri getSegmentUrl(Representation representation, long sequenceNumbe
}

@Override
public int getSegmentCount(long periodDurationUs) {
public long getSegmentCount(long periodDurationUs) {
return mediaSegments.size();
}

Expand Down Expand Up @@ -455,14 +458,17 @@ public RangedUri getSegmentUrl(Representation representation, long sequenceNumbe
}

@Override
public int getSegmentCount(long periodDurationUs) {
public long getSegmentCount(long periodDurationUs) {
if (segmentTimeline != null) {
return segmentTimeline.size();
} else if (endNumber != C.INDEX_UNSET) {
return (int) (endNumber - startNumber + 1);
return endNumber - startNumber + 1;
} else if (periodDurationUs != C.TIME_UNSET) {
long durationUs = (duration * C.MICROS_PER_SECOND) / timescale;
return (int) Util.ceilDivide(periodDurationUs, durationUs);
BigInteger numerator =
BigInteger.valueOf(periodDurationUs).multiply(BigInteger.valueOf(timescale));
BigInteger denominator =
BigInteger.valueOf(duration).multiply(BigInteger.valueOf(C.MICROS_PER_SECOND));
return BigIntegerMath.divide(numerator, denominator, RoundingMode.CEILING).longValue();
} else {
return INDEX_UNBOUNDED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ public long getFirstAvailableSegmentNum(long periodDurationUs, long nowUnixTimeU
}

@Override
public int getSegmentCount(long periodDurationUs) {
public long getSegmentCount(long periodDurationUs) {
return 1;
}

@Override
public int getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
public long getAvailableSegmentCount(long periodDurationUs, long nowUnixTimeUs) {
return 1;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ private void addSegmentsForAdaptationSet(
continue;
}

int segmentCount = index.getSegmentCount(periodDurationUs);
long segmentCount = index.getSegmentCount(periodDurationUs);
if (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
throw new DownloadException("Unbounded segment index");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,43 @@ public void getNextSegmentShiftTimeUse_unboundedSegmentTemplate() {
/* nowUnixTimeUs= */ periodStartUnixTimeUs + 17_500_000))
.isEqualTo(19_500_000);
}

/** Regression test for https://github.com/google/ExoPlayer/issues/8804. */
@Test
public void getSegmentCount_withSegmentTemplate_avoidsIncorrectRounding() {
SegmentBase.SegmentTemplate segmentTemplate =
new SegmentBase.SegmentTemplate(
/* initialization= */ null,
/* timescale= */ 90000,
/* presentationTimeOffset= */ 0,
/* startNumber= */ 0,
/* endNumber= */ C.INDEX_UNSET,
/* duration= */ 179989,
/* segmentTimeline= */ null,
/* availabilityTimeOffsetUs= */ C.TIME_UNSET,
/* initializationTemplate= */ null,
/* mediaTemplate= */ null,
/* timeShiftBufferDepthUs= */ C.TIME_UNSET,
/* periodStartUnixTimeUs= */ C.TIME_UNSET);
assertThat(segmentTemplate.getSegmentCount(2931820000L)).isEqualTo(1466);
}

@Test
public void getSegmentCount_withSegmentTemplate_avoidsOverflow() {
SegmentBase.SegmentTemplate segmentTemplate =
new SegmentBase.SegmentTemplate(
/* initialization= */ null,
/* timescale= */ 1000000,
/* presentationTimeOffset= */ 0,
/* startNumber= */ 0,
/* endNumber= */ C.INDEX_UNSET,
/* duration= */ 179989,
/* segmentTimeline= */ null,
/* availabilityTimeOffsetUs= */ C.TIME_UNSET,
/* initializationTemplate= */ null,
/* mediaTemplate= */ null,
/* timeShiftBufferDepthUs= */ C.TIME_UNSET,
/* periodStartUnixTimeUs= */ C.TIME_UNSET);
assertThat(segmentTemplate.getSegmentCount(1618875028000000L)).isEqualTo(8994299808L);
}
}

0 comments on commit 9a36783

Please sign in to comment.