From 9653fa64fe52b977cb2bdc917b983530e6c8e623 Mon Sep 17 00:00:00 2001 From: ivanbuper Date: Mon, 28 Oct 2024 07:51:31 -0700 Subject: [PATCH] Move `calculateAccumulatedTruncationErrorForResampling()` to `Sonic` This is prework for `Sonic` to provide a precise calculation of output sample counts, and thus allow `SpeedChangingAudioProcessor` to replace `getSpeedAdjustedTimeAsync()` with an accurate synchronous calculation. This is a non functional change. PiperOrigin-RevId: 690608520 --- .../androidx/media3/common/audio/Sonic.java | 34 ++++++++++ .../audio/RandomParameterizedSonicTest.java | 2 +- ...erizedSpeedChangingAudioProcessorTest.java | 2 +- .../media3/common/audio/SonicTest.java | 17 +++++ .../common/audio/SonicTestingUtils.java | 66 ------------------- 5 files changed, 53 insertions(+), 68 deletions(-) delete mode 100644 libraries/common/src/test/java/androidx/media3/common/audio/SonicTestingUtils.java diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java b/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java index ae2c84bad5..cde24121d7 100644 --- a/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java +++ b/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java @@ -19,6 +19,8 @@ package androidx.media3.common.audio; import static androidx.media3.common.util.Assertions.checkState; import static java.lang.Math.min; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.nio.ShortBuffer; import java.util.Arrays; @@ -70,6 +72,38 @@ import java.util.Arrays; private int maxDiff; private double accumulatedSpeedAdjustmentError; + /** + * 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. + * + * @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)}. + */ + /* package */ 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(); + } + /** * Creates a new Sonic audio stream processor. * 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 a27fd44426..95fb22e82e 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,7 +15,7 @@ */ package androidx.media3.common.audio; -import static androidx.media3.common.audio.SonicTestingUtils.calculateAccumulatedTruncationErrorForResampling; +import static androidx.media3.common.audio.Sonic.calculateAccumulatedTruncationErrorForResampling; import static androidx.media3.test.utils.TestUtil.generateFloatInRange; import static com.google.common.truth.Truth.assertThat; import static java.lang.Math.max; diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSpeedChangingAudioProcessorTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSpeedChangingAudioProcessorTest.java index 5a4f8e2074..fba90497b4 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSpeedChangingAudioProcessorTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSpeedChangingAudioProcessorTest.java @@ -15,7 +15,7 @@ */ package androidx.media3.common.audio; -import static androidx.media3.common.audio.SonicTestingUtils.calculateAccumulatedTruncationErrorForResampling; +import static androidx.media3.common.audio.Sonic.calculateAccumulatedTruncationErrorForResampling; import static androidx.media3.test.utils.TestUtil.buildTestData; import static androidx.media3.test.utils.TestUtil.generateFloatInRange; import static androidx.media3.test.utils.TestUtil.generateLong; diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java index e1819b81da..7c27337d8f 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java @@ -15,9 +15,11 @@ */ package androidx.media3.common.audio; +import static androidx.media3.common.audio.Sonic.calculateAccumulatedTruncationErrorForResampling; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.math.BigDecimal; import java.nio.ShortBuffer; import org.junit.Rule; import org.junit.Test; @@ -107,4 +109,19 @@ public class SonicTest { assertThat(outputBuffer.array()).isEqualTo(new short[] {0, 4, 8}); } + + @Test + public void calculateAccumulatedTruncationErrorForResampling_returnsExpectedSampleCount() { + long error = + calculateAccumulatedTruncationErrorForResampling( + /* length= */ BigDecimal.valueOf(26902000), + /* sampleRate= */ BigDecimal.valueOf(48000), + /* resamplingRate= */ new BigDecimal(String.valueOf(0.33f))); + + // Individual error = fractional part of (sampleRate / resamplingRate) = 0.54 (periodic) + // Error count = length / sampleRate = 560.4583. + // Accumulated error = error count * individual error = 560.4583 * 0.54 = 305. + // (All calculations are done on BigDecimal rounded to 20 decimal places, unless indicated). + assertThat(error).isEqualTo(305); + } } 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 deleted file mode 100644 index bac51f01ff..0000000000 --- a/libraries/common/src/test/java/androidx/media3/common/audio/SonicTestingUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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() {} -}