Commit 682987a0 by andrewlewis Committed by Oliver Woodman

Separate input/output handling in BufferProcessors.

This allows BufferProcessors to partially and/or asynchronously handle
input/output. Document contract for queueInput and getOutput.

Update ResamplingBufferProcessor to use the new interface.

Separate submitting bytes vs. writing data to the AudioTrack.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148212269
parent 89655088
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/** /**
* Interface for processors of audio buffers. * Interface for processors of audio buffers.
...@@ -36,30 +37,61 @@ public interface BufferProcessor { ...@@ -36,30 +37,61 @@ public interface BufferProcessor {
} }
/** /**
* Configures this processor to take input buffers with the specified format. * An empty, direct {@link ByteBuffer}.
*/
ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
/**
* Configures the processor to process input buffers with the specified format and returns whether
* the processor must be flushed. After calling this method, {@link #isActive()} returns whether
* the processor needs to handle buffers; if not, the processor will not accept any buffers until
* it is reconfigured. {@link #getOutputChannelCount()} and {@link #getOutputEncoding()} return
* the processor's output format.
* *
* @param sampleRateHz The sample rate of input audio in Hz. * @param sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio. * @param channelCount The number of interleaved channels in input audio.
* @param encoding The encoding of input audio. * @param encoding The encoding of input audio.
* @return Whether the processor must be flushed.
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input. * @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
*/ */
void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException; throws UnhandledFormatException;
/** /**
* Returns the encoding used in buffers output by this processor. * Returns whether the processor is configured and active.
*/
boolean isActive();
/**
* Returns the number of audio channels in the data output by the processor.
*/
int getOutputChannelCount();
/**
* Returns the audio encoding used in the data output by the processor.
*/ */
@C.Encoding @C.Encoding
int getOutputEncoding(); int getOutputEncoding();
/** /**
* Processes the data in the specified input buffer in its entirety. * 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
* read-only. Its position will be advanced by the number of bytes consumed (which may be zero).
* The caller retains ownership of the provided buffer. Calling this method invalidates any
* previous buffer returned by {@link #getOutput()}.
*
* @param buffer The input buffer to process.
*/
void queueInput(ByteBuffer buffer);
/**
* Returns a buffer containing processed output data between its position and limit. The buffer
* will always be a direct byte buffer with native byte order. Calling this method invalidates any
* previously returned buffer. The buffer will be empty if no output is available.
* *
* @param input A buffer containing the input data to process. * @return A buffer containing processed output data between its position and limit.
* @return A buffer containing the processed output. This may be the same as the input buffer if
* no processing was required.
*/ */
ByteBuffer handleBuffer(ByteBuffer input); ByteBuffer getOutput();
/** /**
* Clears any state in preparation for receiving a new stream of buffers. * Clears any state in preparation for receiving a new stream of buffers.
......
...@@ -18,31 +18,55 @@ package com.google.android.exoplayer2.audio; ...@@ -18,31 +18,55 @@ 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.Format;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/** /**
* A {@link BufferProcessor} that outputs buffers in {@link C#ENCODING_PCM_16BIT}. * A {@link BufferProcessor} that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/ */
/* package */ final class ResamplingBufferProcessor implements BufferProcessor { /* package */ final class ResamplingBufferProcessor implements BufferProcessor {
private int channelCount;
@C.PcmEncoding @C.PcmEncoding
private int encoding; private int encoding;
private ByteBuffer buffer;
private ByteBuffer outputBuffer; private ByteBuffer outputBuffer;
/**
* Creates a new buffer processor that converts audio data to {@link C#ENCODING_PCM_16BIT}.
*/
public ResamplingBufferProcessor() { public ResamplingBufferProcessor() {
encoding = C.ENCODING_INVALID; encoding = C.ENCODING_INVALID;
buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
} }
@Override @Override
public void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException { throws UnhandledFormatException {
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 UnhandledFormatException(sampleRateHz, channelCount, encoding);
} }
if (encoding == C.ENCODING_PCM_16BIT) { this.channelCount = channelCount;
outputBuffer = null; if (this.encoding == encoding) {
return false;
} }
this.encoding = encoding; this.encoding = encoding;
if (encoding == C.ENCODING_PCM_16BIT) {
buffer = EMPTY_BUFFER;
}
return true;
}
@Override
public boolean isActive() {
return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT;
}
@Override
public int getOutputChannelCount() {
return channelCount;
} }
@Override @Override
...@@ -51,16 +75,13 @@ import java.nio.ByteBuffer; ...@@ -51,16 +75,13 @@ import java.nio.ByteBuffer;
} }
@Override @Override
public ByteBuffer handleBuffer(ByteBuffer buffer) { public void queueInput(ByteBuffer inputBuffer) {
int position = buffer.position(); // Prepare the output buffer.
int limit = buffer.limit(); int position = inputBuffer.position();
int limit = inputBuffer.limit();
int size = limit - position; int size = limit - position;
int resampledSize; int resampledSize;
switch (encoding) { switch (encoding) {
case C.ENCODING_PCM_16BIT:
// No processing required.
return buffer;
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
resampledSize = size * 2; resampledSize = size * 2;
break; break;
...@@ -70,40 +91,39 @@ import java.nio.ByteBuffer; ...@@ -70,40 +91,39 @@ import java.nio.ByteBuffer;
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
resampledSize = size / 2; resampledSize = size / 2;
break; break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
// Never happens.
throw new IllegalStateException(); throw new IllegalStateException();
} }
if (buffer.capacity() < resampledSize) {
if (outputBuffer == null || outputBuffer.capacity() < resampledSize) { buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder());
outputBuffer = ByteBuffer.allocateDirect(resampledSize).order(buffer.order());
} else { } else {
outputBuffer.clear(); buffer.clear();
} }
// Samples are little endian. // Resample the little endian input and update the input/output buffers.
switch (encoding) { switch (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++) {
outputBuffer.put((byte) 0); buffer.put((byte) 0);
outputBuffer.put((byte) ((buffer.get(i) & 0xFF) - 128)); buffer.put((byte) ((inputBuffer.get(i) & 0xFF) - 128));
} }
break; break;
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
// 24->16 bit resampling. Drop the least significant byte. // 24->16 bit resampling. Drop the least significant byte.
for (int i = position; i < limit; i += 3) { for (int i = position; i < limit; i += 3) {
outputBuffer.put(buffer.get(i + 1)); buffer.put(inputBuffer.get(i + 1));
outputBuffer.put(buffer.get(i + 2)); buffer.put(inputBuffer.get(i + 2));
} }
break; break;
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes. // 32->16 bit resampling. Drop the two least significant bytes.
for (int i = position; i < limit; i += 4) { for (int i = position; i < limit; i += 4) {
outputBuffer.put(buffer.get(i + 2)); buffer.put(inputBuffer.get(i + 2));
outputBuffer.put(buffer.get(i + 3)); buffer.put(inputBuffer.get(i + 3));
} }
break; break;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
...@@ -113,19 +133,27 @@ import java.nio.ByteBuffer; ...@@ -113,19 +133,27 @@ import java.nio.ByteBuffer;
// Never happens. // Never happens.
throw new IllegalStateException(); throw new IllegalStateException();
} }
inputBuffer.position(inputBuffer.limit());
buffer.flip();
outputBuffer = buffer;
}
outputBuffer.flip(); @Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = this.outputBuffer;
this.outputBuffer = EMPTY_BUFFER;
return outputBuffer; return outputBuffer;
} }
@Override @Override
public void flush() { public void flush() {
// Do nothing. outputBuffer = EMPTY_BUFFER;
} }
@Override @Override
public void release() { public void release() {
outputBuffer = null; buffer = EMPTY_BUFFER;
outputBuffer = EMPTY_BUFFER;
} }
} }
...@@ -768,6 +768,28 @@ public final class Util { ...@@ -768,6 +768,28 @@ public final class Util {
} }
/** /**
* Returns the frame size for audio with {@code channelCount} channels in the specified encoding.
*
* @param pcmEncoding The encoding of the audio data.
* @param channelCount The channel count.
* @return The size of one audio frame in bytes.
*/
public static int getPcmFrameSize(@C.PcmEncoding int pcmEncoding, int channelCount) {
switch (pcmEncoding) {
case C.ENCODING_PCM_8BIT:
return channelCount;
case C.ENCODING_PCM_16BIT:
return channelCount * 2;
case C.ENCODING_PCM_24BIT:
return channelCount * 3;
case C.ENCODING_PCM_32BIT:
return channelCount * 4;
default:
throw new IllegalArgumentException();
}
}
/**
* Makes a best guess to infer the type from a file name. * Makes a best guess to infer the type from a file name.
* *
* @param fileName Name of the file. It can include the path of the file. * @param fileName Name of the file. It can include the path of the file.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment