mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Implement static method to return estimated output samples
This method will replace the current estimation mechanism in place on `getSpeedAdjustedTimeAsync()`, which relies on heuristics from previous output rates and depends on the state of the `AudioProcessor`. The new static method should be considerably more accurate. PiperOrigin-RevId: 694607264
This commit is contained in:
parent
53953dd377
commit
f84f44bf6b
@ -25,6 +25,7 @@ import static java.lang.Math.min;
|
||||
import static java.lang.Math.round;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.LongArray;
|
||||
import androidx.media3.common.util.LongArrayQueue;
|
||||
@ -105,6 +106,42 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor {
|
||||
resetState();
|
||||
}
|
||||
|
||||
/** Returns the estimated number of samples output given the provided parameters. */
|
||||
public static long getSampleCountAfterProcessorApplied(
|
||||
SpeedProvider speedProvider,
|
||||
@IntRange(from = 1) int inputSampleRateHz,
|
||||
@IntRange(from = 1) long inputSamples) {
|
||||
checkArgument(speedProvider != null);
|
||||
checkArgument(inputSampleRateHz > 0);
|
||||
checkArgument(inputSamples > 0);
|
||||
|
||||
long outputSamples = 0;
|
||||
long positionSamples = 0;
|
||||
|
||||
while (positionSamples < inputSamples) {
|
||||
long boundarySamples =
|
||||
getNextSpeedChangeSamplePosition(speedProvider, positionSamples, inputSampleRateHz);
|
||||
|
||||
if (boundarySamples == C.INDEX_UNSET || boundarySamples > inputSamples) {
|
||||
boundarySamples = inputSamples;
|
||||
}
|
||||
|
||||
float speed = getSampleAlignedSpeed(speedProvider, positionSamples, inputSampleRateHz);
|
||||
// Input and output sample rates match because SpeedChangingAudioProcessor does not modify the
|
||||
// output sample rate.
|
||||
outputSamples +=
|
||||
Sonic.getExpectedFrameCountAfterProcessorApplied(
|
||||
/* inputSampleRateHz= */ inputSampleRateHz,
|
||||
/* outputSampleRateHz= */ inputSampleRateHz,
|
||||
/* speed= */ speed,
|
||||
/* pitch= */ speed,
|
||||
/* inputFrameCount= */ boundarySamples - positionSamples);
|
||||
positionSamples = boundarySamples;
|
||||
}
|
||||
|
||||
return outputSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationAfterProcessorApplied(long durationUs) {
|
||||
return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs);
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.common.audio;
|
||||
|
||||
import static androidx.media3.common.audio.SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied;
|
||||
import static androidx.media3.test.utils.TestUtil.buildTestData;
|
||||
import static androidx.media3.test.utils.TestUtil.generateFloatInRange;
|
||||
import static androidx.media3.test.utils.TestUtil.generateLong;
|
||||
@ -104,18 +105,9 @@ public class RandomParameterizedSpeedChangingAudioProcessorTest {
|
||||
ByteBuffer outBuffer;
|
||||
long outputFrameCount = 0;
|
||||
long totalInputFrameCount = 0;
|
||||
long expectedOutputFrames = 0;
|
||||
|
||||
for (int i = 0; i < frameCounts.size(); i++) {
|
||||
totalInputFrameCount += frameCounts.get(i);
|
||||
float speed = speeds.get(i).floatValue();
|
||||
expectedOutputFrames +=
|
||||
Sonic.getExpectedFrameCountAfterProcessorApplied(
|
||||
/* inputSampleRateHz= */ AUDIO_FORMAT.sampleRate,
|
||||
/* outputSampleRateHz= */ AUDIO_FORMAT.sampleRate,
|
||||
/* speed= */ speed,
|
||||
/* pitch= */ speed,
|
||||
/* inputFrameCount= */ frameCounts.get(i));
|
||||
}
|
||||
|
||||
SpeedProvider speedProvider =
|
||||
@ -124,6 +116,10 @@ public class RandomParameterizedSpeedChangingAudioProcessorTest {
|
||||
/* frameCounts= */ Ints.toArray(frameCounts),
|
||||
/* speeds= */ Floats.toArray(speeds));
|
||||
|
||||
long expectedOutputFrames =
|
||||
getSampleCountAfterProcessorApplied(
|
||||
speedProvider, AUDIO_FORMAT.sampleRate, totalInputFrameCount);
|
||||
|
||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||
new SpeedChangingAudioProcessor(speedProvider);
|
||||
speedChangingAudioProcessor.configure(AUDIO_FORMAT);
|
||||
|
@ -18,8 +18,11 @@ package androidx.media3.common.audio;
|
||||
import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.audio.AudioProcessor.AudioFormat;
|
||||
import androidx.media3.test.utils.TestSpeedProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -32,8 +35,8 @@ import org.junit.runner.RunWith;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SpeedChangingAudioProcessorTest {
|
||||
|
||||
private static final AudioProcessor.AudioFormat AUDIO_FORMAT =
|
||||
new AudioProcessor.AudioFormat(
|
||||
private static final AudioFormat AUDIO_FORMAT =
|
||||
new AudioFormat(
|
||||
/* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
|
||||
|
||||
@Test
|
||||
@ -600,6 +603,104 @@ public class SpeedChangingAudioProcessorTest {
|
||||
assertThat(outputFrameCount).isWithin(1).of(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSampleCountAfterProcessorApplied_withConstantSpeed_outputsExpectedSamples() {
|
||||
SpeedProvider speedProvider =
|
||||
TestSpeedProvider.createWithFrameCounts(
|
||||
new AudioFormat(/* sampleRate= */ 48000, /* channelCount= */ 1, C.ENCODING_PCM_16BIT),
|
||||
/* frameCounts= */ new int[] {100},
|
||||
/* speeds= */ new float[] {2.f});
|
||||
|
||||
long sampleCountAfterProcessorApplied =
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 100);
|
||||
assertThat(sampleCountAfterProcessorApplied).isEqualTo(50);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSampleCountAfterProcessorApplied_withMultipleSpeeds_outputsExpectedSamples() {
|
||||
SpeedProvider speedProvider =
|
||||
TestSpeedProvider.createWithFrameCounts(
|
||||
AUDIO_FORMAT,
|
||||
/* frameCounts= */ new int[] {100, 400, 50},
|
||||
/* speeds= */ new float[] {2.f, 4f, 0.5f});
|
||||
|
||||
long sampleCountAfterProcessorApplied =
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 550);
|
||||
assertThat(sampleCountAfterProcessorApplied).isEqualTo(250);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
getSampleCountAfterProcessorApplied_beyondLastSpeedRegion_stillAppliesLastSpeedValue() {
|
||||
SpeedProvider speedProvider =
|
||||
TestSpeedProvider.createWithFrameCounts(
|
||||
AUDIO_FORMAT,
|
||||
/* frameCounts= */ new int[] {100, 400, 50},
|
||||
/* speeds= */ new float[] {2.f, 4f, 0.5f});
|
||||
|
||||
long sampleCountAfterProcessorApplied =
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 3000);
|
||||
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5150);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
getSampleCountAfterProcessorApplied_withInputCountBeyondIntRange_outputsExpectedSamples() {
|
||||
SpeedProvider speedProvider =
|
||||
TestSpeedProvider.createWithFrameCounts(
|
||||
AUDIO_FORMAT,
|
||||
/* frameCounts= */ new int[] {1000, 10000, 8200},
|
||||
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
|
||||
long sampleCountAfterProcessorApplied =
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 3_000_000_000L);
|
||||
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5999984250L);
|
||||
}
|
||||
|
||||
// Testing range validation.
|
||||
@SuppressLint("Range")
|
||||
@Test
|
||||
public void getSampleCountAfterProcessorApplied_withNegativeSampleCount_throws() {
|
||||
SpeedProvider speedProvider =
|
||||
TestSpeedProvider.createWithFrameCounts(
|
||||
AUDIO_FORMAT,
|
||||
/* frameCounts= */ new int[] {1000, 10000, 8200},
|
||||
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ -2L));
|
||||
}
|
||||
|
||||
// Testing range validation.
|
||||
@SuppressLint("Range")
|
||||
@Test
|
||||
public void getSampleCountAfterProcessorApplied_withZeroSampleRate_throws() {
|
||||
SpeedProvider speedProvider =
|
||||
TestSpeedProvider.createWithFrameCounts(
|
||||
AUDIO_FORMAT,
|
||||
/* frameCounts= */ new int[] {1000, 10000, 8200},
|
||||
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
speedProvider, /* inputSampleRateHz= */ 0, /* inputSamples= */ 1000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSampleCountAfterProcessorApplied_withNullSpeedProvider_throws() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
|
||||
/* speedProvider= */ null, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 1000L));
|
||||
}
|
||||
|
||||
private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor(
|
||||
SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException {
|
||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||
|
Loading…
x
Reference in New Issue
Block a user