From f2399c1c856a83ce5956167e674aa687a9aedaa9 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 3 Apr 2018 08:13:54 -0700 Subject: [PATCH] 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 --- .../exoplayer2/audio/AudioProcessor.java | 12 ++++--- .../exoplayer2/audio/DefaultAudioSink.java | 8 +++-- .../audio/SilenceSkippingAudioProcessor.java | 5 ++- .../android/exoplayer2/audio/Sonic.java | 14 ++++++++ .../exoplayer2/audio/SonicAudioProcessor.java | 32 +++++++++++++------ .../SilenceSkippingAudioProcessorTest.java | 8 +++++ .../audio/SonicAudioProcessorTest.java | 2 ++ 7 files changed, 64 insertions(+), 17 deletions(-) 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(); }