Add AudioProcessor.AudioFormat

Issue: #6601
PiperOrigin-RevId: 282515179
This commit is contained in:
andrewlewis 2019-11-26 09:08:59 +00:00 committed by Oliver Woodman
parent b7000e64e9
commit 10f142b3ff
13 changed files with 263 additions and 333 deletions

View File

@ -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;
}

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <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.
* @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();
}

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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++) {

View File

@ -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);
}
/**

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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.
}
}