diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java index 7aa4a8e6c8..f82be31f72 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java @@ -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. + * + *
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(); /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 0fa7257f16..e220128ac6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -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(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java index c2af535838..4d04f870b1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java index f9bf583a80..0bf6baa4d0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -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; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index a33d43c92c..01f1869717 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -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; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java index ab926029f1..115862074d 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessorTest.java @@ -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); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java index d060ba3f16..1ba462d4af 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SonicAudioProcessorTest.java @@ -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(); }