Add AudioProcessor.AudioFormat
Issue: #6601 PiperOrigin-RevId: 282515179
This commit is contained in:
parent
b7000e64e9
commit
10f142b3ff
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ext.gvr;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.audio.AudioProcessor;
|
import com.google.android.exoplayer2.audio.AudioProcessor;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.vr.sdk.audio.GvrAudioSurround;
|
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 OUTPUT_FRAME_SIZE = OUTPUT_CHANNEL_COUNT * 2; // 16-bit stereo output.
|
||||||
private static final int NO_SURROUND_FORMAT = GvrAudioSurround.SurroundFormat.INVALID;
|
private static final int NO_SURROUND_FORMAT = GvrAudioSurround.SurroundFormat.INVALID;
|
||||||
|
|
||||||
private int sampleRateHz;
|
private AudioFormat inputAudioFormat;
|
||||||
private int channelCount;
|
|
||||||
private int pendingGvrAudioSurroundFormat;
|
private int pendingGvrAudioSurroundFormat;
|
||||||
@Nullable private GvrAudioSurround gvrAudioSurround;
|
@Nullable private GvrAudioSurround gvrAudioSurround;
|
||||||
private ByteBuffer buffer;
|
private ByteBuffer buffer;
|
||||||
@ -60,8 +58,7 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
|||||||
public GvrAudioProcessor() {
|
public GvrAudioProcessor() {
|
||||||
// Use the identity for the initial orientation.
|
// Use the identity for the initial orientation.
|
||||||
w = 1f;
|
w = 1f;
|
||||||
sampleRateHz = Format.NO_VALUE;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
buffer = EMPTY_BUFFER;
|
buffer = EMPTY_BUFFER;
|
||||||
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
|
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
|
||||||
}
|
}
|
||||||
@ -87,15 +84,13 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
|||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
@Override
|
@Override
|
||||||
public synchronized void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
|
public synchronized AudioFormat configure(AudioFormat inputAudioFormat)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledAudioFormatException {
|
||||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
|
||||||
maybeReleaseGvrAudioSurround();
|
maybeReleaseGvrAudioSurround();
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
this.sampleRateHz = sampleRateHz;
|
switch (inputAudioFormat.channelCount) {
|
||||||
this.channelCount = channelCount;
|
|
||||||
switch (channelCount) {
|
|
||||||
case 1:
|
case 1:
|
||||||
pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO;
|
pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO;
|
||||||
break;
|
break;
|
||||||
@ -115,12 +110,14 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
|||||||
pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.THIRD_ORDER_AMBISONICS;
|
pendingGvrAudioSurroundFormat = GvrAudioSurround.SurroundFormat.THIRD_ORDER_AMBISONICS;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
if (buffer == EMPTY_BUFFER) {
|
if (buffer == EMPTY_BUFFER) {
|
||||||
buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE)
|
buffer = ByteBuffer.allocateDirect(FRAMES_PER_OUTPUT_BUFFER * OUTPUT_FRAME_SIZE)
|
||||||
.order(ByteOrder.nativeOrder());
|
.order(ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
|
this.inputAudioFormat = inputAudioFormat;
|
||||||
|
return new AudioFormat(inputAudioFormat.sampleRate, OUTPUT_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -128,21 +125,6 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
|||||||
return pendingGvrAudioSurroundFormat != NO_SURROUND_FORMAT || gvrAudioSurround != null;
|
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
|
@Override
|
||||||
public void queueInput(ByteBuffer input) {
|
public void queueInput(ByteBuffer input) {
|
||||||
int position = input.position();
|
int position = input.position();
|
||||||
@ -181,7 +163,10 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
|||||||
maybeReleaseGvrAudioSurround();
|
maybeReleaseGvrAudioSurround();
|
||||||
gvrAudioSurround =
|
gvrAudioSurround =
|
||||||
new 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);
|
gvrAudioSurround.updateNativeOrientation(w, x, y, z);
|
||||||
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
|
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
|
||||||
} else if (gvrAudioSurround != null) {
|
} else if (gvrAudioSurround != null) {
|
||||||
@ -195,8 +180,7 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
|||||||
maybeReleaseGvrAudioSurround();
|
maybeReleaseGvrAudioSurround();
|
||||||
updateOrientation(/* w= */ 1f, /* x= */ 0f, /* y= */ 0f, /* z= */ 0f);
|
updateOrientation(/* w= */ 1f, /* x= */ 0f, /* y= */ 0f, /* z= */ 0f);
|
||||||
inputEnded = false;
|
inputEnded = false;
|
||||||
sampleRateHz = Format.NO_VALUE;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
buffer = EMPTY_BUFFER;
|
buffer = EMPTY_BUFFER;
|
||||||
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
|
pendingGvrAudioSurroundFormat = NO_SURROUND_FORMAT;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
package com.google.android.exoplayer2.audio;
|
package com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
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.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
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
|
* Interface for audio processors, which take audio data as input and transform it, potentially
|
||||||
* modifying its channel count, encoding and/or sample rate.
|
* 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
|
* <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.
|
* to be set that affect the output audio and whether the processor is active/inactive.
|
||||||
*/
|
*/
|
||||||
public interface AudioProcessor {
|
public interface AudioProcessor {
|
||||||
|
|
||||||
/** Exception thrown when a processor can't be configured for a given input audio format. */
|
/** PCM audio format that may be handled by an audio processor. */
|
||||||
final class UnhandledFormatException extends Exception {
|
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(
|
/** The sample rate in Hertz. */
|
||||||
int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
|
public final int sampleRate;
|
||||||
super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding "
|
/** The number of interleaved channels. */
|
||||||
+ encoding);
|
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
|
* 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.
|
* method, call {@link #isActive()} to determine whether the audio processor is active. Returns
|
||||||
*
|
* the configured output audio format if this instance 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.
|
|
||||||
*
|
*
|
||||||
* <p>After calling this method, it is necessary to {@link #flush()} the processor to apply the
|
* <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
|
* new configuration before queueing more data. You can (optionally) first drain output in the
|
||||||
* previous configuration by calling {@link #queueEndOfStream()} and {@link #getOutput()}.
|
* previous configuration by calling {@link #queueEndOfStream()} and {@link #getOutput()}.
|
||||||
*
|
*
|
||||||
* @param sampleRateHz The sample rate of input audio in Hz.
|
* @param inputAudioFormat The format of audio that will be queued after the next call to {@link
|
||||||
* @param channelCount The number of interleaved channels in input audio.
|
* #flush()}.
|
||||||
* @param encoding The encoding of input audio.
|
* @return The configured output audio format if this instance is {@link #isActive() active}.
|
||||||
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
|
* @throws UnhandledAudioFormatException Thrown if the specified format can't be handled as input.
|
||||||
*/
|
*/
|
||||||
void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException;
|
||||||
throws UnhandledFormatException;
|
|
||||||
|
|
||||||
/** Returns whether the processor is configured and will process input buffers. */
|
/** Returns whether the processor is configured and will process input buffers. */
|
||||||
boolean isActive();
|
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.
|
* 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
|
* {@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();
|
void flush();
|
||||||
|
|
||||||
/** Resets the processor to its unconfigured state. */
|
/** Resets the processor to its unconfigured state, releasing any resources. */
|
||||||
void reset();
|
void reset();
|
||||||
}
|
}
|
||||||
|
@ -16,24 +16,20 @@
|
|||||||
package com.google.android.exoplayer2.audio;
|
package com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for audio processors that keep an output buffer and an internal buffer that is reused
|
* 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 {
|
public abstract class BaseAudioProcessor implements AudioProcessor {
|
||||||
|
|
||||||
/** The configured input sample rate, in Hertz, or {@link Format#NO_VALUE} if not configured. */
|
/** The configured input audio format. */
|
||||||
protected int sampleRateHz;
|
protected AudioFormat inputAudioFormat;
|
||||||
/** 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;
|
|
||||||
|
|
||||||
|
private AudioFormat outputAudioFormat;
|
||||||
private ByteBuffer buffer;
|
private ByteBuffer buffer;
|
||||||
private ByteBuffer outputBuffer;
|
private ByteBuffer outputBuffer;
|
||||||
private boolean inputEnded;
|
private boolean inputEnded;
|
||||||
@ -41,29 +37,21 @@ public abstract class BaseAudioProcessor implements AudioProcessor {
|
|||||||
public BaseAudioProcessor() {
|
public BaseAudioProcessor() {
|
||||||
buffer = EMPTY_BUFFER;
|
buffer = EMPTY_BUFFER;
|
||||||
outputBuffer = EMPTY_BUFFER;
|
outputBuffer = EMPTY_BUFFER;
|
||||||
channelCount = Format.NO_VALUE;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
sampleRateHz = Format.NO_VALUE;
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
encoding = Format.NO_VALUE;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final AudioFormat configure(AudioFormat inputAudioFormat)
|
||||||
|
throws UnhandledAudioFormatException {
|
||||||
|
this.inputAudioFormat = inputAudioFormat;
|
||||||
|
outputAudioFormat = onConfigure(inputAudioFormat);
|
||||||
|
return isActive() ? outputAudioFormat : AudioFormat.NOT_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return sampleRateHz != Format.NO_VALUE;
|
return outputAudioFormat != AudioFormat.NOT_SET;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return channelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputEncoding() {
|
|
||||||
return encoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return sampleRateHz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -98,20 +86,11 @@ public abstract class BaseAudioProcessor implements AudioProcessor {
|
|||||||
public final void reset() {
|
public final void reset() {
|
||||||
flush();
|
flush();
|
||||||
buffer = EMPTY_BUFFER;
|
buffer = EMPTY_BUFFER;
|
||||||
sampleRateHz = Format.NO_VALUE;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
channelCount = Format.NO_VALUE;
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
encoding = Format.NO_VALUE;
|
|
||||||
onReset();
|
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
|
* 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
|
* 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();
|
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. */
|
/** Called when the end-of-stream is queued to the processor. */
|
||||||
protected void onQueueEndOfStream() {
|
protected void onQueueEndOfStream() {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
|
@ -24,19 +24,17 @@ import java.nio.ByteBuffer;
|
|||||||
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
|
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
|
||||||
* channels. This can be used to reorder, duplicate or discard channels.
|
* channels. This can be used to reorder, duplicate or discard channels.
|
||||||
*/
|
*/
|
||||||
/* package */
|
|
||||||
// the constructor does not initialize fields: pendingOutputChannels, outputChannels
|
// the constructor does not initialize fields: pendingOutputChannels, outputChannels
|
||||||
@SuppressWarnings("nullness:initialization.fields.uninitialized")
|
@SuppressWarnings("nullness:initialization.fields.uninitialized")
|
||||||
final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
|
/* package */ final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
|
||||||
|
|
||||||
@Nullable private int[] pendingOutputChannels;
|
@Nullable private int[] pendingOutputChannels;
|
||||||
|
|
||||||
private boolean active;
|
|
||||||
@Nullable private int[] outputChannels;
|
@Nullable private int[] outputChannels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)}
|
* Resets the channel mapping. After calling this method, call {@link #configure(AudioFormat)} to
|
||||||
* to start using the new channel map.
|
* start using the new channel map.
|
||||||
*
|
*
|
||||||
* @param outputChannels The mapping from input to output channel indices, or {@code null} to
|
* @param outputChannels The mapping from input to output channel indices, or {@code null} to
|
||||||
* leave the input unchanged.
|
* leave the input unchanged.
|
||||||
@ -47,38 +45,30 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledAudioFormatException {
|
||||||
outputChannels = pendingOutputChannels;
|
outputChannels = pendingOutputChannels;
|
||||||
|
|
||||||
int[] outputChannels = this.outputChannels;
|
int[] outputChannels = this.outputChannels;
|
||||||
if (outputChannels == null) {
|
if (outputChannels == null) {
|
||||||
active = false;
|
return AudioFormat.NOT_SET;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputFormat(sampleRateHz, channelCount, encoding);
|
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
|
||||||
active = channelCount != outputChannels.length;
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean active = inputAudioFormat.channelCount != outputChannels.length;
|
||||||
for (int i = 0; i < outputChannels.length; i++) {
|
for (int i = 0; i < outputChannels.length; i++) {
|
||||||
int channelIndex = outputChannels[i];
|
int channelIndex = outputChannels[i];
|
||||||
if (channelIndex >= channelCount) {
|
if (channelIndex >= inputAudioFormat.channelCount) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
active |= (channelIndex != i);
|
active |= (channelIndex != i);
|
||||||
}
|
}
|
||||||
}
|
return active
|
||||||
|
? new AudioFormat(inputAudioFormat.sampleRate, outputChannels.length, C.ENCODING_PCM_16BIT)
|
||||||
@Override
|
: AudioFormat.NOT_SET;
|
||||||
public boolean isActive() {
|
|
||||||
return active;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return outputChannels == null ? channelCount : outputChannels.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -86,14 +76,14 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
|
|||||||
int[] outputChannels = Assertions.checkNotNull(this.outputChannels);
|
int[] outputChannels = Assertions.checkNotNull(this.outputChannels);
|
||||||
int position = inputBuffer.position();
|
int position = inputBuffer.position();
|
||||||
int limit = inputBuffer.limit();
|
int limit = inputBuffer.limit();
|
||||||
int frameCount = (limit - position) / (2 * channelCount);
|
int frameCount = (limit - position) / (2 * inputAudioFormat.channelCount);
|
||||||
int outputSize = frameCount * outputChannels.length * 2;
|
int outputSize = frameCount * outputChannels.length * 2;
|
||||||
ByteBuffer buffer = replaceOutputBuffer(outputSize);
|
ByteBuffer buffer = replaceOutputBuffer(outputSize);
|
||||||
while (position < limit) {
|
while (position < limit) {
|
||||||
for (int channelIndex : outputChannels) {
|
for (int channelIndex : outputChannels) {
|
||||||
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
|
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
|
||||||
}
|
}
|
||||||
position += channelCount * 2;
|
position += inputAudioFormat.channelCount * 2;
|
||||||
}
|
}
|
||||||
inputBuffer.position(limit);
|
inputBuffer.position(limit);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
@ -103,7 +93,6 @@ final class ChannelMappingAudioProcessor extends BaseAudioProcessor {
|
|||||||
protected void onReset() {
|
protected void onReset() {
|
||||||
outputChannels = null;
|
outputChannels = null;
|
||||||
pendingOutputChannels = null;
|
pendingOutputChannels = null;
|
||||||
active = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
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.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -435,18 +436,22 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
if (processingEnabled) {
|
if (processingEnabled) {
|
||||||
trimmingAudioProcessor.setTrimFrameCount(trimStartFrames, trimEndFrames);
|
trimmingAudioProcessor.setTrimFrameCount(trimStartFrames, trimEndFrames);
|
||||||
channelMappingAudioProcessor.setChannelMap(outputChannels);
|
channelMappingAudioProcessor.setChannelMap(outputChannels);
|
||||||
|
AudioProcessor.AudioFormat inputAudioFormat =
|
||||||
|
new AudioProcessor.AudioFormat(sampleRate, channelCount, encoding);
|
||||||
|
AudioProcessor.AudioFormat outputAudioFormat = inputAudioFormat;
|
||||||
for (AudioProcessor audioProcessor : availableAudioProcessors) {
|
for (AudioProcessor audioProcessor : availableAudioProcessors) {
|
||||||
try {
|
try {
|
||||||
audioProcessor.configure(sampleRate, channelCount, encoding);
|
outputAudioFormat = audioProcessor.configure(inputAudioFormat);
|
||||||
} catch (AudioProcessor.UnhandledFormatException e) {
|
} catch (UnhandledAudioFormatException e) {
|
||||||
throw new ConfigurationException(e);
|
throw new ConfigurationException(e);
|
||||||
}
|
}
|
||||||
if (audioProcessor.isActive()) {
|
if (audioProcessor.isActive()) {
|
||||||
channelCount = audioProcessor.getOutputChannelCount();
|
inputAudioFormat = outputAudioFormat;
|
||||||
sampleRate = audioProcessor.getOutputSampleRateHz();
|
|
||||||
encoding = audioProcessor.getOutputEncoding();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sampleRate = outputAudioFormat.sampleRate;
|
||||||
|
channelCount = outputAudioFormat.channelCount;
|
||||||
|
encoding = outputAudioFormat.encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
int outputChannelConfig = getChannelConfig(channelCount, isInputPcm);
|
int outputChannelConfig = getChannelConfig(channelCount, isInputPcm);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.audio;
|
package com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.ByteBuffer;
|
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;
|
private static final double PCM_32_BIT_INT_TO_PCM_32_BIT_FLOAT_FACTOR = 1.0 / 0x7FFFFFFF;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledAudioFormatException {
|
||||||
if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) {
|
if (!Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding)) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
setInputFormat(sampleRateHz, channelCount, encoding);
|
return Util.isEncodingHighResolutionIntegerPcm(inputAudioFormat.encoding)
|
||||||
}
|
? new AudioFormat(
|
||||||
|
inputAudioFormat.sampleRate, inputAudioFormat.channelCount, C.ENCODING_PCM_FLOAT)
|
||||||
@Override
|
: AudioFormat.NOT_SET;
|
||||||
public boolean isActive() {
|
|
||||||
return Util.isEncodingHighResolutionIntegerPcm(encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputEncoding() {
|
|
||||||
return C.ENCODING_PCM_FLOAT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInput(ByteBuffer inputBuffer) {
|
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 position = inputBuffer.position();
|
||||||
int limit = inputBuffer.limit();
|
int limit = inputBuffer.limit();
|
||||||
int size = limit - position;
|
int size = limit - position;
|
||||||
@ -65,7 +60,7 @@ import java.nio.ByteBuffer;
|
|||||||
| ((inputBuffer.get(i + 3) & 0xFF) << 24);
|
| ((inputBuffer.get(i + 3) & 0xFF) << 24);
|
||||||
writePcm32BitFloat(pcm32BitInteger, buffer);
|
writePcm32BitFloat(pcm32BitInteger, buffer);
|
||||||
}
|
}
|
||||||
} else {
|
} else { // Input is 24-bit PCM.
|
||||||
for (int i = position; i < limit; i += 3) {
|
for (int i = position; i < limit; i += 3) {
|
||||||
int pcm32BitInteger =
|
int pcm32BitInteger =
|
||||||
((inputBuffer.get(i) & 0xFF) << 8)
|
((inputBuffer.get(i) & 0xFF) << 8)
|
||||||
|
@ -26,23 +26,17 @@ import java.nio.ByteBuffer;
|
|||||||
/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor {
|
/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledAudioFormatException {
|
||||||
|
@C.PcmEncoding int encoding = inputAudioFormat.encoding;
|
||||||
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
|
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
|
||||||
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
|
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
setInputFormat(sampleRateHz, channelCount, encoding);
|
return encoding != C.ENCODING_PCM_16BIT
|
||||||
}
|
? new AudioFormat(
|
||||||
|
inputAudioFormat.sampleRate, inputAudioFormat.channelCount, C.ENCODING_PCM_16BIT)
|
||||||
@Override
|
: AudioFormat.NOT_SET;
|
||||||
public boolean isActive() {
|
|
||||||
return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputEncoding() {
|
|
||||||
return C.ENCODING_PCM_16BIT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -52,7 +46,7 @@ import java.nio.ByteBuffer;
|
|||||||
int limit = inputBuffer.limit();
|
int limit = inputBuffer.limit();
|
||||||
int size = limit - position;
|
int size = limit - position;
|
||||||
int resampledSize;
|
int resampledSize;
|
||||||
switch (encoding) {
|
switch (inputAudioFormat.encoding) {
|
||||||
case C.ENCODING_PCM_8BIT:
|
case C.ENCODING_PCM_8BIT:
|
||||||
resampledSize = size * 2;
|
resampledSize = size * 2;
|
||||||
break;
|
break;
|
||||||
@ -74,7 +68,7 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
// Resample the little endian input and update the input/output buffers.
|
// Resample the little endian input and update the input/output buffers.
|
||||||
ByteBuffer buffer = replaceOutputBuffer(resampledSize);
|
ByteBuffer buffer = replaceOutputBuffer(resampledSize);
|
||||||
switch (encoding) {
|
switch (inputAudioFormat.encoding) {
|
||||||
case C.ENCODING_PCM_8BIT:
|
case C.ENCODING_PCM_8BIT:
|
||||||
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
|
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
|
||||||
for (int i = position; i < limit; i++) {
|
for (int i = position; i < limit; i++) {
|
||||||
|
@ -119,18 +119,17 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
|
|||||||
// AudioProcessor implementation.
|
// AudioProcessor implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledAudioFormatException {
|
||||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
bytesPerFrame = channelCount * 2;
|
return enabled ? inputAudioFormat : AudioFormat.NOT_SET;
|
||||||
setInputFormat(sampleRateHz, channelCount, encoding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return super.isActive() && enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -165,7 +164,8 @@ public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFlush() {
|
protected void onFlush() {
|
||||||
if (isActive()) {
|
if (enabled) {
|
||||||
|
bytesPerFrame = inputAudioFormat.bytesPerFrame;
|
||||||
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame;
|
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame;
|
||||||
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
|
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
|
||||||
maybeSilenceBuffer = new byte[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.
|
* Returns the number of input frames corresponding to {@code durationUs} microseconds of audio.
|
||||||
*/
|
*/
|
||||||
private int durationUsToFrames(long durationUs) {
|
private int durationUsToFrames(long durationUs) {
|
||||||
return (int) ((durationUs * sampleRateHz) / C.MICROS_PER_SECOND);
|
return (int) ((durationUs * inputAudioFormat.sampleRate) / C.MICROS_PER_SECOND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.audio;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.C.Encoding;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
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 static final int MIN_BYTES_FOR_SPEEDUP_CALCULATION = 1024;
|
||||||
|
|
||||||
private int channelCount;
|
private int pendingOutputSampleRate;
|
||||||
private int sampleRateHz;
|
|
||||||
private float speed;
|
private float speed;
|
||||||
private float pitch;
|
private float pitch;
|
||||||
private int outputSampleRateHz;
|
|
||||||
private int pendingOutputSampleRateHz;
|
private AudioFormat inputAudioFormat;
|
||||||
|
private AudioFormat outputAudioFormat;
|
||||||
|
|
||||||
private boolean pendingSonicRecreation;
|
private boolean pendingSonicRecreation;
|
||||||
@Nullable private Sonic sonic;
|
@Nullable private Sonic sonic;
|
||||||
@ -84,13 +83,12 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
public SonicAudioProcessor() {
|
public SonicAudioProcessor() {
|
||||||
speed = 1f;
|
speed = 1f;
|
||||||
pitch = 1f;
|
pitch = 1f;
|
||||||
channelCount = Format.NO_VALUE;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
sampleRateHz = Format.NO_VALUE;
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
outputSampleRateHz = Format.NO_VALUE;
|
|
||||||
buffer = EMPTY_BUFFER;
|
buffer = EMPTY_BUFFER;
|
||||||
shortBuffer = buffer.asShortBuffer();
|
shortBuffer = buffer.asShortBuffer();
|
||||||
outputBuffer = EMPTY_BUFFER;
|
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
|
* 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
|
* audio at the same sample rate as the input. After calling this method, call {@link
|
||||||
* {@link #configure(int, int, int)} to start using the new sample rate.
|
* #configure(AudioFormat)} to start using the new sample rate.
|
||||||
*
|
*
|
||||||
* @param sampleRateHz The sample rate for output audio, in hertz.
|
* @param sampleRateHz The sample rate for output audio, in hertz.
|
||||||
* @see #configure(int, int, int)
|
* @see #configure(AudioFormat)
|
||||||
*/
|
*/
|
||||||
public void setOutputSampleRateHz(int sampleRateHz) {
|
public void setOutputSampleRateHz(int sampleRateHz) {
|
||||||
pendingOutputSampleRateHz = sampleRateHz;
|
pendingOutputSampleRate = sampleRateHz;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,50 +147,39 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
*/
|
*/
|
||||||
public long scaleDurationForSpeedup(long duration) {
|
public long scaleDurationForSpeedup(long duration) {
|
||||||
if (outputBytes >= MIN_BYTES_FOR_SPEEDUP_CALCULATION) {
|
if (outputBytes >= MIN_BYTES_FOR_SPEEDUP_CALCULATION) {
|
||||||
return outputSampleRateHz == sampleRateHz
|
return outputAudioFormat.sampleRate == inputAudioFormat.sampleRate
|
||||||
? Util.scaleLargeTimestamp(duration, inputBytes, outputBytes)
|
? Util.scaleLargeTimestamp(duration, inputBytes, outputBytes)
|
||||||
: Util.scaleLargeTimestamp(duration, inputBytes * outputSampleRateHz,
|
: Util.scaleLargeTimestamp(
|
||||||
outputBytes * sampleRateHz);
|
duration,
|
||||||
|
inputBytes * outputAudioFormat.sampleRate,
|
||||||
|
outputBytes * inputAudioFormat.sampleRate);
|
||||||
} else {
|
} else {
|
||||||
return (long) ((double) speed * duration);
|
return (long) ((double) speed * duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @Encoding int encoding)
|
public AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException {
|
||||||
throws UnhandledFormatException {
|
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
|
||||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
|
||||||
}
|
}
|
||||||
int outputSampleRateHz = pendingOutputSampleRateHz == SAMPLE_RATE_NO_CHANGE
|
int outputSampleRateHz =
|
||||||
? sampleRateHz : pendingOutputSampleRateHz;
|
pendingOutputSampleRate == SAMPLE_RATE_NO_CHANGE
|
||||||
this.sampleRateHz = sampleRateHz;
|
? inputAudioFormat.sampleRate
|
||||||
this.channelCount = channelCount;
|
: pendingOutputSampleRate;
|
||||||
this.outputSampleRateHz = outputSampleRateHz;
|
this.inputAudioFormat = inputAudioFormat;
|
||||||
|
this.outputAudioFormat =
|
||||||
|
new AudioFormat(outputSampleRateHz, inputAudioFormat.channelCount, C.ENCODING_PCM_16BIT);
|
||||||
pendingSonicRecreation = true;
|
pendingSonicRecreation = true;
|
||||||
|
return outputAudioFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return sampleRateHz != Format.NO_VALUE
|
return outputAudioFormat.sampleRate != Format.NO_VALUE
|
||||||
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
||||||
|| Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|
|| Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|
||||||
|| outputSampleRateHz != sampleRateHz);
|
|| outputAudioFormat.sampleRate != inputAudioFormat.sampleRate);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return channelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputEncoding() {
|
|
||||||
return C.ENCODING_PCM_16BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return outputSampleRateHz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -245,7 +232,13 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
public void flush() {
|
public void flush() {
|
||||||
if (isActive()) {
|
if (isActive()) {
|
||||||
if (pendingSonicRecreation) {
|
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) {
|
} else if (sonic != null) {
|
||||||
sonic.flush();
|
sonic.flush();
|
||||||
}
|
}
|
||||||
@ -260,13 +253,12 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
public void reset() {
|
public void reset() {
|
||||||
speed = 1f;
|
speed = 1f;
|
||||||
pitch = 1f;
|
pitch = 1f;
|
||||||
channelCount = Format.NO_VALUE;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
sampleRateHz = Format.NO_VALUE;
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
outputSampleRateHz = Format.NO_VALUE;
|
|
||||||
buffer = EMPTY_BUFFER;
|
buffer = EMPTY_BUFFER;
|
||||||
shortBuffer = buffer.asShortBuffer();
|
shortBuffer = buffer.asShortBuffer();
|
||||||
outputBuffer = EMPTY_BUFFER;
|
outputBuffer = EMPTY_BUFFER;
|
||||||
pendingOutputSampleRateHz = SAMPLE_RATE_NO_CHANGE;
|
pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE;
|
||||||
pendingSonicRecreation = false;
|
pendingSonicRecreation = false;
|
||||||
sonic = null;
|
sonic = null;
|
||||||
inputBytes = 0;
|
inputBytes = 0;
|
||||||
|
@ -64,8 +64,9 @@ public final class TeeAudioProcessor extends BaseAudioProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
|
public AudioFormat onConfigure(AudioFormat inputAudioFormat) {
|
||||||
setInputFormat(sampleRateHz, channelCount, encoding);
|
// This processor is always active (if passed to the sink) and outputs its input.
|
||||||
|
return inputAudioFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -81,7 +82,8 @@ public final class TeeAudioProcessor extends BaseAudioProcessor {
|
|||||||
@Override
|
@Override
|
||||||
protected void onFlush() {
|
protected void onFlush() {
|
||||||
if (isActive()) {
|
if (isActive()) {
|
||||||
audioBufferSink.flush(sampleRateHz, channelCount, encoding);
|
audioBufferSink.flush(
|
||||||
|
inputAudioFormat.sampleRate, inputAudioFormat.channelCount, inputAudioFormat.encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
|
|||||||
|
|
||||||
@C.PcmEncoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT;
|
@C.PcmEncoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT;
|
||||||
|
|
||||||
private boolean isActive;
|
|
||||||
private int trimStartFrames;
|
private int trimStartFrames;
|
||||||
private int trimEndFrames;
|
private int trimEndFrames;
|
||||||
private int bytesPerFrame;
|
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
|
* 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.
|
* trimming frame counts.
|
||||||
*
|
*
|
||||||
* @param trimStartFrames The number of audio frames to trim from the start of audio.
|
* @param trimStartFrames The number of audio frames to trim from the start of audio.
|
||||||
@ -68,26 +67,20 @@ import java.nio.ByteBuffer;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledAudioFormatException {
|
||||||
if (encoding != OUTPUT_ENCODING) {
|
if (inputAudioFormat.encoding != OUTPUT_ENCODING) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
}
|
}
|
||||||
if (endBufferSize > 0) {
|
if (endBufferSize > 0) {
|
||||||
trimmedFrameCount += endBufferSize / bytesPerFrame;
|
trimmedFrameCount += endBufferSize / bytesPerFrame;
|
||||||
}
|
}
|
||||||
bytesPerFrame = Util.getPcmFrameSize(OUTPUT_ENCODING, channelCount);
|
bytesPerFrame = inputAudioFormat.bytesPerFrame;
|
||||||
endBuffer = new byte[trimEndFrames * bytesPerFrame];
|
endBuffer = new byte[trimEndFrames * bytesPerFrame];
|
||||||
endBufferSize = 0;
|
endBufferSize = 0;
|
||||||
pendingTrimStartBytes = trimStartFrames * bytesPerFrame;
|
pendingTrimStartBytes = trimStartFrames * bytesPerFrame;
|
||||||
isActive = trimStartFrames != 0 || trimEndFrames != 0;
|
|
||||||
receivedInputSinceConfigure = false;
|
receivedInputSinceConfigure = false;
|
||||||
setInputFormat(sampleRateHz, channelCount, encoding);
|
return trimStartFrames != 0 || trimEndFrames != 0 ? inputAudioFormat : AudioFormat.NOT_SET;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -140,7 +133,6 @@ import java.nio.ByteBuffer;
|
|||||||
buffer.flip();
|
buffer.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer getOutput() {
|
public ByteBuffer getOutput() {
|
||||||
if (super.isEnded() && endBufferSize > 0) {
|
if (super.isEnded() && endBufferSize > 0) {
|
||||||
@ -155,7 +147,6 @@ import java.nio.ByteBuffer;
|
|||||||
return super.getOutput();
|
return super.getOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
public boolean isEnded() {
|
||||||
return super.isEnded() && endBufferSize == 0;
|
return super.isEnded() && endBufferSize == 0;
|
||||||
|
@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
@ -31,8 +32,9 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class SilenceSkippingAudioProcessorTest {
|
public final class SilenceSkippingAudioProcessorTest {
|
||||||
|
|
||||||
private static final int TEST_SIGNAL_SAMPLE_RATE_HZ = 1000;
|
private static final AudioFormat AUDIO_FORMAT =
|
||||||
private static final int TEST_SIGNAL_CHANNEL_COUNT = 2;
|
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_SILENCE_DURATION_MS = 1000;
|
||||||
private static final int TEST_SIGNAL_NOISE_DURATION_MS = 1000;
|
private static final int TEST_SIGNAL_NOISE_DURATION_MS = 1000;
|
||||||
private static final int TEST_SIGNAL_FRAME_COUNT = 100000;
|
private static final int TEST_SIGNAL_FRAME_COUNT = 100000;
|
||||||
@ -52,8 +54,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
|
|
||||||
// When configuring it.
|
// When configuring it.
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
|
|
||||||
// It's active.
|
// It's active.
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
@ -65,8 +66,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
silenceSkippingAudioProcessor.setEnabled(false);
|
silenceSkippingAudioProcessor.setEnabled(false);
|
||||||
|
|
||||||
// When configuring it.
|
// When configuring it.
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
|
|
||||||
// It's not active.
|
// It's not active.
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isFalse();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isFalse();
|
||||||
@ -76,8 +76,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
public void testDefaultProcessor_isNotEnabled() throws Exception {
|
public void testDefaultProcessor_isNotEnabled() throws Exception {
|
||||||
// Given a processor in its default state.
|
// Given a processor in its default state.
|
||||||
// When reconfigured.
|
// When reconfigured.
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
|
|
||||||
// It's not active.
|
// It's not active.
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isFalse();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isFalse();
|
||||||
@ -88,16 +87,13 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
// Given a signal with only noise.
|
// Given a signal with only noise.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ,
|
|
||||||
TEST_SIGNAL_CHANNEL_COUNT,
|
|
||||||
TEST_SIGNAL_SILENCE_DURATION_MS,
|
TEST_SIGNAL_SILENCE_DURATION_MS,
|
||||||
/* noiseDurationMs= */ 0,
|
/* noiseDurationMs= */ 0,
|
||||||
TEST_SIGNAL_FRAME_COUNT);
|
TEST_SIGNAL_FRAME_COUNT);
|
||||||
|
|
||||||
// When processing the entire signal.
|
// When processing the entire signal.
|
||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
silenceSkippingAudioProcessor.flush();
|
silenceSkippingAudioProcessor.flush();
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
long totalOutputFrames =
|
long totalOutputFrames =
|
||||||
@ -113,8 +109,6 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
// Given a signal with only silence.
|
// Given a signal with only silence.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ,
|
|
||||||
TEST_SIGNAL_CHANNEL_COUNT,
|
|
||||||
/* silenceDurationMs= */ 0,
|
/* silenceDurationMs= */ 0,
|
||||||
TEST_SIGNAL_NOISE_DURATION_MS,
|
TEST_SIGNAL_NOISE_DURATION_MS,
|
||||||
TEST_SIGNAL_FRAME_COUNT);
|
TEST_SIGNAL_FRAME_COUNT);
|
||||||
@ -123,8 +117,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
||||||
new SilenceSkippingAudioProcessor();
|
new SilenceSkippingAudioProcessor();
|
||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
silenceSkippingAudioProcessor.flush();
|
silenceSkippingAudioProcessor.flush();
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
long totalOutputFrames =
|
long totalOutputFrames =
|
||||||
@ -141,8 +134,6 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
// Given a signal that alternates between silence and noise.
|
// Given a signal that alternates between silence and noise.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ,
|
|
||||||
TEST_SIGNAL_CHANNEL_COUNT,
|
|
||||||
TEST_SIGNAL_SILENCE_DURATION_MS,
|
TEST_SIGNAL_SILENCE_DURATION_MS,
|
||||||
TEST_SIGNAL_NOISE_DURATION_MS,
|
TEST_SIGNAL_NOISE_DURATION_MS,
|
||||||
TEST_SIGNAL_FRAME_COUNT);
|
TEST_SIGNAL_FRAME_COUNT);
|
||||||
@ -151,8 +142,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
||||||
new SilenceSkippingAudioProcessor();
|
new SilenceSkippingAudioProcessor();
|
||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
silenceSkippingAudioProcessor.flush();
|
silenceSkippingAudioProcessor.flush();
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
long totalOutputFrames =
|
long totalOutputFrames =
|
||||||
@ -169,8 +159,6 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
// Given a signal that alternates between silence and noise.
|
// Given a signal that alternates between silence and noise.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ,
|
|
||||||
TEST_SIGNAL_CHANNEL_COUNT,
|
|
||||||
TEST_SIGNAL_SILENCE_DURATION_MS,
|
TEST_SIGNAL_SILENCE_DURATION_MS,
|
||||||
TEST_SIGNAL_NOISE_DURATION_MS,
|
TEST_SIGNAL_NOISE_DURATION_MS,
|
||||||
TEST_SIGNAL_FRAME_COUNT);
|
TEST_SIGNAL_FRAME_COUNT);
|
||||||
@ -179,8 +167,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
||||||
new SilenceSkippingAudioProcessor();
|
new SilenceSkippingAudioProcessor();
|
||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
silenceSkippingAudioProcessor.flush();
|
silenceSkippingAudioProcessor.flush();
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
long totalOutputFrames =
|
long totalOutputFrames =
|
||||||
@ -197,8 +184,6 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
// Given a signal that alternates between silence and noise.
|
// Given a signal that alternates between silence and noise.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ,
|
|
||||||
TEST_SIGNAL_CHANNEL_COUNT,
|
|
||||||
TEST_SIGNAL_SILENCE_DURATION_MS,
|
TEST_SIGNAL_SILENCE_DURATION_MS,
|
||||||
TEST_SIGNAL_NOISE_DURATION_MS,
|
TEST_SIGNAL_NOISE_DURATION_MS,
|
||||||
TEST_SIGNAL_FRAME_COUNT);
|
TEST_SIGNAL_FRAME_COUNT);
|
||||||
@ -207,8 +192,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
||||||
new SilenceSkippingAudioProcessor();
|
new SilenceSkippingAudioProcessor();
|
||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
silenceSkippingAudioProcessor.flush();
|
silenceSkippingAudioProcessor.flush();
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
long totalOutputFrames =
|
long totalOutputFrames =
|
||||||
@ -224,8 +208,6 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
// Given a signal that alternates between silence and noise.
|
// Given a signal that alternates between silence and noise.
|
||||||
InputBufferProvider inputBufferProvider =
|
InputBufferProvider inputBufferProvider =
|
||||||
getInputBufferProviderForAlternatingSilenceAndNoise(
|
getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ,
|
|
||||||
TEST_SIGNAL_CHANNEL_COUNT,
|
|
||||||
TEST_SIGNAL_SILENCE_DURATION_MS,
|
TEST_SIGNAL_SILENCE_DURATION_MS,
|
||||||
TEST_SIGNAL_NOISE_DURATION_MS,
|
TEST_SIGNAL_NOISE_DURATION_MS,
|
||||||
TEST_SIGNAL_FRAME_COUNT);
|
TEST_SIGNAL_FRAME_COUNT);
|
||||||
@ -234,8 +216,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
||||||
new SilenceSkippingAudioProcessor();
|
new SilenceSkippingAudioProcessor();
|
||||||
silenceSkippingAudioProcessor.setEnabled(true);
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
silenceSkippingAudioProcessor.configure(
|
silenceSkippingAudioProcessor.configure(AUDIO_FORMAT);
|
||||||
TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT);
|
|
||||||
silenceSkippingAudioProcessor.flush();
|
silenceSkippingAudioProcessor.flush();
|
||||||
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
assertThat(silenceSkippingAudioProcessor.isActive()).isTrue();
|
||||||
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);
|
process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE);
|
||||||
@ -253,6 +234,7 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
SilenceSkippingAudioProcessor processor,
|
SilenceSkippingAudioProcessor processor,
|
||||||
InputBufferProvider inputBufferProvider,
|
InputBufferProvider inputBufferProvider,
|
||||||
int inputBufferSize) {
|
int inputBufferSize) {
|
||||||
|
int bytesPerFrame = AUDIO_FORMAT.bytesPerFrame;
|
||||||
processor.flush();
|
processor.flush();
|
||||||
long totalOutputFrames = 0;
|
long totalOutputFrames = 0;
|
||||||
while (inputBufferProvider.hasRemaining()) {
|
while (inputBufferProvider.hasRemaining()) {
|
||||||
@ -260,14 +242,14 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
while (inputBuffer.hasRemaining()) {
|
while (inputBuffer.hasRemaining()) {
|
||||||
processor.queueInput(inputBuffer);
|
processor.queueInput(inputBuffer);
|
||||||
ByteBuffer outputBuffer = processor.getOutput();
|
ByteBuffer outputBuffer = processor.getOutput();
|
||||||
totalOutputFrames += outputBuffer.remaining() / (2 * processor.getOutputChannelCount());
|
totalOutputFrames += outputBuffer.remaining() / bytesPerFrame;
|
||||||
outputBuffer.clear();
|
outputBuffer.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processor.queueEndOfStream();
|
processor.queueEndOfStream();
|
||||||
while (!processor.isEnded()) {
|
while (!processor.isEnded()) {
|
||||||
ByteBuffer outputBuffer = processor.getOutput();
|
ByteBuffer outputBuffer = processor.getOutput();
|
||||||
totalOutputFrames += outputBuffer.remaining() / (2 * processor.getOutputChannelCount());
|
totalOutputFrames += outputBuffer.remaining() / bytesPerFrame;
|
||||||
outputBuffer.clear();
|
outputBuffer.clear();
|
||||||
}
|
}
|
||||||
return totalOutputFrames;
|
return totalOutputFrames;
|
||||||
@ -278,16 +260,16 @@ public final class SilenceSkippingAudioProcessorTest {
|
|||||||
* between silence/noise of the specified durations to fill {@code totalFrameCount}.
|
* between silence/noise of the specified durations to fill {@code totalFrameCount}.
|
||||||
*/
|
*/
|
||||||
private static InputBufferProvider getInputBufferProviderForAlternatingSilenceAndNoise(
|
private static InputBufferProvider getInputBufferProviderForAlternatingSilenceAndNoise(
|
||||||
int sampleRateHz,
|
|
||||||
int channelCount,
|
|
||||||
int silenceDurationMs,
|
int silenceDurationMs,
|
||||||
int noiseDurationMs,
|
int noiseDurationMs,
|
||||||
int totalFrameCount) {
|
int totalFrameCount) {
|
||||||
|
int sampleRate = AUDIO_FORMAT.sampleRate;
|
||||||
|
int channelCount = AUDIO_FORMAT.channelCount;
|
||||||
Pcm16BitAudioBuilder audioBuilder = new Pcm16BitAudioBuilder(channelCount, totalFrameCount);
|
Pcm16BitAudioBuilder audioBuilder = new Pcm16BitAudioBuilder(channelCount, totalFrameCount);
|
||||||
while (!audioBuilder.isFull()) {
|
while (!audioBuilder.isFull()) {
|
||||||
int silenceDurationFrames = (silenceDurationMs * sampleRateHz) / 1000;
|
int silenceDurationFrames = (silenceDurationMs * sampleRate) / 1000;
|
||||||
audioBuilder.appendFrames(/* count= */ silenceDurationFrames, /* channelLevels= */ (short) 0);
|
audioBuilder.appendFrames(/* count= */ silenceDurationFrames, /* channelLevels= */ (short) 0);
|
||||||
int noiseDurationFrames = (noiseDurationMs * sampleRateHz) / 1000;
|
int noiseDurationFrames = (noiseDurationMs * sampleRate) / 1000;
|
||||||
audioBuilder.appendFrames(
|
audioBuilder.appendFrames(
|
||||||
/* count= */ noiseDurationFrames, /* channelLevels= */ Short.MAX_VALUE);
|
/* count= */ noiseDurationFrames, /* channelLevels= */ Short.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import static org.junit.Assert.fail;
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -28,6 +30,16 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class SonicAudioProcessorTest {
|
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;
|
private SonicAudioProcessor sonicAudioProcessor;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -39,59 +51,36 @@ public final class SonicAudioProcessorTest {
|
|||||||
public void testReconfigureWithSameSampleRate() throws Exception {
|
public void testReconfigureWithSameSampleRate() throws Exception {
|
||||||
// When configured for resampling from 44.1 kHz to 48 kHz, the output sample rate is correct.
|
// When configured for resampling from 44.1 kHz to 48 kHz, the output sample rate is correct.
|
||||||
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
|
AudioFormat outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
||||||
assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000);
|
|
||||||
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
||||||
|
assertThat(outputAudioFormat.sampleRate).isEqualTo(48000);
|
||||||
// When reconfigured with 48 kHz input, there is no resampling.
|
// When reconfigured with 48 kHz input, there is no resampling.
|
||||||
sonicAudioProcessor.configure(48000, 2, C.ENCODING_PCM_16BIT);
|
outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_48000_HZ);
|
||||||
assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000);
|
|
||||||
assertThat(sonicAudioProcessor.isActive()).isFalse();
|
assertThat(sonicAudioProcessor.isActive()).isFalse();
|
||||||
|
assertThat(outputAudioFormat.sampleRate).isEqualTo(48000);
|
||||||
// When reconfigure with 44.1 kHz input, resampling is enabled again.
|
// When reconfigure with 44.1 kHz input, resampling is enabled again.
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
|
outputAudioFormat = sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
||||||
assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(48000);
|
|
||||||
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
||||||
|
assertThat(outputAudioFormat.sampleRate).isEqualTo(48000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoSampleRateChange() throws Exception {
|
public void testNoSampleRateChange() throws Exception {
|
||||||
// Configure for resampling 44.1 kHz to 48 kHz.
|
// Configure for resampling 44.1 kHz to 48 kHz.
|
||||||
sonicAudioProcessor.setOutputSampleRateHz(48000);
|
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.
|
// Reconfigure to not modify the sample rate.
|
||||||
sonicAudioProcessor.setOutputSampleRateHz(SonicAudioProcessor.SAMPLE_RATE_NO_CHANGE);
|
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.
|
// The sample rate is unmodified, and the audio processor is not active.
|
||||||
assertThat(sonicAudioProcessor.getOutputSampleRateHz()).isEqualTo(22050);
|
|
||||||
assertThat(sonicAudioProcessor.isActive()).isFalse();
|
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
|
@Test
|
||||||
public void testIsActiveWithSpeedChange() throws Exception {
|
public void testIsActiveWithSpeedChange() throws Exception {
|
||||||
sonicAudioProcessor.setSpeed(1.5f);
|
sonicAudioProcessor.setSpeed(1.5f);
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
|
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
||||||
sonicAudioProcessor.flush();
|
sonicAudioProcessor.flush();
|
||||||
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
||||||
}
|
}
|
||||||
@ -99,35 +88,45 @@ public final class SonicAudioProcessorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testIsActiveWithPitchChange() throws Exception {
|
public void testIsActiveWithPitchChange() throws Exception {
|
||||||
sonicAudioProcessor.setPitch(1.5f);
|
sonicAudioProcessor.setPitch(1.5f);
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
|
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
||||||
sonicAudioProcessor.flush();
|
sonicAudioProcessor.flush();
|
||||||
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
assertThat(sonicAudioProcessor.isActive()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsNotActiveWithNoChange() throws Exception {
|
public void testIsNotActiveWithNoChange() throws Exception {
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_16BIT);
|
sonicAudioProcessor.configure(AUDIO_FORMAT_44100_HZ);
|
||||||
assertThat(sonicAudioProcessor.isActive()).isFalse();
|
assertThat(sonicAudioProcessor.isActive()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDoesNotSupportNon16BitInput() throws Exception {
|
public void testDoesNotSupportNon16BitInput() throws Exception {
|
||||||
try {
|
try {
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_8BIT);
|
sonicAudioProcessor.configure(
|
||||||
|
new AudioFormat(
|
||||||
|
/* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_8BIT));
|
||||||
fail();
|
fail();
|
||||||
} catch (AudioProcessor.UnhandledFormatException e) {
|
} catch (UnhandledAudioFormatException e) {
|
||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_24BIT);
|
sonicAudioProcessor.configure(
|
||||||
|
new AudioFormat(
|
||||||
|
/* sampleRate= */ 44100,
|
||||||
|
/* channelCount= */ 2,
|
||||||
|
/* encoding= */ C.ENCODING_PCM_24BIT));
|
||||||
fail();
|
fail();
|
||||||
} catch (AudioProcessor.UnhandledFormatException e) {
|
} catch (UnhandledAudioFormatException e) {
|
||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
sonicAudioProcessor.configure(44100, 2, C.ENCODING_PCM_32BIT);
|
sonicAudioProcessor.configure(
|
||||||
|
new AudioFormat(
|
||||||
|
/* sampleRate= */ 44100,
|
||||||
|
/* channelCount= */ 2,
|
||||||
|
/* encoding= */ C.ENCODING_PCM_32BIT));
|
||||||
fail();
|
fail();
|
||||||
} catch (AudioProcessor.UnhandledFormatException e) {
|
} catch (UnhandledAudioFormatException e) {
|
||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user