mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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:
|
* Audio:
|
||||||
* Do not bypass `SonicAudioProcessor` when `SpeedChangingAudioProcessor`
|
* Do not bypass `SonicAudioProcessor` when `SpeedChangingAudioProcessor`
|
||||||
is configured with default parameters.
|
is configured with default parameters.
|
||||||
|
* Fix underflow in `Sonic#getOutputSize()` that could cause
|
||||||
|
`DefaultAudioSink` to stall.
|
||||||
* Video:
|
* Video:
|
||||||
* Text:
|
* Text:
|
||||||
* Metadata:
|
* Metadata:
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package androidx.media3.common.audio;
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
@ -257,6 +258,7 @@ import java.util.Arrays;
|
||||||
* @param buffer A {@link ShortBuffer} into which output will be written.
|
* @param buffer A {@link ShortBuffer} into which output will be written.
|
||||||
*/
|
*/
|
||||||
public void getOutput(ShortBuffer buffer) {
|
public void getOutput(ShortBuffer buffer) {
|
||||||
|
checkState(outputFrameCount >= 0);
|
||||||
int framesToRead = min(buffer.remaining() / channelCount, outputFrameCount);
|
int framesToRead = min(buffer.remaining() / channelCount, outputFrameCount);
|
||||||
buffer.put(outputBuffer, 0, framesToRead * channelCount);
|
buffer.put(outputBuffer, 0, framesToRead * channelCount);
|
||||||
outputFrameCount -= framesToRead;
|
outputFrameCount -= framesToRead;
|
||||||
|
|
@ -306,7 +308,8 @@ import java.util.Arrays;
|
||||||
processStreamInput();
|
processStreamInput();
|
||||||
// Throw away any extra frames we generated due to the silence we added.
|
// Throw away any extra frames we generated due to the silence we added.
|
||||||
if (outputFrameCount > expectedOutputFrames) {
|
if (outputFrameCount > expectedOutputFrames) {
|
||||||
outputFrameCount = expectedOutputFrames;
|
// expectedOutputFrames might be negative, so set lower bound to 0.
|
||||||
|
outputFrameCount = max(expectedOutputFrames, 0);
|
||||||
}
|
}
|
||||||
// Empty input and pitch buffers.
|
// Empty input and pitch buffers.
|
||||||
inputFrameCount = 0;
|
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. */
|
/** Returns the size of output that can be read with {@link #getOutput(ShortBuffer)}, in bytes. */
|
||||||
public int getOutputSize() {
|
public int getOutputSize() {
|
||||||
|
checkState(outputFrameCount >= 0);
|
||||||
return outputFrameCount * channelCount * BYTES_PER_SAMPLE;
|
return outputFrameCount * channelCount * BYTES_PER_SAMPLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,8 @@ public final class RandomParameterizedSonicTest {
|
||||||
readSampleCount += outBuffer.position();
|
readSampleCount += outBuffer.position();
|
||||||
outBuffer.clear();
|
outBuffer.clear();
|
||||||
}
|
}
|
||||||
|
assertThat(sonic.getOutputSize()).isAtLeast(0);
|
||||||
}
|
}
|
||||||
sonic.flush();
|
|
||||||
|
|
||||||
long expectedSamples =
|
long expectedSamples =
|
||||||
Sonic.getExpectedFrameCountAfterProcessorApplied(
|
Sonic.getExpectedFrameCountAfterProcessorApplied(
|
||||||
|
|
@ -196,8 +196,8 @@ public final class RandomParameterizedSonicTest {
|
||||||
readSampleCount += outBuffer.position();
|
readSampleCount += outBuffer.position();
|
||||||
outBuffer.clear();
|
outBuffer.clear();
|
||||||
}
|
}
|
||||||
|
assertThat(sonic.getOutputSize()).isAtLeast(0);
|
||||||
}
|
}
|
||||||
sonic.flush();
|
|
||||||
|
|
||||||
long expectedSamples =
|
long expectedSamples =
|
||||||
Sonic.getExpectedFrameCountAfterProcessorApplied(
|
Sonic.getExpectedFrameCountAfterProcessorApplied(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package androidx.media3.common.audio;
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.test.utils.TestUtil.getPeriodicSamplesBuffer;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
|
@ -99,6 +100,22 @@ public final class SonicAudioProcessorTest {
|
||||||
assertThat(processor.isActive()).isTrue();
|
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
|
@Test
|
||||||
public void doesNotSupportNon16BitInput() throws Exception {
|
public void doesNotSupportNon16BitInput() throws Exception {
|
||||||
try {
|
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.calculateAccumulatedTruncationErrorForResampling;
|
||||||
import static androidx.media3.common.audio.Sonic.getExpectedFrameCountAfterProcessorApplied;
|
import static androidx.media3.common.audio.Sonic.getExpectedFrameCountAfterProcessorApplied;
|
||||||
import static androidx.media3.common.audio.Sonic.getExpectedInputFrameCountForOutputFrameCount;
|
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 static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
@ -112,6 +113,53 @@ public class SonicTest {
|
||||||
assertThat(outputBuffer.array()).isEqualTo(new short[] {0, 4, 8});
|
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
|
@Test
|
||||||
public void
|
public void
|
||||||
getExpectedFrameCountAfterProcessorApplied_timeStretchingFaster_returnsExpectedSampleCount() {
|
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.audio.AudioProcessor.EMPTY_BUFFER;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
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 com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
|
@ -46,12 +47,14 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
|
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
assertThat(inputBuffer).isEqualTo(getInputBuffer(/* frameCount= */ 5));
|
assertThat(inputBuffer)
|
||||||
|
.isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -61,12 +64,14 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
|
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
assertThat(inputBuffer).isEqualTo(getInputBuffer(/* frameCount= */ 5));
|
assertThat(inputBuffer)
|
||||||
|
.isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -76,7 +81,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
speedChangingAudioProcessor.queueEndOfStream();
|
speedChangingAudioProcessor.queueEndOfStream();
|
||||||
|
|
@ -93,7 +99,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
speedChangingAudioProcessor.queueEndOfStream();
|
speedChangingAudioProcessor.queueEndOfStream();
|
||||||
|
|
@ -111,7 +118,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
|
|
@ -131,7 +139,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
|
|
@ -159,7 +168,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
|
|
@ -187,7 +197,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
ByteBuffer outputBuffer = getAudioProcessorOutput(speedChangingAudioProcessor);
|
ByteBuffer outputBuffer = getAudioProcessorOutput(speedChangingAudioProcessor);
|
||||||
|
|
@ -214,7 +225,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
/* speeds= */ new float[] {1, 2});
|
/* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
int inputBufferLimit = inputBuffer.limit();
|
int inputBufferLimit = inputBuffer.limit();
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
|
|
@ -233,7 +245,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
/* speeds= */ new float[] {1, 2});
|
/* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
int inputBufferLimit = inputBuffer.limit();
|
int inputBufferLimit = inputBuffer.limit();
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
|
|
@ -252,7 +265,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
/* speeds= */ new float[] {1, 2});
|
/* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.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()) {
|
||||||
|
|
@ -276,7 +290,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
|
|
@ -295,7 +310,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
|
|
@ -314,7 +330,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
speedChangingAudioProcessor.queueEndOfStream();
|
speedChangingAudioProcessor.queueEndOfStream();
|
||||||
|
|
@ -330,7 +347,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
speedChangingAudioProcessor.queueEndOfStream();
|
speedChangingAudioProcessor.queueEndOfStream();
|
||||||
|
|
@ -358,7 +376,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
getAudioProcessorOutput(speedChangingAudioProcessor);
|
getAudioProcessorOutput(speedChangingAudioProcessor);
|
||||||
|
|
@ -373,7 +392,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
getAudioProcessorOutput(speedChangingAudioProcessor);
|
getAudioProcessorOutput(speedChangingAudioProcessor);
|
||||||
|
|
@ -390,7 +410,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
|
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
|
||||||
/* inputTimeUs= */ 50L, outputTimesUs::add);
|
/* inputTimeUs= */ 50L, outputTimesUs::add);
|
||||||
|
|
@ -421,7 +442,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
/* speeds= */ new float[] {2, 1, 3});
|
/* speeds= */ new float[] {2, 1, 3});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
// Use the audio processor before a flush
|
// Use the audio processor before a flush
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
getAudioProcessorOutput(speedChangingAudioProcessor);
|
getAudioProcessorOutput(speedChangingAudioProcessor);
|
||||||
|
|
@ -458,7 +480,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 3);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 3, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
|
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
|
||||||
/* inputTimeUs= */ 300L, outputTimesUs::add);
|
/* inputTimeUs= */ 300L, outputTimesUs::add);
|
||||||
|
|
@ -487,7 +510,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
getAudioProcessorOutput(speedChangingAudioProcessor);
|
getAudioProcessorOutput(speedChangingAudioProcessor);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
|
|
@ -513,7 +537,8 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
/* speeds= */ new float[] {2, 1, 5, 2});
|
/* speeds= */ new float[] {2, 1, 5, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 441 * 4);
|
ByteBuffer inputBuffer =
|
||||||
|
getNonRandomByteBuffer(/* frameCount= */ 441 * 4, AUDIO_FORMAT.bytesPerFrame);
|
||||||
while (inputBuffer.position() < inputBuffer.limit()) {
|
while (inputBuffer.position() < inputBuffer.limit()) {
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
}
|
}
|
||||||
|
|
@ -552,7 +577,7 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
/* 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 = getInputBuffer(1000);
|
ByteBuffer input = getNonRandomByteBuffer(1000, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
speedChangingAudioProcessor.queueInput(input);
|
speedChangingAudioProcessor.queueInput(input);
|
||||||
outputFrameCount +=
|
outputFrameCount +=
|
||||||
|
|
@ -587,7 +612,7 @@ 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 = getInputBuffer(12);
|
ByteBuffer input = getNonRandomByteBuffer(12, AUDIO_FORMAT.bytesPerFrame);
|
||||||
|
|
||||||
while (input.hasRemaining()) {
|
while (input.hasRemaining()) {
|
||||||
speedChangingAudioProcessor.queueInput(input);
|
speedChangingAudioProcessor.queueInput(input);
|
||||||
|
|
@ -614,7 +639,7 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
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 = getInputBuffer(1500);
|
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame);
|
||||||
int outputFrameCount = 0;
|
int outputFrameCount = 0;
|
||||||
|
|
||||||
while (input.hasRemaining()) {
|
while (input.hasRemaining()) {
|
||||||
|
|
@ -653,7 +678,7 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
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 = getInputBuffer(1500);
|
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame);
|
||||||
int outputFrameCount = 0;
|
int outputFrameCount = 0;
|
||||||
|
|
||||||
while (input.hasRemaining()) {
|
while (input.hasRemaining()) {
|
||||||
|
|
@ -810,16 +835,6 @@ public class SpeedChangingAudioProcessorTest {
|
||||||
return speedChangingAudioProcessor;
|
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) {
|
private static ByteBuffer getAudioProcessorOutput(AudioProcessor audioProcessor) {
|
||||||
ByteBuffer concatenatedOutputBuffers = EMPTY_BUFFER;
|
ByteBuffer concatenatedOutputBuffers = EMPTY_BUFFER;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
||||||
|
|
@ -890,6 +890,46 @@ public class TestUtil {
|
||||||
return (long) (origin + random.nextFloat() * (bound - origin));
|
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 static final class NoUidOrShufflingTimeline extends Timeline {
|
||||||
|
|
||||||
private final Timeline delegate;
|
private final Timeline delegate;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue