1 /*
2  * Copyright (C) 2016 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.extractor;
17 
18 import com.google.android.exoplayer2.C;
19 import com.google.android.exoplayer2.upstream.DataSource;
20 import com.google.android.exoplayer2.util.Assertions;
21 import com.google.android.exoplayer2.util.Util;
22 import java.io.EOFException;
23 import java.io.IOException;
24 import java.util.Arrays;
25 
26 /**
27  * An {@link ExtractorInput} that wraps a {@link DataSource}.
28  */
29 public final class DefaultExtractorInput implements ExtractorInput {
30 
31   private static final int PEEK_MIN_FREE_SPACE_AFTER_RESIZE = 64 * 1024;
32   private static final int PEEK_MAX_FREE_SPACE = 512 * 1024;
33   private static final byte[] SCRATCH_SPACE = new byte[4096];
34 
35   private final DataSource dataSource;
36   private final long streamLength;
37 
38   private long position;
39   private byte[] peekBuffer;
40   private int peekBufferPosition;
41   private int peekBufferLength;
42 
43   /**
44    * @param dataSource The wrapped {@link DataSource}.
45    * @param position The initial position in the stream.
46    * @param length The length of the stream, or {@link C#LENGTH_UNSET} if it is unknown.
47    */
DefaultExtractorInput(DataSource dataSource, long position, long length)48   public DefaultExtractorInput(DataSource dataSource, long position, long length) {
49     this.dataSource = dataSource;
50     this.position = position;
51     this.streamLength = length;
52     peekBuffer = new byte[PEEK_MIN_FREE_SPACE_AFTER_RESIZE];
53   }
54 
55   @Override
read(byte[] target, int offset, int length)56   public int read(byte[] target, int offset, int length) throws IOException, InterruptedException {
57     int bytesRead = readFromPeekBuffer(target, offset, length);
58     if (bytesRead == 0) {
59       bytesRead = readFromDataSource(target, offset, length, 0, true);
60     }
61     commitBytesRead(bytesRead);
62     return bytesRead;
63   }
64 
65   @Override
readFully(byte[] target, int offset, int length, boolean allowEndOfInput)66   public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
67       throws IOException, InterruptedException {
68     int bytesRead = readFromPeekBuffer(target, offset, length);
69     while (bytesRead < length && bytesRead != C.RESULT_END_OF_INPUT) {
70       bytesRead = readFromDataSource(target, offset, length, bytesRead, allowEndOfInput);
71     }
72     commitBytesRead(bytesRead);
73     return bytesRead != C.RESULT_END_OF_INPUT;
74   }
75 
76   @Override
readFully(byte[] target, int offset, int length)77   public void readFully(byte[] target, int offset, int length)
78       throws IOException, InterruptedException {
79     readFully(target, offset, length, false);
80   }
81 
82   @Override
skip(int length)83   public int skip(int length) throws IOException, InterruptedException {
84     int bytesSkipped = skipFromPeekBuffer(length);
85     if (bytesSkipped == 0) {
86       bytesSkipped =
87           readFromDataSource(SCRATCH_SPACE, 0, Math.min(length, SCRATCH_SPACE.length), 0, true);
88     }
89     commitBytesRead(bytesSkipped);
90     return bytesSkipped;
91   }
92 
93   @Override
skipFully(int length, boolean allowEndOfInput)94   public boolean skipFully(int length, boolean allowEndOfInput)
95       throws IOException, InterruptedException {
96     int bytesSkipped = skipFromPeekBuffer(length);
97     while (bytesSkipped < length && bytesSkipped != C.RESULT_END_OF_INPUT) {
98       bytesSkipped = readFromDataSource(SCRATCH_SPACE, -bytesSkipped,
99           Math.min(length, bytesSkipped + SCRATCH_SPACE.length), bytesSkipped, allowEndOfInput);
100     }
101     commitBytesRead(bytesSkipped);
102     return bytesSkipped != C.RESULT_END_OF_INPUT;
103   }
104 
105   @Override
skipFully(int length)106   public void skipFully(int length) throws IOException, InterruptedException {
107     skipFully(length, false);
108   }
109 
110   @Override
peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)111   public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
112       throws IOException, InterruptedException {
113     if (!advancePeekPosition(length, allowEndOfInput)) {
114       return false;
115     }
116     System.arraycopy(peekBuffer, peekBufferPosition - length, target, offset, length);
117     return true;
118   }
119 
120   @Override
peekFully(byte[] target, int offset, int length)121   public void peekFully(byte[] target, int offset, int length)
122       throws IOException, InterruptedException {
123     peekFully(target, offset, length, false);
124   }
125 
126   @Override
advancePeekPosition(int length, boolean allowEndOfInput)127   public boolean advancePeekPosition(int length, boolean allowEndOfInput)
128       throws IOException, InterruptedException {
129     ensureSpaceForPeek(length);
130     int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length);
131     while (bytesPeeked < length) {
132       bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked,
133           allowEndOfInput);
134       if (bytesPeeked == C.RESULT_END_OF_INPUT) {
135         return false;
136       }
137     }
138     peekBufferPosition += length;
139     peekBufferLength = Math.max(peekBufferLength, peekBufferPosition);
140     return true;
141   }
142 
143   @Override
advancePeekPosition(int length)144   public void advancePeekPosition(int length) throws IOException, InterruptedException {
145     advancePeekPosition(length, false);
146   }
147 
148   @Override
resetPeekPosition()149   public void resetPeekPosition() {
150     peekBufferPosition = 0;
151   }
152 
153   @Override
getPeekPosition()154   public long getPeekPosition() {
155     return position + peekBufferPosition;
156   }
157 
158   @Override
getPosition()159   public long getPosition() {
160     return position;
161   }
162 
163   @Override
getLength()164   public long getLength() {
165     return streamLength;
166   }
167 
168   @Override
setRetryPosition(long position, E e)169   public <E extends Throwable> void setRetryPosition(long position, E e) throws E {
170     Assertions.checkArgument(position >= 0);
171     this.position = position;
172     throw e;
173   }
174 
175   /**
176    * Ensures {@code peekBuffer} is large enough to store at least {@code length} bytes from the
177    * current peek position.
178    */
ensureSpaceForPeek(int length)179   private void ensureSpaceForPeek(int length) {
180     int requiredLength = peekBufferPosition + length;
181     if (requiredLength > peekBuffer.length) {
182       int newPeekCapacity = Util.constrainValue(peekBuffer.length * 2,
183           requiredLength + PEEK_MIN_FREE_SPACE_AFTER_RESIZE, requiredLength + PEEK_MAX_FREE_SPACE);
184       peekBuffer = Arrays.copyOf(peekBuffer, newPeekCapacity);
185     }
186   }
187 
188   /**
189    * Skips from the peek buffer.
190    *
191    * @param length The maximum number of bytes to skip from the peek buffer.
192    * @return The number of bytes skipped.
193    */
skipFromPeekBuffer(int length)194   private int skipFromPeekBuffer(int length) {
195     int bytesSkipped = Math.min(peekBufferLength, length);
196     updatePeekBuffer(bytesSkipped);
197     return bytesSkipped;
198   }
199 
200   /**
201    * Reads from the peek buffer
202    *
203    * @param target A target array into which data should be written.
204    * @param offset The offset into the target array at which to write.
205    * @param length The maximum number of bytes to read from the peek buffer.
206    * @return The number of bytes read.
207    */
readFromPeekBuffer(byte[] target, int offset, int length)208   private int readFromPeekBuffer(byte[] target, int offset, int length) {
209     if (peekBufferLength == 0) {
210       return 0;
211     }
212     int peekBytes = Math.min(peekBufferLength, length);
213     System.arraycopy(peekBuffer, 0, target, offset, peekBytes);
214     updatePeekBuffer(peekBytes);
215     return peekBytes;
216   }
217 
218   /**
219    * Updates the peek buffer's length, position and contents after consuming data.
220    *
221    * @param bytesConsumed The number of bytes consumed from the peek buffer.
222    */
updatePeekBuffer(int bytesConsumed)223   private void updatePeekBuffer(int bytesConsumed) {
224     peekBufferLength -= bytesConsumed;
225     peekBufferPosition = 0;
226     byte[] newPeekBuffer = peekBuffer;
227     if (peekBufferLength < peekBuffer.length - PEEK_MAX_FREE_SPACE) {
228       newPeekBuffer = new byte[peekBufferLength + PEEK_MIN_FREE_SPACE_AFTER_RESIZE];
229     }
230     System.arraycopy(peekBuffer, bytesConsumed, newPeekBuffer, 0, peekBufferLength);
231     peekBuffer = newPeekBuffer;
232   }
233 
234   /**
235    * Starts or continues a read from the data source.
236    *
237    * @param target A target array into which data should be written.
238    * @param offset The offset into the target array at which to write.
239    * @param length The maximum number of bytes to read from the input.
240    * @param bytesAlreadyRead The number of bytes already read from the input.
241    * @param allowEndOfInput True if encountering the end of the input having read no data is
242    *     allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
243    *     should be considered an error, causing an {@link EOFException} to be thrown.
244    * @return The total number of bytes read so far, or {@link C#RESULT_END_OF_INPUT} if
245    *     {@code allowEndOfInput} is true and the input has ended having read no bytes.
246    * @throws EOFException If the end of input was encountered having partially satisfied the read
247    *     (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were
248    *     read and {@code allowEndOfInput} is false.
249    * @throws IOException If an error occurs reading from the input.
250    * @throws InterruptedException If the thread is interrupted.
251    */
readFromDataSource(byte[] target, int offset, int length, int bytesAlreadyRead, boolean allowEndOfInput)252   private int readFromDataSource(byte[] target, int offset, int length, int bytesAlreadyRead,
253       boolean allowEndOfInput) throws InterruptedException, IOException {
254     if (Thread.interrupted()) {
255       throw new InterruptedException();
256     }
257     int bytesRead = dataSource.read(target, offset + bytesAlreadyRead, length - bytesAlreadyRead);
258     if (bytesRead == C.RESULT_END_OF_INPUT) {
259       if (bytesAlreadyRead == 0 && allowEndOfInput) {
260         return C.RESULT_END_OF_INPUT;
261       }
262       throw new EOFException();
263     }
264     return bytesAlreadyRead + bytesRead;
265   }
266 
267   /**
268    * Advances the position by the specified number of bytes read.
269    *
270    * @param bytesRead The number of bytes read.
271    */
commitBytesRead(int bytesRead)272   private void commitBytesRead(int bytesRead) {
273     if (bytesRead != C.RESULT_END_OF_INPUT) {
274       position += bytesRead;
275     }
276   }
277 
278 }
279