• 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 com.google.android.exoplayer2.C;
19 import com.google.android.exoplayer2.util.Util;
20 import java.nio.ByteBuffer;
21 
22 /** Audio processor for trimming samples from the start/end of data. */
23 /* package */ final class TrimmingAudioProcessor extends BaseAudioProcessor {
24 
25   @C.PcmEncoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT;
26 
27   private int trimStartFrames;
28   private int trimEndFrames;
29   private boolean reconfigurationPending;
30 
31   private int pendingTrimStartBytes;
32   private byte[] endBuffer;
33   private int endBufferSize;
34   private long trimmedFrameCount;
35 
36   /** Creates a new audio processor for trimming samples from the start/end of data. */
TrimmingAudioProcessor()37   public TrimmingAudioProcessor() {
38     endBuffer = Util.EMPTY_BYTE_ARRAY;
39   }
40 
41   /**
42    * Sets the number of audio frames to trim from the start and end of audio passed to this
43    * processor. After calling this method, call {@link #configure(AudioFormat)} to apply the new
44    * trimming frame counts.
45    *
46    * @param trimStartFrames The number of audio frames to trim from the start of audio.
47    * @param trimEndFrames The number of audio frames to trim from the end of audio.
48    * @see AudioSink#configure(int, int, int, int, int[], int, int)
49    */
setTrimFrameCount(int trimStartFrames, int trimEndFrames)50   public void setTrimFrameCount(int trimStartFrames, int trimEndFrames) {
51     this.trimStartFrames = trimStartFrames;
52     this.trimEndFrames = trimEndFrames;
53   }
54 
55   /** Sets the trimmed frame count returned by {@link #getTrimmedFrameCount()} to zero. */
resetTrimmedFrameCount()56   public void resetTrimmedFrameCount() {
57     trimmedFrameCount = 0;
58   }
59 
60   /**
61    * Returns the number of audio frames trimmed since the last call to {@link
62    * #resetTrimmedFrameCount()}.
63    */
getTrimmedFrameCount()64   public long getTrimmedFrameCount() {
65     return trimmedFrameCount;
66   }
67 
68   @Override
onConfigure(AudioFormat inputAudioFormat)69   public AudioFormat onConfigure(AudioFormat inputAudioFormat)
70       throws UnhandledAudioFormatException {
71     if (inputAudioFormat.encoding != OUTPUT_ENCODING) {
72       throw new UnhandledAudioFormatException(inputAudioFormat);
73     }
74     reconfigurationPending = true;
75     return trimStartFrames != 0 || trimEndFrames != 0 ? inputAudioFormat : AudioFormat.NOT_SET;
76   }
77 
78   @Override
queueInput(ByteBuffer inputBuffer)79   public void queueInput(ByteBuffer inputBuffer) {
80     int position = inputBuffer.position();
81     int limit = inputBuffer.limit();
82     int remaining = limit - position;
83 
84     if (remaining == 0) {
85       return;
86     }
87 
88     // Trim any pending start bytes from the input buffer.
89     int trimBytes = Math.min(remaining, pendingTrimStartBytes);
90     trimmedFrameCount += trimBytes / inputAudioFormat.bytesPerFrame;
91     pendingTrimStartBytes -= trimBytes;
92     inputBuffer.position(position + trimBytes);
93     if (pendingTrimStartBytes > 0) {
94       // Nothing to output yet.
95       return;
96     }
97     remaining -= trimBytes;
98 
99     // endBuffer must be kept as full as possible, so that we trim the right amount of media if we
100     // don't receive any more input. After taking into account the number of bytes needed to keep
101     // endBuffer as full as possible, the output should be any surplus bytes currently in endBuffer
102     // followed by any surplus bytes in the new inputBuffer.
103     int remainingBytesToOutput = endBufferSize + remaining - endBuffer.length;
104     ByteBuffer buffer = replaceOutputBuffer(remainingBytesToOutput);
105 
106     // Output from endBuffer.
107     int endBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, endBufferSize);
108     buffer.put(endBuffer, 0, endBufferBytesToOutput);
109     remainingBytesToOutput -= endBufferBytesToOutput;
110 
111     // Output from inputBuffer, restoring its limit afterwards.
112     int inputBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, remaining);
113     inputBuffer.limit(inputBuffer.position() + inputBufferBytesToOutput);
114     buffer.put(inputBuffer);
115     inputBuffer.limit(limit);
116     remaining -= inputBufferBytesToOutput;
117 
118     // Compact endBuffer, then repopulate it using the new input.
119     endBufferSize -= endBufferBytesToOutput;
120     System.arraycopy(endBuffer, endBufferBytesToOutput, endBuffer, 0, endBufferSize);
121     inputBuffer.get(endBuffer, endBufferSize, remaining);
122     endBufferSize += remaining;
123 
124     buffer.flip();
125   }
126 
127   @Override
getOutput()128   public ByteBuffer getOutput() {
129     if (super.isEnded() && endBufferSize > 0) {
130       // Because audio processors may be drained in the middle of the stream we assume that the
131       // contents of the end buffer need to be output. For gapless transitions, configure will
132       // always be called, so the end buffer is cleared in onQueueEndOfStream.
133       replaceOutputBuffer(endBufferSize).put(endBuffer, 0, endBufferSize).flip();
134       endBufferSize = 0;
135     }
136     return super.getOutput();
137   }
138 
139   @Override
isEnded()140   public boolean isEnded() {
141     return super.isEnded() && endBufferSize == 0;
142   }
143 
144   @Override
onQueueEndOfStream()145   protected void onQueueEndOfStream() {
146     if (reconfigurationPending) {
147       // Trim audio in the end buffer.
148       if (endBufferSize > 0) {
149         trimmedFrameCount += endBufferSize / inputAudioFormat.bytesPerFrame;
150       }
151       endBufferSize = 0;
152     }
153   }
154 
155   @Override
onFlush()156   protected void onFlush() {
157     if (reconfigurationPending) {
158       // Flushing activates the new configuration, so prepare to trim bytes from the start/end.
159       reconfigurationPending = false;
160       endBuffer = new byte[trimEndFrames * inputAudioFormat.bytesPerFrame];
161       pendingTrimStartBytes = trimStartFrames * inputAudioFormat.bytesPerFrame;
162     }
163 
164     // TODO(internal b/77292509): Flushing occurs to activate a configuration (handled above) but
165     // also when seeking within a stream. This implementation currently doesn't handle seek to start
166     // (where we need to trim at the start again), nor seeks to non-zero positions before start
167     // trimming has occurred (where we should set pendingTrimStartBytes to zero). These cases can be
168     // fixed by trimming in queueInput based on timestamp, once that information is available.
169 
170     // Any data in the end buffer should no longer be output if we are playing from a different
171     // position, so discard it and refill the buffer using new input.
172     endBufferSize = 0;
173   }
174 
175   @Override
onReset()176   protected void onReset() {
177     endBuffer = Util.EMPTY_BYTE_ARRAY;
178   }
179 
180 }
181