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