Make flush() update parameters, and make Sonic flushable

Previously it was necessary to create a new Sonic instance every time the
processor was flushed. Add a flush() method to Sonic so that it can be reused
when seeking. It still needs to be recreated when parameters change.

SonicAudioProcessor and SilenceSkippingAudioProcessor have methods for setting
parameters that are documented as taking effect after a call to flush(), but
actually the value returned by isActive() was updated immediately. Track the
pending values and apply them in flush() to fix this.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=191442336
This commit is contained in:
andrewlewis 2018-04-03 08:13:54 -07:00 committed by Oliver Woodman
parent cb01b281df
commit f2399c1c85
7 changed files with 64 additions and 17 deletions

View File

@ -29,6 +29,9 @@ import java.nio.ByteOrder;
* {@link #getOutputChannelCount()}, {@link #getOutputEncoding()} and {@link
* #getOutputSampleRateHz()} may only be called if the processor is active. Call {@link #reset()} to
* reset the processor to its unconfigured state and release any resources.
*
* <p>In addition to being able to modify the format of audio, implementations may allow parameters
* to be set that affect the output audio and whether the processor is active/inactive.
*/
public interface AudioProcessor {
@ -47,10 +50,9 @@ public interface AudioProcessor {
/**
* Configures the processor to process input audio with the specified format and returns whether
* to {@link #flush()} it. After calling this method, {@link #isActive()} returns whether the
* processor needs to handle buffers; if not, the processor will not accept any buffers until it
* is reconfigured. If the processor is active, {@link #getOutputSampleRateHz()}, {@link
* #getOutputChannelCount()} and {@link #getOutputEncoding()} return its output format.
* to {@link #flush()} it. After calling this method, if the processor is active, {@link
* #getOutputSampleRateHz()}, {@link #getOutputChannelCount()} and {@link #getOutputEncoding()}
* return its output format.
*
* @param sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio.
@ -61,7 +63,7 @@ public interface AudioProcessor {
boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException;
/** Returns whether the processor is configured and active. */
/** Returns whether the processor is configured and will process input buffers. */
boolean isActive();
/**

View File

@ -531,8 +531,12 @@ public final class DefaultAudioSink implements AudioSink {
drainingPlaybackParameters, Math.max(0, presentationTimeUs),
framesToDurationUs(getWrittenFrames())));
drainingPlaybackParameters = null;
// The audio processors have drained, so flush them. This will cause any active speed
// adjustment audio processor to start producing audio with the new parameters.
// Flush the audio processors so that any new parameters take effect.
// TODO: Move parameter setting from setPlaybackParameters to here, so that it's not
// necessary to flush the processors twice.
sonicAudioProcessor.flush();
silenceSkippingAudioProcessor.flush();
setupAudioProcessors();
}

View File

@ -72,6 +72,7 @@ import java.nio.ByteOrder;
private int bytesPerFrame;
private boolean enabled;
private boolean pendingEnabled;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
@ -113,7 +114,7 @@ import java.nio.ByteOrder;
* @param enabled Whether to skip silence in the input.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
pendingEnabled = enabled;
}
/**
@ -207,6 +208,7 @@ import java.nio.ByteOrder;
@Override
public void flush() {
enabled = pendingEnabled;
if (isActive()) {
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame;
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
@ -228,6 +230,7 @@ import java.nio.ByteOrder;
@Override
public void reset() {
enabled = false;
pendingEnabled = false;
flush();
buffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;

View File

@ -143,6 +143,20 @@ import java.util.Arrays;
pitchFrameCount = 0;
}
/** Clears state in preparation for receiving a new stream of input buffers. */
public void flush() {
inputFrameCount = 0;
outputFrameCount = 0;
pitchFrameCount = 0;
oldRatePosition = 0;
newRatePosition = 0;
remainingInputToCopyFrameCount = 0;
prevPeriod = 0;
prevMinDiff = 0;
minDiff = 0;
maxDiff = 0;
}
/** Returns the number of output frames that can be read with {@link #getOutput(ShortBuffer)}. */
public int getFramesAvailable() {
return outputFrameCount;

View File

@ -62,15 +62,16 @@ public final class SonicAudioProcessor implements AudioProcessor {
*/
private static final int MIN_BYTES_FOR_SPEEDUP_CALCULATION = 1024;
private int pendingOutputSampleRateHz;
private int channelCount;
private int sampleRateHz;
private @Nullable Sonic sonic;
private float speed;
private float pitch;
private int outputSampleRateHz;
private float pendingSpeed;
private float pendingPitch;
private int pendingOutputSampleRateHz;
private @Nullable Sonic sonic;
private ByteBuffer buffer;
private ShortBuffer shortBuffer;
private ByteBuffer outputBuffer;
@ -84,6 +85,8 @@ public final class SonicAudioProcessor implements AudioProcessor {
public SonicAudioProcessor() {
speed = 1f;
pitch = 1f;
pendingSpeed = 1f;
pendingPitch = 1f;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
outputSampleRateHz = Format.NO_VALUE;
@ -100,8 +103,8 @@ public final class SonicAudioProcessor implements AudioProcessor {
* @return The actual new playback speed.
*/
public float setSpeed(float speed) {
this.speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED);
return this.speed;
pendingSpeed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED);
return pendingSpeed;
}
/**
@ -111,8 +114,8 @@ public final class SonicAudioProcessor implements AudioProcessor {
* @return The actual new pitch.
*/
public float setPitch(float pitch) {
this.pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
return pitch;
pendingPitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
return pendingPitch;
}
/**
@ -161,6 +164,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.outputSampleRateHz = outputSampleRateHz;
sonic = null;
return true;
}
@ -232,8 +236,16 @@ public final class SonicAudioProcessor implements AudioProcessor {
@Override
public void flush() {
sonic =
isActive() ? new Sonic(sampleRateHz, channelCount, speed, pitch, outputSampleRateHz) : null;
boolean parametersChanged = pendingSpeed != speed || pendingPitch != pitch;
speed = pendingSpeed;
pitch = pendingPitch;
if (isActive()) {
if (sonic == null || parametersChanged) {
sonic = new Sonic(sampleRateHz, channelCount, speed, pitch, outputSampleRateHz);
} else {
sonic.flush();
}
}
outputBuffer = EMPTY_BUFFER;
inputBytes = 0;
outputBytes = 0;
@ -244,6 +256,8 @@ public final class SonicAudioProcessor implements AudioProcessor {
public void reset() {
speed = 1f;
pitch = 1f;
pendingSpeed = 1f;
pendingPitch = 1f;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
outputSampleRateHz = Format.NO_VALUE;

View File

@ -56,6 +56,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
// It's active.
assertThat(reconfigured).isTrue();
@ -115,6 +116,7 @@ public final class SilenceSkippingAudioProcessorTest {
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
assertThat(reconfigured).isTrue();
silenceSkippingAudioProcessor.flush();
// When reconfiguring it with the same sample rate.
reconfigured =
@ -142,6 +144,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
long totalOutputFrames =
@ -170,6 +173,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
long totalOutputFrames =
@ -199,6 +203,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
long totalOutputFrames =
@ -228,6 +233,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
long totalOutputFrames =
@ -257,6 +263,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
long totalOutputFrames =
@ -285,6 +292,7 @@ public final class SilenceSkippingAudioProcessorTest {
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);

View File

@ -94,6 +94,7 @@ public final class SonicAudioProcessorTest {
public void testIsActiveWithSpeedChange() throws Exception {
sonicAudioProcessor.setSpeed(1.5f);
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
sonicAudioProcessor.flush();
assertThat(sonicAudioProcessor.isActive()).isTrue();
}
@ -101,6 +102,7 @@ public final class SonicAudioProcessorTest {
public void testIsActiveWithPitchChange() throws Exception {
sonicAudioProcessor.setPitch(1.5f);
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
sonicAudioProcessor.flush();
assertThat(sonicAudioProcessor.isActive()).isTrue();
}