From 10f142b3ff677b63c33b18c9d1f653aedfd2251e Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 26 Nov 2019 09:08:59 +0000 Subject: [PATCH] Add AudioProcessor.AudioFormat Issue: #6601 PiperOrigin-RevId: 282515179 --- .../exoplayer2/ext/gvr/GvrAudioProcessor.java | 46 +++------ .../exoplayer2/audio/AudioProcessor.java | 96 +++++++++++-------- .../exoplayer2/audio/BaseAudioProcessor.java | 63 +++++------- .../audio/ChannelMappingAudioProcessor.java | 47 ++++----- .../exoplayer2/audio/DefaultAudioSink.java | 15 ++- .../audio/FloatResamplingAudioProcessor.java | 29 +++--- .../audio/ResamplingAudioProcessor.java | 26 ++--- .../audio/SilenceSkippingAudioProcessor.java | 18 ++-- .../exoplayer2/audio/SonicAudioProcessor.java | 86 ++++++++--------- .../exoplayer2/audio/TeeAudioProcessor.java | 8 +- .../audio/TrimmingAudioProcessor.java | 23 ++--- .../SilenceSkippingAudioProcessorTest.java | 58 ++++------- .../audio/SonicAudioProcessorTest.java | 81 ++++++++-------- 13 files changed, 263 insertions(+), 333 deletions(-) diff --git a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java index a6a6577831..7748e053b4 100644 --- a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java +++ b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrAudioProcessor.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ext.gvr; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.AudioProcessor; import com.google.android.exoplayer2.util.Assertions; import com.google.vr.sdk.audio.GvrAudioSurround; @@ -44,8 +43,7 @@ public final class GvrAudioProcessor implements AudioProcessor { private static final int OUTPUT_FRAME_SIZE = OUTPUT_CHANNEL_COUNT * 2; // 16-bit stereo output. private static final int NO_SURROUND_FORMAT = GvrAudioSurround.SurroundFormat.INVALID; - private int sampleRateHz; - private int channelCount; + private AudioFormat inputAudioFormat; private int pendingGvrAudioSurroundFormat; @Nullable private GvrAudioSurround gvrAudioSurround; private ByteBuffer buffer; @@ -60,8 +58,7 @@ public final class GvrAudioProcessor implements AudioProcessor { public GvrAudioProcessor() { // Use the identity for the initial orientation. w = 1f; - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; + inputAudioFormat = AudioFormat.NOT_SET; buffer = EMPTY_BUFFER; pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT; } @@ -87,15 +84,13 @@ public final class GvrAudioProcessor implements AudioProcessor { @SuppressWarnings("ReferenceEquality") @Override - public synchronized void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) - throws UnhandledFormatException { - if (encoding != C.ENCODING_PCM_16BIT) { + public synchronized AudioFormat configure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) { maybeReleaseGvrAudioSurround(); - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + throw new UnhandledAudioFormatException(inputAudioFormat); } - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; - switch (channelCount) { + switch (inputAudioFormat.channelCount) { case 1: pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO; break; @@ -115,12 +110,14 @@ public final class GvrAudioProcessor implements AudioProcessor { pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.THIRD_ORDER_AMBISONICS; break; default: - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + throw new UnhandledAudioFormatException(inputAudioFormat); } if (buffer == EMPTY_BUFFER) { buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE) .order(ByteOrder.nativeOrder()); } + this.inputAudioFormat = inputAudioFormat; + return new AudioFormat(inputAudioFormat.sampleRate, OUTPUT_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); } @Override @@ -128,21 +125,6 @@ public final class GvrAudioProcessor implements AudioProcessor { return pendingGvrAudioSurroundFormat != NO_SURROUND_FORMAT || gvrAudioSurround != null; } - @Override - public int getOutputChannelCount() { - return OUTPUT_CHANNEL_COUNT; - } - - @Override - public int getOutputEncoding() { - return C.ENCODING_PCM_16BIT; - } - - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; - } - @Override public void queueInput(ByteBuffer input) { int position = input.position(); @@ -181,7 +163,10 @@ public final class GvrAudioProcessor implements AudioProcessor { maybeReleaseGvrAudioSurround(); gvrAudioSurround = new GvrAudioSurround( - pendingGvrAudioSurroundFormat, sampleRateHz, channelCount, FRAMES_PER_OUTPUT_BUFFER); + pendingGvrAudioSurroundFormat, + inputAudioFormat.sampleRate, + inputAudioFormat.channelCount, + FRAMES_PER_OUTPUT_BUFFER); gvrAudioSurround.updateNativeOrientation(w, x, y, z); pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT; } else if (gvrAudioSurround != null) { @@ -195,8 +180,7 @@ public final class GvrAudioProcessor implements AudioProcessor { maybeReleaseGvrAudioSurround(); updateOrientation(/* w= */ 1f, /* x= */ 0f, /* y= */ 0f, /* z= */ 0f); inputEnded = false; - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; + inputAudioFormat = AudioFormat.NOT_SET; buffer = EMPTY_BUFFER; pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT; } 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 b4f5040be5..1b0ddff16c 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 @@ -16,6 +16,8 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -23,24 +25,56 @@ import java.nio.ByteOrder; * Interface for audio processors, which take audio data as input and transform it, potentially * modifying its channel count, encoding and/or sample rate. * - *

Call {@link #configure(int, int, int)} to configure the processor to receive input audio, then - * call {@link #isActive()} to determine whether the processor is active in the new configuration. - * {@link #queueInput(ByteBuffer)}, {@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 { - /** Exception thrown when a processor can't be configured for a given input audio format. */ - final class UnhandledFormatException extends Exception { + /** PCM audio format that may be handled by an audio processor. */ + final class AudioFormat { + public static final AudioFormat NOT_SET = + new AudioFormat( + /* sampleRate= */ Format.NO_VALUE, + /* channelCount= */ Format.NO_VALUE, + /* encoding= */ Format.NO_VALUE); - public UnhandledFormatException( - int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { - super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding " - + encoding); + /** The sample rate in Hertz. */ + public final int sampleRate; + /** The number of interleaved channels. */ + public final int channelCount; + /** The type of linear PCM encoding. */ + @C.PcmEncoding public final int encoding; + /** The number of bytes used to represent one audio frame. */ + public final int bytesPerFrame; + + public AudioFormat(int sampleRate, int channelCount, @C.PcmEncoding int encoding) { + this.sampleRate = sampleRate; + this.channelCount = channelCount; + this.encoding = encoding; + bytesPerFrame = + Util.isEncodingLinearPcm(encoding) + ? Util.getPcmFrameSize(encoding, channelCount) + : Format.NO_VALUE; + } + + @Override + public String toString() { + return "AudioFormat[" + + "sampleRate=" + + sampleRate + + ", channelCount=" + + channelCount + + ", encoding=" + + encoding + + ']'; + } + } + + /** Exception thrown when a processor can't be configured for a given input audio format. */ + final class UnhandledAudioFormatException extends Exception { + + public UnhandledAudioFormatException(AudioFormat inputAudioFormat) { + super("Unhandled format: " + inputAudioFormat); } } @@ -50,45 +84,23 @@ public interface AudioProcessor { /** * Configures the processor to process input audio with the specified format. After calling this - * method, call {@link #isActive()} to determine whether the audio processor is active. - * - *

If the audio processor is active after configuration, call {@link #getOutputSampleRateHz()}, - * {@link #getOutputChannelCount()} and {@link #getOutputEncoding()} to get its new output format. + * method, call {@link #isActive()} to determine whether the audio processor is active. Returns + * the configured output audio format if this instance is active. * *

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. - * @throws UnhandledFormatException Thrown if the specified format can't be handled as input. + * @param inputAudioFormat The format of audio that will be queued after the next call to {@link + * #flush()}. + * @return The configured output audio format if this instance is {@link #isActive() active}. + * @throws UnhandledAudioFormatException Thrown if the specified format can't be handled as input. */ - void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) - throws UnhandledFormatException; + AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException; /** Returns whether the processor is configured and will process input buffers. */ boolean isActive(); - /** - * Returns the number of audio channels in the data output by the processor. The value may change - * as a result of calling {@link #configure(int, int, int)}. - */ - int getOutputChannelCount(); - - /** - * Returns the audio encoding used in the data output by the processor. The value may change as a - * result of calling {@link #configure(int, int, int)}. - */ - @C.PcmEncoding - int getOutputEncoding(); - - /** - * Returns the sample rate of audio output by the processor, in hertz. The value may change as a - * result of calling {@link #configure(int, int, int)}. - */ - int getOutputSampleRateHz(); - /** * Queues audio data between the position and limit of the input {@code buffer} for processing. * {@code buffer} must be a direct byte buffer with native byte order. Its contents are treated as @@ -130,6 +142,6 @@ public interface AudioProcessor { */ void flush(); - /** Resets the processor to its unconfigured state. */ + /** Resets the processor to its unconfigured state, releasing any resources. */ void reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java index 8fcf39367b..c9e5465644 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java @@ -16,24 +16,20 @@ package com.google.android.exoplayer2.audio; import androidx.annotation.CallSuper; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Base class for audio processors that keep an output buffer and an internal buffer that is reused - * whenever input is queued. + * whenever input is queued. Subclasses should override {@link #onConfigure(AudioFormat)} to return + * the output audio format for the processor if it's active. */ public abstract class BaseAudioProcessor implements AudioProcessor { - /** The configured input sample rate, in Hertz, or {@link Format#NO_VALUE} if not configured. */ - protected int sampleRateHz; - /** The configured input channel count, or {@link Format#NO_VALUE} if not configured. */ - protected int channelCount; - /** The configured input encoding, or {@link Format#NO_VALUE} if not configured. */ - @C.PcmEncoding protected int encoding; + /** The configured input audio format. */ + protected AudioFormat inputAudioFormat; + private AudioFormat outputAudioFormat; private ByteBuffer buffer; private ByteBuffer outputBuffer; private boolean inputEnded; @@ -41,29 +37,21 @@ public abstract class BaseAudioProcessor implements AudioProcessor { public BaseAudioProcessor() { buffer = EMPTY_BUFFER; outputBuffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; - encoding = Format.NO_VALUE; + inputAudioFormat = AudioFormat.NOT_SET; + outputAudioFormat = AudioFormat.NOT_SET; + } + + @Override + public final AudioFormat configure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + this.inputAudioFormat = inputAudioFormat; + outputAudioFormat = onConfigure(inputAudioFormat); + return isActive() ? outputAudioFormat : AudioFormat.NOT_SET; } @Override public boolean isActive() { - return sampleRateHz != Format.NO_VALUE; - } - - @Override - public int getOutputChannelCount() { - return channelCount; - } - - @Override - public int getOutputEncoding() { - return encoding; - } - - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; + return outputAudioFormat != AudioFormat.NOT_SET; } @Override @@ -98,20 +86,11 @@ public abstract class BaseAudioProcessor implements AudioProcessor { public final void reset() { flush(); buffer = EMPTY_BUFFER; - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; - encoding = Format.NO_VALUE; + inputAudioFormat = AudioFormat.NOT_SET; + outputAudioFormat = AudioFormat.NOT_SET; onReset(); } - /** Sets the input format of this processor. */ - protected final void setInputFormat( - int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; - this.encoding = encoding; - } - /** * Replaces the current output buffer with a buffer of at least {@code count} bytes and returns * it. Callers should write to the returned buffer then {@link ByteBuffer#flip()} it so it can be @@ -132,6 +111,12 @@ public abstract class BaseAudioProcessor implements AudioProcessor { return outputBuffer.hasRemaining(); } + /** Called when the processor is configured for a new input format. */ + protected AudioFormat onConfigure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + return AudioFormat.NOT_SET; + } + /** Called when the end-of-stream is queued to the processor. */ protected void onQueueEndOfStream() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java index 8aea8506e4..87be1ee88a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java @@ -24,19 +24,17 @@ import java.nio.ByteBuffer; * An {@link AudioProcessor} that applies a mapping from input channels onto specified output * channels. This can be used to reorder, duplicate or discard channels. */ -/* package */ // the constructor does not initialize fields: pendingOutputChannels, outputChannels @SuppressWarnings("nullness:initialization.fields.uninitialized") -final class ChannelMappingAudioProcessor extends BaseAudioProcessor { +/* package */ final class ChannelMappingAudioProcessor extends BaseAudioProcessor { @Nullable private int[] pendingOutputChannels; - private boolean active; @Nullable private int[] outputChannels; /** - * Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)} - * to start using the new channel map. + * Resets the channel mapping. After calling this method, call {@link #configure(AudioFormat)} to + * start using the new channel map. * * @param outputChannels The mapping from input to output channel indices, or {@code null} to * leave the input unchanged. @@ -47,38 +45,30 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor { } @Override - public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) - throws UnhandledFormatException { + public AudioFormat onConfigure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { outputChannels = pendingOutputChannels; int[] outputChannels = this.outputChannels; if (outputChannels == null) { - active = false; - return; - } - if (encoding != C.ENCODING_PCM_16BIT) { - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + return AudioFormat.NOT_SET; } - setInputFormat(sampleRateHz, channelCount, encoding); - active = channelCount != outputChannels.length; + if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) { + throw new UnhandledAudioFormatException(inputAudioFormat); + } + + boolean active = inputAudioFormat.channelCount != outputChannels.length; for (int i = 0; i < outputChannels.length; i++) { int channelIndex = outputChannels[i]; - if (channelIndex >= channelCount) { - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + if (channelIndex >= inputAudioFormat.channelCount) { + throw new UnhandledAudioFormatException(inputAudioFormat); } active |= (channelIndex != i); } - } - - @Override - public boolean isActive() { - return active; - } - - @Override - public int getOutputChannelCount() { - return outputChannels == null ? channelCount : outputChannels.length; + return active + ? new AudioFormat(inputAudioFormat.sampleRate, outputChannels.length, C.ENCODING_PCM_16BIT) + : AudioFormat.NOT_SET; } @Override @@ -86,14 +76,14 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor { int[] outputChannels = Assertions.checkNotNull(this.outputChannels); int position = inputBuffer.position(); int limit = inputBuffer.limit(); - int frameCount = (limit - position) / (2 * channelCount); + int frameCount = (limit - position) / (2 * inputAudioFormat.channelCount); int outputSize = frameCount * outputChannels.length * 2; ByteBuffer buffer = replaceOutputBuffer(outputSize); while (position < limit) { for (int channelIndex : outputChannels) { buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex)); } - position += channelCount * 2; + position += inputAudioFormat.channelCount * 2; } inputBuffer.position(limit); buffer.flip(); @@ -103,7 +93,6 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor { protected void onReset() { outputChannels = null; pendingOutputChannels = null; - active = false; } } 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 7cdd700058..27823e3006 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 @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -435,18 +436,22 @@ public final class DefaultAudioSink implements AudioSink { if (processingEnabled) { trimmingAudioProcessor.setTrimFrameCount(trimStartFrames, trimEndFrames); channelMappingAudioProcessor.setChannelMap(outputChannels); + AudioProcessor.AudioFormat inputAudioFormat = + new AudioProcessor.AudioFormat(sampleRate, channelCount, encoding); + AudioProcessor.AudioFormat outputAudioFormat = inputAudioFormat; for (AudioProcessor audioProcessor : availableAudioProcessors) { try { - audioProcessor.configure(sampleRate, channelCount, encoding); - } catch (AudioProcessor.UnhandledFormatException e) { + outputAudioFormat = audioProcessor.configure(inputAudioFormat); + } catch (UnhandledAudioFormatException e) { throw new ConfigurationException(e); } if (audioProcessor.isActive()) { - channelCount = audioProcessor.getOutputChannelCount(); - sampleRate = audioProcessor.getOutputSampleRateHz(); - encoding = audioProcessor.getOutputEncoding(); + inputAudioFormat = outputAudioFormat; } } + sampleRate = outputAudioFormat.sampleRate; + channelCount = outputAudioFormat.channelCount; + encoding = outputAudioFormat.encoding; } int outputChannelConfig = getChannelConfig(channelCount, isInputPcm); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java index 1e6af46fbc..a75e675e6e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; @@ -29,27 +30,21 @@ import java.nio.ByteBuffer; private static final double PCM_32_BIT_INT_TO_PCM_32_BIT_FLOAT_FACTOR = 1.0 / 0x7FFFFFFF; @Override - public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) - throws UnhandledFormatException { - if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) { - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + public AudioFormat onConfigure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + if (!Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding)) { + throw new UnhandledAudioFormatException(inputAudioFormat); } - setInputFormat(sampleRateHz, channelCount, encoding); - } - - @Override - public boolean isActive() { - return Util.isEncodingHighResolutionIntegerPcm(encoding); - } - - @Override - public int getOutputEncoding() { - return C.ENCODING_PCM_FLOAT; + return Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding) + ? new AudioFormat( + inputAudioFormat.sampleRate, inputAudioFormat.channelCount, C.ENCODING_PCM_FLOAT) + : AudioFormat.NOT_SET; } @Override public void queueInput(ByteBuffer inputBuffer) { - boolean isInput32Bit = encoding == C.ENCODING_PCM_32BIT; + Assertions.checkState(Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding)); + boolean isInput32Bit = inputAudioFormat.encoding == C.ENCODING_PCM_32BIT; int position = inputBuffer.position(); int limit = inputBuffer.limit(); int size = limit - position; @@ -65,7 +60,7 @@ import java.nio.ByteBuffer; | ((inputBuffer.get(i + 3) & 0xFF) << 24); writePcm32BitFloat(pcm32BitInteger, buffer); } - } else { + } else { // Input is 24-bit PCM. for (int i = position; i < limit; i += 3) { int pcm32BitInteger = ((inputBuffer.get(i) & 0xFF) << 8) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java index 4817504933..1bfa1897c8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java @@ -26,23 +26,17 @@ import java.nio.ByteBuffer; /* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor { @Override - public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) - throws UnhandledFormatException { + public AudioFormat onConfigure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + @C.PcmEncoding int encoding = inputAudioFormat.encoding; 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); + throw new UnhandledAudioFormatException(inputAudioFormat); } - setInputFormat(sampleRateHz, channelCount, encoding); - } - - @Override - public boolean isActive() { - return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT; - } - - @Override - public int getOutputEncoding() { - return C.ENCODING_PCM_16BIT; + return encoding != C.ENCODING_PCM_16BIT + ? new AudioFormat( + inputAudioFormat.sampleRate, inputAudioFormat.channelCount, C.ENCODING_PCM_16BIT) + : AudioFormat.NOT_SET; } @Override @@ -52,7 +46,7 @@ import java.nio.ByteBuffer; int limit = inputBuffer.limit(); int size = limit - position; int resampledSize; - switch (encoding) { + switch (inputAudioFormat.encoding) { case C.ENCODING_PCM_8BIT: resampledSize = size * 2; break; @@ -74,7 +68,7 @@ import java.nio.ByteBuffer; // Resample the little endian input and update the input/output buffers. ByteBuffer buffer = replaceOutputBuffer(resampledSize); - switch (encoding) { + switch (inputAudioFormat.encoding) { case C.ENCODING_PCM_8BIT: // 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up. for (int i = position; i < limit; i++) { 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 35e8f8aa5f..59feed9bd2 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 @@ -119,18 +119,17 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { // AudioProcessor implementation. @Override - 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); + public AudioFormat onConfigure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) { + throw new UnhandledAudioFormatException(inputAudioFormat); } - bytesPerFrame = channelCount * 2; - setInputFormat(sampleRateHz, channelCount, encoding); + return enabled ? inputAudioFormat : AudioFormat.NOT_SET; } @Override public boolean isActive() { - return super.isActive() && enabled; + return enabled; } @Override @@ -165,7 +164,8 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { @Override protected void onFlush() { - if (isActive()) { + if (enabled) { + bytesPerFrame = inputAudioFormat.bytesPerFrame; int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame; if (maybeSilenceBuffer.length != maybeSilenceBufferSize) { maybeSilenceBuffer = new byte[maybeSilenceBufferSize]; @@ -317,7 +317,7 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { * Returns the number of input frames corresponding to {@code durationUs} microseconds of audio. */ private int durationUsToFrames(long durationUs) { - return (int) ((durationUs * sampleRateHz) / C.MICROS_PER_SECOND); + return (int) ((durationUs * inputAudioFormat.sampleRate) / C.MICROS_PER_SECOND); } /** 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 5bffc8fc68..4ebadab80c 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 @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.audio; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.C.Encoding; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; @@ -62,12 +61,12 @@ public final class SonicAudioProcessor implements AudioProcessor { */ private static final int MIN_BYTES_FOR_SPEEDUP_CALCULATION = 1024; - private int channelCount; - private int sampleRateHz; + private int pendingOutputSampleRate; private float speed; private float pitch; - private int outputSampleRateHz; - private int pendingOutputSampleRateHz; + + private AudioFormat inputAudioFormat; + private AudioFormat outputAudioFormat; private boolean pendingSonicRecreation; @Nullable private Sonic sonic; @@ -84,13 +83,12 @@ public final class SonicAudioProcessor implements AudioProcessor { public SonicAudioProcessor() { speed = 1f; pitch = 1f; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; - outputSampleRateHz = Format.NO_VALUE; + inputAudioFormat = AudioFormat.NOT_SET; + outputAudioFormat = AudioFormat.NOT_SET; buffer = EMPTY_BUFFER; shortBuffer = buffer.asShortBuffer(); outputBuffer = EMPTY_BUFFER; - pendingOutputSampleRateHz = SAMPLE_RATE_NO_CHANGE; + pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE; } /** @@ -129,14 +127,14 @@ public final class SonicAudioProcessor implements AudioProcessor { /** * Sets the sample rate for output audio, in hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output - * audio at the same sample rate as the input. After calling this method, call - * {@link #configure(int, int, int)} to start using the new sample rate. + * audio at the same sample rate as the input. After calling this method, call {@link + * #configure(AudioFormat)} to start using the new sample rate. * * @param sampleRateHz The sample rate for output audio, in hertz. - * @see #configure(int, int, int) + * @see #configure(AudioFormat) */ public void setOutputSampleRateHz(int sampleRateHz) { - pendingOutputSampleRateHz = sampleRateHz; + pendingOutputSampleRate = sampleRateHz; } /** @@ -149,50 +147,39 @@ public final class SonicAudioProcessor implements AudioProcessor { */ public long scaleDurationForSpeedup(long duration) { if (outputBytes >= MIN_BYTES_FOR_SPEEDUP_CALCULATION) { - return outputSampleRateHz == sampleRateHz + return outputAudioFormat.sampleRate == inputAudioFormat.sampleRate ? Util.scaleLargeTimestamp(duration, inputBytes, outputBytes) - : Util.scaleLargeTimestamp(duration, inputBytes * outputSampleRateHz, - outputBytes * sampleRateHz); + : Util.scaleLargeTimestamp( + duration, + inputBytes * outputAudioFormat.sampleRate, + outputBytes * inputAudioFormat.sampleRate); } else { return (long) ((double) speed * duration); } } @Override - public void configure(int sampleRateHz, int channelCount, @Encoding int encoding) - throws UnhandledFormatException { - if (encoding != C.ENCODING_PCM_16BIT) { - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + public AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException { + if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) { + throw new UnhandledAudioFormatException(inputAudioFormat); } - int outputSampleRateHz = pendingOutputSampleRateHz == SAMPLE_RATE_NO_CHANGE - ? sampleRateHz : pendingOutputSampleRateHz; - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; - this.outputSampleRateHz = outputSampleRateHz; + int outputSampleRateHz = + pendingOutputSampleRate == SAMPLE_RATE_NO_CHANGE + ? inputAudioFormat.sampleRate + : pendingOutputSampleRate; + this.inputAudioFormat = inputAudioFormat; + this.outputAudioFormat = + new AudioFormat(outputSampleRateHz, inputAudioFormat.channelCount, C.ENCODING_PCM_16BIT); pendingSonicRecreation = true; + return outputAudioFormat; } @Override public boolean isActive() { - return sampleRateHz != Format.NO_VALUE + return outputAudioFormat.sampleRate != Format.NO_VALUE && (Math.abs(speed - 1f) >= CLOSE_THRESHOLD || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD - || outputSampleRateHz != sampleRateHz); - } - - @Override - public int getOutputChannelCount() { - return channelCount; - } - - @Override - public int getOutputEncoding() { - return C.ENCODING_PCM_16BIT; - } - - @Override - public int getOutputSampleRateHz() { - return outputSampleRateHz; + || outputAudioFormat.sampleRate != inputAudioFormat.sampleRate); } @Override @@ -245,7 +232,13 @@ public final class SonicAudioProcessor implements AudioProcessor { public void flush() { if (isActive()) { if (pendingSonicRecreation) { - sonic = new Sonic(sampleRateHz, channelCount, speed, pitch, outputSampleRateHz); + sonic = + new Sonic( + inputAudioFormat.sampleRate, + inputAudioFormat.channelCount, + speed, + pitch, + outputAudioFormat.sampleRate); } else if (sonic != null) { sonic.flush(); } @@ -260,13 +253,12 @@ public final class SonicAudioProcessor implements AudioProcessor { public void reset() { speed = 1f; pitch = 1f; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; - outputSampleRateHz = Format.NO_VALUE; + inputAudioFormat = AudioFormat.NOT_SET; + outputAudioFormat = AudioFormat.NOT_SET; buffer = EMPTY_BUFFER; shortBuffer = buffer.asShortBuffer(); outputBuffer = EMPTY_BUFFER; - pendingOutputSampleRateHz = SAMPLE_RATE_NO_CHANGE; + pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE; pendingSonicRecreation = false; sonic = null; inputBytes = 0; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java index 49b17916dc..652e3eea54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java @@ -64,8 +64,9 @@ public final class TeeAudioProcessor extends BaseAudioProcessor { } @Override - public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { - setInputFormat(sampleRateHz, channelCount, encoding); + public AudioFormat onConfigure(AudioFormat inputAudioFormat) { + // This processor is always active (if passed to the sink) and outputs its input. + return inputAudioFormat; } @Override @@ -81,7 +82,8 @@ public final class TeeAudioProcessor extends BaseAudioProcessor { @Override protected void onFlush() { if (isActive()) { - audioBufferSink.flush(sampleRateHz, channelCount, encoding); + audioBufferSink.flush( + inputAudioFormat.sampleRate, inputAudioFormat.channelCount, inputAudioFormat.encoding); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java index 161b7eb652..f7d7529876 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java @@ -24,7 +24,6 @@ import java.nio.ByteBuffer; @C.PcmEncoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT; - private boolean isActive; private int trimStartFrames; private int trimEndFrames; private int bytesPerFrame; @@ -42,7 +41,7 @@ import java.nio.ByteBuffer; /** * Sets the number of audio frames to trim from the start and end of audio passed to this - * processor. After calling this method, call {@link #configure(int, int, int)} to apply the new + * processor. After calling this method, call {@link #configure(AudioFormat)} to apply the new * trimming frame counts. * * @param trimStartFrames The number of audio frames to trim from the start of audio. @@ -68,26 +67,20 @@ import java.nio.ByteBuffer; } @Override - public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) - throws UnhandledFormatException { - if (encoding != OUTPUT_ENCODING) { - throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); + public AudioFormat onConfigure(AudioFormat inputAudioFormat) + throws UnhandledAudioFormatException { + if (inputAudioFormat.encoding != OUTPUT_ENCODING) { + throw new UnhandledAudioFormatException(inputAudioFormat); } if (endBufferSize > 0) { trimmedFrameCount += endBufferSize / bytesPerFrame; } - bytesPerFrame = Util.getPcmFrameSize(OUTPUT_ENCODING, channelCount); + bytesPerFrame = inputAudioFormat.bytesPerFrame; endBuffer = new byte[trimEndFrames * bytesPerFrame]; endBufferSize = 0; pendingTrimStartBytes = trimStartFrames * bytesPerFrame; - isActive = trimStartFrames != 0 || trimEndFrames != 0; receivedInputSinceConfigure = false; - setInputFormat(sampleRateHz, channelCount, encoding); - } - - @Override - public boolean isActive() { - return isActive; + return trimStartFrames != 0 || trimEndFrames != 0 ? inputAudioFormat : AudioFormat.NOT_SET; } @Override @@ -140,7 +133,6 @@ import java.nio.ByteBuffer; buffer.flip(); } - @SuppressWarnings("ReferenceEquality") @Override public ByteBuffer getOutput() { if (super.isEnded() && endBufferSize > 0) { @@ -155,7 +147,6 @@ import java.nio.ByteBuffer; return super.getOutput(); } - @SuppressWarnings("ReferenceEquality") @Override public boolean isEnded() { return super.isEnded() && endBufferSize == 0; 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 854975e654..e8eb530d99 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 @@ -19,6 +19,7 @@ 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.AudioFormat; import com.google.android.exoplayer2.util.Assertions; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -31,8 +32,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class SilenceSkippingAudioProcessorTest { - private static final int TEST_SIGNAL_SAMPLE_RATE_HZ = 1000; - private static final int TEST_SIGNAL_CHANNEL_COUNT = 2; + private static final AudioFormat AUDIO_FORMAT = + new AudioFormat( + /* sampleRate= */ 1000, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT); private static final int TEST_SIGNAL_SILENCE_DURATION_MS = 1000; private static final int TEST_SIGNAL_NOISE_DURATION_MS = 1000; private static final int TEST_SIGNAL_FRAME_COUNT = 100000; @@ -52,8 +54,7 @@ public final class SilenceSkippingAudioProcessorTest { silenceSkippingAudioProcessor.setEnabled(true); // When configuring it. - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); // It's active. assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); @@ -65,8 +66,7 @@ public final class SilenceSkippingAudioProcessorTest { silenceSkippingAudioProcessor.setEnabled(false); // When configuring it. - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); // It's not active. assertThat(silenceSkippingAudioProcessor.isActive()).isFalse(); @@ -76,8 +76,7 @@ public final class SilenceSkippingAudioProcessorTest { public void testDefaultProcessor_isNotEnabled() throws Exception { // Given a processor in its default state. // When reconfigured. - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); // It's not active. assertThat(silenceSkippingAudioProcessor.isActive()).isFalse(); @@ -88,16 +87,13 @@ public final class SilenceSkippingAudioProcessorTest { // Given a signal with only noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( - TEST_SIGNAL_SAMPLE_RATE_HZ, - TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, /* noiseDurationMs= */ 0, TEST_SIGNAL_FRAME_COUNT); // When processing the entire signal. silenceSkippingAudioProcessor.setEnabled(true); - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); silenceSkippingAudioProcessor.flush(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = @@ -113,8 +109,6 @@ public final class SilenceSkippingAudioProcessorTest { // Given a signal with only silence. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( - TEST_SIGNAL_SAMPLE_RATE_HZ, - TEST_SIGNAL_CHANNEL_COUNT, /* silenceDurationMs= */ 0, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); @@ -123,8 +117,7 @@ public final class SilenceSkippingAudioProcessorTest { SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); silenceSkippingAudioProcessor.flush(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = @@ -141,8 +134,6 @@ public final class SilenceSkippingAudioProcessorTest { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( - TEST_SIGNAL_SAMPLE_RATE_HZ, - TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); @@ -151,8 +142,7 @@ public final class SilenceSkippingAudioProcessorTest { SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); silenceSkippingAudioProcessor.flush(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = @@ -169,8 +159,6 @@ public final class SilenceSkippingAudioProcessorTest { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( - TEST_SIGNAL_SAMPLE_RATE_HZ, - TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); @@ -179,8 +167,7 @@ public final class SilenceSkippingAudioProcessorTest { SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); silenceSkippingAudioProcessor.flush(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = @@ -197,8 +184,6 @@ public final class SilenceSkippingAudioProcessorTest { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( - TEST_SIGNAL_SAMPLE_RATE_HZ, - TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); @@ -207,8 +192,7 @@ public final class SilenceSkippingAudioProcessorTest { SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); silenceSkippingAudioProcessor.flush(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = @@ -224,8 +208,6 @@ public final class SilenceSkippingAudioProcessorTest { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( - TEST_SIGNAL_SAMPLE_RATE_HZ, - TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); @@ -234,8 +216,7 @@ public final class SilenceSkippingAudioProcessorTest { SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); - silenceSkippingAudioProcessor.configure( - TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); + silenceSkippingAudioProcessor.configure(AUDIO_FORMAT); silenceSkippingAudioProcessor.flush(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE); @@ -253,6 +234,7 @@ public final class SilenceSkippingAudioProcessorTest { SilenceSkippingAudioProcessor processor, InputBufferProvider inputBufferProvider, int inputBufferSize) { + int bytesPerFrame = AUDIO_FORMAT.bytesPerFrame; processor.flush(); long totalOutputFrames = 0; while (inputBufferProvider.hasRemaining()) { @@ -260,14 +242,14 @@ public final class SilenceSkippingAudioProcessorTest { while (inputBuffer.hasRemaining()) { processor.queueInput(inputBuffer); ByteBuffer outputBuffer = processor.getOutput(); - totalOutputFrames += outputBuffer.remaining() / (2 * processor.getOutputChannelCount()); + totalOutputFrames += outputBuffer.remaining() / bytesPerFrame; outputBuffer.clear(); } } processor.queueEndOfStream(); while (!processor.isEnded()) { ByteBuffer outputBuffer = processor.getOutput(); - totalOutputFrames += outputBuffer.remaining() / (2 * processor.getOutputChannelCount()); + totalOutputFrames += outputBuffer.remaining() / bytesPerFrame; outputBuffer.clear(); } return totalOutputFrames; @@ -278,16 +260,16 @@ public final class SilenceSkippingAudioProcessorTest { * between silence/noise of the specified durations to fill {@code totalFrameCount}. */ private static InputBufferProvider getInputBufferProviderForAlternatingSilenceAndNoise( - int sampleRateHz, - int channelCount, int silenceDurationMs, int noiseDurationMs, int totalFrameCount) { + int sampleRate = AUDIO_FORMAT.sampleRate; + int channelCount = AUDIO_FORMAT.channelCount; Pcm16BitAudioBuilder audioBuilder = new Pcm16BitAudioBuilder(channelCount, totalFrameCount); while (!audioBuilder.isFull()) { - int silenceDurationFrames = (silenceDurationMs * sampleRateHz) / 1000; + int silenceDurationFrames = (silenceDurationMs * sampleRate) / 1000; audioBuilder.appendFrames(/* count= */ silenceDurationFrames, /* channelLevels= */ (short) 0); - int noiseDurationFrames = (noiseDurationMs * sampleRateHz) / 1000; + int noiseDurationFrames = (noiseDurationMs * sampleRate) / 1000; audioBuilder.appendFrames( /* count= */ noiseDurationFrames, /* channelLevels= */ Short.MAX_VALUE); } 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 837d7a97a4..e6b448774c 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 @@ -20,6 +20,8 @@ import static org.junit.Assert.fail; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat; +import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +30,16 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class SonicAudioProcessorTest { + private static final AudioFormat AUDIO_FORMAT_22050_HZ = + new AudioFormat( + /* sampleRate= */ 22050, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT); + private static final AudioFormat AUDIO_FORMAT_44100_HZ = + new AudioFormat( + /* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT); + private static final AudioFormat AUDIO_FORMAT_48000_HZ = + new AudioFormat( + /* sampleRate= */ 48000, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT); + private SonicAudioProcessor sonicAudioProcessor; @Before @@ -39,59 +51,36 @@ public final class SonicAudioProcessorTest { public void testReconfigureWithSameSampleRate() throws Exception { // When configured for resampling from 44.1 kHz to 48 kHz, the output sample rate is correct. sonicAudioProcessor.setOutputSampleRateHz(48000); - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000); + AudioFormat outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ); assertThat(sonicAudioProcessor.isActive()).isTrue(); + assertThat(outputAudioFormat.sampleRate).isEqualTo(48000); // When reconfigured with 48 kHz input, there is no resampling. - sonicAudioProcessor.configure(48000, 2, C.ENCODING_PCM_16BIT); - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000); + outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_48000_HZ); assertThat(sonicAudioProcessor.isActive()).isFalse(); + assertThat(outputAudioFormat.sampleRate).isEqualTo(48000); // When reconfigure with 44.1 kHz input, resampling is enabled again. - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000); + outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ); assertThat(sonicAudioProcessor.isActive()).isTrue(); + assertThat(outputAudioFormat.sampleRate).isEqualTo(48000); } @Test public void testNoSampleRateChange() throws Exception { // Configure for resampling 44.1 kHz to 48 kHz. sonicAudioProcessor.setOutputSampleRateHz(48000); - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); + sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ); + assertThat(sonicAudioProcessor.isActive()).isTrue(); // Reconfigure to not modify the sample rate. sonicAudioProcessor.setOutputSampleRateHz(SonicAudioProcessor.SAMPLE_RATE_NO_CHANGE); - sonicAudioProcessor.configure(22050, 2, C.ENCODING_PCM_16BIT); + sonicAudioProcessor.configure(AUDIO_FORMAT_22050_HZ); // The sample rate is unmodified, and the audio processor is not active. - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(22050); assertThat(sonicAudioProcessor.isActive()).isFalse(); } - @Test - public void testBecomesActiveAfterConfigure() throws Exception { - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); - // Set a new sample rate. - sonicAudioProcessor.setOutputSampleRateHz(22050); - // The new sample rate is not active yet. - assertThat(sonicAudioProcessor.isActive()).isFalse(); - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(44100); - } - - @Test - public void testSampleRateChangeBecomesActiveAfterConfigure() throws Exception { - // Configure for resampling 44.1 kHz to 48 kHz. - sonicAudioProcessor.setOutputSampleRateHz(48000); - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); - // Set a new sample rate, which isn't active yet. - sonicAudioProcessor.setOutputSampleRateHz(22050); - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000); - // The new sample rate takes effect on reconfiguration. - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); - assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(22050); - } - @Test public void testIsActiveWithSpeedChange() throws Exception { sonicAudioProcessor.setSpeed(1.5f); - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); + sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ); sonicAudioProcessor.flush(); assertThat(sonicAudioProcessor.isActive()).isTrue(); } @@ -99,35 +88,45 @@ public final class SonicAudioProcessorTest { @Test public void testIsActiveWithPitchChange() throws Exception { sonicAudioProcessor.setPitch(1.5f); - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); + sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ); sonicAudioProcessor.flush(); assertThat(sonicAudioProcessor.isActive()).isTrue(); } @Test public void testIsNotActiveWithNoChange() throws Exception { - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT); + sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ); assertThat(sonicAudioProcessor.isActive()).isFalse(); } @Test public void testDoesNotSupportNon16BitInput() throws Exception { try { - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_8BIT); + sonicAudioProcessor.configure( + new AudioFormat( + /* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_8BIT)); fail(); - } catch (AudioProcessor.UnhandledFormatException e) { + } catch (UnhandledAudioFormatException e) { // Expected. } try { - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_24BIT); + sonicAudioProcessor.configure( + new AudioFormat( + /* sampleRate= */ 44100, + /* channelCount= */ 2, + /* encoding= */ C.ENCODING_PCM_24BIT)); fail(); - } catch (AudioProcessor.UnhandledFormatException e) { + } catch (UnhandledAudioFormatException e) { // Expected. } try { - sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_32BIT); + sonicAudioProcessor.configure( + new AudioFormat( + /* sampleRate= */ 44100, + /* channelCount= */ 2, + /* encoding= */ C.ENCODING_PCM_32BIT)); fail(); - } catch (AudioProcessor.UnhandledFormatException e) { + } catch (UnhandledAudioFormatException e) { // Expected. } }