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