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 daab04e4ab..01963fe686 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 @@ -32,16 +32,16 @@ import java.util.Arrays; private static final int AMDF_FREQUENCY = 4000; private final int inputSampleRateHz; - private final int numChannels; + private final int channelCount; private final float speed; private final float pitch; private final float rate; private final int minPeriod; private final int maxPeriod; - private final int maxRequired; + private final int maxRequiredFrameCount; private final short[] downSampleBuffer; - private int inputBufferSize; + private int inputBufferSizeFrames; private short[] inputBuffer; private int outputBufferSize; private short[] outputBuffer; @@ -49,10 +49,10 @@ import java.util.Arrays; private short[] pitchBuffer; private int oldRatePosition; private int newRatePosition; - private int numInputSamples; - private int numOutputSamples; - private int numPitchSamples; - private int remainingInputToCopy; + private int inputFrameCount; + private int outputFrameCount; + private int pitchFrameCount; + private int remainingInputToCopyFrameCount; private int prevPeriod; private int prevMinDiff; private int minDiff; @@ -62,25 +62,25 @@ import java.util.Arrays; * Creates a new Sonic audio stream processor. * * @param inputSampleRateHz The sample rate of input audio, in hertz. - * @param numChannels The number of channels in the input audio. + * @param channelCount The number of channels in the input audio. * @param speed The speedup factor for output audio. * @param pitch The pitch factor for output audio. * @param outputSampleRateHz The sample rate for output audio, in hertz. */ - public Sonic(int inputSampleRateHz, int numChannels, float speed, float pitch, - int outputSampleRateHz) { + public Sonic( + int inputSampleRateHz, int channelCount, float speed, float pitch, int outputSampleRateHz) { this.inputSampleRateHz = inputSampleRateHz; - this.numChannels = numChannels; + this.channelCount = channelCount; minPeriod = inputSampleRateHz / MAXIMUM_PITCH; maxPeriod = inputSampleRateHz / MINIMUM_PITCH; - maxRequired = 2 * maxPeriod; - downSampleBuffer = new short[maxRequired]; - inputBufferSize = maxRequired; - inputBuffer = new short[maxRequired * numChannels]; - outputBufferSize = maxRequired; - outputBuffer = new short[maxRequired * numChannels]; - pitchBufferSize = maxRequired; - pitchBuffer = new short[maxRequired * numChannels]; + maxRequiredFrameCount = 2 * maxPeriod; + downSampleBuffer = new short[maxRequiredFrameCount]; + inputBufferSizeFrames = maxRequiredFrameCount; + inputBuffer = new short[maxRequiredFrameCount * channelCount]; + outputBufferSize = maxRequiredFrameCount; + outputBuffer = new short[maxRequiredFrameCount * channelCount]; + pitchBufferSize = maxRequiredFrameCount; + pitchBuffer = new short[maxRequiredFrameCount * channelCount]; oldRatePosition = 0; newRatePosition = 0; prevPeriod = 0; @@ -96,11 +96,11 @@ import java.util.Arrays; * @param buffer A {@link ShortBuffer} containing input data between its position and limit. */ 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; + int framesToWrite = buffer.remaining() / channelCount; + int bytesToWrite = framesToWrite * channelCount * 2; + enlargeInputBufferIfNeeded(framesToWrite); + buffer.get(inputBuffer, inputFrameCount * channelCount, bytesToWrite / 2); + inputFrameCount += framesToWrite; processStreamInput(); } @@ -111,11 +111,15 @@ import java.util.Arrays; * @param buffer A {@link ShortBuffer} into which output will be written. */ 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); + int framesToRead = Math.min(buffer.remaining() / channelCount, outputFrameCount); + buffer.put(outputBuffer, 0, framesToRead * channelCount); + outputFrameCount -= framesToRead; + System.arraycopy( + outputBuffer, + framesToRead * channelCount, + outputBuffer, + 0, + outputFrameCount * channelCount); } /** @@ -123,80 +127,82 @@ import java.util.Arrays; * added to the output, but flushing in the middle of words could introduce distortion. */ public void queueEndOfStream() { - int remainingSamples = numInputSamples; + int remainingFrameCount = inputFrameCount; float s = speed / pitch; float r = rate * pitch; - int expectedOutputSamples = - numOutputSamples + (int) ((remainingSamples / s + numPitchSamples) / r + 0.5f); + int expectedOutputFrames = + outputFrameCount + (int) ((remainingFrameCount / s + pitchFrameCount) / r + 0.5f); // Add enough silence to flush both input and pitch buffers. - enlargeInputBufferIfNeeded(remainingSamples + 2 * maxRequired); - for (int xSample = 0; xSample < 2 * maxRequired * numChannels; xSample++) { - inputBuffer[remainingSamples * numChannels + xSample] = 0; + enlargeInputBufferIfNeeded(remainingFrameCount + 2 * maxRequiredFrameCount); + for (int xSample = 0; xSample < 2 * maxRequiredFrameCount * channelCount; xSample++) { + inputBuffer[remainingFrameCount * channelCount + xSample] = 0; } - numInputSamples += 2 * maxRequired; + inputFrameCount += 2 * maxRequiredFrameCount; processStreamInput(); - // Throw away any extra samples we generated due to the silence we added. - if (numOutputSamples > expectedOutputSamples) { - numOutputSamples = expectedOutputSamples; + // Throw away any extra frames we generated due to the silence we added. + if (outputFrameCount > expectedOutputFrames) { + outputFrameCount = expectedOutputFrames; } // Empty input and pitch buffers. - numInputSamples = 0; - remainingInputToCopy = 0; - numPitchSamples = 0; + inputFrameCount = 0; + remainingInputToCopyFrameCount = 0; + pitchFrameCount = 0; } - /** - * Returns the number of output samples that can be read with {@link #getOutput(ShortBuffer)}. - */ - public int getSamplesAvailable() { - return numOutputSamples; + /** Returns the number of output frames that can be read with {@link #getOutput(ShortBuffer)}. */ + public int getFramesAvailable() { + return outputFrameCount; } // Internal methods. - private void enlargeOutputBufferIfNeeded(int numSamples) { - if (numOutputSamples + numSamples > outputBufferSize) { - outputBufferSize += (outputBufferSize / 2) + numSamples; - outputBuffer = Arrays.copyOf(outputBuffer, outputBufferSize * numChannels); + private void enlargeOutputBufferIfNeeded(int frameCount) { + if (outputFrameCount + frameCount > outputBufferSize) { + outputBufferSize += (outputBufferSize / 2) + frameCount; + outputBuffer = Arrays.copyOf(outputBuffer, outputBufferSize * channelCount); } } - private void enlargeInputBufferIfNeeded(int numSamples) { - if (numInputSamples + numSamples > inputBufferSize) { - inputBufferSize += (inputBufferSize / 2) + numSamples; - inputBuffer = Arrays.copyOf(inputBuffer, inputBufferSize * numChannels); + private void enlargeInputBufferIfNeeded(int frameCount) { + if (inputFrameCount + frameCount > inputBufferSizeFrames) { + inputBufferSizeFrames += (inputBufferSizeFrames / 2) + frameCount; + inputBuffer = Arrays.copyOf(inputBuffer, inputBufferSizeFrames * channelCount); } } - private void removeProcessedInputSamples(int position) { - int remainingSamples = numInputSamples - position; - System.arraycopy(inputBuffer, position * numChannels, inputBuffer, 0, - remainingSamples * numChannels); - numInputSamples = remainingSamples; + private void removeProcessedInputFrames(int positionFrames) { + int remainingFrames = inputFrameCount - positionFrames; + System.arraycopy( + inputBuffer, positionFrames * channelCount, inputBuffer, 0, remainingFrames * channelCount); + inputFrameCount = remainingFrames; } - private void copyToOutput(short[] samples, int position, int numSamples) { - enlargeOutputBufferIfNeeded(numSamples); - System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, - numSamples * numChannels); - numOutputSamples += numSamples; + private void copyToOutput(short[] samples, int positionFrames, int frameCount) { + enlargeOutputBufferIfNeeded(frameCount); + System.arraycopy( + samples, + positionFrames * channelCount, + outputBuffer, + outputFrameCount * channelCount, + frameCount * channelCount); + outputFrameCount += frameCount; } - private int copyInputToOutput(int position) { - int numSamples = Math.min(maxRequired, remainingInputToCopy); - copyToOutput(inputBuffer, position, numSamples); - remainingInputToCopy -= numSamples; - return numSamples; + private int copyInputToOutput(int positionFrames) { + int frameCount = Math.min(maxRequiredFrameCount, remainingInputToCopyFrameCount); + copyToOutput(inputBuffer, positionFrames, frameCount); + remainingInputToCopyFrameCount -= frameCount; + return frameCount; } private void downSampleInput(short[] samples, int position, int skip) { // If skip is greater than one, average skip samples together and write them to the down-sample - // buffer. If numChannels is greater than one, mix the channels together as we down sample. - int numSamples = maxRequired / skip; - int samplesPerValue = numChannels * skip; - position *= numChannels; - for (int i = 0; i < numSamples; i++) { + // buffer. If channelCount is greater than one, mix the channels together as we down sample. + int frameCount = maxRequiredFrameCount / skip; + int samplesPerValue = channelCount * skip; + position *= channelCount; + for (int i = 0; i < frameCount; i++) { int value = 0; for (int j = 0; j < samplesPerValue; j++) { value += samples[position + i * samplesPerValue + j]; @@ -213,7 +219,7 @@ import java.util.Arrays; int worstPeriod = 255; int minDiff = 1; int maxDiff = 0; - position *= numChannels; + position *= channelCount; for (int period = minPeriod; period <= maxPeriod; period++) { int diff = 0; for (int i = 0; i < period; i++) { @@ -242,28 +248,22 @@ import java.util.Arrays; * Returns whether the previous pitch period estimate is a better approximation, which can occur * at the abrupt end of voiced words. */ - private boolean previousPeriodBetter(int minDiff, int maxDiff, boolean preferNewPeriod) { + private boolean previousPeriodBetter(int minDiff, int maxDiff) { if (minDiff == 0 || prevPeriod == 0) { return false; } - if (preferNewPeriod) { - if (maxDiff > minDiff * 3) { - // Got a reasonable match this period - return false; - } - if (minDiff * 2 <= prevMinDiff * 3) { - // Mismatch is not that much greater this period - return false; - } - } else { - if (minDiff <= prevMinDiff) { - return false; - } + if (maxDiff > minDiff * 3) { + // Got a reasonable match this period. + return false; + } + if (minDiff * 2 <= prevMinDiff * 3) { + // Mismatch is not that much greater this period. + return false; } return true; } - private int findPitchPeriod(short[] samples, int position, boolean preferNewPeriod) { + private int findPitchPeriod(short[] samples, int position) { // Find the pitch period. This is a critical step, and we may have to try multiple ways to get a // good answer. This version uses AMDF. To improve speed, we down sample by an integer factor // get in the 11 kHz range, and then do it again with a narrower frequency range without down @@ -271,7 +271,7 @@ import java.util.Arrays; int period; int retPeriod; int skip = inputSampleRateHz > AMDF_FREQUENCY ? inputSampleRateHz / AMDF_FREQUENCY : 1; - if (numChannels == 1 && skip == 1) { + if (channelCount == 1 && skip == 1) { period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod); } else { downSampleInput(samples, position, skip); @@ -286,7 +286,7 @@ import java.util.Arrays; if (maxP > maxPeriod) { maxP = maxPeriod; } - if (numChannels == 1) { + if (channelCount == 1) { period = findPitchPeriodInRange(samples, position, minP, maxP); } else { downSampleInput(samples, position, 1); @@ -294,7 +294,7 @@ import java.util.Arrays; } } } - if (previousPeriodBetter(minDiff, maxDiff, preferNewPeriod)) { + if (previousPeriodBetter(minDiff, maxDiff)) { retPeriod = prevPeriod; } else { retPeriod = period; @@ -304,30 +304,38 @@ import java.util.Arrays; return retPeriod; } - private void moveNewSamplesToPitchBuffer(int originalNumOutputSamples) { - int numSamples = numOutputSamples - originalNumOutputSamples; - if (numPitchSamples + numSamples > pitchBufferSize) { - pitchBufferSize += (pitchBufferSize / 2) + numSamples; - pitchBuffer = Arrays.copyOf(pitchBuffer, pitchBufferSize * numChannels); + private void moveNewSamplesToPitchBuffer(int originalOutputFrameCount) { + int frameCount = outputFrameCount - originalOutputFrameCount; + if (pitchFrameCount + frameCount > pitchBufferSize) { + pitchBufferSize += (pitchBufferSize / 2) + frameCount; + pitchBuffer = Arrays.copyOf(pitchBuffer, pitchBufferSize * channelCount); } - System.arraycopy(outputBuffer, originalNumOutputSamples * numChannels, pitchBuffer, - numPitchSamples * numChannels, numSamples * numChannels); - numOutputSamples = originalNumOutputSamples; - numPitchSamples += numSamples; + System.arraycopy( + outputBuffer, + originalOutputFrameCount * channelCount, + pitchBuffer, + pitchFrameCount * channelCount, + frameCount * channelCount); + outputFrameCount = originalOutputFrameCount; + pitchFrameCount += frameCount; } - private void removePitchSamples(int numSamples) { - if (numSamples == 0) { + private void removePitchFrames(int frameCount) { + if (frameCount == 0) { return; } - System.arraycopy(pitchBuffer, numSamples * numChannels, pitchBuffer, 0, - (numPitchSamples - numSamples) * numChannels); - numPitchSamples -= numSamples; + System.arraycopy( + pitchBuffer, + frameCount * channelCount, + pitchBuffer, + 0, + (pitchFrameCount - frameCount) * channelCount); + pitchFrameCount -= frameCount; } private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) { short left = in[inPos]; - short right = in[inPos + numChannels]; + short right = in[inPos + channelCount]; int position = newRatePosition * oldSampleRate; int leftPosition = oldRatePosition * newSampleRate; int rightPosition = (oldRatePosition + 1) * newSampleRate; @@ -336,8 +344,8 @@ import java.util.Arrays; return (short) ((ratio * left + (width - ratio) * right) / width); } - private void adjustRate(float rate, int originalNumOutputSamples) { - if (numOutputSamples == originalNumOutputSamples) { + private void adjustRate(float rate, int originalOutputFrameCount) { + if (outputFrameCount == originalOutputFrameCount) { return; } int newSampleRate = (int) (inputSampleRateHz / rate); @@ -347,17 +355,17 @@ import java.util.Arrays; newSampleRate /= 2; oldSampleRate /= 2; } - moveNewSamplesToPitchBuffer(originalNumOutputSamples); + moveNewSamplesToPitchBuffer(originalOutputFrameCount); // Leave at least one pitch sample in the buffer. - for (int position = 0; position < numPitchSamples - 1; position++) { + for (int position = 0; position < pitchFrameCount - 1; position++) { while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) { enlargeOutputBufferIfNeeded(1); - for (int i = 0; i < numChannels; i++) { - outputBuffer[numOutputSamples * numChannels + i] = - interpolate(pitchBuffer, position * numChannels + i, oldSampleRate, newSampleRate); + for (int i = 0; i < channelCount; i++) { + outputBuffer[outputFrameCount * channelCount + i] = + interpolate(pitchBuffer, position * channelCount + i, oldSampleRate, newSampleRate); } newRatePosition++; - numOutputSamples++; + outputFrameCount++; } oldRatePosition++; if (oldRatePosition == oldSampleRate) { @@ -366,91 +374,116 @@ import java.util.Arrays; newRatePosition = 0; } } - removePitchSamples(numPitchSamples - 1); + removePitchFrames(pitchFrameCount - 1); } private int skipPitchPeriod(short[] samples, int position, float speed, int period) { // Skip over a pitch period, and copy period/speed samples to the output. - int newSamples; + int newFrameCount; if (speed >= 2.0f) { - newSamples = (int) (period / (speed - 1.0f)); + newFrameCount = (int) (period / (speed - 1.0f)); } else { - newSamples = period; - remainingInputToCopy = (int) (period * (2.0f - speed) / (speed - 1.0f)); + newFrameCount = period; + remainingInputToCopyFrameCount = (int) (period * (2.0f - speed) / (speed - 1.0f)); } - enlargeOutputBufferIfNeeded(newSamples); - overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position, samples, + enlargeOutputBufferIfNeeded(newFrameCount); + overlapAdd( + newFrameCount, + channelCount, + outputBuffer, + outputFrameCount, + samples, + position, + samples, position + period); - numOutputSamples += newSamples; - return newSamples; + outputFrameCount += newFrameCount; + return newFrameCount; } private int insertPitchPeriod(short[] samples, int position, float speed, int period) { // Insert a pitch period, and determine how much input to copy directly. - int newSamples; + int newFrameCount; if (speed < 0.5f) { - newSamples = (int) (period * speed / (1.0f - speed)); + newFrameCount = (int) (period * speed / (1.0f - speed)); } else { - newSamples = period; - remainingInputToCopy = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); + newFrameCount = period; + remainingInputToCopyFrameCount = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed)); } - enlargeOutputBufferIfNeeded(period + newSamples); - System.arraycopy(samples, position * numChannels, outputBuffer, numOutputSamples * numChannels, - period * numChannels); - overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples, - position + period, samples, position); - numOutputSamples += period + newSamples; - return newSamples; + enlargeOutputBufferIfNeeded(period + newFrameCount); + System.arraycopy( + samples, + position * channelCount, + outputBuffer, + outputFrameCount * channelCount, + period * channelCount); + overlapAdd( + newFrameCount, + channelCount, + outputBuffer, + outputFrameCount + period, + samples, + position + period, + samples, + position); + outputFrameCount += period + newFrameCount; + return newFrameCount; } private void changeSpeed(float speed) { - if (numInputSamples < maxRequired) { + if (inputFrameCount < maxRequiredFrameCount) { return; } - int numSamples = numInputSamples; - int position = 0; + int frameCount = inputFrameCount; + int positionFrames = 0; do { - if (remainingInputToCopy > 0) { - position += copyInputToOutput(position); + if (remainingInputToCopyFrameCount > 0) { + positionFrames += copyInputToOutput(positionFrames); } else { - int period = findPitchPeriod(inputBuffer, position, true); + int period = findPitchPeriod(inputBuffer, positionFrames); if (speed > 1.0) { - position += period + skipPitchPeriod(inputBuffer, position, speed, period); + positionFrames += period + skipPitchPeriod(inputBuffer, positionFrames, speed, period); } else { - position += insertPitchPeriod(inputBuffer, position, speed, period); + positionFrames += insertPitchPeriod(inputBuffer, positionFrames, speed, period); } } - } while (position + maxRequired <= numSamples); - removeProcessedInputSamples(position); + } while (positionFrames + maxRequiredFrameCount <= frameCount); + removeProcessedInputFrames(positionFrames); } private void processStreamInput() { // Resample as many pitch periods as we have buffered on the input. - int originalNumOutputSamples = numOutputSamples; + int originalOutputFrameCount = outputFrameCount; float s = speed / pitch; float r = rate * pitch; if (s > 1.00001 || s < 0.99999) { changeSpeed(s); } else { - copyToOutput(inputBuffer, 0, numInputSamples); - numInputSamples = 0; + copyToOutput(inputBuffer, 0, inputFrameCount); + inputFrameCount = 0; } if (r != 1.0f) { - adjustRate(r, originalNumOutputSamples); + adjustRate(r, originalOutputFrameCount); } } - private static void overlapAdd(int numSamples, int numChannels, short[] out, int outPos, - short[] rampDown, int rampDownPos, short[] rampUp, int rampUpPos) { - for (int i = 0; i < numChannels; i++) { - int o = outPos * numChannels + i; - int u = rampUpPos * numChannels + i; - int d = rampDownPos * numChannels + i; - for (int t = 0; t < numSamples; t++) { - out[o] = (short) ((rampDown[d] * (numSamples - t) + rampUp[u] * t) / numSamples); - o += numChannels; - d += numChannels; - u += numChannels; + private static void overlapAdd( + int frameCount, + int channelCount, + short[] out, + int outPosition, + short[] rampDown, + int rampDownPosition, + short[] rampUp, + int rampUpPosition) { + for (int i = 0; i < channelCount; i++) { + int o = outPosition * channelCount + i; + int u = rampUpPosition * channelCount + i; + int d = rampDownPosition * channelCount + i; + for (int t = 0; t < frameCount; t++) { + out[o] = (short) ((rampDown[d] * (frameCount - t) + rampUp[u] * t) / frameCount); + o += channelCount; + d += channelCount; + u += channelCount; } } } 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 9beb65bec6..a33d43c92c 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 @@ -195,7 +195,7 @@ public final class SonicAudioProcessor implements AudioProcessor { sonic.queueInput(shortBuffer); inputBuffer.position(inputBuffer.position() + inputSize); } - int outputSize = sonic.getSamplesAvailable() * channelCount * 2; + int outputSize = sonic.getFramesAvailable() * channelCount * 2; if (outputSize > 0) { if (buffer.capacity() < outputSize) { buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); @@ -227,7 +227,7 @@ public final class SonicAudioProcessor implements AudioProcessor { @Override public boolean isEnded() { - return inputEnded && (sonic == null || sonic.getSamplesAvailable() == 0); + return inputEnded && (sonic == null || sonic.getFramesAvailable() == 0); } @Override