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 androidx.media3.common.Player.Commands;
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.base.Charsets;
|
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.AsyncFunction;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
@ -103,6 +105,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
@ -1528,7 +1531,7 @@ public final class Util {
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static long sampleCountToDurationUs(long sampleCount, int sampleRate) {
|
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
|
@UnstableApi
|
||||||
public static long durationUsToSampleCount(long durationUs, int sampleRate) {
|
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;
|
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.
|
* Scales a large timestamp.
|
||||||
*
|
*
|
||||||
* <p>Logically, scaling consists of a multiplication followed by a division. The actual
|
* <p>Equivalent to {@link #scaleLargeValue(long, long, long, RoundingMode)} with {@link
|
||||||
* operations performed are designed to minimize the probability of overflow.
|
* RoundingMode#FLOOR}.
|
||||||
*
|
*
|
||||||
* @param timestamp The timestamp to scale.
|
* @param timestamp The timestamp to scale.
|
||||||
* @param multiplier The multiplier.
|
* @param multiplier The multiplier.
|
||||||
@ -1651,16 +1856,7 @@ public final class Util {
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) {
|
public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) {
|
||||||
if (divisor >= multiplier && (divisor % multiplier) == 0) {
|
return scaleLargeValue(timestamp, multiplier, divisor, RoundingMode.FLOOR);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1673,24 +1869,7 @@ public final class Util {
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static long[] scaleLargeTimestamps(List<Long> timestamps, long multiplier, long divisor) {
|
public static long[] scaleLargeTimestamps(List<Long> timestamps, long multiplier, long divisor) {
|
||||||
long[] scaledTimestamps = new long[timestamps.size()];
|
return scaleLargeValues(timestamps, multiplier, divisor, RoundingMode.FLOOR);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1702,22 +1881,7 @@ public final class Util {
|
|||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) {
|
public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) {
|
||||||
if (divisor >= multiplier && (divisor % multiplier) == 0) {
|
scaleLargeValuesInPlace(timestamps, multiplier, divisor, RoundingMode.FLOOR);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale);
|
||||||
|
|
||||||
if (track.editListDurations == null) {
|
if (track.editListDurations == null) {
|
||||||
|
try {
|
||||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
||||||
|
} catch (ArithmeticException e) {
|
||||||
|
throw new RuntimeException("track.timescale=" + track.timescale, e);
|
||||||
|
}
|
||||||
return new TrackSampleTable(
|
return new TrackSampleTable(
|
||||||
track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ track 0:
|
|||||||
flags = 0
|
flags = 0
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 0
|
flags = 0
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -70,7 +70,7 @@ track 0:
|
|||||||
flags = 0
|
flags = 0
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 0
|
flags = 0
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -70,7 +70,7 @@ track 0:
|
|||||||
flags = 0
|
flags = 0
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 0
|
flags = 0
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -70,7 +70,7 @@ track 0:
|
|||||||
flags = 0
|
flags = 0
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 0
|
flags = 0
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -70,7 +70,7 @@ track 0:
|
|||||||
flags = 0
|
flags = 0
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 0
|
flags = 0
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -68,7 +68,7 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -44,7 +44,7 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 7:
|
sample 7:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 8:
|
sample 8:
|
||||||
|
@ -20,7 +20,7 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 1:
|
sample 1:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 2:
|
sample 2:
|
||||||
|
@ -68,7 +68,7 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 520, hash FEE56928
|
data = length 520, hash FEE56928
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 599, hash 41F496C5
|
data = length 599, hash 41F496C5
|
||||||
sample 14:
|
sample 14:
|
||||||
|
@ -95,7 +95,7 @@ track 0:
|
|||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
encryption key = length 16, hash 9FDDEA52
|
encryption key = length 16, hash 9FDDEA52
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1073741825
|
flags = 1073741825
|
||||||
data = length 616, hash 3F657E23
|
data = length 616, hash 3F657E23
|
||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
|
@ -59,7 +59,7 @@ track 0:
|
|||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
encryption key = length 16, hash 9FDDEA52
|
encryption key = length 16, hash 9FDDEA52
|
||||||
sample 7:
|
sample 7:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1073741825
|
flags = 1073741825
|
||||||
data = length 616, hash 3F657E23
|
data = length 616, hash 3F657E23
|
||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
|
@ -23,7 +23,7 @@ track 0:
|
|||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
encryption key = length 16, hash 9FDDEA52
|
encryption key = length 16, hash 9FDDEA52
|
||||||
sample 1:
|
sample 1:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1073741825
|
flags = 1073741825
|
||||||
data = length 616, hash 3F657E23
|
data = length 616, hash 3F657E23
|
||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
|
@ -95,7 +95,7 @@ track 0:
|
|||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
encryption key = length 16, hash 9FDDEA52
|
encryption key = length 16, hash 9FDDEA52
|
||||||
sample 13:
|
sample 13:
|
||||||
time = 519999
|
time = 520000
|
||||||
flags = 1073741825
|
flags = 1073741825
|
||||||
data = length 616, hash 3F657E23
|
data = length 616, hash 3F657E23
|
||||||
crypto mode = 1
|
crypto mode = 1
|
||||||
|
@ -230,11 +230,11 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 52:
|
sample 52:
|
||||||
time = 513499
|
time = 513500
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 53:
|
sample 53:
|
||||||
time = 523499
|
time = 523500
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 54:
|
sample 54:
|
||||||
|
@ -34,11 +34,11 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 3:
|
sample 3:
|
||||||
time = 513499
|
time = 513500
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 4:
|
sample 4:
|
||||||
time = 523499
|
time = 523500
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 5:
|
sample 5:
|
||||||
|
@ -230,11 +230,11 @@ track 0:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 52:
|
sample 52:
|
||||||
time = 513499
|
time = 513500
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 53:
|
sample 53:
|
||||||
time = 523499
|
time = 523500
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 3, hash 4732
|
data = length 3, hash 4732
|
||||||
sample 54:
|
sample 54:
|
||||||
|
@ -738,7 +738,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 685, hash 564F62A
|
data = length 685, hash 564F62A
|
||||||
sample 47:
|
sample 47:
|
||||||
time = 1045624
|
time = 1045625
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 691, hash 71BBA88D
|
data = length 691, hash 71BBA88D
|
||||||
sample 48:
|
sample 48:
|
||||||
@ -930,7 +930,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 664, hash A9D0717
|
data = length 664, hash A9D0717
|
||||||
sample 95:
|
sample 95:
|
||||||
time = 2069624
|
time = 2069625
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 672, hash 6F2663EA
|
data = length 672, hash 6F2663EA
|
||||||
sample 96:
|
sample 96:
|
||||||
@ -1310,7 +1310,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 822, hash 2A045560
|
data = length 822, hash 2A045560
|
||||||
sample 190:
|
sample 190:
|
||||||
time = 4096249
|
time = 4096250
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 643, hash 551E7C72
|
data = length 643, hash 551E7C72
|
||||||
sample 191:
|
sample 191:
|
||||||
@ -1322,7 +1322,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 640, hash E714454F
|
data = length 640, hash E714454F
|
||||||
sample 193:
|
sample 193:
|
||||||
time = 4160249
|
time = 4160250
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 646, hash 6DD5E81B
|
data = length 646, hash 6DD5E81B
|
||||||
sample 194:
|
sample 194:
|
||||||
|
@ -606,7 +606,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 685, hash 564F62A
|
data = length 685, hash 564F62A
|
||||||
sample 47:
|
sample 47:
|
||||||
time = 1045624
|
time = 1045625
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 691, hash 71BBA88D
|
data = length 691, hash 71BBA88D
|
||||||
sample 48:
|
sample 48:
|
||||||
@ -798,7 +798,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 664, hash A9D0717
|
data = length 664, hash A9D0717
|
||||||
sample 95:
|
sample 95:
|
||||||
time = 2069624
|
time = 2069625
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 672, hash 6F2663EA
|
data = length 672, hash 6F2663EA
|
||||||
sample 96:
|
sample 96:
|
||||||
@ -1178,7 +1178,7 @@ track 1:
|
|||||||
flags = 1
|
flags = 1
|
||||||
data = length 822, hash 2A045560
|
data = length 822, hash 2A045560
|
||||||
sample 190:
|
sample 190:
|
||||||
time = 4096249
|
time = 4096250
|
||||||
flags = 1
|
flags = 1
|
||||||
data = length 643, hash 551E7C72
|
data = length 643, hash 551E7C72
|
||||||
sample 191:
|
sample 191:
|
||||||
|
@ -41,7 +41,7 @@ MediaCodecAdapter (exotest.audio.ac4):
|
|||||||
timeUs = 1000000480000
|
timeUs = 1000000480000
|
||||||
contents = length 520, hash FEE56928
|
contents = length 520, hash FEE56928
|
||||||
input buffer #13:
|
input buffer #13:
|
||||||
timeUs = 1000000519999
|
timeUs = 1000000520000
|
||||||
contents = length 599, hash 41F496C5
|
contents = length 599, hash 41F496C5
|
||||||
input buffer #14:
|
input buffer #14:
|
||||||
timeUs = 1000000560000
|
timeUs = 1000000560000
|
||||||
@ -117,7 +117,7 @@ MediaCodecAdapter (exotest.audio.ac4):
|
|||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #13:
|
output buffer #13:
|
||||||
timeUs = 1000000519999
|
timeUs = 1000000520000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #14:
|
output buffer #14:
|
||||||
@ -186,7 +186,7 @@ AudioSink:
|
|||||||
time = 1000000480000
|
time = 1000000480000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #13:
|
buffer #13:
|
||||||
time = 1000000519999
|
time = 1000000520000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #14:
|
buffer #14:
|
||||||
time = 1000000560000
|
time = 1000000560000
|
||||||
|
@ -41,7 +41,7 @@ MediaCodecAdapter (exotest.audio.ac4):
|
|||||||
timeUs = 1000000480000
|
timeUs = 1000000480000
|
||||||
contents = length 520, hash FEE56928
|
contents = length 520, hash FEE56928
|
||||||
input buffer #13:
|
input buffer #13:
|
||||||
timeUs = 1000000519999
|
timeUs = 1000000520000
|
||||||
contents = length 599, hash 41F496C5
|
contents = length 599, hash 41F496C5
|
||||||
input buffer #14:
|
input buffer #14:
|
||||||
timeUs = 1000000560000
|
timeUs = 1000000560000
|
||||||
@ -117,7 +117,7 @@ MediaCodecAdapter (exotest.audio.ac4):
|
|||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #13:
|
output buffer #13:
|
||||||
timeUs = 1000000519999
|
timeUs = 1000000520000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #14:
|
output buffer #14:
|
||||||
@ -186,7 +186,7 @@ AudioSink:
|
|||||||
time = 1000000480000
|
time = 1000000480000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #13:
|
buffer #13:
|
||||||
time = 1000000519999
|
time = 1000000520000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #14:
|
buffer #14:
|
||||||
time = 1000000560000
|
time = 1000000560000
|
||||||
|
@ -158,10 +158,10 @@ MediaCodecAdapter (exotest.audio.opus):
|
|||||||
timeUs = 1000000503500
|
timeUs = 1000000503500
|
||||||
contents = length 3, hash 4732
|
contents = length 3, hash 4732
|
||||||
input buffer #52:
|
input buffer #52:
|
||||||
timeUs = 1000000513499
|
timeUs = 1000000513500
|
||||||
contents = length 3, hash 4732
|
contents = length 3, hash 4732
|
||||||
input buffer #53:
|
input buffer #53:
|
||||||
timeUs = 1000000523499
|
timeUs = 1000000523500
|
||||||
contents = length 3, hash 4732
|
contents = length 3, hash 4732
|
||||||
input buffer #54:
|
input buffer #54:
|
||||||
timeUs = 1000000533500
|
timeUs = 1000000533500
|
||||||
@ -519,11 +519,11 @@ MediaCodecAdapter (exotest.audio.opus):
|
|||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #52:
|
output buffer #52:
|
||||||
timeUs = 1000000513499
|
timeUs = 1000000513500
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #53:
|
output buffer #53:
|
||||||
timeUs = 1000000523499
|
timeUs = 1000000523500
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #54:
|
output buffer #54:
|
||||||
@ -875,10 +875,10 @@ AudioSink:
|
|||||||
time = 1000000503500
|
time = 1000000503500
|
||||||
data = 1
|
data = 1
|
||||||
buffer #51:
|
buffer #51:
|
||||||
time = 1000000513499
|
time = 1000000513500
|
||||||
data = 1
|
data = 1
|
||||||
buffer #52:
|
buffer #52:
|
||||||
time = 1000000523499
|
time = 1000000523500
|
||||||
data = 1
|
data = 1
|
||||||
buffer #53:
|
buffer #53:
|
||||||
time = 1000000533500
|
time = 1000000533500
|
||||||
|
@ -146,7 +146,7 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
timeUs = 1000001024000
|
timeUs = 1000001024000
|
||||||
contents = length 30, hash E7FD80C4
|
contents = length 30, hash E7FD80C4
|
||||||
input buffer #48:
|
input buffer #48:
|
||||||
timeUs = 1000001044999
|
timeUs = 1000001045000
|
||||||
contents = length 42, hash 79A5EBAC
|
contents = length 42, hash 79A5EBAC
|
||||||
input buffer #49:
|
input buffer #49:
|
||||||
timeUs = 1000001066000
|
timeUs = 1000001066000
|
||||||
@ -290,10 +290,10 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
timeUs = 1000002048000
|
timeUs = 1000002048000
|
||||||
contents = length 34, hash 16D99E00
|
contents = length 34, hash 16D99E00
|
||||||
input buffer #96:
|
input buffer #96:
|
||||||
timeUs = 1000002068999
|
timeUs = 1000002069000
|
||||||
contents = length 35, hash 1CE3D79C
|
contents = length 35, hash 1CE3D79C
|
||||||
input buffer #97:
|
input buffer #97:
|
||||||
timeUs = 1000002089999
|
timeUs = 1000002090000
|
||||||
contents = length 35, hash 552D9CF8
|
contents = length 35, hash 552D9CF8
|
||||||
input buffer #98:
|
input buffer #98:
|
||||||
timeUs = 1000002112000
|
timeUs = 1000002112000
|
||||||
@ -578,16 +578,16 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
timeUs = 1000004096000
|
timeUs = 1000004096000
|
||||||
contents = length 942, hash E4FA1D93
|
contents = length 942, hash E4FA1D93
|
||||||
input buffer #192:
|
input buffer #192:
|
||||||
timeUs = 1000004116999
|
timeUs = 1000004117000
|
||||||
contents = length 896, hash 3AD1120
|
contents = length 896, hash 3AD1120
|
||||||
input buffer #193:
|
input buffer #193:
|
||||||
timeUs = 1000004137999
|
timeUs = 1000004138000
|
||||||
contents = length 903, hash 5841ACE
|
contents = length 903, hash 5841ACE
|
||||||
input buffer #194:
|
input buffer #194:
|
||||||
timeUs = 1000004159999
|
timeUs = 1000004160000
|
||||||
contents = length 883, hash FCD9B32A
|
contents = length 883, hash FCD9B32A
|
||||||
input buffer #195:
|
input buffer #195:
|
||||||
timeUs = 1000004180999
|
timeUs = 1000004181000
|
||||||
contents = length 945, hash A5D31FA1
|
contents = length 945, hash A5D31FA1
|
||||||
input buffer #196:
|
input buffer #196:
|
||||||
timeUs = 1000004202000
|
timeUs = 1000004202000
|
||||||
@ -1049,7 +1049,7 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #48:
|
output buffer #48:
|
||||||
timeUs = 1000001044999
|
timeUs = 1000001045000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #49:
|
output buffer #49:
|
||||||
@ -1241,11 +1241,11 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #96:
|
output buffer #96:
|
||||||
timeUs = 1000002068999
|
timeUs = 1000002069000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #97:
|
output buffer #97:
|
||||||
timeUs = 1000002089999
|
timeUs = 1000002090000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #98:
|
output buffer #98:
|
||||||
@ -1625,19 +1625,19 @@ MediaCodecAdapter (exotest.audio.aac):
|
|||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #192:
|
output buffer #192:
|
||||||
timeUs = 1000004116999
|
timeUs = 1000004117000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #193:
|
output buffer #193:
|
||||||
timeUs = 1000004137999
|
timeUs = 1000004138000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #194:
|
output buffer #194:
|
||||||
timeUs = 1000004159999
|
timeUs = 1000004160000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #195:
|
output buffer #195:
|
||||||
timeUs = 1000004180999
|
timeUs = 1000004181000
|
||||||
size = 0
|
size = 0
|
||||||
rendered = false
|
rendered = false
|
||||||
output buffer #196:
|
output buffer #196:
|
||||||
@ -2139,7 +2139,7 @@ AudioSink:
|
|||||||
time = 1000001024000
|
time = 1000001024000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #48:
|
buffer #48:
|
||||||
time = 1000001044999
|
time = 1000001045000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #49:
|
buffer #49:
|
||||||
time = 1000001066000
|
time = 1000001066000
|
||||||
@ -2283,10 +2283,10 @@ AudioSink:
|
|||||||
time = 1000002048000
|
time = 1000002048000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #96:
|
buffer #96:
|
||||||
time = 1000002068999
|
time = 1000002069000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #97:
|
buffer #97:
|
||||||
time = 1000002089999
|
time = 1000002090000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #98:
|
buffer #98:
|
||||||
time = 1000002112000
|
time = 1000002112000
|
||||||
@ -2571,16 +2571,16 @@ AudioSink:
|
|||||||
time = 1000004096000
|
time = 1000004096000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #192:
|
buffer #192:
|
||||||
time = 1000004116999
|
time = 1000004117000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #193:
|
buffer #193:
|
||||||
time = 1000004137999
|
time = 1000004138000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #194:
|
buffer #194:
|
||||||
time = 1000004159999
|
time = 1000004160000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #195:
|
buffer #195:
|
||||||
time = 1000004180999
|
time = 1000004181000
|
||||||
data = 1
|
data = 1
|
||||||
buffer #196:
|
buffer #196:
|
||||||
time = 1000004202000
|
time = 1000004202000
|
||||||
|
Loading…
x
Reference in New Issue
Block a user