• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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