mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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 6e91f0d4c5
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
This commit is contained in:
parent
320a45f7d6
commit
885ddb167e
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<Long> 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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Logically, scaling consists of a multiplication followed by a division. The actual
|
||||
* operations performed are designed to minimize the probability of overflow.
|
||||
* <p>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<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;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Util#scaleLargeTimestamp}
|
||||
* <li>{@link Util#scaleLargeTimestamps}
|
||||
* <li>{@link Util#scaleLargeTimestampsInPlace}
|
||||
* </ul>
|
||||
*/
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
public class UtilScaleLargeTimestampParameterizedTest {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static ImmutableList<Object[]> 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);
|
||||
}
|
||||
}
|
@ -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:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Util#scaleLargeValue}
|
||||
* <li>{@link Util#scaleLargeValues}
|
||||
* <li>{@link Util#scaleLargeValuesInPlace}
|
||||
* <li>{@link Util#scaleLargeTimestamp}
|
||||
* <li>{@link Util#scaleLargeTimestamps}
|
||||
* <li>{@link Util#scaleLargeTimestampsInPlace}
|
||||
* </ul>
|
||||
*/
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
public class UtilScaleLargeValueParameterizedTest {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static ImmutableList<Object[]> 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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user