Commit 9a367836 by olly Committed by Oliver Woodman

DASH: Avoid rounding error in getSegmentCount

Issue: #8804
PiperOrigin-RevId: 369484117
parent 7a2eaa96
......@@ -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)).
......
......@@ -1139,7 +1139,7 @@ public final class DashMediaSource extends BaseMediaSource {
if (index == null) {
return periodStartTimeInManifestUs;
}
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
long availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) {
return periodStartTimeInManifestUs;
}
......@@ -1171,7 +1171,7 @@ public final class DashMediaSource extends BaseMediaSource {
if (index == null) {
return periodStartTimeInManifestUs + periodDurationUs;
}
int availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
long availableSegmentCount = index.getAvailableSegmentCount(periodDurationUs, nowUnixTimeUs);
if (availableSegmentCount == 0) {
return periodStartTimeInManifestUs;
}
......
......@@ -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.
......@@ -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
......
......@@ -48,12 +48,12 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
}
@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;
}
......
......@@ -222,7 +222,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
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
......@@ -465,7 +465,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
&& ((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) {
......@@ -723,7 +723,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
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(
......@@ -777,7 +777,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
+ segmentNumShift;
}
public int getSegmentCount() {
public long getSegmentCount() {
return segmentIndex.getSegmentCount(periodDurationUs);
}
......
......@@ -352,12 +352,12 @@ public abstract class Representation {
}
@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);
}
......
......@@ -24,6 +24,9 @@ import androidx.annotation.VisibleForTesting;
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;
/**
......@@ -214,7 +217,7 @@ public abstract class SegmentBase {
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))
......@@ -264,8 +267,8 @@ public abstract class SegmentBase {
}
/** 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;
}
......@@ -298,7 +301,7 @@ public abstract class SegmentBase {
}
/** 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. */
......@@ -356,7 +359,7 @@ public abstract class SegmentBase {
}
@Override
public int getSegmentCount(long periodDurationUs) {
public long getSegmentCount(long periodDurationUs) {
return mediaSegments.size();
}
......@@ -455,14 +458,17 @@ public abstract class SegmentBase {
}
@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;
}
......
......@@ -63,12 +63,12 @@ import com.google.android.exoplayer2.source.dash.DashSegmentIndex;
}
@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;
}
......
......@@ -183,7 +183,7 @@ public final class DashDownloader extends SegmentDownloader<DashManifest> {
continue;
}
int segmentCount = index.getSegmentCount(periodDurationUs);
long segmentCount = index.getSegmentCount(periodDurationUs);
if (segmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
throw new DownloadException("Unbounded segment index");
}
......
......@@ -182,4 +182,43 @@ public final class SegmentBaseTest {
/* 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);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment