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 static java.lang.Math.round;
|
||||||
|
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.IntRange;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.util.LongArray;
|
import androidx.media3.common.util.LongArray;
|
||||||
import androidx.media3.common.util.LongArrayQueue;
|
import androidx.media3.common.util.LongArrayQueue;
|
||||||
@ -105,6 +106,42 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor {
|
|||||||
resetState();
|
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
|
@Override
|
||||||
public long getDurationAfterProcessorApplied(long durationUs) {
|
public long getDurationAfterProcessorApplied(long durationUs) {
|
||||||
return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs);
|
return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs);
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.audio;
|
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.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;
|
||||||
@ -104,18 +105,9 @@ public class RandomParameterizedSpeedChangingAudioProcessorTest {
|
|||||||
ByteBuffer outBuffer;
|
ByteBuffer outBuffer;
|
||||||
long outputFrameCount = 0;
|
long outputFrameCount = 0;
|
||||||
long totalInputFrameCount = 0;
|
long totalInputFrameCount = 0;
|
||||||
long expectedOutputFrames = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < frameCounts.size(); i++) {
|
for (int i = 0; i < frameCounts.size(); i++) {
|
||||||
totalInputFrameCount += frameCounts.get(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 =
|
SpeedProvider speedProvider =
|
||||||
@ -124,6 +116,10 @@ public class RandomParameterizedSpeedChangingAudioProcessorTest {
|
|||||||
/* frameCounts= */ Ints.toArray(frameCounts),
|
/* frameCounts= */ Ints.toArray(frameCounts),
|
||||||
/* speeds= */ Floats.toArray(speeds));
|
/* speeds= */ Floats.toArray(speeds));
|
||||||
|
|
||||||
|
long expectedOutputFrames =
|
||||||
|
getSampleCountAfterProcessorApplied(
|
||||||
|
speedProvider, AUDIO_FORMAT.sampleRate, totalInputFrameCount);
|
||||||
|
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
new SpeedChangingAudioProcessor(speedProvider);
|
new SpeedChangingAudioProcessor(speedProvider);
|
||||||
speedChangingAudioProcessor.configure(AUDIO_FORMAT);
|
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.audio.AudioProcessor.EMPTY_BUFFER;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.C;
|
||||||
|
import androidx.media3.common.audio.AudioProcessor.AudioFormat;
|
||||||
import androidx.media3.test.utils.TestSpeedProvider;
|
import androidx.media3.test.utils.TestSpeedProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -32,8 +35,8 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class SpeedChangingAudioProcessorTest {
|
public class SpeedChangingAudioProcessorTest {
|
||||||
|
|
||||||
private static final AudioProcessor.AudioFormat AUDIO_FORMAT =
|
private static final AudioFormat AUDIO_FORMAT =
|
||||||
new AudioProcessor.AudioFormat(
|
new AudioFormat(
|
||||||
/* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
|
/* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -600,6 +603,104 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
assertThat(outputFrameCount).isWithin(1).of(4);
|
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(
|
private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor(
|
||||||
SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException {
|
SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException {
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user