diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/SpeedChangingAudioProcessor.java b/libraries/common/src/main/java/androidx/media3/common/audio/SpeedChangingAudioProcessor.java index e2e1eb8e71..ac8f9ca05a 100644 --- a/libraries/common/src/main/java/androidx/media3/common/audio/SpeedChangingAudioProcessor.java +++ b/libraries/common/src/main/java/androidx/media3/common/audio/SpeedChangingAudioProcessor.java @@ -24,7 +24,6 @@ import static androidx.media3.common.util.Util.sampleCountToDurationUs; import static java.lang.Math.min; import static java.lang.Math.round; -import android.annotation.SuppressLint; import androidx.annotation.GuardedBy; import androidx.annotation.IntRange; import androidx.media3.common.C; @@ -45,10 +44,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** * An {@link AudioProcessor} that changes the speed of audio samples depending on their timestamp. */ -// TODO(b/288221200): Consider making the processor inactive and skipping it in the processor chain -// when speed is 1. @UnstableApi -public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { +public final class SpeedChangingAudioProcessor implements AudioProcessor { private final Object lock; @@ -97,7 +94,18 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { private boolean endOfStreamQueuedToSonic; + /** The current input audio format. */ + private AudioFormat inputAudioFormat; + + private AudioFormat pendingInputAudioFormat; + private AudioFormat pendingOutputAudioFormat; + private boolean inputEnded; + public SpeedChangingAudioProcessor(SpeedProvider speedProvider) { + pendingInputAudioFormat = AudioFormat.NOT_SET; + pendingOutputAudioFormat = AudioFormat.NOT_SET; + inputAudioFormat = AudioFormat.NOT_SET; + this.speedProvider = speedProvider; lock = new Object(); sonicAudioProcessor = @@ -105,7 +113,7 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { pendingCallbackInputTimesUs = new LongArrayQueue(); pendingCallbacks = new ArrayDeque<>(); speedAdjustedTimeAsyncInputTimeUs = C.TIME_UNSET; - resetState(); + resetInternalState(); } /** Returns the estimated number of samples output given the provided parameters. */ @@ -145,14 +153,20 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { } @Override - public long getDurationAfterProcessorApplied(long durationUs) { - return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs); + public AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException { + pendingInputAudioFormat = inputAudioFormat; + pendingOutputAudioFormat = sonicAudioProcessor.configure(inputAudioFormat); + return pendingOutputAudioFormat; } @Override - public AudioFormat onConfigure(AudioFormat inputAudioFormat) - throws UnhandledAudioFormatException { - return sonicAudioProcessor.configure(inputAudioFormat); + public boolean isActive() { + return !pendingOutputAudioFormat.equals(AudioFormat.NOT_SET); + } + + @Override + public long getDurationAfterProcessorApplied(long durationUs) { + return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs); } @Override @@ -191,15 +205,14 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { } @Override - protected void onQueueEndOfStream() { + public void queueEndOfStream() { + inputEnded = true; if (!endOfStreamQueuedToSonic) { sonicAudioProcessor.queueEndOfStream(); endOfStreamQueuedToSonic = true; } } - // Not using BaseAudioProcessor's buffers. - @SuppressLint("MissingSuperCall") @Override public ByteBuffer getOutput() { ByteBuffer output = sonicAudioProcessor.getOutput(); @@ -209,18 +222,24 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { @Override public boolean isEnded() { - return super.isEnded() && sonicAudioProcessor.isEnded(); + return inputEnded && sonicAudioProcessor.isEnded(); } @Override - protected void onFlush() { - resetState(); + public void flush() { + inputEnded = false; + inputAudioFormat = pendingInputAudioFormat; + resetInternalState(); sonicAudioProcessor.flush(); } @Override - protected void onReset() { - resetState(); + public void reset() { + flush(); + pendingInputAudioFormat = AudioFormat.NOT_SET; + pendingOutputAudioFormat = AudioFormat.NOT_SET; + inputAudioFormat = AudioFormat.NOT_SET; + resetInternalState(); sonicAudioProcessor.reset(); } @@ -352,7 +371,6 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { // Invalidate any previously created buffers in SonicAudioProcessor and the base class. sonicAudioProcessor.flush(); endOfStreamQueuedToSonic = false; - super.getOutput(); } } } @@ -396,7 +414,7 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor { @EnsuresNonNull({"inputSegmentStartTimesUs", "outputSegmentStartTimesUs"}) @RequiresNonNull("lock") - private void resetState(@UnknownInitialization SpeedChangingAudioProcessor this) { + private void resetInternalState(@UnknownInitialization SpeedChangingAudioProcessor this) { synchronized (lock) { inputSegmentStartTimesUs = new LongArray(); outputSegmentStartTimesUs = new LongArray(); diff --git a/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java b/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java index c95aff64bd..824ffc6451 100644 --- a/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/audio/SpeedChangingAudioProcessorTest.java @@ -701,6 +701,28 @@ public class SpeedChangingAudioProcessorTest { /* speedProvider= */ null, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 1000L)); } + @Test + public void isActive_beforeConfigure_returnsFalse() { + SpeedProvider speedProvider = + TestSpeedProvider.createWithFrameCounts( + AUDIO_FORMAT, /* frameCounts= */ new int[] {1000}, /* speeds= */ new float[] {2f}); + + SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider); + assertThat(processor.isActive()).isFalse(); + } + + @Test + public void isActive_afterConfigure_returnsTrue() + throws AudioProcessor.UnhandledAudioFormatException { + SpeedProvider speedProvider = + TestSpeedProvider.createWithFrameCounts( + AUDIO_FORMAT, /* frameCounts= */ new int[] {1000}, /* speeds= */ new float[] {2f}); + + SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider); + processor.configure(AUDIO_FORMAT); + assertThat(processor.isActive()).isTrue(); + } + private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor( SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException { SpeedChangingAudioProcessor speedChangingAudioProcessor =