Commit 2969bba6 by Oliver Woodman

Fix timestamp rollover issue for DASH live.

The timestamp scaling in SegmentBase.getSegmentTimeUs was
overflowing for some streams. Apply a similar trick to that
applied in the SmoothStreaming case to fix it.
parent c5342630
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.dash.mpd; package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
import java.util.List; import java.util.List;
...@@ -155,7 +157,7 @@ public abstract class SegmentBase { ...@@ -155,7 +157,7 @@ public abstract class SegmentBase {
} else { } else {
unscaledSegmentTime = (sequenceNumber - startNumber) * duration; unscaledSegmentTime = (sequenceNumber - startNumber) * duration;
} }
return (unscaledSegmentTime * 1000000) / timescale; return Util.scaleLargeTimestamp(unscaledSegmentTime, 1000000, timescale);
} }
public abstract RangedUri getSegmentUrl(Representation representation, int index); public abstract RangedUri getSegmentUrl(Representation representation, int index);
......
...@@ -53,19 +53,8 @@ public class SmoothStreamingManifest { ...@@ -53,19 +53,8 @@ public class SmoothStreamingManifest {
this.isLive = isLive; this.isLive = isLive;
this.protectionElement = protectionElement; this.protectionElement = protectionElement;
this.streamElements = streamElements; this.streamElements = streamElements;
if (timescale >= MICROS_PER_SECOND && (timescale % MICROS_PER_SECOND) == 0) { dvrWindowLengthUs = Util.scaleLargeTimestamp(dvrWindowLength, MICROS_PER_SECOND, timescale);
long divisionFactor = timescale / MICROS_PER_SECOND; durationUs = Util.scaleLargeTimestamp(duration, MICROS_PER_SECOND, timescale);
dvrWindowLengthUs = dvrWindowLength / divisionFactor;
durationUs = duration / divisionFactor;
} else if (timescale < MICROS_PER_SECOND && (MICROS_PER_SECOND % timescale) == 0) {
long multiplicationFactor = MICROS_PER_SECOND / timescale;
dvrWindowLengthUs = dvrWindowLength * multiplicationFactor;
durationUs = duration * multiplicationFactor;
} else {
double multiplicationFactor = (double) MICROS_PER_SECOND / timescale;
dvrWindowLengthUs = (long) (dvrWindowLength * multiplicationFactor);
durationUs = (long) (duration * multiplicationFactor);
}
} }
/** /**
...@@ -186,26 +175,10 @@ public class SmoothStreamingManifest { ...@@ -186,26 +175,10 @@ public class SmoothStreamingManifest {
this.tracks = tracks; this.tracks = tracks;
this.chunkCount = chunkStartTimes.size(); this.chunkCount = chunkStartTimes.size();
this.chunkStartTimes = chunkStartTimes; this.chunkStartTimes = chunkStartTimes;
chunkStartTimesUs = new long[chunkStartTimes.size()]; lastChunkDurationUs =
if (timescale >= MICROS_PER_SECOND && (timescale % MICROS_PER_SECOND) == 0) { Util.scaleLargeTimestamp(lastChunkDuration, MICROS_PER_SECOND, timescale);
long divisionFactor = timescale / MICROS_PER_SECOND; chunkStartTimesUs =
for (int i = 0; i < chunkStartTimesUs.length; i++) { Util.scaleLargeTimestamps(chunkStartTimes, MICROS_PER_SECOND, timescale);
chunkStartTimesUs[i] = chunkStartTimes.get(i) / divisionFactor;
}
lastChunkDurationUs = lastChunkDuration / divisionFactor;
} else if (timescale < MICROS_PER_SECOND && (MICROS_PER_SECOND % timescale) == 0) {
long multiplicationFactor = MICROS_PER_SECOND / timescale;
for (int i = 0; i < chunkStartTimesUs.length; i++) {
chunkStartTimesUs[i] = chunkStartTimes.get(i) * multiplicationFactor;
}
lastChunkDurationUs = lastChunkDuration * multiplicationFactor;
} else {
double multiplicationFactor = (double) MICROS_PER_SECOND / timescale;
for (int i = 0; i < chunkStartTimesUs.length; i++) {
chunkStartTimesUs[i] = (long) (chunkStartTimes.get(i) * multiplicationFactor);
}
lastChunkDurationUs = (long) (lastChunkDuration * multiplicationFactor);
}
} }
/** /**
......
...@@ -346,4 +346,57 @@ public final class Util { ...@@ -346,4 +346,57 @@ public final class Util {
return time; return time;
} }
/**
* Scales a large timestamp.
* <p>
* Logically, scaling consists of a multiplication followed by a division. The actual operations
* performed are designed to minimize the probability of overflow.
*
* @param timestamp The timestamp to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
* @return The scaled timestamp.
*/
public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) {
if (divisor >= multiplier && (divisor % multiplier) == 0) {
long divisionFactor = divisor / multiplier;
return timestamp / divisionFactor;
} else if (divisor < multiplier && (multiplier % divisor) == 0) {
long multiplicationFactor = multiplier / divisor;
return timestamp * multiplicationFactor;
} else {
double multiplicationFactor = (double) multiplier / divisor;
return (long) (timestamp * multiplicationFactor);
}
}
/**
* Applies {@link #scaleLargeTimestamp(long, long, long)} to a list of unscaled timestamps.
*
* @param timestamps The timestamps to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
* @return The scaled timestamps.
*/
public static long[] scaleLargeTimestamps(List<Long> timestamps, long multiplier, long divisor) {
long[] scaledTimestamps = new long[timestamps.size()];
if (divisor >= multiplier && (divisor % multiplier) == 0) {
long divisionFactor = divisor / multiplier;
for (int i = 0; i < scaledTimestamps.length; i++) {
scaledTimestamps[i] = timestamps.get(i) / divisionFactor;
}
} else if (divisor < multiplier && (multiplier % divisor) == 0) {
long multiplicationFactor = multiplier / divisor;
for (int i = 0; i < scaledTimestamps.length; i++) {
scaledTimestamps[i] = timestamps.get(i) * multiplicationFactor;
}
} else {
double multiplicationFactor = (double) multiplier / divisor;
for (int i = 0; i < scaledTimestamps.length; i++) {
scaledTimestamps[i] = (long) (timestamps.get(i) * multiplicationFactor);
}
}
return scaledTimestamps;
}
} }
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