mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
This commit is contained in:
parent
a913a16db4
commit
9653fa64fe
@ -19,6 +19,8 @@ package androidx.media3.common.audio;
|
|||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -70,6 +72,38 @@ import java.util.Arrays;
|
|||||||
private int maxDiff;
|
private int maxDiff;
|
||||||
private double accumulatedSpeedAdjustmentError;
|
private double accumulatedSpeedAdjustmentError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns expected accumulated truncation error for {@link Sonic}'s resampling algorithm, given
|
||||||
|
* an input length, input sample rate, and resampling rate.
|
||||||
|
*
|
||||||
|
* <p><b>Note:</b> 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.
|
* Creates a new Sonic audio stream processor.
|
||||||
*
|
*
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.audio;
|
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 androidx.media3.test.utils.TestUtil.generateFloatInRange;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.audio;
|
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.buildTestData;
|
||||||
import static androidx.media3.test.utils.TestUtil.generateFloatInRange;
|
import static androidx.media3.test.utils.TestUtil.generateFloatInRange;
|
||||||
import static androidx.media3.test.utils.TestUtil.generateLong;
|
import static androidx.media3.test.utils.TestUtil.generateLong;
|
||||||
|
@ -15,9 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.audio;
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.audio.Sonic.calculateAccumulatedTruncationErrorForResampling;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -107,4 +109,19 @@ public class SonicTest {
|
|||||||
|
|
||||||
assertThat(outputBuffer.array()).isEqualTo(new short[] {0, 4, 8});
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> This method is only necessary until we address b/361768785 and fix the
|
|
||||||
* underlying truncation issue.
|
|
||||||
*
|
|
||||||
* <p>The accumulated truncation error is calculated as follows:
|
|
||||||
*
|
|
||||||
* <ol>
|
|
||||||
* <li>Individual truncation error: Divide sample rate by resampling rate, and calculate delta
|
|
||||||
* between floating point result and truncated int representation.
|
|
||||||
* <li>Truncation accumulation count: Divide length by sample rate to obtain number of times
|
|
||||||
* that truncation error accumulates.
|
|
||||||
* <li>Accumulated truncation error: Multiply results of 1 and 2.
|
|
||||||
* </ol>
|
|
||||||
*
|
|
||||||
* @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() {}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user