From 1dcfae452a51ba84d50649d9717d0bb4d75039b4 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 28 Mar 2017 03:27:32 -0700 Subject: [PATCH] Avoid input/output copies in SonicAudioProcessor. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=151431376 --- .../android/exoplayer2/audio/Sonic.java | 65 +++++++------------ .../exoplayer2/audio/SonicAudioProcessor.java | 54 ++++++++------- 2 files changed, 48 insertions(+), 71 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java index 47acccd967..5d6f01b6e0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/Sonic.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.util.Assertions; +import java.nio.ShortBuffer; import java.util.Arrays; /** @@ -112,60 +113,39 @@ import java.util.Arrays; } /** - * Writes {@code numBytes} from {@code buffer} as input. + * Queues remaining data from {@code buffer}, and advances its position by the number of bytes + * consumed. * - * @param buffer A buffer containing input data. - * @param numBytes The number of bytes of input data to read from {@code buffer}. + * @param buffer A {@link ShortBuffer} containing input data between its position and limit. */ - public void writeBytesToStream(byte[] buffer, int numBytes) { - int numSamples = numBytes / (2 * numChannels); - short sample; - - enlargeInputBufferIfNeeded(numSamples); - int xBuffer = numInputSamples * numChannels; - for (int xByte = 0; xByte + 1 < numBytes; xByte += 2) { - sample = (short) ((buffer[xByte] & 0xff) | (buffer[xByte + 1] << 8)); - inputBuffer[xBuffer++] = sample; - } - numInputSamples += numSamples; + public void queueInput(ShortBuffer buffer) { + int samplesToWrite = buffer.remaining() / numChannels; + int bytesToWrite = samplesToWrite * numChannels * 2; + enlargeInputBufferIfNeeded(samplesToWrite); + buffer.get(inputBuffer, numInputSamples * numChannels, bytesToWrite / 2); + numInputSamples += samplesToWrite; processStreamInput(); } /** - * Reads up to {@code maxBytes} of output into {@code buffer}. + * Gets available output, outputting to the start of {@code buffer}. The buffer's position will be + * advanced by the number of bytes written. * - * @param buffer The buffer into which output will be written. - * @param maxBytes The maximum number of bytes to write. - * @return The number of bytes read from the stream. + * @param buffer A {@link ShortBuffer} into which output will be written. */ - public int readBytesFromStream(byte[] buffer, int maxBytes) { - int maxSamples = maxBytes / (2 * numChannels); - int numSamples = numOutputSamples; - int remainingSamples = 0; - - if (numSamples == 0 || maxSamples == 0) { - return 0; - } - if (numSamples > maxSamples) { - remainingSamples = numSamples - maxSamples; - numSamples = maxSamples; - } - for (int xSample = 0; xSample < numSamples * numChannels; xSample++) { - short sample = outputBuffer[xSample]; - buffer[xSample << 1] = (byte) (sample & 0xff); - buffer[(xSample << 1) + 1] = (byte) (sample >> 8); - } - System.arraycopy(outputBuffer, numSamples * numChannels, outputBuffer, 0, - remainingSamples * numChannels); - numOutputSamples = remainingSamples; - return 2 * numSamples * numChannels; + public void getOutput(ShortBuffer buffer) { + int samplesToRead = Math.min(buffer.remaining() / numChannels, numOutputSamples); + buffer.put(outputBuffer, 0, samplesToRead * numChannels); + numOutputSamples -= samplesToRead; + System.arraycopy(outputBuffer, samplesToRead * numChannels, outputBuffer, 0, + numOutputSamples * numChannels); } /** * Forces generating output using whatever data has been queued already. No extra delay will be * added to the output, but flushing in the middle of words could introduce distortion. */ - public void flushStream() { + public void queueEndOfStream() { int remainingSamples = numInputSamples; float s = speed / pitch; int expectedOutputSamples = @@ -189,10 +169,9 @@ import java.util.Arrays; } /** - * Returns the number of output samples that can be read with - * {@link #readBytesFromStream(byte[], int)}. + * Returns the number of output samples that can be read with {@link #getOutput(ShortBuffer)}. */ - public int samplesAvailable() { + public int getSamplesAvailable() { return numOutputSamples; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java index ae1fae40c2..3f45afd53e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SonicAudioProcessor.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.ShortBuffer; /** * An {@link AudioProcessor} that uses the Sonic library to modify the speed/pitch of audio. @@ -50,8 +51,6 @@ import java.nio.ByteOrder; */ private static final float CLOSE_THRESHOLD = 0.01f; - private static final byte[] EMPTY_ARRAY = new byte[0]; - private int channelCount; private int sampleRateHz; @@ -59,9 +58,9 @@ import java.nio.ByteOrder; private float speed; private float pitch; - private byte[] inputArray; + private ShortBuffer shortBuffer; + private ByteBuffer buffer; - private byte[] bufferArray; private ByteBuffer outputBuffer; private long inputBytes; private long outputBytes; @@ -76,9 +75,8 @@ import java.nio.ByteOrder; channelCount = Format.NO_VALUE; sampleRateHz = Format.NO_VALUE; buffer = EMPTY_BUFFER; + shortBuffer = buffer.asShortBuffer(); outputBuffer = EMPTY_BUFFER; - inputArray = EMPTY_ARRAY; - bufferArray = EMPTY_ARRAY; } /** @@ -142,31 +140,32 @@ import java.nio.ByteOrder; @Override public void queueInput(ByteBuffer inputBuffer) { - // TODO: Remove this extra copy. - int inputBytesToRead = inputBuffer.remaining(); - if (inputArray == null || inputArray.length < inputBytesToRead) { - inputArray = new byte[inputBytesToRead]; + if (inputBuffer.hasRemaining()) { + ShortBuffer shortBuffer = inputBuffer.asShortBuffer(); + int inputSize = inputBuffer.remaining(); + inputBytes += inputSize; + sonic.queueInput(shortBuffer); + inputBuffer.position(inputBuffer.position() + inputSize); } - inputBuffer.get(inputArray, 0, inputBytesToRead); - sonic.writeBytesToStream(inputArray, inputBytesToRead); - int outputSize = sonic.samplesAvailable() * channelCount * 2; - if (buffer.capacity() < outputSize) { - buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); - bufferArray = new byte[outputSize]; - } else { - buffer.clear(); + int outputSize = sonic.getSamplesAvailable() * channelCount * 2; + if (outputSize > 0) { + if (buffer.capacity() < outputSize) { + buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); + shortBuffer = buffer.asShortBuffer(); + } else { + buffer.clear(); + shortBuffer.clear(); + } + sonic.getOutput(shortBuffer); + outputBytes += outputSize; + buffer.limit(outputSize); + outputBuffer = buffer; } - inputBytes += inputBytesToRead; - int outputBytesRead = sonic.readBytesFromStream(bufferArray, outputSize); - buffer.put(bufferArray, 0, outputBytesRead); - buffer.flip(); - outputBytes += outputSize; - outputBuffer = buffer; } @Override public void queueEndOfStream() { - sonic.flushStream(); + sonic.queueEndOfStream(); inputEnded = true; } @@ -179,7 +178,7 @@ import java.nio.ByteOrder; @Override public boolean isEnded() { - return inputEnded && (sonic == null || sonic.samplesAvailable() == 0); + return inputEnded && (sonic == null || sonic.getSamplesAvailable() == 0); } @Override @@ -198,8 +197,7 @@ import java.nio.ByteOrder; sonic = null; buffer = EMPTY_BUFFER; outputBuffer = EMPTY_BUFFER; - inputArray = EMPTY_ARRAY; - bufferArray = EMPTY_ARRAY; + shortBuffer = null; } }