From 885ddb167e3ac164e2ee6dfcf3886c703a45bc38 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 12 Sep 2023 10:20:42 -0700 Subject: [PATCH] Add general-purpose overflow-resistant divide+multiply util method This is equivalent to the existing `scaleLargeTimestamp` method with the following changes/improvements: * No longer specific to timestamps (there was nothing inherently time-specific about the logic in `scaleLargeTimestamp`, but the name and docs suggested it shouldn't be used for non-timestamp use-cases). * Additional 'perfect division' checks between `value` and `divisor`. * The caller can now provide a `RoundingMode`. * Robust against `multiplier == 0`. * Some extra branches before falling through to (potentially lossy) floating-point math, including trying to simplify the fraction with greatest common divisor to reduce the chance of overflowing `long`. This was discussed during review of https://github.com/androidx/media/commit/6e91f0d4c5986af238cc77c1a9daa7f012d98a6c This change also includes some golden test file updates - these represent a bug fix where floating-point maths had previously resulted in a timestamp being incorrectly rounded down to the previous microsecond. These changes are due to the 'some more branches' mentioned above. PiperOrigin-RevId: 564760748 --- .../androidx/media3/common/util/Util.java | 260 +++++++++++++--- ...lScaleLargeTimestampParameterizedTest.java | 204 ------------- .../UtilScaleLargeValueParameterizedTest.java | 283 ++++++++++++++++++ .../media3/extractor/mp4/AtomParsers.java | 6 +- .../extractordumps/mp4/sample_ac4.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_ac4.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_ac4.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_ac4.mp4.3.dump | 2 +- .../mp4/sample_ac4.mp4.unknown_length.dump | 2 +- .../mp4/sample_ac4_fragmented.mp4.0.dump | 2 +- .../mp4/sample_ac4_fragmented.mp4.1.dump | 2 +- .../mp4/sample_ac4_fragmented.mp4.2.dump | 2 +- ...ple_ac4_fragmented.mp4.unknown_length.dump | 2 +- .../mp4/sample_ac4_protected.mp4.0.dump | 2 +- .../mp4/sample_ac4_protected.mp4.1.dump | 2 +- .../mp4/sample_ac4_protected.mp4.2.dump | 2 +- ...mple_ac4_protected.mp4.unknown_length.dump | 2 +- .../extractordumps/mp4/sample_opus.mp4.0.dump | 4 +- .../extractordumps/mp4/sample_opus.mp4.1.dump | 4 +- .../mp4/sample_opus.mp4.unknown_length.dump | 4 +- .../assets/muxerdumps/hdr10-720p.mp4.dump | 8 +- .../muxerdumps/partial_hdr10-720p.mp4.dump | 6 +- .../playbackdumps/mp4/sample_ac4.mp4.dump | 6 +- .../mp4/sample_ac4_fragmented.mp4.dump | 6 +- .../playbackdumps/mp4/sample_opus.mp4.dump | 12 +- .../test/assets/playbackdumps/rtsp/aac.dump | 42 +-- 26 files changed, 559 insertions(+), 312 deletions(-) delete mode 100644 libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeTimestampParameterizedTest.java create mode 100644 libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeValueParameterizedTest.java diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index 09f8fca8c7..19c53791bb 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -91,6 +91,8 @@ import androidx.media3.common.Player; import androidx.media3.common.Player.Commands; import com.google.common.base.Ascii; import com.google.common.base.Charsets; +import com.google.common.math.DoubleMath; +import com.google.common.math.LongMath; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -103,6 +105,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.math.BigDecimal; +import java.math.RoundingMode; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayDeque; @@ -1528,7 +1531,7 @@ public final class Util { */ @UnstableApi public static long sampleCountToDurationUs(long sampleCount, int sampleRate) { - return (sampleCount * C.MICROS_PER_SECOND) / sampleRate; + return scaleLargeValue(sampleCount, C.MICROS_PER_SECOND, sampleRate, RoundingMode.FLOOR); } /** @@ -1545,7 +1548,7 @@ public final class Util { */ @UnstableApi public static long durationUsToSampleCount(long durationUs, int sampleRate) { - return Util.ceilDivide(durationUs * sampleRate, C.MICROS_PER_SECOND); + return scaleLargeValue(durationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.CEILING); } /** @@ -1638,11 +1641,213 @@ public final class Util { return time; } + /** + * Scales a large value by a multiplier and a divisor. + * + *

The order of operations in this implementation is designed to minimize the probability of + * overflow. The implementation tries to stay in integer arithmetic as long as possible, but falls + * through to floating-point arithmetic if the values can't be combined without overflowing signed + * 64-bit longs. + * + *

If the mathematical result would overflow or underflow a 64-bit long, the result will be + * either {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}, respectively. + * + * @param value The value to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @param roundingMode The rounding mode to use if the result of the division is not an integer. + * @return The scaled value. + */ + // LongMath.saturatedMultiply is @Beta in the version of Guava we currently depend on (31.1) + // but it is no longer @Beta from 32.0.0. This suppression is therefore safe because there's + // no version of Guava after 31.1 that doesn't contain this symbol. + // TODO(b/290045069): Remove this suppression when we depend on Guava 32+. + @SuppressWarnings("UnstableApiUsage") + @UnstableApi + public static long scaleLargeValue( + long value, long multiplier, long divisor, RoundingMode roundingMode) { + if (value == 0 || multiplier == 0) { + return 0; + } + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = LongMath.divide(divisor, multiplier, RoundingMode.UNNECESSARY); + return LongMath.divide(value, divisionFactor, roundingMode); + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = LongMath.divide(multiplier, divisor, RoundingMode.UNNECESSARY); + return LongMath.saturatedMultiply(value, multiplicationFactor); + } else if (divisor >= value && (divisor % value) == 0) { + long divisionFactor = LongMath.divide(divisor, value, RoundingMode.UNNECESSARY); + return LongMath.divide(multiplier, divisionFactor, roundingMode); + } else if (divisor < value && (value % divisor) == 0) { + long multiplicationFactor = LongMath.divide(value, divisor, RoundingMode.UNNECESSARY); + return LongMath.saturatedMultiply(multiplier, multiplicationFactor); + } else { + return scaleLargeValueFallback(value, multiplier, divisor, roundingMode); + } + } + + /** + * Applies {@link #scaleLargeValue(long, long, long, RoundingMode)} to a list of unscaled values. + * + * @param values The values to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @param roundingMode The rounding mode to use if the result of the division is not an integer. + * @return The scaled values. + */ + // LongMath.saturatedMultiply is @Beta in the version of Guava we currently depend on (31.1) + // but it is no longer @Beta from 32.0.0. This suppression is therefore safe because there's + // no version of Guava after 31.1 that doesn't contain this symbol. + // TODO(b/290045069): Remove this suppression when we depend on Guava 32+. + @SuppressWarnings("UnstableApiUsage") + @UnstableApi + public static long[] scaleLargeValues( + List values, long multiplier, long divisor, RoundingMode roundingMode) { + long[] result = new long[values.size()]; + if (multiplier == 0) { + // Array is initialized with all zeroes by default. + return result; + } + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = LongMath.divide(divisor, multiplier, RoundingMode.UNNECESSARY); + for (int i = 0; i < result.length; i++) { + result[i] = LongMath.divide(values.get(i), divisionFactor, roundingMode); + } + return result; + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = LongMath.divide(multiplier, divisor, RoundingMode.UNNECESSARY); + for (int i = 0; i < result.length; i++) { + result[i] = LongMath.saturatedMultiply(values.get(i), multiplicationFactor); + } + return result; + } else { + for (int i = 0; i < result.length; i++) { + long value = values.get(i); + if (value == 0) { + // Array is initialized with all zeroes by default. + continue; + } + if (divisor >= value && (divisor % value) == 0) { + long divisionFactor = LongMath.divide(divisor, value, RoundingMode.UNNECESSARY); + result[i] = LongMath.divide(multiplier, divisionFactor, roundingMode); + } else if (divisor < value && (value % divisor) == 0) { + long multiplicationFactor = LongMath.divide(value, divisor, RoundingMode.UNNECESSARY); + result[i] = LongMath.saturatedMultiply(multiplier, multiplicationFactor); + } else { + result[i] = scaleLargeValueFallback(value, multiplier, divisor, roundingMode); + } + } + return result; + } + } + + /** + * Applies {@link #scaleLargeValue(long, long, long, RoundingMode)} to an array of unscaled + * values. + * + * @param values The values to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @param roundingMode The rounding mode to use if the result of the division is not an integer. + */ + @UnstableApi + public static void scaleLargeValuesInPlace( + long[] values, long multiplier, long divisor, RoundingMode roundingMode) { + if (multiplier == 0) { + Arrays.fill(values, 0); + return; + } + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = LongMath.divide(divisor, multiplier, RoundingMode.UNNECESSARY); + for (int i = 0; i < values.length; i++) { + values[i] = LongMath.divide(values[i], divisionFactor, roundingMode); + } + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = LongMath.divide(multiplier, divisor, RoundingMode.UNNECESSARY); + for (int i = 0; i < values.length; i++) { + values[i] = LongMath.saturatedMultiply(values[i], multiplicationFactor); + } + } else { + for (int i = 0; i < values.length; i++) { + if (values[i] == 0) { + continue; + } + if (divisor >= values[i] && (divisor % values[i]) == 0) { + long divisionFactor = LongMath.divide(divisor, values[i], RoundingMode.UNNECESSARY); + values[i] = LongMath.divide(multiplier, divisionFactor, roundingMode); + } else if (divisor < values[i] && (values[i] % divisor) == 0) { + long multiplicationFactor = LongMath.divide(values[i], divisor, RoundingMode.UNNECESSARY); + values[i] = LongMath.saturatedMultiply(multiplier, multiplicationFactor); + } else { + values[i] = scaleLargeValueFallback(values[i], multiplier, divisor, roundingMode); + } + } + } + } + + /** + * Scales a large value by a multiplier and a divisor. + * + *

If naively multiplying {@code value} and {@code multiplier} will overflow a 64-bit long, + * this implementation uses {@link LongMath#gcd(long, long)} to try and simplify the fraction + * before computing the result. If simplifying is not possible (or the simplified result will + * still result in an overflow) then the implementation falls back to floating-point arithmetic. + * + *

If the mathematical result would overflow or underflow a 64-bit long, the result will be + * either {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}, respectively. + * + *

This implementation should be used after simpler simplifying efforts have failed (such as + * checking if {@code value} or {@code multiplier} are exact multiples of {@code divisor}). + */ + // LongMath.saturatedMultiply is @Beta in the version of Guava we currently depend on (31.1) + // but it is no longer @Beta from 32.0.0. This suppression is therefore safe because there's + // no version of Guava after 31.1 that doesn't contain this symbol. + // TODO(b/290045069): Remove this suppression when we depend on Guava 32+. + @SuppressWarnings("UnstableApiUsage") + private static long scaleLargeValueFallback( + long value, long multiplier, long divisor, RoundingMode roundingMode) { + long numerator = LongMath.saturatedMultiply(value, multiplier); + if (numerator != Long.MAX_VALUE && numerator != Long.MIN_VALUE) { + return LongMath.divide(numerator, divisor, roundingMode); + } else { + // Directly multiplying value and multiplier will overflow a long, so we try and cancel + // with GCD and try directly multiplying again below. If that still overflows we fall + // through to floating point arithmetic. + long gcdOfMultiplierAndDivisor = LongMath.gcd(multiplier, divisor); + long simplifiedMultiplier = + LongMath.divide(multiplier, gcdOfMultiplierAndDivisor, RoundingMode.UNNECESSARY); + long simplifiedDivisor = + LongMath.divide(divisor, gcdOfMultiplierAndDivisor, RoundingMode.UNNECESSARY); + long gcdOfValueAndSimplifiedDivisor = LongMath.gcd(value, simplifiedDivisor); + long simplifiedValue = + LongMath.divide(value, gcdOfValueAndSimplifiedDivisor, RoundingMode.UNNECESSARY); + simplifiedDivisor = + LongMath.divide( + simplifiedDivisor, gcdOfValueAndSimplifiedDivisor, RoundingMode.UNNECESSARY); + long simplifiedNumerator = LongMath.saturatedMultiply(simplifiedValue, simplifiedMultiplier); + if (simplifiedNumerator != Long.MAX_VALUE && simplifiedNumerator != Long.MIN_VALUE) { + return LongMath.divide(simplifiedNumerator, simplifiedDivisor, roundingMode); + } else { + double multiplicationFactor = (double) simplifiedMultiplier / simplifiedDivisor; + double result = simplifiedValue * multiplicationFactor; + // Clamp values that are too large to be represented by 64-bit signed long. If we don't + // explicitly clamp then DoubleMath.roundToLong will throw ArithmeticException. + if (result > Long.MAX_VALUE) { + return Long.MAX_VALUE; + } else if (result < Long.MIN_VALUE) { + return Long.MIN_VALUE; + } else { + return DoubleMath.roundToLong(result, roundingMode); + } + } + } + } + /** * 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. + *

Equivalent to {@link #scaleLargeValue(long, long, long, RoundingMode)} with {@link + * RoundingMode#FLOOR}. * * @param timestamp The timestamp to scale. * @param multiplier The multiplier. @@ -1651,16 +1856,7 @@ public final class Util { */ @UnstableApi 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); - } + return scaleLargeValue(timestamp, multiplier, divisor, RoundingMode.FLOOR); } /** @@ -1673,24 +1869,7 @@ public final class Util { */ @UnstableApi 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; + return scaleLargeValues(timestamps, multiplier, divisor, RoundingMode.FLOOR); } /** @@ -1702,22 +1881,7 @@ public final class Util { */ @UnstableApi public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) { - if (divisor >= multiplier && (divisor % multiplier) == 0) { - long divisionFactor = divisor / multiplier; - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] /= divisionFactor; - } - } else if (divisor < multiplier && (multiplier % divisor) == 0) { - long multiplicationFactor = multiplier / divisor; - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] *= multiplicationFactor; - } - } else { - double multiplicationFactor = (double) multiplier / divisor; - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] = (long) (timestamps[i] * multiplicationFactor); - } - } + scaleLargeValuesInPlace(timestamps, multiplier, divisor, RoundingMode.FLOOR); } /** diff --git a/libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeTimestampParameterizedTest.java b/libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeTimestampParameterizedTest.java deleted file mode 100644 index c2fb01ecf6..0000000000 --- a/libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeTimestampParameterizedTest.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.media3.common.util; - -import static androidx.media3.common.util.Assertions.checkState; -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import com.google.common.collect.ImmutableList; -import com.google.common.math.LongMath; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.ParameterizedRobolectricTestRunner; -import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; - -/** - * Parameterized tests for: - * - *

- */ -@RunWith(ParameterizedRobolectricTestRunner.class) -public class UtilScaleLargeTimestampParameterizedTest { - - @Parameters(name = "{0}") - public static ImmutableList implementations() { - return ImmutableList.of( - new Object[] {"single-timestamp", (ScaleLargeTimestampFn) Util::scaleLargeTimestamp}, - new Object[] { - "timestamp-list", - (ScaleLargeTimestampFn) - (timestamp, multiplier, divisor) -> - Util.scaleLargeTimestamps(ImmutableList.of(timestamp), multiplier, divisor)[0] - }, - new Object[] { - "timestamp-array-in-place", - (ScaleLargeTimestampFn) - (timestamp, multiplier, divisor) -> { - long[] timestamps = new long[] {timestamp}; - Util.scaleLargeTimestampsInPlace(timestamps, multiplier, divisor); - return timestamps[0]; - } - }); - } - - // Every parameter has to be assigned to a field, even if it's only used to name the test. - @SuppressWarnings("unused") - @ParameterizedRobolectricTestRunner.Parameter(0) - public String name; - - @ParameterizedRobolectricTestRunner.Parameter(1) - public ScaleLargeTimestampFn implementation; - - @Test - public void zeroValue() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 0, /* multiplier= */ 10, /* divisor= */ 2); - assertThat(result).isEqualTo(0); - } - - @Test - public void zeroDivisor_throwsException() { - assertThrows( - ArithmeticException.class, - () -> - implementation.scaleLargeTimestamp( - /* timestamp= */ 2, /* multiplier= */ 10, /* divisor= */ 0)); - } - - @Test - public void divisorMultipleOfMultiplier() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 7, /* multiplier= */ 2, /* divisor= */ 4); - assertThat(result).isEqualTo(3); - } - - @Test - public void multiplierMultipleOfDivisor() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 7, /* multiplier= */ 4, /* divisor= */ 2); - assertThat(result).isEqualTo(14); - } - - @Test - public void multiplierMultipleOfDivisor_resultOverflowsLong() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 7, /* multiplier= */ 1L << 62, /* divisor= */ 2); - assertThat(result).isEqualTo(-2305843009213693952L); - } - - @Test - public void multiplierMultipleOfDivisor_resultUnderflowsLong() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ -7, /* multiplier= */ 1L << 62, /* divisor= */ 2); - assertThat(result).isEqualTo(2305843009213693952L); - } - - @Test - public void divisorMultipleOfValue() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 2, /* multiplier= */ 7, /* divisor= */ 4); - assertThat(result).isEqualTo(3); - } - - @Test - public void valueMultipleOfDivisor() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 4, /* multiplier= */ 7, /* divisor= */ 2); - assertThat(result).isEqualTo(14); - } - - @Test - public void valueMultipleOfDivisor_resultOverflowsLong_clampedToMaxLong() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 1L << 62, /* multiplier= */ 7, /* divisor= */ 2); - assertThat(result).isEqualTo(Long.MAX_VALUE); - } - - @Test - public void valueMultipleOfDivisor_resultUnderflowsLong_clampedToMinLong() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 1L << 62, /* multiplier= */ -7, /* divisor= */ 2); - assertThat(result).isEqualTo(Long.MIN_VALUE); - } - - @Test - public void numeratorDoesntOverflow() { - // Deliberately choose value, multiplier and divisor so no pair trivially cancels to 1. - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ 12, /* multiplier= */ 15, /* divisor= */ 20); - assertThat(result).isEqualTo(9); - } - - @Test - public void numeratorWouldOverflowIfNotCancelled() { - // Deliberately choose value, multiplier and divisor so that both value/divisor and - // multiplier/divisor have to be cancelled otherwise multiplier*value will overflow a long. - long value = LongMath.checkedMultiply(3, 1L << 61); - long multiplier = LongMath.checkedMultiply(5, 1L << 60); - long divisor = LongMath.checkedMultiply(15, 1L << 59); - - long result = implementation.scaleLargeTimestamp(value, multiplier, divisor); - - assertThat(result).isEqualTo(1L << 62); - } - - // TODO(b/290045069): Remove this suppression when we depend on Guava 32+. - @SuppressWarnings("UnstableApiUsage") - @Test - public void numeratorOverflowsAndCantBeCancelled() { - // Use three Mersenne primes so nothing can cancel, and the numerator will (just) overflow 64 - // bits - forcing the implementation down the floating-point branch. - long value = (1L << 61) - 1; - long multiplier = (1L << 5) - 1; - // Confirm that naively multiplying value and multiplier overflows. - checkState(LongMath.saturatedMultiply(value, multiplier) == Long.MAX_VALUE); - - long result = - implementation.scaleLargeTimestamp(value, multiplier, /* divisor= */ (1L << 31) - 1); - - assertThat(result).isEqualTo(33285996559L); - } - - @Test - public void resultOverflows_truncatedToMaxLong() { - long result = - implementation.scaleLargeTimestamp( - /* timestamp= */ (1L << 61) - 1, - /* multiplier= */ (1L << 61) - 1, - /* divisor= */ (1L << 31) - 1); - - assertThat(result).isEqualTo(Long.MAX_VALUE); - } - - private interface ScaleLargeTimestampFn { - long scaleLargeTimestamp(long timestamp, long multiplier, long divisor); - } -} diff --git a/libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeValueParameterizedTest.java b/libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeValueParameterizedTest.java new file mode 100644 index 0000000000..65be0c828a --- /dev/null +++ b/libraries/common/src/test/java/androidx/media3/common/util/UtilScaleLargeValueParameterizedTest.java @@ -0,0 +1,283 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.common.util; + +import static androidx.media3.common.util.Assertions.checkState; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.math.LongMath; +import java.math.RoundingMode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; +import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; + +/** + * Parameterized tests for: + * + * + */ +@RunWith(ParameterizedRobolectricTestRunner.class) +public class UtilScaleLargeValueParameterizedTest { + + @Parameters(name = "{0}") + public static ImmutableList implementations() { + return ImmutableList.of( + new Object[] {"single-value", (ScaleLargeValueFn) Util::scaleLargeValue}, + new Object[] { + "list", + (ScaleLargeValueFn) + (value, multiplier, divisor, roundingMode) -> + Util.scaleLargeValues(ImmutableList.of(value), multiplier, divisor, roundingMode)[ + 0] + }, + new Object[] { + "array-in-place", + (ScaleLargeValueFn) + (value, multiplier, divisor, roundingMode) -> { + long[] values = new long[] {value}; + Util.scaleLargeValuesInPlace(values, multiplier, divisor, roundingMode); + return values[0]; + } + }, + new Object[] { + "single-timestamp", + (ScaleLargeValueFn) + (long timestamp, long multiplier, long divisor, RoundingMode roundingMode) -> { + assumeTrue( + roundingMode == RoundingMode.UNNECESSARY || roundingMode == RoundingMode.FLOOR); + return Util.scaleLargeTimestamp(timestamp, multiplier, divisor); + } + }, + new Object[] { + "timestamp-list", + (ScaleLargeValueFn) + (timestamp, multiplier, divisor, roundingMode) -> { + assumeTrue( + roundingMode == RoundingMode.UNNECESSARY || roundingMode == RoundingMode.FLOOR); + return Util.scaleLargeTimestamps(ImmutableList.of(timestamp), multiplier, divisor)[ + 0]; + } + }, + new Object[] { + "timestamp-array-in-place", + (ScaleLargeValueFn) + (timestamp, multiplier, divisor, roundingMode) -> { + assumeTrue( + roundingMode == RoundingMode.UNNECESSARY || roundingMode == RoundingMode.FLOOR); + long[] timestamps = new long[] {timestamp}; + Util.scaleLargeTimestampsInPlace(timestamps, multiplier, divisor); + return timestamps[0]; + } + }); + } + + // Every parameter has to be assigned to a field, even if it's only used to name the test. + @SuppressWarnings("unused") + @ParameterizedRobolectricTestRunner.Parameter(0) + public String name; + + @ParameterizedRobolectricTestRunner.Parameter(1) + public ScaleLargeValueFn implementation; + + @Test + public void zeroValue() { + // Deliberately use prime multiplier and divisor so they can't cancel. + long result = + implementation.scaleLargeValue( + /* value= */ 0, /* multiplier= */ 5, /* divisor= */ 2, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(0); + } + + @Test + public void zeroMultiplier() { + long result = + implementation.scaleLargeValue( + /* value= */ 10, /* multiplier= */ 0, /* divisor= */ 2, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(0); + } + + @Test + public void zeroDivisor_throwsException() { + assertThrows( + ArithmeticException.class, + () -> + implementation.scaleLargeValue( + /* value= */ 2, /* multiplier= */ 10, /* divisor= */ 0, RoundingMode.UNNECESSARY)); + } + + @Test + public void divisorMultipleOfMultiplier() { + long result = + implementation.scaleLargeValue( + /* value= */ 7, /* multiplier= */ 2, /* divisor= */ 4, RoundingMode.FLOOR); + assertThat(result).isEqualTo(3); + } + + @Test + public void multiplierMultipleOfDivisor() { + long result = + implementation.scaleLargeValue( + /* value= */ 7, /* multiplier= */ 4, /* divisor= */ 2, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(14); + } + + @Test + public void multiplierMultipleOfDivisor_resultOverflowsLong_clampedToMaxLong() { + long result = + implementation.scaleLargeValue( + /* value= */ 7, /* multiplier= */ 1L << 62, /* divisor= */ 2, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(Long.MAX_VALUE); + } + + @Test + public void multiplierMultipleOfDivisor_resultUnderflowsLong_clampedToMinLong() { + long result = + implementation.scaleLargeValue( + /* value= */ -7, + /* multiplier= */ 1L << 62, + /* divisor= */ 2, + RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(Long.MIN_VALUE); + } + + @Test + public void divisorMultipleOfValue() { + long result = + implementation.scaleLargeValue( + /* value= */ 2, /* multiplier= */ 7, /* divisor= */ 4, RoundingMode.FLOOR); + assertThat(result).isEqualTo(3); + } + + @Test + public void valueMultipleOfDivisor() { + long result = + implementation.scaleLargeValue( + /* value= */ 4, /* multiplier= */ 7, /* divisor= */ 2, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(14); + } + + @Test + public void valueMultipleOfDivisor_resultOverflowsLong_clampedToMaxLong() { + long result = + implementation.scaleLargeValue( + /* value= */ 1L << 62, /* multiplier= */ 7, /* divisor= */ 2, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(Long.MAX_VALUE); + } + + @Test + public void valueMultipleOfDivisor_resultUnderflowsLong_clampedToMinLong() { + long result = + implementation.scaleLargeValue( + /* value= */ 1L << 62, + /* multiplier= */ -7, + /* divisor= */ 2, + RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(Long.MIN_VALUE); + } + + @Test + public void numeratorDoesntOverflow() { + // Deliberately choose value, multiplier and divisor so no pair trivially cancels to 1. + long result = + implementation.scaleLargeValue( + /* value= */ 12, /* multiplier= */ 15, /* divisor= */ 20, RoundingMode.UNNECESSARY); + assertThat(result).isEqualTo(9); + } + + @Test + public void numeratorWouldOverflowIfNotCancelled() { + // Deliberately choose value, multiplier and divisor so that both value/divisor and + // multiplier/divisor have to be cancelled otherwise multiplier*value will overflow a long. + long value = LongMath.checkedMultiply(3, 1L << 61); + long multiplier = LongMath.checkedMultiply(5, 1L << 60); + long divisor = LongMath.checkedMultiply(15, 1L << 59); + + long result = + implementation.scaleLargeValue(value, multiplier, divisor, RoundingMode.UNNECESSARY); + + assertThat(result).isEqualTo(1L << 62); + } + + /** + * This test uses real values from sample_ac4.mp4 that have an exact integer result, but the + * floating-point branch of {@link Util#scaleLargeValue} produces an incorrect fractional result. + * + *

Here we scale them up to ensure multiplier*value would overflow a long, so the + * implementation needs to simplify the fraction first to avoid falling through to the + * floating-point branch (which will cause this test to fail because passing + * RoundingMode.UNNECESSARY won't be allowed). + */ + // TODO(b/290045069): Remove this suppression when we depend on Guava 32+. + @SuppressWarnings("UnstableApiUsage") + @Test + public void cancelsRatherThanFallThroughToFloatingPoint() { + long value = 24960; + long multiplier = 1_000_000_000_000_000_000L; + // Confirm that naively multiplying value and multiplier overflows. + checkState(LongMath.saturatedMultiply(value, multiplier) == Long.MAX_VALUE); + + long result = + implementation.scaleLargeValue( + value, multiplier, /* divisor= */ 48_000_000_000_000_000L, RoundingMode.UNNECESSARY); + + assertThat(result).isEqualTo(520000); + } + + // TODO(b/290045069): Remove this suppression when we depend on Guava 32+. + @SuppressWarnings("UnstableApiUsage") + @Test + public void numeratorOverflowsAndCantBeCancelled() { + // Use three Mersenne primes so nothing can cancel, and the numerator will (just) overflow 64 + // bits - forcing the implementation down the floating-point branch. + long value = (1L << 61) - 1; + long multiplier = (1L << 5) - 1; + // Confirm that naively multiplying value and multiplier overflows. + checkState(LongMath.saturatedMultiply(value, multiplier) == Long.MAX_VALUE); + + long result = + implementation.scaleLargeValue( + value, multiplier, /* divisor= */ (1L << 31) - 1, RoundingMode.FLOOR); + + assertThat(result).isEqualTo(33285996559L); + } + + @Test + public void resultOverflows_truncatedToMaxLong() { + long result = + implementation.scaleLargeValue( + /* value= */ (1L << 61) - 1, + /* multiplier= */ (1L << 61) - 1, + /* divisor= */ (1L << 31) - 1, + RoundingMode.FLOOR); + + assertThat(result).isEqualTo(Long.MAX_VALUE); + } + + private interface ScaleLargeValueFn { + long scaleLargeValue(long value, long multiplier, long divisor, RoundingMode roundingMode); + } +} diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java index 15d5aa0428..8c47038884 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/AtomParsers.java @@ -617,7 +617,11 @@ import java.util.List; long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale); if (track.editListDurations == null) { - Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + try { + Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + } catch (ArithmeticException e) { + throw new RuntimeException("track.timescale=" + track.timescale, e); + } return new TrackSampleTable( track, offsets, sizes, maximumSize, timestamps, flags, durationUs); } diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.0.dump index 5ef2c7cbe6..d97448c547 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.0.dump @@ -70,7 +70,7 @@ track 0: flags = 0 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 0 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.1.dump index 5ef2c7cbe6..d97448c547 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.1.dump @@ -70,7 +70,7 @@ track 0: flags = 0 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 0 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.2.dump index 5ef2c7cbe6..d97448c547 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.2.dump @@ -70,7 +70,7 @@ track 0: flags = 0 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 0 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.3.dump index 5ef2c7cbe6..d97448c547 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.3.dump @@ -70,7 +70,7 @@ track 0: flags = 0 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 0 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.unknown_length.dump index 5ef2c7cbe6..d97448c547 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4.mp4.unknown_length.dump @@ -70,7 +70,7 @@ track 0: flags = 0 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 0 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.0.dump index 8868166056..b2c54a9039 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.0.dump @@ -68,7 +68,7 @@ track 0: flags = 1 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 1 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.1.dump index e23156e0fb..f8d4804fdb 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.1.dump @@ -44,7 +44,7 @@ track 0: flags = 1 data = length 520, hash FEE56928 sample 7: - time = 519999 + time = 520000 flags = 1 data = length 599, hash 41F496C5 sample 8: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.2.dump index 82a11a4800..43751731c0 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.2.dump @@ -20,7 +20,7 @@ track 0: flags = 1 data = length 520, hash FEE56928 sample 1: - time = 519999 + time = 520000 flags = 1 data = length 599, hash 41F496C5 sample 2: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.unknown_length.dump index 8868166056..b2c54a9039 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_fragmented.mp4.unknown_length.dump @@ -68,7 +68,7 @@ track 0: flags = 1 data = length 520, hash FEE56928 sample 13: - time = 519999 + time = 520000 flags = 1 data = length 599, hash 41F496C5 sample 14: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.0.dump index 0d0b8317e0..425e0f67e9 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.0.dump @@ -95,7 +95,7 @@ track 0: crypto mode = 1 encryption key = length 16, hash 9FDDEA52 sample 13: - time = 519999 + time = 520000 flags = 1073741825 data = length 616, hash 3F657E23 crypto mode = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.1.dump index aeffcabdbd..64935a88ef 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.1.dump @@ -59,7 +59,7 @@ track 0: crypto mode = 1 encryption key = length 16, hash 9FDDEA52 sample 7: - time = 519999 + time = 520000 flags = 1073741825 data = length 616, hash 3F657E23 crypto mode = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.2.dump index ce0badc5c9..5fad73c5c5 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.2.dump @@ -23,7 +23,7 @@ track 0: crypto mode = 1 encryption key = length 16, hash 9FDDEA52 sample 1: - time = 519999 + time = 520000 flags = 1073741825 data = length 616, hash 3F657E23 crypto mode = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.unknown_length.dump index 0d0b8317e0..425e0f67e9 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_protected.mp4.unknown_length.dump @@ -95,7 +95,7 @@ track 0: crypto mode = 1 encryption key = length 16, hash 9FDDEA52 sample 13: - time = 519999 + time = 520000 flags = 1073741825 data = length 616, hash 3F657E23 crypto mode = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump index e02ee654f5..0841fad075 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.0.dump @@ -230,11 +230,11 @@ track 0: flags = 1 data = length 3, hash 4732 sample 52: - time = 513499 + time = 513500 flags = 1 data = length 3, hash 4732 sample 53: - time = 523499 + time = 523500 flags = 1 data = length 3, hash 4732 sample 54: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump index 4c06b11615..6e8325d298 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.1.dump @@ -34,11 +34,11 @@ track 0: flags = 1 data = length 3, hash 4732 sample 3: - time = 513499 + time = 513500 flags = 1 data = length 3, hash 4732 sample 4: - time = 523499 + time = 523500 flags = 1 data = length 3, hash 4732 sample 5: diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump index e02ee654f5..0841fad075 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_opus.mp4.unknown_length.dump @@ -230,11 +230,11 @@ track 0: flags = 1 data = length 3, hash 4732 sample 52: - time = 513499 + time = 513500 flags = 1 data = length 3, hash 4732 sample 53: - time = 523499 + time = 523500 flags = 1 data = length 3, hash 4732 sample 54: diff --git a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump index e424e7cb8a..089e807173 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump @@ -738,7 +738,7 @@ track 1: flags = 1 data = length 685, hash 564F62A sample 47: - time = 1045624 + time = 1045625 flags = 1 data = length 691, hash 71BBA88D sample 48: @@ -930,7 +930,7 @@ track 1: flags = 1 data = length 664, hash A9D0717 sample 95: - time = 2069624 + time = 2069625 flags = 1 data = length 672, hash 6F2663EA sample 96: @@ -1310,7 +1310,7 @@ track 1: flags = 1 data = length 822, hash 2A045560 sample 190: - time = 4096249 + time = 4096250 flags = 1 data = length 643, hash 551E7C72 sample 191: @@ -1322,7 +1322,7 @@ track 1: flags = 1 data = length 640, hash E714454F sample 193: - time = 4160249 + time = 4160250 flags = 1 data = length 646, hash 6DD5E81B sample 194: diff --git a/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump index b34e911d50..378f2af77d 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/partial_hdr10-720p.mp4.dump @@ -606,7 +606,7 @@ track 1: flags = 1 data = length 685, hash 564F62A sample 47: - time = 1045624 + time = 1045625 flags = 1 data = length 691, hash 71BBA88D sample 48: @@ -798,7 +798,7 @@ track 1: flags = 1 data = length 664, hash A9D0717 sample 95: - time = 2069624 + time = 2069625 flags = 1 data = length 672, hash 6F2663EA sample 96: @@ -1178,7 +1178,7 @@ track 1: flags = 1 data = length 822, hash 2A045560 sample 190: - time = 4096249 + time = 4096250 flags = 1 data = length 643, hash 551E7C72 sample 191: diff --git a/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4.mp4.dump b/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4.mp4.dump index e89a88d962..cdca5b077f 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4.mp4.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4.mp4.dump @@ -41,7 +41,7 @@ MediaCodecAdapter (exotest.audio.ac4): timeUs = 1000000480000 contents = length 520, hash FEE56928 input buffer #13: - timeUs = 1000000519999 + timeUs = 1000000520000 contents = length 599, hash 41F496C5 input buffer #14: timeUs = 1000000560000 @@ -117,7 +117,7 @@ MediaCodecAdapter (exotest.audio.ac4): size = 0 rendered = false output buffer #13: - timeUs = 1000000519999 + timeUs = 1000000520000 size = 0 rendered = false output buffer #14: @@ -186,7 +186,7 @@ AudioSink: time = 1000000480000 data = 1 buffer #13: - time = 1000000519999 + time = 1000000520000 data = 1 buffer #14: time = 1000000560000 diff --git a/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4_fragmented.mp4.dump b/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4_fragmented.mp4.dump index e89a88d962..cdca5b077f 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4_fragmented.mp4.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_ac4_fragmented.mp4.dump @@ -41,7 +41,7 @@ MediaCodecAdapter (exotest.audio.ac4): timeUs = 1000000480000 contents = length 520, hash FEE56928 input buffer #13: - timeUs = 1000000519999 + timeUs = 1000000520000 contents = length 599, hash 41F496C5 input buffer #14: timeUs = 1000000560000 @@ -117,7 +117,7 @@ MediaCodecAdapter (exotest.audio.ac4): size = 0 rendered = false output buffer #13: - timeUs = 1000000519999 + timeUs = 1000000520000 size = 0 rendered = false output buffer #14: @@ -186,7 +186,7 @@ AudioSink: time = 1000000480000 data = 1 buffer #13: - time = 1000000519999 + time = 1000000520000 data = 1 buffer #14: time = 1000000560000 diff --git a/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_opus.mp4.dump b/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_opus.mp4.dump index d8a3ffaafa..7984a13575 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_opus.mp4.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/mp4/sample_opus.mp4.dump @@ -158,10 +158,10 @@ MediaCodecAdapter (exotest.audio.opus): timeUs = 1000000503500 contents = length 3, hash 4732 input buffer #52: - timeUs = 1000000513499 + timeUs = 1000000513500 contents = length 3, hash 4732 input buffer #53: - timeUs = 1000000523499 + timeUs = 1000000523500 contents = length 3, hash 4732 input buffer #54: timeUs = 1000000533500 @@ -519,11 +519,11 @@ MediaCodecAdapter (exotest.audio.opus): size = 0 rendered = false output buffer #52: - timeUs = 1000000513499 + timeUs = 1000000513500 size = 0 rendered = false output buffer #53: - timeUs = 1000000523499 + timeUs = 1000000523500 size = 0 rendered = false output buffer #54: @@ -875,10 +875,10 @@ AudioSink: time = 1000000503500 data = 1 buffer #51: - time = 1000000513499 + time = 1000000513500 data = 1 buffer #52: - time = 1000000523499 + time = 1000000523500 data = 1 buffer #53: time = 1000000533500 diff --git a/libraries/test_data/src/test/assets/playbackdumps/rtsp/aac.dump b/libraries/test_data/src/test/assets/playbackdumps/rtsp/aac.dump index 6f65522b4d..4697b4c9cb 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/rtsp/aac.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/rtsp/aac.dump @@ -146,7 +146,7 @@ MediaCodecAdapter (exotest.audio.aac): timeUs = 1000001024000 contents = length 30, hash E7FD80C4 input buffer #48: - timeUs = 1000001044999 + timeUs = 1000001045000 contents = length 42, hash 79A5EBAC input buffer #49: timeUs = 1000001066000 @@ -290,10 +290,10 @@ MediaCodecAdapter (exotest.audio.aac): timeUs = 1000002048000 contents = length 34, hash 16D99E00 input buffer #96: - timeUs = 1000002068999 + timeUs = 1000002069000 contents = length 35, hash 1CE3D79C input buffer #97: - timeUs = 1000002089999 + timeUs = 1000002090000 contents = length 35, hash 552D9CF8 input buffer #98: timeUs = 1000002112000 @@ -578,16 +578,16 @@ MediaCodecAdapter (exotest.audio.aac): timeUs = 1000004096000 contents = length 942, hash E4FA1D93 input buffer #192: - timeUs = 1000004116999 + timeUs = 1000004117000 contents = length 896, hash 3AD1120 input buffer #193: - timeUs = 1000004137999 + timeUs = 1000004138000 contents = length 903, hash 5841ACE input buffer #194: - timeUs = 1000004159999 + timeUs = 1000004160000 contents = length 883, hash FCD9B32A input buffer #195: - timeUs = 1000004180999 + timeUs = 1000004181000 contents = length 945, hash A5D31FA1 input buffer #196: timeUs = 1000004202000 @@ -1049,7 +1049,7 @@ MediaCodecAdapter (exotest.audio.aac): size = 0 rendered = false output buffer #48: - timeUs = 1000001044999 + timeUs = 1000001045000 size = 0 rendered = false output buffer #49: @@ -1241,11 +1241,11 @@ MediaCodecAdapter (exotest.audio.aac): size = 0 rendered = false output buffer #96: - timeUs = 1000002068999 + timeUs = 1000002069000 size = 0 rendered = false output buffer #97: - timeUs = 1000002089999 + timeUs = 1000002090000 size = 0 rendered = false output buffer #98: @@ -1625,19 +1625,19 @@ MediaCodecAdapter (exotest.audio.aac): size = 0 rendered = false output buffer #192: - timeUs = 1000004116999 + timeUs = 1000004117000 size = 0 rendered = false output buffer #193: - timeUs = 1000004137999 + timeUs = 1000004138000 size = 0 rendered = false output buffer #194: - timeUs = 1000004159999 + timeUs = 1000004160000 size = 0 rendered = false output buffer #195: - timeUs = 1000004180999 + timeUs = 1000004181000 size = 0 rendered = false output buffer #196: @@ -2139,7 +2139,7 @@ AudioSink: time = 1000001024000 data = 1 buffer #48: - time = 1000001044999 + time = 1000001045000 data = 1 buffer #49: time = 1000001066000 @@ -2283,10 +2283,10 @@ AudioSink: time = 1000002048000 data = 1 buffer #96: - time = 1000002068999 + time = 1000002069000 data = 1 buffer #97: - time = 1000002089999 + time = 1000002090000 data = 1 buffer #98: time = 1000002112000 @@ -2571,16 +2571,16 @@ AudioSink: time = 1000004096000 data = 1 buffer #192: - time = 1000004116999 + time = 1000004117000 data = 1 buffer #193: - time = 1000004137999 + time = 1000004138000 data = 1 buffer #194: - time = 1000004159999 + time = 1000004160000 data = 1 buffer #195: - time = 1000004180999 + time = 1000004181000 data = 1 buffer #196: time = 1000004202000