From 2969bba60fd502bbac2d11cdab399fdd3b438ea3 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 27 Nov 2014 18:14:19 +0000 Subject: [PATCH] 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. --- .../exoplayer/dash/mpd/SegmentBase.java | 4 +- .../SmoothStreamingManifest.java | 39 +++----------- .../google/android/exoplayer/util/Util.java | 53 +++++++++++++++++++ 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java index 89a9dd49be..df92d029bc 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer.dash.mpd; +import com.google.android.exoplayer.util.Util; + import android.net.Uri; import java.util.List; @@ -155,7 +157,7 @@ public abstract class SegmentBase { } else { unscaledSegmentTime = (sequenceNumber - startNumber) * duration; } - return (unscaledSegmentTime * 1000000) / timescale; + return Util.scaleLargeTimestamp(unscaledSegmentTime, 1000000, timescale); } public abstract RangedUri getSegmentUrl(Representation representation, int index); diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java index a26ca6a48e..7b45aed9cc 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java @@ -53,19 +53,8 @@ public class SmoothStreamingManifest { this.isLive = isLive; this.protectionElement = protectionElement; this.streamElements = streamElements; - if (timescale >= MICROS_PER_SECOND && (timescale % MICROS_PER_SECOND) == 0) { - long divisionFactor = timescale / MICROS_PER_SECOND; - 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); - } + dvrWindowLengthUs = Util.scaleLargeTimestamp(dvrWindowLength, MICROS_PER_SECOND, timescale); + durationUs = Util.scaleLargeTimestamp(duration, MICROS_PER_SECOND, timescale); } /** @@ -186,26 +175,10 @@ public class SmoothStreamingManifest { this.tracks = tracks; this.chunkCount = chunkStartTimes.size(); this.chunkStartTimes = chunkStartTimes; - chunkStartTimesUs = new long[chunkStartTimes.size()]; - if (timescale >= MICROS_PER_SECOND && (timescale % MICROS_PER_SECOND) == 0) { - long divisionFactor = timescale / MICROS_PER_SECOND; - for (int i = 0; i < chunkStartTimesUs.length; i++) { - 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); - } + lastChunkDurationUs = + Util.scaleLargeTimestamp(lastChunkDuration, MICROS_PER_SECOND, timescale); + chunkStartTimesUs = + Util.scaleLargeTimestamps(chunkStartTimes, MICROS_PER_SECOND, timescale); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java index 4c08c5a528..b8cd40215d 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java @@ -346,4 +346,57 @@ public final class Util { return time; } + /** + * Scales a large timestamp. + *

+ * 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 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; + } + }