Use static methods for getSpeedAdjustedTimeAsync and getMediaDurationUs

This change simplifies SpeedChangingAudioProcessor by removing unneeded
heuristics and synchronization added as workarounds to estimate input
and output frame counts.

The synchronization between the video processing and audio processing
threads cannot be completely removed yet because the static methods
depend on the input sample rate for the calculations. However, once
`SpeedChangingAudioProcessor` has been configured, then
`#getSpeedAdjustedTimeAsync()` should invoke the callback immediately.

PiperOrigin-RevId: 712893246
This commit is contained in:
ivanbuper 2025-01-07 06:46:56 -08:00 committed by Copybara-Service
parent 2dc6af1fae
commit 871381288c
2 changed files with 300 additions and 368 deletions

View File

@ -21,25 +21,24 @@ import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.SpeedProviderUtil.getNextSpeedChangeSamplePosition; import static androidx.media3.common.util.SpeedProviderUtil.getNextSpeedChangeSamplePosition;
import static androidx.media3.common.util.SpeedProviderUtil.getSampleAlignedSpeed; import static androidx.media3.common.util.SpeedProviderUtil.getSampleAlignedSpeed;
import static androidx.media3.common.util.Util.sampleCountToDurationUs; import static androidx.media3.common.util.Util.sampleCountToDurationUs;
import static androidx.media3.common.util.Util.scaleLargeValue;
import static java.lang.Math.min; import static java.lang.Math.min;
import static java.lang.Math.round;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.LongArray; import androidx.media3.common.Format;
import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.SpeedProviderUtil; import androidx.media3.common.util.SpeedProviderUtil;
import androidx.media3.common.util.TimestampConsumer; import androidx.media3.common.util.TimestampConsumer;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import java.math.RoundingMode;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
import java.util.function.LongConsumer; import java.util.function.LongConsumer;
import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* An {@link AudioProcessor} that changes the speed of audio samples depending on their timestamp. * An {@link AudioProcessor} that changes the speed of audio samples depending on their timestamp.
@ -67,34 +66,12 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@GuardedBy("lock") @GuardedBy("lock")
private final Queue<TimestampConsumer> pendingCallbacks; private final Queue<TimestampConsumer> pendingCallbacks;
// Elements in the same positions in the arrays are associated.
@GuardedBy("lock")
private LongArray inputSegmentStartTimesUs;
@GuardedBy("lock")
private LongArray outputSegmentStartTimesUs;
@GuardedBy("lock")
private long lastProcessedInputTimeUs;
@GuardedBy("lock")
private long lastSpeedAdjustedInputTimeUs;
@GuardedBy("lock")
private long lastSpeedAdjustedOutputTimeUs;
@GuardedBy("lock")
private long speedAdjustedTimeAsyncInputTimeUs;
@GuardedBy("lock")
private float currentSpeed; private float currentSpeed;
private long framesRead; private long framesRead;
private boolean endOfStreamQueuedToSonic; private boolean endOfStreamQueuedToSonic;
/** The current input audio format. */ /** The current input audio format. */
@GuardedBy("lock")
private AudioFormat inputAudioFormat; private AudioFormat inputAudioFormat;
private AudioFormat pendingInputAudioFormat; private AudioFormat pendingInputAudioFormat;
@ -112,7 +89,6 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
new SynchronizedSonicAudioProcessor(lock, /* keepActiveWithDefaultParameters= */ true); new SynchronizedSonicAudioProcessor(lock, /* keepActiveWithDefaultParameters= */ true);
pendingCallbackInputTimesUs = new LongArrayQueue(); pendingCallbackInputTimesUs = new LongArrayQueue();
pendingCallbacks = new ArrayDeque<>(); pendingCallbacks = new ArrayDeque<>();
speedAdjustedTimeAsyncInputTimeUs = C.TIME_UNSET;
resetInternalState(/* shouldResetSpeed= */ true); resetInternalState(/* shouldResetSpeed= */ true);
} }
@ -120,10 +96,10 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
public static long getSampleCountAfterProcessorApplied( public static long getSampleCountAfterProcessorApplied(
SpeedProvider speedProvider, SpeedProvider speedProvider,
@IntRange(from = 1) int inputSampleRateHz, @IntRange(from = 1) int inputSampleRateHz,
@IntRange(from = 1) long inputSamples) { @IntRange(from = 0) long inputSamples) {
checkArgument(speedProvider != null); checkArgument(speedProvider != null);
checkArgument(inputSampleRateHz > 0); checkArgument(inputSampleRateHz > 0);
checkArgument(inputSamples > 0); checkArgument(inputSamples >= 0);
long outputSamples = 0; long outputSamples = 0;
long positionSamples = 0; long positionSamples = 0;
@ -171,18 +147,22 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@Override @Override
public void queueInput(ByteBuffer inputBuffer) { public void queueInput(ByteBuffer inputBuffer) {
long currentTimeUs = sampleCountToDurationUs(framesRead, inputAudioFormat.sampleRate); AudioFormat format;
float newSpeed = getSampleAlignedSpeed(speedProvider, framesRead, inputAudioFormat.sampleRate); synchronized (lock) {
long nextSpeedChangeSamplePosition = format = inputAudioFormat;
getNextSpeedChangeSamplePosition(speedProvider, framesRead, inputAudioFormat.sampleRate); }
updateSpeed(newSpeed, currentTimeUs); float newSpeed = getSampleAlignedSpeed(speedProvider, framesRead, format.sampleRate);
long nextSpeedChangeSamplePosition =
getNextSpeedChangeSamplePosition(speedProvider, framesRead, format.sampleRate);
updateSpeed(newSpeed);
int inputBufferLimit = inputBuffer.limit(); int inputBufferLimit = inputBuffer.limit();
int bytesToNextSpeedChange; int bytesToNextSpeedChange;
if (nextSpeedChangeSamplePosition != C.INDEX_UNSET) { if (nextSpeedChangeSamplePosition != C.INDEX_UNSET) {
bytesToNextSpeedChange = bytesToNextSpeedChange =
(int) ((nextSpeedChangeSamplePosition - framesRead) * inputAudioFormat.bytesPerFrame); (int) ((nextSpeedChangeSamplePosition - framesRead) * format.bytesPerFrame);
// Update the input buffer limit to make sure that all samples processed have the same speed. // Update the input buffer limit to make sure that all samples processed have the same speed.
inputBuffer.limit(min(inputBufferLimit, inputBuffer.position() + bytesToNextSpeedChange)); inputBuffer.limit(min(inputBufferLimit, inputBuffer.position() + bytesToNextSpeedChange));
} else { } else {
@ -197,10 +177,8 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
endOfStreamQueuedToSonic = true; endOfStreamQueuedToSonic = true;
} }
long bytesRead = inputBuffer.position() - startPosition; long bytesRead = inputBuffer.position() - startPosition;
checkState( checkState(bytesRead % format.bytesPerFrame == 0, "A frame was not queued completely.");
bytesRead % inputAudioFormat.bytesPerFrame == 0, "A frame was not queued completely."); framesRead += bytesRead / format.bytesPerFrame;
framesRead += bytesRead / inputAudioFormat.bytesPerFrame;
updateLastProcessedInputTime();
inputBuffer.limit(inputBufferLimit); inputBuffer.limit(inputBufferLimit);
} }
@ -215,9 +193,7 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@Override @Override
public ByteBuffer getOutput() { public ByteBuffer getOutput() {
ByteBuffer output = sonicAudioProcessor.getOutput(); return sonicAudioProcessor.getOutput();
processPendingCallbacks();
return output;
} }
@Override @Override
@ -228,9 +204,12 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@Override @Override
public void flush() { public void flush() {
inputEnded = false; inputEnded = false;
inputAudioFormat = pendingInputAudioFormat;
resetInternalState(/* shouldResetSpeed= */ false); resetInternalState(/* shouldResetSpeed= */ false);
synchronized (lock) {
inputAudioFormat = pendingInputAudioFormat;
sonicAudioProcessor.flush(); sonicAudioProcessor.flush();
processPendingCallbacks();
}
} }
@Override @Override
@ -238,7 +217,11 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
flush(); flush();
pendingInputAudioFormat = AudioFormat.NOT_SET; pendingInputAudioFormat = AudioFormat.NOT_SET;
pendingOutputAudioFormat = AudioFormat.NOT_SET; pendingOutputAudioFormat = AudioFormat.NOT_SET;
synchronized (lock) {
inputAudioFormat = AudioFormat.NOT_SET; inputAudioFormat = AudioFormat.NOT_SET;
pendingCallbackInputTimesUs.clear();
pendingCallbacks.clear();
}
resetInternalState(/* shouldResetSpeed= */ true); resetInternalState(/* shouldResetSpeed= */ true);
sonicAudioProcessor.reset(); sonicAudioProcessor.reset();
} }
@ -261,110 +244,119 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
* @param callback The callback called with the output time. May be called on a different thread * @param callback The callback called with the output time. May be called on a different thread
* from the caller of this method. * from the caller of this method.
*/ */
// TODO(b/381553948): Accept an executor on which to dispatch the callback.
public void getSpeedAdjustedTimeAsync(long inputTimeUs, TimestampConsumer callback) { public void getSpeedAdjustedTimeAsync(long inputTimeUs, TimestampConsumer callback) {
int sampleRate;
synchronized (lock) { synchronized (lock) {
checkArgument(speedAdjustedTimeAsyncInputTimeUs < inputTimeUs); sampleRate = inputAudioFormat.sampleRate;
speedAdjustedTimeAsyncInputTimeUs = inputTimeUs;
if ((inputTimeUs <= lastProcessedInputTimeUs && pendingCallbackInputTimesUs.isEmpty()) if (sampleRate == Format.NO_VALUE) {
|| isEnded()) {
callback.onTimestamp(calculateSpeedAdjustedTime(inputTimeUs));
return;
}
pendingCallbackInputTimesUs.add(inputTimeUs); pendingCallbackInputTimesUs.add(inputTimeUs);
pendingCallbacks.add(callback); pendingCallbacks.add(callback);
return;
} }
} }
// TODO(b/381553948): Use an executor to invoke callback.
callback.onTimestamp(
getDurationUsAfterProcessorApplied(speedProvider, sampleRate, inputTimeUs));
}
/** /**
* Returns the input media duration for the given playout duration. * Returns the input media duration in microseconds for the given playout duration.
* *
* <p>Both durations are counted from the last {@link #reset()} or {@link #flush()} of the audio * <p>This method returns the inverse of {@link #getSpeedAdjustedTimeAsync} when the instance has
* processor. * been configured and flushed. Otherwise, it returns {@code playoutDurationUs}.
*
* <p>The {@code playoutDurationUs} must be less than last processed buffer output time.
* *
* @param playoutDurationUs The playout duration in microseconds. * @param playoutDurationUs The playout duration in microseconds.
* @return The corresponding input duration in microseconds.
*/ */
public long getMediaDurationUs(long playoutDurationUs) { public long getMediaDurationUs(long playoutDurationUs) {
int sampleRate;
synchronized (lock) { synchronized (lock) {
int floorIndex = outputSegmentStartTimesUs.size() - 1; sampleRate = inputAudioFormat.sampleRate;
while (floorIndex > 0 && outputSegmentStartTimesUs.get(floorIndex) > playoutDurationUs) {
floorIndex--;
} }
long lastSegmentOutputDurationUs = if (sampleRate == Format.NO_VALUE) {
playoutDurationUs - outputSegmentStartTimesUs.get(floorIndex); return playoutDurationUs;
long lastSegmentInputDurationUs;
if (floorIndex == outputSegmentStartTimesUs.size() - 1) {
lastSegmentInputDurationUs = getMediaDurationUsAtCurrentSpeed(lastSegmentOutputDurationUs);
} else {
lastSegmentInputDurationUs =
round(
lastSegmentOutputDurationUs
* divide(
inputSegmentStartTimesUs.get(floorIndex + 1)
- inputSegmentStartTimesUs.get(floorIndex),
outputSegmentStartTimesUs.get(floorIndex + 1)
- outputSegmentStartTimesUs.get(floorIndex)));
}
return inputSegmentStartTimesUs.get(floorIndex) + lastSegmentInputDurationUs;
} }
long outputSamples =
scaleLargeValue(playoutDurationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.HALF_EVEN);
long inputSamples = getInputFrameCountForOutput(speedProvider, sampleRate, outputSamples);
return sampleCountToDurationUs(inputSamples, sampleRate);
} }
/** /**
* Assuming enough audio has been processed, calculates the time at which the {@code inputTimeUs} * Returns the number of input frames needed to output a specific number of frames, given a speed
* is outputted at after the speed changes has been applied. * provider, input sample rate, and number of output frames.
*
* <p>This is the inverse operation of {@link #getSampleCountAfterProcessorApplied}.
*/ */
@SuppressWarnings("GuardedBy") // All call sites are guarded. @VisibleForTesting
private long calculateSpeedAdjustedTime(long inputTimeUs) { /* package */ static long getInputFrameCountForOutput(
int floorIndex = inputSegmentStartTimesUs.size() - 1; SpeedProvider speedProvider,
while (floorIndex > 0 && inputSegmentStartTimesUs.get(floorIndex) > inputTimeUs) { @IntRange(from = 1) int inputSampleRate,
floorIndex--; @IntRange(from = 0) long outputFrameCount) {
} checkArgument(inputSampleRate > 0);
long lastSegmentOutputDurationUs; checkArgument(outputFrameCount >= 0);
if (floorIndex == inputSegmentStartTimesUs.size() - 1) {
if (lastSpeedAdjustedInputTimeUs < inputSegmentStartTimesUs.get(floorIndex)) { long inputSampleCount = 0;
lastSpeedAdjustedInputTimeUs = inputSegmentStartTimesUs.get(floorIndex); while (outputFrameCount > 0) {
lastSpeedAdjustedOutputTimeUs = outputSegmentStartTimesUs.get(floorIndex); long boundarySamples =
} getNextSpeedChangeSamplePosition(speedProvider, inputSampleCount, inputSampleRate);
long lastSegmentInputDurationUs = inputTimeUs - lastSpeedAdjustedInputTimeUs; float speed = getSampleAlignedSpeed(speedProvider, inputSampleCount, inputSampleRate);
lastSegmentOutputDurationUs = getPlayoutDurationUsAtCurrentSpeed(lastSegmentInputDurationUs);
long outputSamplesForSection =
Sonic.getExpectedFrameCountAfterProcessorApplied(
/* inputSampleRateHz= */ inputSampleRate,
/* outputSampleRateHz= */ inputSampleRate,
/* speed= */ speed,
/* pitch= */ speed,
/* inputFrameCount= */ boundarySamples - inputSampleCount);
if (boundarySamples == C.INDEX_UNSET || outputSamplesForSection > outputFrameCount) {
inputSampleCount +=
Sonic.getExpectedInputFrameCountForOutputFrameCount(
/* inputSampleRateHz= */ inputSampleRate,
/* outputSampleRateHz= */ inputSampleRate,
/* speed= */ speed,
/* pitch= */ speed,
outputFrameCount);
outputFrameCount = 0;
} else { } else {
long lastSegmentInputDurationUs = inputTimeUs - lastSpeedAdjustedInputTimeUs; outputFrameCount -= outputSamplesForSection;
lastSegmentOutputDurationUs = inputSampleCount = boundarySamples;
round(
lastSegmentInputDurationUs
* divide(
outputSegmentStartTimesUs.get(floorIndex + 1)
- outputSegmentStartTimesUs.get(floorIndex),
inputSegmentStartTimesUs.get(floorIndex + 1)
- inputSegmentStartTimesUs.get(floorIndex)));
} }
lastSpeedAdjustedInputTimeUs = inputTimeUs;
lastSpeedAdjustedOutputTimeUs += lastSegmentOutputDurationUs;
return lastSpeedAdjustedOutputTimeUs;
} }
private static double divide(long dividend, long divisor) { return inputSampleCount;
return ((double) dividend) / divisor; }
private static long getDurationUsAfterProcessorApplied(
SpeedProvider speedProvider, int sampleRate, long inputDurationUs) {
long inputSamples =
scaleLargeValue(inputDurationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.HALF_EVEN);
long outputSamples =
getSampleCountAfterProcessorApplied(speedProvider, sampleRate, inputSamples);
return sampleCountToDurationUs(outputSamples, sampleRate);
} }
private void processPendingCallbacks() { private void processPendingCallbacks() {
synchronized (lock) { synchronized (lock) {
while (!pendingCallbacks.isEmpty() if (inputAudioFormat.sampleRate == Format.NO_VALUE) {
&& (pendingCallbackInputTimesUs.element() <= lastProcessedInputTimeUs || isEnded())) { return;
pendingCallbacks }
.remove()
.onTimestamp(calculateSpeedAdjustedTime(pendingCallbackInputTimesUs.remove())); while (!pendingCallbacks.isEmpty()) {
long inputTimeUs = pendingCallbackInputTimesUs.remove();
TimestampConsumer consumer = pendingCallbacks.remove();
// TODO(b/381553948): Use an executor to invoke callback.
consumer.onTimestamp(
getDurationUsAfterProcessorApplied(
speedProvider, inputAudioFormat.sampleRate, inputTimeUs));
} }
} }
} }
private void updateSpeed(float newSpeed, long timeUs) { private void updateSpeed(float newSpeed) {
synchronized (lock) {
if (newSpeed != currentSpeed) { if (newSpeed != currentSpeed) {
updateSpeedChangeArrays(timeUs);
currentSpeed = newSpeed; currentSpeed = newSpeed;
sonicAudioProcessor.setSpeed(newSpeed); sonicAudioProcessor.setSpeed(newSpeed);
sonicAudioProcessor.setPitch(newSpeed); sonicAudioProcessor.setPitch(newSpeed);
@ -373,44 +365,6 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
endOfStreamQueuedToSonic = false; endOfStreamQueuedToSonic = false;
} }
} }
}
@SuppressWarnings("GuardedBy") // All call sites are guarded.
private void updateSpeedChangeArrays(long currentSpeedChangeInputTimeUs) {
long lastSpeedChangeOutputTimeUs =
outputSegmentStartTimesUs.get(outputSegmentStartTimesUs.size() - 1);
long lastSpeedChangeInputTimeUs =
inputSegmentStartTimesUs.get(inputSegmentStartTimesUs.size() - 1);
long lastSpeedSegmentMediaDurationUs =
currentSpeedChangeInputTimeUs - lastSpeedChangeInputTimeUs;
inputSegmentStartTimesUs.add(currentSpeedChangeInputTimeUs);
outputSegmentStartTimesUs.add(
lastSpeedChangeOutputTimeUs
+ getPlayoutDurationUsAtCurrentSpeed(lastSpeedSegmentMediaDurationUs));
}
private long getPlayoutDurationUsAtCurrentSpeed(long mediaDurationUs) {
return sonicAudioProcessor.getPlayoutDuration(mediaDurationUs);
}
private long getMediaDurationUsAtCurrentSpeed(long playoutDurationUs) {
return sonicAudioProcessor.getMediaDuration(playoutDurationUs);
}
private void updateLastProcessedInputTime() {
synchronized (lock) {
// TODO - b/320242819: Investigate whether bytesRead can be used here rather than
// sonicAudioProcessor.getProcessedInputBytes().
long currentProcessedInputDurationUs =
Util.scaleLargeTimestamp(
/* timestamp= */ sonicAudioProcessor.getProcessedInputBytes(),
/* multiplier= */ C.MICROS_PER_SECOND,
/* divisor= */ (long) inputAudioFormat.sampleRate * inputAudioFormat.bytesPerFrame);
lastProcessedInputTimeUs =
inputSegmentStartTimesUs.get(inputSegmentStartTimesUs.size() - 1)
+ currentProcessedInputDurationUs;
}
}
/** /**
* Resets internal fields to their default value. * Resets internal fields to their default value.
@ -420,28 +374,12 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
* *
* @param shouldResetSpeed Whether {@link #currentSpeed} should be reset to its default value. * @param shouldResetSpeed Whether {@link #currentSpeed} should be reset to its default value.
*/ */
@EnsuresNonNull({"inputSegmentStartTimesUs", "outputSegmentStartTimesUs"})
@RequiresNonNull("lock")
private void resetInternalState( private void resetInternalState(
@UnknownInitialization SpeedChangingAudioProcessor this, boolean shouldResetSpeed) { @UnknownInitialization SpeedChangingAudioProcessor this, boolean shouldResetSpeed) {
synchronized (lock) {
inputSegmentStartTimesUs = new LongArray();
outputSegmentStartTimesUs = new LongArray();
inputSegmentStartTimesUs.add(0);
outputSegmentStartTimesUs.add(0);
lastProcessedInputTimeUs = 0;
lastSpeedAdjustedInputTimeUs = 0;
lastSpeedAdjustedOutputTimeUs = 0;
if (shouldResetSpeed) { if (shouldResetSpeed) {
currentSpeed = 1f; currentSpeed = 1f;
} }
}
framesRead = 0; framesRead = 0;
endOfStreamQueuedToSonic = false; endOfStreamQueuedToSonic = false;
// TODO: b/339842724 - This should ideally also reset speedAdjustedTimeAsyncInputTimeUs and
// clear pendingCallbacks and pendingCallbacksInputTimes. We can't do this at the moment
// because some clients register callbacks with getSpeedAdjustedTimeAsync before this audio
// processor is flushed.
} }
} }

View File

@ -16,7 +16,7 @@
package androidx.media3.common.audio; 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.audio.SpeedChangingAudioProcessor.getInputFrameCountForOutput;
import static androidx.media3.test.utils.TestUtil.getNonRandomByteBuffer; import static androidx.media3.test.utils.TestUtil.getNonRandomByteBuffer;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
@ -36,53 +36,59 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class SpeedChangingAudioProcessorTest { public class SpeedChangingAudioProcessorTest {
private static final AudioFormat AUDIO_FORMAT = private static final AudioFormat AUDIO_FORMAT_44_100HZ =
new AudioFormat( new AudioFormat(
/* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT); /* sampleRate= */ 44_100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
private static final AudioFormat AUDIO_FORMAT_50_000HZ =
new AudioFormat(
/* sampleRate= */ 50_000, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
@Test @Test
public void queueInput_noSpeedChange_doesNotOverwriteInput() throws Exception { public void queueInput_noSpeedChange_doesNotOverwriteInput() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
assertThat(inputBuffer) assertThat(inputBuffer)
.isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame)); .isEqualTo(
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame));
} }
@Test @Test
public void queueInput_speedChange_doesNotOverwriteInput() throws Exception { public void queueInput_speedChange_doesNotOverwriteInput() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
assertThat(inputBuffer) assertThat(inputBuffer)
.isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame)); .isEqualTo(
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame));
} }
@Test @Test
public void queueInput_noSpeedChange_copiesSamples() throws Exception { public void queueInput_noSpeedChange_copiesSamples() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
@ -96,11 +102,11 @@ public class SpeedChangingAudioProcessorTest {
public void queueInput_speedChange_modifiesSamples() throws Exception { public void queueInput_speedChange_modifiesSamples() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
@ -115,11 +121,13 @@ public class SpeedChangingAudioProcessorTest {
public void queueInput_noSpeedChangeAfterSpeedChange_copiesSamples() throws Exception { public void queueInput_noSpeedChangeAfterSpeedChange_copiesSamples() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
@ -136,11 +144,13 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {1, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
@ -150,7 +160,7 @@ public class SpeedChangingAudioProcessorTest {
speedProvider = speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
inputBuffer.rewind(); inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
@ -165,11 +175,13 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {3, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
@ -179,7 +191,7 @@ public class SpeedChangingAudioProcessorTest {
speedProvider = speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
inputBuffer.rewind(); inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
@ -194,18 +206,20 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {2, 3});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
ByteBuffer outputBuffer = getAudioProcessorOutput(speedChangingAudioProcessor); ByteBuffer outputBuffer = getAudioProcessorOutput(speedChangingAudioProcessor);
speedProvider = speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
inputBuffer.rewind(); inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
@ -218,7 +232,7 @@ public class SpeedChangingAudioProcessorTest {
@Test @Test
public void queueInput_multipleSpeedsInBufferWithLimitAtFrameBoundary_readsDataUntilSpeedLimit() public void queueInput_multipleSpeedsInBufferWithLimitAtFrameBoundary_readsDataUntilSpeedLimit()
throws Exception { throws Exception {
long speedChangeTimeUs = 4 * C.MICROS_PER_SECOND / AUDIO_FORMAT.sampleRate; long speedChangeTimeUs = 4 * C.MICROS_PER_SECOND / AUDIO_FORMAT_44_100HZ.sampleRate;
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes( TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0L, speedChangeTimeUs}, /* startTimesUs= */ new long[] {0L, speedChangeTimeUs},
@ -226,19 +240,19 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
int inputBufferLimit = inputBuffer.limit(); int inputBufferLimit = inputBuffer.limit();
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT.bytesPerFrame); assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT_44_100HZ.bytesPerFrame);
assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit); assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit);
} }
@Test @Test
public void queueInput_multipleSpeedsInBufferWithLimitInsideFrame_readsDataUntilSpeedLimit() public void queueInput_multipleSpeedsInBufferWithLimitInsideFrame_readsDataUntilSpeedLimit()
throws Exception { throws Exception {
long speedChangeTimeUs = (long) (3.5 * C.MICROS_PER_SECOND / AUDIO_FORMAT.sampleRate); long speedChangeTimeUs = (long) (3.5 * C.MICROS_PER_SECOND / AUDIO_FORMAT_44_100HZ.sampleRate);
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes( TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0L, speedChangeTimeUs}, /* startTimesUs= */ new long[] {0L, speedChangeTimeUs},
@ -246,12 +260,12 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
int inputBufferLimit = inputBuffer.limit(); int inputBufferLimit = inputBuffer.limit();
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT.bytesPerFrame); assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT_44_100HZ.bytesPerFrame);
assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit); assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit);
} }
@ -266,18 +280,18 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
// SpeedChangingAudioProcessor only queues samples until the next speed change. // SpeedChangingAudioProcessor only queues samples until the next speed change.
while (inputBuffer.hasRemaining()) { while (inputBuffer.hasRemaining()) {
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
outputFrames += outputFrames +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
} }
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
outputFrames += outputFrames +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
// We allow 1 sample of tolerance per speed change. // We allow 1 sample of tolerance per speed change.
assertThat(outputFrames).isWithin(1).of(3); assertThat(outputFrames).isWithin(1).of(3);
} }
@ -287,11 +301,13 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
@ -307,11 +323,13 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {1, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind(); inputBuffer.rewind();
@ -327,11 +345,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
@ -344,11 +362,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception { throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
@ -360,7 +378,7 @@ public class SpeedChangingAudioProcessorTest {
public void queueEndOfStream_noInputQueued_endsProcessor() throws Exception { public void queueEndOfStream_noInputQueued_endsProcessor() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
@ -373,11 +391,11 @@ public class SpeedChangingAudioProcessorTest {
public void isEnded_afterNoSpeedChangeAndOutputRetrieved_isFalse() throws Exception { public void isEnded_afterNoSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor); getAudioProcessorOutput(speedChangingAudioProcessor);
@ -389,11 +407,11 @@ public class SpeedChangingAudioProcessorTest {
public void isEnded_afterSpeedChangeAndOutputRetrieved_isFalse() throws Exception { public void isEnded_afterSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor); getAudioProcessorOutput(speedChangingAudioProcessor);
@ -402,147 +420,89 @@ public class SpeedChangingAudioProcessorTest {
} }
@Test @Test
public void getSpeedAdjustedTimeAsync_callbacksCalledWithCorrectParameters() throws Exception { public void getSpeedAdjustedTimeAsync_beforeFlush_callbacksCalledWithCorrectParametersAfterFlush()
throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>(); ArrayList<Long> outputTimesUs = new ArrayList<>();
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate). // Sample period = 20us.
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {6, 6},
/* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); new SpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync( speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 50L, outputTimesUs::add); /* inputTimeUs= */ 40L, outputTimesUs::add);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync( speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 100L, outputTimesUs::add); /* inputTimeUs= */ 80L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync( speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 150L, outputTimesUs::add); /* inputTimeUs= */ 160L, outputTimesUs::add);
// 150 is after the speed change so floor(113 / 2 + (150 - 113)*1) -> 93 assertThat(outputTimesUs).isEmpty();
assertThat(outputTimesUs).containsExactly(25L, 50L, 93L); speedChangingAudioProcessor.flush();
assertThat(outputTimesUs).containsExactly(20L, 40L, 100L);
} }
@Test @Test
public void getSpeedAdjustedTimeAsync_afterFlush_callbacksCalledWithCorrectParameters() public void getSpeedAdjustedTimeAsync_afterCallToFlush_callbacksCalledWithCorrectParameters()
throws Exception { throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>(); ArrayList<Long> outputTimesUs = new ArrayList<>();
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate). Also add another speed change // Sample period = 20us.
// to 3x at a later point that should not be used if the flush is handled correctly.
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {5, 5, 5}, /* frameCounts= */ new int[] {6, 6},
/* speeds= */ new float[] {2, 1, 3}); /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); new SpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
// Use the audio processor before a flush
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
// Flush and use it again.
speedChangingAudioProcessor.flush(); speedChangingAudioProcessor.flush();
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 50L, outputTimesUs::add);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 100L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 150L, outputTimesUs::add);
// 150 is after the speed change so floor(113 / 2 + (150 - 113)*1) -> 93 speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
assertThat(outputTimesUs).containsExactly(25L, 50L, 93L); /* inputTimeUs= */ 40L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 80L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 160L, outputTimesUs::add);
assertThat(outputTimesUs).containsExactly(20L, 40L, 100L);
} }
@Test @Test
public void getSpeedAdjustedTimeAsync_timeAfterEndTime_callbacksCalledWithCorrectParameters() public void getSpeedAdjustedTimeAsync_timeAfterEndTime_callbacksCalledWithCorrectParameters()
throws Exception { throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>(); ArrayList<Long> outputTimesUs = new ArrayList<>();
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate). // The speed change is at 120Us (6*MICROS_PER_SECOND/sampleRate).
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {6, 6},
/* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); new SpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
getNonRandomByteBuffer(/* frameCount= */ 3, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.flush();
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 300L, outputTimesUs::add);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
getAudioProcessorOutput(speedChangingAudioProcessor);
// 150 is after the speed change so floor(113 / 2 + (300 - 113)*1) -> 243
assertThat(outputTimesUs).containsExactly(243L);
}
@Test
public void
getSpeedAdjustedTimeAsync_timeAfterEndTimeAfterProcessorEnded_callbacksCalledWithCorrectParameters()
throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>();
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate).
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
getAudioProcessorOutput(speedChangingAudioProcessor);
checkState(speedChangingAudioProcessor.isEnded());
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync( speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 300L, outputTimesUs::add); /* inputTimeUs= */ 300L, outputTimesUs::add);
// 150 is after the speed change so floor(113 / 2 + (300 - 113)*1) -> 243 assertThat(outputTimesUs).containsExactly(240L);
assertThat(outputTimesUs).containsExactly(243L);
} }
@Test @Test
public void getMediaDurationUs_returnsCorrectValues() throws Exception { public void getMediaDurationUs_returnsCorrectValues() throws Exception {
// The speed changes happen every 10ms (441 samples @ 441.KHz) // The speed changes happen every 10ms (500 samples @ 50.KHz)
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {441, 441, 441, 441}, /* frameCounts= */ new int[] {500, 500, 500, 500},
/* speeds= */ new float[] {2, 1, 5, 2}); /* speeds= */ new float[] {2, 1, 5, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); new SpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
getNonRandomByteBuffer(/* frameCount= */ 441 * 4, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.flush();
while (inputBuffer.position() < inputBuffer.limit()) {
speedChangingAudioProcessor.queueInput(inputBuffer);
}
getAudioProcessorOutput(speedChangingAudioProcessor);
// input (in ms) (0, 10, 20, 30, 40) -> // input (in ms) (0, 10, 20, 30, 40) ->
// output (in ms) (0, 10/2, 10/2 + 10, 10/2 + 10 + 10/5, 10/2 + 10 + 10/5 + 10/2) // output (in ms) (0, 10/2, 10/2 + 10, 10/2 + 10 + 10/5, 10/2 + 10 + 10/5 + 10/2)
@ -572,30 +532,30 @@ public class SpeedChangingAudioProcessorTest {
int outputFrameCount = 0; int outputFrameCount = 0;
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 1000, 1000}, /* frameCounts= */ new int[] {1000, 1000, 1000},
/* speeds= */ new float[] {2, 4, 2}); // 500, 250, 500 = 1250 /* speeds= */ new float[] {2, 4, 2}); // 500, 250, 500 = 1250
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer input = getNonRandomByteBuffer(1000, AUDIO_FORMAT.bytesPerFrame); ByteBuffer input = getNonRandomByteBuffer(1000, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
input.rewind(); input.rewind();
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
input.rewind(); input.rewind();
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
assertThat(outputFrameCount).isWithin(2).of(1250); assertThat(outputFrameCount).isWithin(2).of(1250);
} }
@ -612,17 +572,17 @@ public class SpeedChangingAudioProcessorTest {
/* speeds= */ new float[] {2, 3, 8, 4}); /* speeds= */ new float[] {2, 3, 8, 4});
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer input = getNonRandomByteBuffer(12, AUDIO_FORMAT.bytesPerFrame); ByteBuffer input = getNonRandomByteBuffer(12, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
while (input.hasRemaining()) { while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
} }
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
// Allow one sample of tolerance per effectively applied speed change. // Allow one sample of tolerance per effectively applied speed change.
assertThat(outputFrameCount).isWithin(1).of(4); assertThat(outputFrameCount).isWithin(1).of(4);
@ -633,23 +593,23 @@ public class SpeedChangingAudioProcessorTest {
throws AudioProcessor.UnhandledAudioFormatException { throws AudioProcessor.UnhandledAudioFormatException {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 1000}, /* frameCounts= */ new int[] {1000, 1000},
/* speeds= */ new float[] {1, 2}); // 1000, 500. /* speeds= */ new float[] {1, 2}); // 1000, 500.
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
// 1500 input frames falls in the middle of the 2x region. // 1500 input frames falls in the middle of the 2x region.
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame); ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
int outputFrameCount = 0; int outputFrameCount = 0;
while (input.hasRemaining()) { while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
} }
speedChangingAudioProcessor.flush(); speedChangingAudioProcessor.flush();
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
assertThat(outputFrameCount).isEqualTo(1250); assertThat(outputFrameCount).isEqualTo(1250);
input.rewind(); input.rewind();
@ -659,11 +619,11 @@ public class SpeedChangingAudioProcessorTest {
while (input.hasRemaining()) { while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
} }
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
assertThat(outputFrameCount).isWithin(1).of(2500); // 1250 * 2. assertThat(outputFrameCount).isWithin(1).of(2500); // 1250 * 2.
} }
@ -672,23 +632,23 @@ public class SpeedChangingAudioProcessorTest {
throws AudioProcessor.UnhandledAudioFormatException { throws AudioProcessor.UnhandledAudioFormatException {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 1000}, /* frameCounts= */ new int[] {1000, 1000},
/* speeds= */ new float[] {2, 4}); // 500, 250. /* speeds= */ new float[] {2, 4}); // 500, 250.
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider); getConfiguredSpeedChangingAudioProcessor(speedProvider);
// 1500 input frames falls in the middle of the 2x region. // 1500 input frames falls in the middle of the 2x region.
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame); ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
int outputFrameCount = 0; int outputFrameCount = 0;
while (input.hasRemaining()) { while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
} }
speedChangingAudioProcessor.flush(); speedChangingAudioProcessor.flush();
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
assertThat(outputFrameCount).isWithin(1).of(625); assertThat(outputFrameCount).isWithin(1).of(625);
input.rewind(); input.rewind();
@ -698,11 +658,11 @@ public class SpeedChangingAudioProcessorTest {
while (input.hasRemaining()) { while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input); speedChangingAudioProcessor.queueInput(input);
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
} }
speedChangingAudioProcessor.queueEndOfStream(); speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount += outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame; speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
assertThat(outputFrameCount).isWithin(2).of(1250); // 625 * 2. assertThat(outputFrameCount).isWithin(2).of(1250); // 625 * 2.
} }
@ -716,7 +676,7 @@ public class SpeedChangingAudioProcessorTest {
long sampleCountAfterProcessorApplied = long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied( SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 100); speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 100);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(50); assertThat(sampleCountAfterProcessorApplied).isEqualTo(50);
} }
@ -724,13 +684,13 @@ public class SpeedChangingAudioProcessorTest {
public void getSampleCountAfterProcessorApplied_withMultipleSpeeds_outputsExpectedSamples() { public void getSampleCountAfterProcessorApplied_withMultipleSpeeds_outputsExpectedSamples() {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {100, 400, 50}, /* frameCounts= */ new int[] {100, 400, 50},
/* speeds= */ new float[] {2.f, 4f, 0.5f}); /* speeds= */ new float[] {2.f, 4f, 0.5f});
long sampleCountAfterProcessorApplied = long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied( SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 550); speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 550);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(250); assertThat(sampleCountAfterProcessorApplied).isEqualTo(250);
} }
@ -739,13 +699,13 @@ public class SpeedChangingAudioProcessorTest {
getSampleCountAfterProcessorApplied_beyondLastSpeedRegion_stillAppliesLastSpeedValue() { getSampleCountAfterProcessorApplied_beyondLastSpeedRegion_stillAppliesLastSpeedValue() {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {100, 400, 50}, /* frameCounts= */ new int[] {100, 400, 50},
/* speeds= */ new float[] {2.f, 4f, 0.5f}); /* speeds= */ new float[] {2.f, 4f, 0.5f});
long sampleCountAfterProcessorApplied = long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied( SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 3000); speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 3000);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5150); assertThat(sampleCountAfterProcessorApplied).isEqualTo(5150);
} }
@ -754,38 +714,38 @@ public class SpeedChangingAudioProcessorTest {
getSampleCountAfterProcessorApplied_withInputCountBeyondIntRange_outputsExpectedSamples() { getSampleCountAfterProcessorApplied_withInputCountBeyondIntRange_outputsExpectedSamples() {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200}, /* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f}); /* speeds= */ new float[] {0.2f, 8f, 0.5f});
long sampleCountAfterProcessorApplied = long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied( SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 3_000_000_000L); speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 3_000_000_000L);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5999984250L); assertThat(sampleCountAfterProcessorApplied).isEqualTo(5_999_984_250L);
} }
// Testing range validation. // Testing range validation.
@SuppressLint("Range") @SuppressLint("Range")
@Test @Test
public void getSampleCountAfterProcessorApplied_withNegativeSampleCount_throws() { public void getSampleCountAfterProcessorApplied_withNegativeFrameCount_throws() {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200}, /* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f}); /* speeds= */ new float[] {0.2f, 8f, 0.5f});
assertThrows( assertThrows(
IllegalArgumentException.class, IllegalArgumentException.class,
() -> () ->
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied( SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ -2L)); speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ -2L));
} }
// Testing range validation. // Testing range validation.
@SuppressLint("Range") @SuppressLint("Range")
@Test @Test
public void getSampleCountAfterProcessorApplied_withZeroSampleRate_throws() { public void getSampleCountAfterProcessorApplied_withZeroFrameRate_throws() {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200}, /* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f}); /* speeds= */ new float[] {0.2f, 8f, 0.5f});
assertThrows( assertThrows(
@ -801,14 +761,32 @@ public class SpeedChangingAudioProcessorTest {
IllegalArgumentException.class, IllegalArgumentException.class,
() -> () ->
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied( SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
/* speedProvider= */ null, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 1000L)); /* speedProvider= */ null,
AUDIO_FORMAT_44_100HZ.sampleRate,
/* inputSamples= */ 1000L));
}
@Test
public void getSampleCountAfterProcessorApplied_withZeroInputFrames_returnsZero() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 0L);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(0L);
} }
@Test @Test
public void isActive_beforeConfigure_returnsFalse() { public void isActive_beforeConfigure_returnsFalse() {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {1000}, /* speeds= */ new float[] {2f}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000},
/* speeds= */ new float[] {2f});
SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider); SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider);
assertThat(processor.isActive()).isFalse(); assertThat(processor.isActive()).isFalse();
@ -819,18 +797,34 @@ public class SpeedChangingAudioProcessorTest {
throws AudioProcessor.UnhandledAudioFormatException { throws AudioProcessor.UnhandledAudioFormatException {
SpeedProvider speedProvider = SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts( TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {1000}, /* speeds= */ new float[] {2f}); AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000},
/* speeds= */ new float[] {2f});
SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider); SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider);
processor.configure(AUDIO_FORMAT); processor.configure(AUDIO_FORMAT_44_100HZ);
assertThat(processor.isActive()).isTrue(); assertThat(processor.isActive()).isTrue();
} }
@Test
public void getInputFrameCountForOutput_withZeroOutputFrames_returnsZero() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
long inputFrames =
getInputFrameCountForOutput(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* outputFrameCount= */ 0L);
assertThat(inputFrames).isEqualTo(0L);
}
private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor( private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor(
SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException { SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException {
SpeedChangingAudioProcessor speedChangingAudioProcessor = SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider); new SpeedChangingAudioProcessor(speedProvider);
speedChangingAudioProcessor.configure(AUDIO_FORMAT); speedChangingAudioProcessor.configure(AUDIO_FORMAT_44_100HZ);
speedChangingAudioProcessor.flush(); speedChangingAudioProcessor.flush();
return speedChangingAudioProcessor; return speedChangingAudioProcessor;
} }