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