From b78395b32599080253cca09352b67fd27985d6e1 Mon Sep 17 00:00:00 2001 From: ivanbuper Date: Thu, 17 Oct 2024 05:21:52 -0700 Subject: [PATCH] Extract method for calculating expected accumulated truncation error This is prework for implementing `RandomParameterizedSpeedChangingAudioProcessorTest`, which depends on Sonic's resampling algorithm's behaviour. This is a non-functional refactor. PiperOrigin-RevId: 686874593 --- .../audio/RandomParameterizedSonicTest.java | 22 ++----- .../common/audio/SonicTestingUtils.java | 66 +++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 libraries/common/src/test/java/androidx/media3/common/audio/SonicTestingUtils.java diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java index cc99465617..a27fd44426 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java @@ -15,6 +15,7 @@ */ package androidx.media3.common.audio; +import static androidx.media3.common.audio.SonicTestingUtils.calculateAccumulatedTruncationErrorForResampling; import static androidx.media3.test.utils.TestUtil.generateFloatInRange; import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.max; @@ -163,31 +164,18 @@ public final class RandomParameterizedSonicTest { } sonic.flush(); - BigDecimal bigSampleRate = new BigDecimal(SAMPLE_RATE); BigDecimal bigLength = new BigDecimal(String.valueOf(streamLength)); // The scale of expectedSize will be bigLength.scale() - speed.scale(). Thus, the result should // always yield an integer. BigDecimal expectedSize = bigLength.divide(speed, RoundingMode.HALF_EVEN); - // Calculate number of times that Sonic accumulates truncation error. Set scale to 20 decimal - // places, so that division doesn't return an integral. - BigDecimal errorCount = - bigLength.divide(bigSampleRate, /* scale= */ 20, RoundingMode.HALF_EVEN); - - // Calculate what truncation error Sonic is accumulating, calculated as: - // inputSampleRate / speed - (int) inputSampleRate / speed. Set scale to 20 decimal places, so - // that division doesn't return an integral. - BigDecimal individualError = - bigSampleRate.divide(speed, /* scale */ 20, RoundingMode.HALF_EVEN); - individualError = - individualError.subtract(individualError.setScale(/* newScale= */ 0, RoundingMode.FLOOR)); - // Calculate total accumulated error = (int) floor(errorCount * individualError). - BigDecimal accumulatedError = - errorCount.multiply(individualError).setScale(/* newScale= */ 0, RoundingMode.FLOOR); + long accumulatedTruncationError = + calculateAccumulatedTruncationErrorForResampling( + bigLength, new BigDecimal(SAMPLE_RATE), speed); assertThat(readSampleCount) .isWithin(1) - .of(expectedSize.longValueExact() - accumulatedError.longValueExact()); + .of(expectedSize.longValueExact() - accumulatedTruncationError); } @Test diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/SonicTestingUtils.java b/libraries/common/src/test/java/androidx/media3/common/audio/SonicTestingUtils.java new file mode 100644 index 0000000000..bac51f01ff --- /dev/null +++ b/libraries/common/src/test/java/androidx/media3/common/audio/SonicTestingUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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 + * + * http://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.audio; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** Testing utils class related to {@link Sonic} */ +/* package */ final class SonicTestingUtils { + /** + * Returns expected accumulated truncation error for {@link Sonic}'s resampling algorithm, given + * an input length, input sample rate, and resampling rate. + * + *

Note: This method is only necessary until we address b/361768785 and fix the + * underlying truncation issue. + * + *

The accumulated truncation error is calculated as follows: + * + *

    + *
  1. Individual truncation error: Divide sample rate by resampling rate, and calculate delta + * between floating point result and truncated int representation. + *
  2. Truncation accumulation count: Divide length by sample rate to obtain number of times + * that truncation error accumulates. + *
  3. Accumulated truncation error: Multiply results of 1 and 2. + *
+ * + * @param length Length of input in frames. + * @param sampleRate Input sample rate of {@link Sonic} instance. + * @param resamplingRate Resampling rate given by {@code pitch * (inputSampleRate / + * outputSampleRate)}. + */ + public static long calculateAccumulatedTruncationErrorForResampling( + BigDecimal length, BigDecimal sampleRate, BigDecimal resamplingRate) { + // Calculate number of times that Sonic accumulates truncation error. Set scale to 20 decimal + // places, so that division doesn't return an integer. + BigDecimal errorCount = length.divide(sampleRate, /* scale= */ 20, RoundingMode.HALF_EVEN); + + // Calculate what truncation error Sonic is accumulating, calculated as: + // inputSampleRate / resamplingRate - (int) inputSampleRate / resamplingRate. Set scale to 20 + // decimal places, so that division doesn't return an integer. + BigDecimal individualError = + sampleRate.divide(resamplingRate, /* scale */ 20, RoundingMode.HALF_EVEN); + individualError = + individualError.subtract(individualError.setScale(/* newScale= */ 0, RoundingMode.FLOOR)); + // Calculate total accumulated error = (int) floor(errorCount * individualError). + BigDecimal accumulatedError = + errorCount.multiply(individualError).setScale(/* newScale= */ 0, RoundingMode.FLOOR); + + return accumulatedError.longValueExact(); + } + + private SonicTestingUtils() {} +}