Skip to content

Commit

Permalink
Add AudioProcessor.AudioFormat
Browse files Browse the repository at this point in the history
Issue: #6601
PiperOrigin-RevId: 282515179
  • Loading branch information
andrewlewis authored and ojw28 committed Nov 27, 2019
1 parent a2b3ad8 commit 07b3cbc
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 329 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
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;
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -87,15 +84,13 @@ public synchronized void updateOrientation(float w, float x, float y, float z) {

@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;
Expand All @@ -115,34 +110,21 @@ public synchronized void configure(int sampleRateHz, int channelCount, @C.Encodi
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
public boolean isActive() {
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();
Expand Down Expand Up @@ -181,7 +163,10 @@ public void flush() {
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) {
Expand All @@ -195,8 +180,7 @@ public synchronized void reset() {
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,65 @@
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;

/**
* 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 {

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

/** 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 UnhandledFormatException extends Exception {
final class UnhandledAudioFormatException extends Exception {

public UnhandledFormatException(
int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding "
+ encoding);
public UnhandledAudioFormatException(AudioFormat inputAudioFormat) {
super("Unhandled format: " + inputAudioFormat);
}

}
Expand All @@ -50,45 +84,23 @@ public UnhandledFormatException(

/**
* 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
Expand Down Expand Up @@ -130,6 +142,6 @@ void configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
*/
void flush();

/** Resets the processor to its unconfigured state. */
/** Resets the processor to its unconfigured state, releasing any resources. */
void reset();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,42 @@
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;

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 boolean isActive() {
return sampleRateHz != Format.NO_VALUE;
}

@Override
public int getOutputChannelCount() {
return channelCount;
}

@Override
public int getOutputEncoding() {
return encoding;
public final AudioFormat configure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException {
this.inputAudioFormat = inputAudioFormat;
outputAudioFormat = onConfigure(inputAudioFormat);
return isActive() ? outputAudioFormat : AudioFormat.NOT_SET;
}

@Override
public int getOutputSampleRateHz() {
return sampleRateHz;
public boolean isActive() {
return outputAudioFormat != AudioFormat.NOT_SET;
}

@Override
Expand Down Expand Up @@ -98,20 +86,11 @@ public final void flush() {
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
Expand All @@ -132,6 +111,12 @@ protected final boolean hasPendingOutput() {
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.
Expand Down
Loading

0 comments on commit 07b3cbc

Please sign in to comment.