mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Fix underflow in Sonic#getOutputSize() after #queueEndOfStream()
For the [0.5; 1) speed range, the combination of having a "slow down"
speed (i.e. more output frames than input frames), and Sonic potentially
needing to copy more input frames that are available in the input buffer
can lead to an unexpected underflow.
Specifically, the underflow happens in Sonic#queueEndOfStream() when the
following conditions are met (skipping some minor ones):
1. `inputFrameCount < remainingInputToCopyFrameCount`
2. `0.5f <= speed < 1`.
3. `outputFrameCount <
(inputFrameCount / remainingInputToCopyFrameCount) / 2`.
This underflow caused `SonicAudioProcessor#isEnded()` to return a false
negative (because `getOutputSize() != 0`), which would stall the
`DefaultAudioSink` waiting for processing to end after EOS.
In practical terms, the underflow is relatively easy to reproduce if we
consume all of Sonic's output and then immediately queue EOS without
queueing any more input in between. This should cause both
`inputFrameCount` and `outputFrameCount` to drop to 0.
PiperOrigin-RevId: 711773565
This commit is contained in:
parent
cd5d5bde27
commit
7ecaebe3d6
7 changed files with 167 additions and 41 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* <p>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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue