Always drain/flush AudioProcessors after configuration

This simplifies the contract of configure and is in preparation for
fixing a bug where more input can't be queued when draining audio
processors for a configuration change.

Issue: #6601
PiperOrigin-RevId: 282514367
This commit is contained in:
andrewlewis 2019-11-26 09:04:15 +00:00 committed by Oliver Woodman
parent 64569a3e5a
commit b7000e64e9
12 changed files with 38 additions and 126 deletions

View File

@ -87,16 +87,12 @@ public final class GvrAudioProcessor implements AudioProcessor {
@SuppressWarnings("ReferenceEquality")
@Override
public synchronized boolean configure(
int sampleRateHz, int channelCount, @C.Encoding int encoding)
public synchronized void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_16BIT) {
maybeReleaseGvrAudioSurround();
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
switch (channelCount) {
@ -125,7 +121,6 @@ public final class GvrAudioProcessor implements AudioProcessor {
buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE)
.order(ByteOrder.nativeOrder());
}
return true;
}
@Override

View File

@ -55,18 +55,16 @@ public interface AudioProcessor {
* <p>If the audio processor is active after configuration, call {@link #getOutputSampleRateHz()},
* {@link #getOutputChannelCount()} and {@link #getOutputEncoding()} to get its new output format.
*
* <p>If this method returns {@code true}, it is necessary to {@link #flush()} the processor
* before queueing more data, but you can (optionally) first drain output in the previous
* configuration by calling {@link #queueEndOfStream()} and {@link #getOutput()}. If this method
* returns {@code false}, it is safe to queue new input immediately.
* <p>After calling this method, it is necessary to {@link #flush()} the processor to apply the
* new configuration before queueing more data. You can (optionally) first drain output in the
* previous configuration by calling {@link #queueEndOfStream()} and {@link #getOutput()}.
*
* @param sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio.
* @param encoding The encoding of input audio.
* @return Whether the processor must be {@link #flush() flushed} before queueing more input.
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
*/
boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
throws UnhandledFormatException;
/** Returns whether the processor is configured and will process input buffers. */

View File

@ -104,18 +104,12 @@ public abstract class BaseAudioProcessor implements AudioProcessor {
onReset();
}
/** Sets the input format of this processor, returning whether the input format has changed. */
protected final boolean setInputFormat(
/** Sets the input format of this processor. */
protected final void setInputFormat(
int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
if (sampleRateHz == this.sampleRateHz
&& channelCount == this.channelCount
&& encoding == this.encoding) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.encoding = encoding;
return true;
}
/**

View File

@ -19,7 +19,6 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
@ -48,23 +47,20 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
throws UnhandledFormatException {
boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels);
outputChannels = pendingOutputChannels;
int[] outputChannels = this.outputChannels;
if (outputChannels == null) {
active = false;
return outputChannelsChanged;
return;
}
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (!outputChannelsChanged && !setInputFormat(sampleRateHz, channelCount, encoding)) {
return false;
}
setInputFormat(sampleRateHz, channelCount, encoding);
active = channelCount != outputChannels.length;
for (int i = 0; i < outputChannels.length; i++) {
int channelIndex = outputChannels[i];
@ -73,7 +69,6 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
}
active |= (channelIndex != i);
}
return true;
}
@Override

View File

@ -246,7 +246,7 @@ public final class DefaultAudioSink implements AudioSink {
private final ArrayDeque<PlaybackParametersCheckpoint> playbackParametersCheckpoints;
@Nullable private Listener listener;
/** Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}). */
/** Used to keep the audio session active on pre-V21 builds (see {@link #initialize(long)}). */
@Nullable private AudioTrack keepSessionIdAudioTrack;
@Nullable private Configuration pendingConfiguration;
@ -432,13 +432,12 @@ public final class DefaultAudioSink implements AudioSink {
shouldConvertHighResIntPcmToFloat
? toFloatPcmAvailableAudioProcessors
: toIntPcmAvailableAudioProcessors;
boolean flushAudioProcessors = false;
if (processingEnabled) {
trimmingAudioProcessor.setTrimFrameCount(trimStartFrames, trimEndFrames);
channelMappingAudioProcessor.setChannelMap(outputChannels);
for (AudioProcessor audioProcessor : availableAudioProcessors) {
try {
flushAudioProcessors |= audioProcessor.configure(sampleRate, channelCount, encoding);
audioProcessor.configure(sampleRate, channelCount, encoding);
} catch (AudioProcessor.UnhandledFormatException e) {
throw new ConfigurationException(e);
}
@ -473,11 +472,7 @@ public final class DefaultAudioSink implements AudioSink {
processingEnabled,
canApplyPlaybackParameters,
availableAudioProcessors);
// If we have a pending configuration already, we always drain audio processors as the preceding
// configuration may have required it (even if this one doesn't).
boolean drainAudioProcessors = flushAudioProcessors || this.pendingConfiguration != null;
if (isInitialized()
&& (!pendingConfiguration.canReuseAudioTrack(configuration) || drainAudioProcessors)) {
if (isInitialized()) {
this.pendingConfiguration = pendingConfiguration;
} else {
configuration = pendingConfiguration;

View File

@ -29,12 +29,12 @@ import java.nio.ByteBuffer;
private static final double PCM_32_BIT_INT_TO_PCM_32_BIT_FLOAT_FACTOR = 1.0 / 0x7FFFFFFF;
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
throws UnhandledFormatException {
if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
return setInputFormat(sampleRateHz, channelCount, encoding);
setInputFormat(sampleRateHz, channelCount, encoding);
}
@Override

View File

@ -26,13 +26,13 @@ import java.nio.ByteBuffer;
/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor {
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
return setInputFormat(sampleRateHz, channelCount, encoding);
setInputFormat(sampleRateHz, channelCount, encoding);
}
@Override

View File

@ -119,13 +119,13 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
// AudioProcessor implementation.
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
bytesPerFrame = channelCount * 2;
return setInputFormat(sampleRateHz, channelCount, encoding);
setInputFormat(sampleRateHz, channelCount, encoding);
}
@Override

View File

@ -159,22 +159,17 @@ public final class SonicAudioProcessor implements AudioProcessor {
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
public void configure(int sampleRateHz, int channelCount, @Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
int outputSampleRateHz = pendingOutputSampleRateHz == SAMPLE_RATE_NO_CHANGE
? sampleRateHz : pendingOutputSampleRateHz;
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount
&& this.outputSampleRateHz == outputSampleRateHz) {
return false;
}
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.outputSampleRateHz = outputSampleRateHz;
pendingSonicRecreation = true;
return true;
}
@Override

View File

@ -64,8 +64,8 @@ public final class TeeAudioProcessor extends BaseAudioProcessor {
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
return setInputFormat(sampleRateHz, channelCount, encoding);
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
setInputFormat(sampleRateHz, channelCount, encoding);
}
@Override

View File

@ -68,7 +68,7 @@ import java.nio.ByteBuffer;
}
@Override
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
throws UnhandledFormatException {
if (encoding != OUTPUT_ENCODING) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
@ -80,11 +80,9 @@ import java.nio.ByteBuffer;
endBuffer = new byte[trimEndFrames * bytesPerFrame];
endBufferSize = 0;
pendingTrimStartBytes = trimStartFrames * bytesPerFrame;
boolean wasActive = isActive;
isActive = trimStartFrames != 0 || trimEndFrames != 0;
receivedInputSinceConfigure = false;
setInputFormat(sampleRateHz, channelCount, encoding);
return wasActive != isActive;
}
@Override

View File

@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledFormatException;
import com.google.android.exoplayer2.util.Assertions;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -53,13 +52,10 @@ public final class SilenceSkippingAudioProcessorTest {
silenceSkippingAudioProcessor.setEnabled(true);
// When configuring it.
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
silenceSkippingAudioProcessor.flush();
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
// It's active.
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
}
@ -87,47 +83,6 @@ public final class SilenceSkippingAudioProcessorTest {
assertThat(silenceSkippingAudioProcessor.isActive()).isFalse();
}
@Test
public void testChangingSampleRate_requiresReconfiguration() throws Exception {
// Given an enabled processor and configured processor.
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
if (reconfigured) {
silenceSkippingAudioProcessor.flush();
}
// When reconfiguring it with a different sample rate.
reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ * 2, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
// It's reconfigured.
assertThat(reconfigured).isTrue();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
}
@Test
public void testReconfiguringWithSameSampleRate_doesNotRequireReconfiguration() throws Exception {
// Given an enabled processor and configured processor.
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
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 =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
// It's not reconfigured but it is active.
assertThat(reconfigured).isFalse();
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
}
@Test
public void testSkipInSilentSignal_skipsEverything() throws Exception {
// Given a signal with only noise.
@ -141,11 +96,9 @@ public final class SilenceSkippingAudioProcessorTest {
// When processing the entire signal.
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
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 =
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);
@ -170,11 +123,9 @@ public final class SilenceSkippingAudioProcessorTest {
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor();
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
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 =
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);
@ -200,11 +151,9 @@ public final class SilenceSkippingAudioProcessorTest {
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor();
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
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 =
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);
@ -230,11 +179,9 @@ public final class SilenceSkippingAudioProcessorTest {
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor();
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
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 =
process(silenceSkippingAudioProcessor, inputBufferProvider, /* inputBufferSize= */ 80);
@ -260,11 +207,9 @@ public final class SilenceSkippingAudioProcessorTest {
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor();
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
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 =
process(silenceSkippingAudioProcessor, inputBufferProvider, /* inputBufferSize= */ 120);
@ -289,11 +234,9 @@ public final class SilenceSkippingAudioProcessorTest {
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
new SilenceSkippingAudioProcessor();
silenceSkippingAudioProcessor.setEnabled(true);
boolean reconfigured =
silenceSkippingAudioProcessor.configure(
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
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);
silenceSkippingAudioProcessor.flush();
@ -309,8 +252,7 @@ public final class SilenceSkippingAudioProcessorTest {
private static long process(
SilenceSkippingAudioProcessor processor,
InputBufferProvider inputBufferProvider,
int inputBufferSize)
throws UnhandledFormatException {
int inputBufferSize) {
processor.flush();
long totalOutputFrames = 0;
while (inputBufferProvider.hasRemaining()) {