1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.audio; 17 18 import androidx.annotation.Nullable; 19 import com.google.android.exoplayer2.C; 20 import com.google.android.exoplayer2.Format; 21 import com.google.android.exoplayer2.util.Assertions; 22 import com.google.android.exoplayer2.util.Util; 23 import java.nio.ByteBuffer; 24 import java.nio.ByteOrder; 25 import java.nio.ShortBuffer; 26 27 /** 28 * An {@link AudioProcessor} that uses the Sonic library to modify audio speed/pitch/sample rate. 29 */ 30 public final class SonicAudioProcessor implements AudioProcessor { 31 32 /** 33 * The maximum allowed playback speed in {@link #setSpeed(float)}. 34 */ 35 public static final float MAXIMUM_SPEED = 8.0f; 36 /** 37 * The minimum allowed playback speed in {@link #setSpeed(float)}. 38 */ 39 public static final float MINIMUM_SPEED = 0.1f; 40 /** 41 * Indicates that the output sample rate should be the same as the input. 42 */ 43 public static final int SAMPLE_RATE_NO_CHANGE = -1; 44 45 /** 46 * The threshold below which the difference between two pitch/speed factors is negligible. 47 */ 48 private static final float CLOSE_THRESHOLD = 0.01f; 49 50 /** 51 * The minimum number of output bytes at which the speedup is calculated using the input/output 52 * byte counts, rather than using the current playback parameters speed. 53 */ 54 private static final int MIN_BYTES_FOR_SPEEDUP_CALCULATION = 1024; 55 56 private int pendingOutputSampleRate; 57 private float speed; 58 59 private AudioFormat pendingInputAudioFormat; 60 private AudioFormat pendingOutputAudioFormat; 61 private AudioFormat inputAudioFormat; 62 private AudioFormat outputAudioFormat; 63 64 private boolean pendingSonicRecreation; 65 @Nullable private Sonic sonic; 66 private ByteBuffer buffer; 67 private ShortBuffer shortBuffer; 68 private ByteBuffer outputBuffer; 69 private long inputBytes; 70 private long outputBytes; 71 private boolean inputEnded; 72 73 /** 74 * Creates a new Sonic audio processor. 75 */ SonicAudioProcessor()76 public SonicAudioProcessor() { 77 speed = 1f; 78 pendingInputAudioFormat = AudioFormat.NOT_SET; 79 pendingOutputAudioFormat = AudioFormat.NOT_SET; 80 inputAudioFormat = AudioFormat.NOT_SET; 81 outputAudioFormat = AudioFormat.NOT_SET; 82 buffer = EMPTY_BUFFER; 83 shortBuffer = buffer.asShortBuffer(); 84 outputBuffer = EMPTY_BUFFER; 85 pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE; 86 } 87 88 /** 89 * Sets the playback speed. This method may only be called after draining data through the 90 * processor. The value returned by {@link #isActive()} may change, and the processor must be 91 * {@link #flush() flushed} before queueing more data. 92 * 93 * @param speed The requested new playback speed. 94 * @return The actual new playback speed. 95 */ setSpeed(float speed)96 public float setSpeed(float speed) { 97 speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED); 98 if (this.speed != speed) { 99 this.speed = speed; 100 pendingSonicRecreation = true; 101 } 102 return speed; 103 } 104 105 /** 106 * Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output 107 * audio at the same sample rate as the input. After calling this method, call {@link 108 * #configure(AudioFormat)} to configure the processor with the new sample rate. 109 * 110 * @param sampleRateHz The sample rate for output audio, in Hertz. 111 * @see #configure(AudioFormat) 112 */ setOutputSampleRateHz(int sampleRateHz)113 public void setOutputSampleRateHz(int sampleRateHz) { 114 pendingOutputSampleRate = sampleRateHz; 115 } 116 117 /** 118 * Returns the specified duration scaled to take into account the speedup factor of this instance, 119 * in the same units as {@code duration}. 120 * 121 * @param duration The duration to scale taking into account speedup. 122 * @return The specified duration scaled to take into account speedup, in the same units as 123 * {@code duration}. 124 */ scaleDurationForSpeedup(long duration)125 public long scaleDurationForSpeedup(long duration) { 126 if (outputBytes >= MIN_BYTES_FOR_SPEEDUP_CALCULATION) { 127 return outputAudioFormat.sampleRate == inputAudioFormat.sampleRate 128 ? Util.scaleLargeTimestamp(duration, inputBytes, outputBytes) 129 : Util.scaleLargeTimestamp( 130 duration, 131 inputBytes * outputAudioFormat.sampleRate, 132 outputBytes * inputAudioFormat.sampleRate); 133 } else { 134 return (long) ((double) speed * duration); 135 } 136 } 137 138 @Override configure(AudioFormat inputAudioFormat)139 public AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException { 140 if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) { 141 throw new UnhandledAudioFormatException(inputAudioFormat); 142 } 143 int outputSampleRateHz = 144 pendingOutputSampleRate == SAMPLE_RATE_NO_CHANGE 145 ? inputAudioFormat.sampleRate 146 : pendingOutputSampleRate; 147 pendingInputAudioFormat = inputAudioFormat; 148 pendingOutputAudioFormat = 149 new AudioFormat(outputSampleRateHz, inputAudioFormat.channelCount, C.ENCODING_PCM_16BIT); 150 pendingSonicRecreation = true; 151 return pendingOutputAudioFormat; 152 } 153 154 @Override isActive()155 public boolean isActive() { 156 return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE 157 && (Math.abs(speed - 1f) >= CLOSE_THRESHOLD 158 || pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate); 159 } 160 161 @Override queueInput(ByteBuffer inputBuffer)162 public void queueInput(ByteBuffer inputBuffer) { 163 Sonic sonic = Assertions.checkNotNull(this.sonic); 164 if (inputBuffer.hasRemaining()) { 165 ShortBuffer shortBuffer = inputBuffer.asShortBuffer(); 166 int inputSize = inputBuffer.remaining(); 167 inputBytes += inputSize; 168 sonic.queueInput(shortBuffer); 169 inputBuffer.position(inputBuffer.position() + inputSize); 170 } 171 int outputSize = sonic.getOutputSize(); 172 if (outputSize > 0) { 173 if (buffer.capacity() < outputSize) { 174 buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); 175 shortBuffer = buffer.asShortBuffer(); 176 } else { 177 buffer.clear(); 178 shortBuffer.clear(); 179 } 180 sonic.getOutput(shortBuffer); 181 outputBytes += outputSize; 182 buffer.limit(outputSize); 183 outputBuffer = buffer; 184 } 185 } 186 187 @Override queueEndOfStream()188 public void queueEndOfStream() { 189 if (sonic != null) { 190 sonic.queueEndOfStream(); 191 } 192 inputEnded = true; 193 } 194 195 @Override getOutput()196 public ByteBuffer getOutput() { 197 ByteBuffer outputBuffer = this.outputBuffer; 198 this.outputBuffer = EMPTY_BUFFER; 199 return outputBuffer; 200 } 201 202 @Override isEnded()203 public boolean isEnded() { 204 return inputEnded && (sonic == null || sonic.getOutputSize() == 0); 205 } 206 207 @Override flush()208 public void flush() { 209 if (isActive()) { 210 inputAudioFormat = pendingInputAudioFormat; 211 outputAudioFormat = pendingOutputAudioFormat; 212 if (pendingSonicRecreation) { 213 sonic = 214 new Sonic( 215 inputAudioFormat.sampleRate, 216 inputAudioFormat.channelCount, 217 speed, 218 outputAudioFormat.sampleRate); 219 } else if (sonic != null) { 220 sonic.flush(); 221 } 222 } 223 outputBuffer = EMPTY_BUFFER; 224 inputBytes = 0; 225 outputBytes = 0; 226 inputEnded = false; 227 } 228 229 @Override reset()230 public void reset() { 231 speed = 1f; 232 pendingInputAudioFormat = AudioFormat.NOT_SET; 233 pendingOutputAudioFormat = AudioFormat.NOT_SET; 234 inputAudioFormat = AudioFormat.NOT_SET; 235 outputAudioFormat = AudioFormat.NOT_SET; 236 buffer = EMPTY_BUFFER; 237 shortBuffer = buffer.asShortBuffer(); 238 outputBuffer = EMPTY_BUFFER; 239 pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE; 240 pendingSonicRecreation = false; 241 sonic = null; 242 inputBytes = 0; 243 outputBytes = 0; 244 inputEnded = false; 245 } 246 247 } 248