/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.exoplayer2.extractor; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; import java.util.Arrays; /** * An {@link ExtractorInput} that wraps a {@link DataSource}. */ public final class DefaultExtractorInput implements ExtractorInput { private static final int PEEK_MIN_FREE_SPACE_AFTER_RESIZE = 64 * 1024; private static final int PEEK_MAX_FREE_SPACE = 512 * 1024; private static final byte[] SCRATCH_SPACE = new byte[4096]; private final DataSource dataSource; private final long streamLength; private long position; private byte[] peekBuffer; private int peekBufferPosition; private int peekBufferLength; /** * @param dataSource The wrapped {@link DataSource}. * @param position The initial position in the stream. * @param length The length of the stream, or {@link C#LENGTH_UNSET} if it is unknown. */ public DefaultExtractorInput(DataSource dataSource, long position, long length) { this.dataSource = dataSource; this.position = position; this.streamLength = length; peekBuffer = new byte[PEEK_MIN_FREE_SPACE_AFTER_RESIZE]; } @Override public int read(byte[] target, int offset, int length) throws IOException, InterruptedException { int bytesRead = readFromPeekBuffer(target, offset, length); if (bytesRead == 0) { bytesRead = readFromDataSource(target, offset, length, 0, true); } commitBytesRead(bytesRead); return bytesRead; } @Override public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) throws IOException, InterruptedException { int bytesRead = readFromPeekBuffer(target, offset, length); while (bytesRead < length && bytesRead != C.RESULT_END_OF_INPUT) { bytesRead = readFromDataSource(target, offset, length, bytesRead, allowEndOfInput); } commitBytesRead(bytesRead); return bytesRead != C.RESULT_END_OF_INPUT; } @Override public void readFully(byte[] target, int offset, int length) throws IOException, InterruptedException { readFully(target, offset, length, false); } @Override public int skip(int length) throws IOException, InterruptedException { int bytesSkipped = skipFromPeekBuffer(length); if (bytesSkipped == 0) { bytesSkipped = readFromDataSource(SCRATCH_SPACE, 0, Math.min(length, SCRATCH_SPACE.length), 0, true); } commitBytesRead(bytesSkipped); return bytesSkipped; } @Override public boolean skipFully(int length, boolean allowEndOfInput) throws IOException, InterruptedException { int bytesSkipped = skipFromPeekBuffer(length); while (bytesSkipped < length && bytesSkipped != C.RESULT_END_OF_INPUT) { bytesSkipped = readFromDataSource(SCRATCH_SPACE, -bytesSkipped, Math.min(length, bytesSkipped + SCRATCH_SPACE.length), bytesSkipped, allowEndOfInput); } commitBytesRead(bytesSkipped); return bytesSkipped != C.RESULT_END_OF_INPUT; } @Override public void skipFully(int length) throws IOException, InterruptedException { skipFully(length, false); } @Override public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput) throws IOException, InterruptedException { if (!advancePeekPosition(length, allowEndOfInput)) { return false; } System.arraycopy(peekBuffer, peekBufferPosition - length, target, offset, length); return true; } @Override public void peekFully(byte[] target, int offset, int length) throws IOException, InterruptedException { peekFully(target, offset, length, false); } @Override public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException, InterruptedException { ensureSpaceForPeek(length); int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); while (bytesPeeked < length) { bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, allowEndOfInput); if (bytesPeeked == C.RESULT_END_OF_INPUT) { return false; } } peekBufferPosition += length; peekBufferLength = Math.max(peekBufferLength, peekBufferPosition); return true; } @Override public void advancePeekPosition(int length) throws IOException, InterruptedException { advancePeekPosition(length, false); } @Override public void resetPeekPosition() { peekBufferPosition = 0; } @Override public long getPeekPosition() { return position + peekBufferPosition; } @Override public long getPosition() { return position; } @Override public long getLength() { return streamLength; } @Override public void setRetryPosition(long position, E e) throws E { Assertions.checkArgument(position >= 0); this.position = position; throw e; } /** * Ensures {@code peekBuffer} is large enough to store at least {@code length} bytes from the * current peek position. */ private void ensureSpaceForPeek(int length) { int requiredLength = peekBufferPosition + length; if (requiredLength > peekBuffer.length) { int newPeekCapacity = Util.constrainValue(peekBuffer.length * 2, requiredLength + PEEK_MIN_FREE_SPACE_AFTER_RESIZE, requiredLength + PEEK_MAX_FREE_SPACE); peekBuffer = Arrays.copyOf(peekBuffer, newPeekCapacity); } } /** * Skips from the peek buffer. * * @param length The maximum number of bytes to skip from the peek buffer. * @return The number of bytes skipped. */ private int skipFromPeekBuffer(int length) { int bytesSkipped = Math.min(peekBufferLength, length); updatePeekBuffer(bytesSkipped); return bytesSkipped; } /** * Reads from the peek buffer * * @param target A target array into which data should be written. * @param offset The offset into the target array at which to write. * @param length The maximum number of bytes to read from the peek buffer. * @return The number of bytes read. */ private int readFromPeekBuffer(byte[] target, int offset, int length) { if (peekBufferLength == 0) { return 0; } int peekBytes = Math.min(peekBufferLength, length); System.arraycopy(peekBuffer, 0, target, offset, peekBytes); updatePeekBuffer(peekBytes); return peekBytes; } /** * Updates the peek buffer's length, position and contents after consuming data. * * @param bytesConsumed The number of bytes consumed from the peek buffer. */ private void updatePeekBuffer(int bytesConsumed) { peekBufferLength -= bytesConsumed; peekBufferPosition = 0; byte[] newPeekBuffer = peekBuffer; if (peekBufferLength < peekBuffer.length - PEEK_MAX_FREE_SPACE) { newPeekBuffer = new byte[peekBufferLength + PEEK_MIN_FREE_SPACE_AFTER_RESIZE]; } System.arraycopy(peekBuffer, bytesConsumed, newPeekBuffer, 0, peekBufferLength); peekBuffer = newPeekBuffer; } /** * Starts or continues a read from the data source. * * @param target A target array into which data should be written. * @param offset The offset into the target array at which to write. * @param length The maximum number of bytes to read from the input. * @param bytesAlreadyRead The number of bytes already read from the input. * @param allowEndOfInput True if encountering the end of the input having read no data is * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it * should be considered an error, causing an {@link EOFException} to be thrown. * @return The total number of bytes read so far, or {@link C#RESULT_END_OF_INPUT} if * {@code allowEndOfInput} is true and the input has ended having read no bytes. * @throws EOFException If the end of input was encountered having partially satisfied the read * (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were * read and {@code allowEndOfInput} is false. * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ private int readFromDataSource(byte[] target, int offset, int length, int bytesAlreadyRead, boolean allowEndOfInput) throws InterruptedException, IOException { if (Thread.interrupted()) { throw new InterruptedException(); } int bytesRead = dataSource.read(target, offset + bytesAlreadyRead, length - bytesAlreadyRead); if (bytesRead == C.RESULT_END_OF_INPUT) { if (bytesAlreadyRead == 0 && allowEndOfInput) { return C.RESULT_END_OF_INPUT; } throw new EOFException(); } return bytesAlreadyRead + bytesRead; } /** * Advances the position by the specified number of bytes read. * * @param bytesRead The number of bytes read. */ private void commitBytesRead(int bytesRead) { if (bytesRead != C.RESULT_END_OF_INPUT) { position += bytesRead; } } }