diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a5914577c3..f52a00afe4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -20,6 +20,8 @@ * Audio: * Do not bypass `SonicAudioProcessor` when `SpeedChangingAudioProcessor` is configured with default parameters. + * Fix underflow in `Sonic#getOutputSize()` that could cause + `DefaultAudioSink` to stall. * Video: * Text: * Metadata: diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java b/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java index a16913899c..a4e8189d87 100644 --- a/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java +++ b/libraries/common/src/main/java/androidx/media3/common/audio/Sonic.java @@ -17,6 +17,7 @@ package androidx.media3.common.audio; import static androidx.media3.common.util.Assertions.checkState; +import static java.lang.Math.max; import static java.lang.Math.min; import java.math.BigDecimal; @@ -257,6 +258,7 @@ import java.util.Arrays; * @param buffer A {@link ShortBuffer} into which output will be written. */ public void getOutput(ShortBuffer buffer) { + checkState(outputFrameCount >= 0); int framesToRead = min(buffer.remaining() / channelCount, outputFrameCount); buffer.put(outputBuffer, 0, framesToRead * channelCount); outputFrameCount -= framesToRead; @@ -306,7 +308,8 @@ import java.util.Arrays; processStreamInput(); // Throw away any extra frames we generated due to the silence we added. if (outputFrameCount > expectedOutputFrames) { - outputFrameCount = expectedOutputFrames; + // expectedOutputFrames might be negative, so set lower bound to 0. + outputFrameCount = max(expectedOutputFrames, 0); } // Empty input and pitch buffers. inputFrameCount = 0; @@ -331,6 +334,7 @@ import java.util.Arrays; /** Returns the size of output that can be read with {@link #getOutput(ShortBuffer)}, in bytes. */ public int getOutputSize() { + checkState(outputFrameCount >= 0); return outputFrameCount * channelCount * BYTES_PER_SAMPLE; } diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java index c2500b26ea..6560c0f6fe 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/RandomParameterizedSonicTest.java @@ -160,8 +160,8 @@ public final class RandomParameterizedSonicTest { readSampleCount += outBuffer.position(); outBuffer.clear(); } + assertThat(sonic.getOutputSize()).isAtLeast(0); } - sonic.flush(); long expectedSamples = Sonic.getExpectedFrameCountAfterProcessorApplied( @@ -196,8 +196,8 @@ public final class RandomParameterizedSonicTest { readSampleCount += outBuffer.position(); outBuffer.clear(); } + assertThat(sonic.getOutputSize()).isAtLeast(0); } - sonic.flush(); long expectedSamples = Sonic.getExpectedFrameCountAfterProcessorApplied( diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/SonicAudioProcessorTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/SonicAudioProcessorTest.java index b5581a3a4b..74c14d4f6e 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/SonicAudioProcessorTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/SonicAudioProcessorTest.java @@ -15,6 +15,7 @@ */ package androidx.media3.common.audio; +import static androidx.media3.test.utils.TestUtil.getPeriodicSamplesBuffer; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -99,6 +100,22 @@ public final class SonicAudioProcessorTest { assertThat(processor.isActive()).isTrue(); } + @Test + public void queueEndOfStream_withOutputFrameCountUnderflow_setsIsEndedToTrue() throws Exception { + sonicAudioProcessor.setSpeed(0.95f); + sonicAudioProcessor.configure(AUDIO_FORMAT_48000_HZ); + sonicAudioProcessor.flush(); + + // Multiply by channel count. + sonicAudioProcessor.queueInput( + getPeriodicSamplesBuffer(/* sampleCount= */ 1700 * 2, /* period= */ 192 * 2)); + // Drain output, so that pending output frame count is 0. + assertThat(sonicAudioProcessor.getOutput().hasRemaining()).isTrue(); + sonicAudioProcessor.queueEndOfStream(); + + assertThat(sonicAudioProcessor.isEnded()).isTrue(); + } + @Test public void doesNotSupportNon16BitInput() throws Exception { try { diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java index 3855111249..ff5ac63f5c 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/SonicTest.java @@ -18,6 +18,7 @@ package androidx.media3.common.audio; import static androidx.media3.common.audio.Sonic.calculateAccumulatedTruncationErrorForResampling; import static androidx.media3.common.audio.Sonic.getExpectedFrameCountAfterProcessorApplied; import static androidx.media3.common.audio.Sonic.getExpectedInputFrameCountForOutputFrameCount; +import static androidx.media3.test.utils.TestUtil.getPeriodicSamplesBuffer; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -112,6 +113,53 @@ public class SonicTest { assertThat(outputBuffer.array()).isEqualTo(new short[] {0, 4, 8}); } + @Test + public void queueEndOfStream_withOutputCountUnderflow_setsNonNegativeOutputSize() { + // For speed ranges [0.5; 1) and (1; 1.5], Sonic might need to copy more input frames onto its + // output buffer than are available in the input buffer. Sonic keeps track of this "borrowed + // frames" number in #remainingInputToCopyFrameCount. When we call #queueEndOfStream(), then + // Sonic outputs a final number of frames based roughly on pendingOutputFrameCount + + // (inputFrameCount - remainingInputToCopyFrameCount) / speed + remainingInputToCopyFrameCount, + // which could result in a negative number if inputFrameCount < remainingInputToCopyFrameCount + // and 0.5 <= speed < 1. #getOutputSize() should still always return a non-negative number. + ShortBuffer inputBuffer = + getPeriodicSamplesBuffer(/* sampleCount= */ 1700, /* period= */ 192).asShortBuffer(); + Sonic sonic = + new Sonic( + /* inputSampleRateHz= */ 48000, + /* channelCount= */ 1, + /* speed= */ 0.95f, + /* pitch= */ 1, + /* outputSampleRateHz= */ 48000); + + sonic.queueInput(inputBuffer); + ShortBuffer outputBuffer = ShortBuffer.allocate(sonic.getOutputSize() / 2); + // Drain output, so that pending output frame count is 0. + sonic.getOutput(outputBuffer); + assertThat(sonic.getOutputSize()).isEqualTo(0); + // Queue EOS with empty pending input and output. + sonic.queueEndOfStream(); + + assertThat(sonic.getOutputSize()).isEqualTo(0); + } + + @Test + public void queueEndOfStream_withNoInput_setsNonNegativeOutputSize() { + Sonic sonic = + new Sonic( + /* inputSampleRateHz= */ 48000, + /* channelCount= */ 1, + /* speed= */ 0.95f, + /* pitch= */ 1, + /* outputSampleRateHz= */ 48000); + ShortBuffer outputBuffer = ShortBuffer.allocate(sonic.getOutputSize() / 2); + + sonic.getOutput(outputBuffer); + sonic.queueEndOfStream(); + + assertThat(sonic.getOutputSize()).isAtLeast(0); + } + @Test public void getExpectedFrameCountAfterProcessorApplied_timeStretchingFaster_returnsExpectedSampleCount() { diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java index 1e1bf0560b..b116aa29d8 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java @@ -17,6 +17,7 @@ package androidx.media3.common.audio; import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER; import static androidx.media3.common.util.Assertions.checkState; +import static androidx.media3.test.utils.TestUtil.getNonRandomByteBuffer; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -46,12 +47,14 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); - assertThat(inputBuffer).isEqualTo(getInputBuffer(/* frameCount= */ 5)); + assertThat(inputBuffer) + .isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame)); } @Test @@ -61,12 +64,14 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); - assertThat(inputBuffer).isEqualTo(getInputBuffer(/* frameCount= */ 5)); + assertThat(inputBuffer) + .isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame)); } @Test @@ -76,7 +81,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueEndOfStream(); @@ -93,7 +99,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueEndOfStream(); @@ -111,7 +118,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); @@ -131,7 +139,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); @@ -159,7 +168,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); @@ -187,7 +197,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); ByteBuffer outputBuffer = getAudioProcessorOutput(speedChangingAudioProcessor); @@ -214,7 +225,8 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {1, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); int inputBufferLimit = inputBuffer.limit(); speedChangingAudioProcessor.queueInput(inputBuffer); @@ -233,7 +245,8 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {1, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); int inputBufferLimit = inputBuffer.limit(); speedChangingAudioProcessor.queueInput(inputBuffer); @@ -252,7 +265,8 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {1, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); // SpeedChangingAudioProcessor only queues samples until the next speed change. while (inputBuffer.hasRemaining()) { @@ -276,7 +290,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); @@ -295,7 +310,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); inputBuffer.rewind(); @@ -314,7 +330,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueEndOfStream(); @@ -330,7 +347,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); speedChangingAudioProcessor.queueEndOfStream(); @@ -358,7 +376,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); getAudioProcessorOutput(speedChangingAudioProcessor); @@ -373,7 +392,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); getAudioProcessorOutput(speedChangingAudioProcessor); @@ -390,7 +410,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.getSpeedAdjustedTimeAsync( /* inputTimeUs= */ 50L, outputTimesUs::add); @@ -421,7 +442,8 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {2, 1, 3}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); // Use the audio processor before a flush speedChangingAudioProcessor.queueInput(inputBuffer); getAudioProcessorOutput(speedChangingAudioProcessor); @@ -458,7 +480,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 3); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 3, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.getSpeedAdjustedTimeAsync( /* inputTimeUs= */ 300L, outputTimesUs::add); @@ -487,7 +510,8 @@ public class SpeedChangingAudioProcessorTest { AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(inputBuffer); getAudioProcessorOutput(speedChangingAudioProcessor); inputBuffer.rewind(); @@ -513,7 +537,8 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {2, 1, 5, 2}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 441 * 4); + ByteBuffer inputBuffer = + getNonRandomByteBuffer(/* frameCount= */ 441 * 4, AUDIO_FORMAT.bytesPerFrame); while (inputBuffer.position() < inputBuffer.limit()) { speedChangingAudioProcessor.queueInput(inputBuffer); } @@ -552,7 +577,7 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {2, 4, 2}); // 500, 250, 500 = 1250 SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer input = getInputBuffer(1000); + ByteBuffer input = getNonRandomByteBuffer(1000, AUDIO_FORMAT.bytesPerFrame); speedChangingAudioProcessor.queueInput(input); outputFrameCount += @@ -587,7 +612,7 @@ public class SpeedChangingAudioProcessorTest { /* speeds= */ new float[] {2, 3, 8, 4}); SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); - ByteBuffer input = getInputBuffer(12); + ByteBuffer input = getNonRandomByteBuffer(12, AUDIO_FORMAT.bytesPerFrame); while (input.hasRemaining()) { speedChangingAudioProcessor.queueInput(input); @@ -614,7 +639,7 @@ public class SpeedChangingAudioProcessorTest { SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); // 1500 input frames falls in the middle of the 2x region. - ByteBuffer input = getInputBuffer(1500); + ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame); int outputFrameCount = 0; while (input.hasRemaining()) { @@ -653,7 +678,7 @@ public class SpeedChangingAudioProcessorTest { SpeedChangingAudioProcessor speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider); // 1500 input frames falls in the middle of the 2x region. - ByteBuffer input = getInputBuffer(1500); + ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame); int outputFrameCount = 0; while (input.hasRemaining()) { @@ -810,16 +835,6 @@ public class SpeedChangingAudioProcessorTest { return speedChangingAudioProcessor; } - private static ByteBuffer getInputBuffer(int frameCount) { - int bufferSize = frameCount * AUDIO_FORMAT.bytesPerFrame; - ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder()); - for (int i = 0; i < bufferSize; i++) { - buffer.put((byte) (i % (Byte.MAX_VALUE + 1))); - } - buffer.rewind(); - return buffer; - } - private static ByteBuffer getAudioProcessorOutput(AudioProcessor audioProcessor) { ByteBuffer concatenatedOutputBuffers = EMPTY_BUFFER; while (true) { diff --git a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestUtil.java b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestUtil.java index 39326de156..bdc511ecec 100644 --- a/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestUtil.java +++ b/libraries/test_utils/src/main/java/androidx/media3/test/utils/TestUtil.java @@ -890,6 +890,46 @@ public class TestUtil { return (long) (origin + random.nextFloat() * (bound - origin)); } + /** + * Returns a non-random {@link ByteBuffer} filled with {@code frameCount * bytesPerFrame} bytes. + */ + public static ByteBuffer getNonRandomByteBuffer(int frameCount, int bytesPerFrame) { + int bufferSize = frameCount * bytesPerFrame; + ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder()); + for (int i = 0; i < bufferSize; i++) { + buffer.put((byte) i); + } + buffer.rewind(); + return buffer; + } + + /** + * Returns a {@link ByteBuffer} filled with alternating 16-bit PCM samples as per the provided + * period length. + * + *

The generated samples alternate between {@link Short#MAX_VALUE} and {@link Short#MIN_VALUE} + * every {@code period / 2} samples. + * + * @param sampleCount Number of total PCM samples (not frames) to generate. + * @param period Length in PCM samples of one full cycle. + */ + public static ByteBuffer getPeriodicSamplesBuffer(int sampleCount, int period) { + int halfPeriod = period / 2; + ByteBuffer buffer = ByteBuffer.allocateDirect(sampleCount * 2).order(ByteOrder.nativeOrder()); + boolean isHigh = false; + int counter = 0; + while (counter < sampleCount) { + short sample = isHigh ? Short.MAX_VALUE : Short.MIN_VALUE; + for (int i = 0; i < halfPeriod && counter < sampleCount; i++) { + buffer.putShort(sample); + counter++; + } + isHigh = !isHigh; + } + buffer.rewind(); + return buffer; + } + private static final class NoUidOrShufflingTimeline extends Timeline { private final Timeline delegate;