diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java deleted file mode 100644 index 6b140c9ab2..0000000000 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2014 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.exoplayer.extractor.mp3; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import com.google.android.exoplayer.extractor.DefaultExtractorInput; -import com.google.android.exoplayer.extractor.ExtractorInput; -import com.google.android.exoplayer.extractor.TrackOutput; -import com.google.android.exoplayer.testutil.FakeDataSource; -import com.google.android.exoplayer.testutil.TestUtil; -import com.google.android.exoplayer.upstream.DataSpec; -import com.google.android.exoplayer.util.ParsableByteArray; - -import android.net.Uri; -import android.test.InstrumentationTestCase; - -import org.mockito.Mock; - -import java.nio.BufferOverflowException; -import java.util.Arrays; - -/** - * Tests for {@link BufferingInput}. - */ -public class BufferingInputTest extends InstrumentationTestCase { - - private static final String TEST_URI = "http://www.google.com"; - private static final byte[] STREAM_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - - private ExtractorInput fakeExtractorInput; - - /** Used for verifying interactions. */ - @Mock private ExtractorInput mockExtractorInput; - @Mock private TrackOutput mockTrackOutput; - - @Override - public void setUp() throws Exception { - TestUtil.setUpMockito(this); - - FakeDataSource.Builder builder = new FakeDataSource.Builder(); - builder.appendReadData(STREAM_DATA); - FakeDataSource fakeDataSource = builder.build(); - fakeDataSource.open(new DataSpec(Uri.parse(TEST_URI))); - fakeExtractorInput = new DefaultExtractorInput(fakeDataSource, 0, STREAM_DATA.length); - } - - public void testReadFromExtractor() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[4]; - input.read(fakeExtractorInput, target, 0, 4); - assertMatchesStreamData(target, 0, 4); - } - - public void testReadCapacityFromExtractor() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[5]; - input.read(fakeExtractorInput, target, 0, 5); - assertMatchesStreamData(target, 0, 5); - } - - public void testReadOverCapacityFromExtractorFails() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[6]; - try { - input.read(fakeExtractorInput, target, 0, 6); - fail(); - } catch (BufferOverflowException e) { - // Expected. - } - } - - public void testReadFromBuffer() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[5]; - input.read(fakeExtractorInput, target, 0, 5); - - // When reading already-buffered data - input.returnToMark(); - input.read(mockExtractorInput, target, 0, 5); - assertMatchesStreamData(target, 0, 5); - - // There is no interaction with the extractor input. - verifyZeroInteractions(mockExtractorInput); - } - - public void testReadFromBufferPartially() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[5]; - input.read(fakeExtractorInput, target, 0, 5); - - // When reading already-buffered data - input.returnToMark(); - input.read(mockExtractorInput, target, 0, 4); - assertMatchesStreamData(target, 0, 4); - - // There is no interaction with the extractor input. - verifyZeroInteractions(mockExtractorInput); - } - - public void testResetDiscardsData() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[5]; - input.read(fakeExtractorInput, target, 0, 5); - - // When the buffer is reset - input.reset(); - - // Then it is possible to read up to the capacity again. - input.read(fakeExtractorInput, target, 0, 5); - assertMatchesStreamData(target, 5, 5); - } - - public void testGetAvailableByteCountAtWritePosition() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[5]; - input.read(fakeExtractorInput, target, 0, 5); - assertEquals(0, input.getAvailableByteCount()); - } - - public void testGetAvailableByteCountBeforeWritePosition() throws Exception { - BufferingInput input = new BufferingInput(5); - byte[] target = new byte[5]; - input.read(fakeExtractorInput, target, 0, 3); - input.mark(); - input.read(fakeExtractorInput, target, 0, 3); - input.mark(); - input.read(fakeExtractorInput, target, 0, 2); - input.returnToMark(); - - // The reading position is calculated correctly. - assertEquals(2, input.getAvailableByteCount()); - assertEquals(8, fakeExtractorInput.getPosition()); - } - - public void testGetParsableByteArray() throws Exception { - BufferingInput input = new BufferingInput(5); - input.skip(fakeExtractorInput, 4); - input.mark(); - input.skip(fakeExtractorInput, 3); - input.returnToMark(); - ParsableByteArray parsableByteArray = input.getParsableByteArray(fakeExtractorInput, 4); - - // The returned array matches the input's internal buffer. - assertMatchesStreamData(parsableByteArray.data, 0, 7); - } - - public void testGetParsableByteArrayPastCapacity() throws Exception { - BufferingInput input = new BufferingInput(5); - input.skip(fakeExtractorInput, 4); - input.mark(); - input.skip(fakeExtractorInput, 3); - input.mark(); - input.skip(fakeExtractorInput, 1); - input.returnToMark(); - ParsableByteArray parsableByteArray = input.getParsableByteArray(fakeExtractorInput, 2); - - // The second call to mark() copied the buffer data to the start. - assertMatchesStreamData(parsableByteArray.data, 7, 2); - } - - public void testDrainEntireBuffer() throws Exception { - BufferingInput input = new BufferingInput(5); - input.skip(fakeExtractorInput, 3); - input.returnToMark(); - - // When draining the first three bytes - input.drainToOutput(mockTrackOutput, 3); - - // They are appended as sample data. - verify(mockTrackOutput).sampleData(any(ParsableByteArray.class), eq(3)); - } - - public void testDrainTwice() throws Exception { - BufferingInput input = new BufferingInput(5); - input.skip(fakeExtractorInput, 3); - input.returnToMark(); - - // When draining one then two bytes - input.drainToOutput(mockTrackOutput, 1); - assertEquals(2, input.drainToOutput(mockTrackOutput, 3)); - - // They are appended as sample data. - verify(mockTrackOutput).sampleData(any(ParsableByteArray.class), eq(1)); - verify(mockTrackOutput).sampleData(any(ParsableByteArray.class), eq(2)); - } - - public void testDrainPastCapacity() throws Exception { - BufferingInput input = new BufferingInput(5); - input.skip(fakeExtractorInput, 4); - input.mark(); - input.skip(fakeExtractorInput, 5); - input.returnToMark(); - - // When draining the entire buffer - input.drainToOutput(mockTrackOutput, 5); - - // The sample data is appended as one whole buffer. - verify(mockTrackOutput).sampleData(any(ParsableByteArray.class), eq(5)); - } - - private static void assertMatchesStreamData(byte[] read, int offset, int length) { - assertTrue(Arrays.equals(Arrays.copyOfRange(STREAM_DATA, offset, offset + length), - Arrays.copyOfRange(read, 0, length))); - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/BufferingInput.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/BufferingInput.java deleted file mode 100644 index f628b70de3..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/BufferingInput.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2014 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.exoplayer.extractor.mp3; - -import com.google.android.exoplayer.extractor.ExtractorInput; -import com.google.android.exoplayer.extractor.TrackOutput; -import com.google.android.exoplayer.util.ParsableByteArray; - -import java.io.EOFException; -import java.io.IOException; -import java.nio.BufferOverflowException; - -/** - * Buffers bytes read from an {@link ExtractorInput} to allow re-reading buffered bytes within a - * window starting at a marked position. - */ -/* package */ final class BufferingInput { - - private final ParsableByteArray buffer; - private final int capacity; - - private int readPosition; - private int writePosition; - private int markPosition; - - /** - * Constructs a new buffer for reading from extractor inputs that can store up to {@code capacity} - * bytes. - * - * @param capacity Number of bytes that can be stored in the buffer. - */ - public BufferingInput(int capacity) { - this.capacity = capacity; - buffer = new ParsableByteArray(capacity * 2); - } - - /** Discards any pending data in the buffer and returns the writing position to zero. */ - public void reset() { - readPosition = 0; - writePosition = 0; - markPosition = 0; - } - - /** - * Moves the mark to be at the reading position. Any data before the reading position is - * discarded. After calling this method, calling {@link #returnToMark} will move the reading - * position back to the mark position. - */ - public void mark() { - if (readPosition > capacity) { - System.arraycopy(buffer.data, readPosition, buffer.data, 0, writePosition - readPosition); - writePosition -= readPosition; - readPosition = 0; - } - markPosition = readPosition; - } - - /** Moves the reading position back to the mark position. */ - public void returnToMark() { - readPosition = markPosition; - } - - /** Returns the number of bytes available for reading from the current position. */ - public int getAvailableByteCount() { - return writePosition - readPosition; - } - - /** - * Buffers any more data required to read {@code length} bytes from the reading position, and - * returns a {@link ParsableByteArray} that wraps the buffer's byte array, with its position set - * to the current reading position. The read position is then updated for having read - * {@code length} bytes. - * - * @param extractorInput {@link ExtractorInput} from which additional data should be read. - * @param length Number of bytes that will be readable in the returned array. - * @return {@link ParsableByteArray} from which {@code length} bytes can be read. - * @throws IOException Thrown if there was an error reading from the stream. - * @throws InterruptedException Thrown if reading from the stream was interrupted. - */ - public ParsableByteArray getParsableByteArray(ExtractorInput extractorInput, int length) - throws IOException, InterruptedException { - if (!ensureLoaded(extractorInput, length)) { - throw new EOFException(); - } - ParsableByteArray parsableByteArray = new ParsableByteArray(buffer.data, writePosition); - parsableByteArray.setPosition(readPosition); - readPosition += length; - return parsableByteArray; - } - - /** - * Drains as much buffered data as possible up to {@code length} bytes to {@code trackOutput}. - * - * @param trackOutput Track output to populate with up to {@code length} bytes of sample data. - * @param length Number of bytes to try to read from the buffer. - * @return The number of buffered bytes written. - */ - public int drainToOutput(TrackOutput trackOutput, int length) { - if (length == 0) { - return 0; - } - buffer.setPosition(readPosition); - int bytesToDrain = Math.min(writePosition - readPosition, length); - trackOutput.sampleData(buffer, bytesToDrain); - readPosition += bytesToDrain; - return bytesToDrain; - } - - /** - * Skips {@code length} bytes from the reading position, reading from {@code extractorInput} to - * populate the buffer if required. - * - * @param extractorInput {@link ExtractorInput} from which additional data should be read. - * @param length Number of bytes to skip. - * @throws IOException Thrown if there was an error reading from the stream. - * @throws InterruptedException Thrown if reading from the stream was interrupted. - */ - public void skip(ExtractorInput extractorInput, int length) - throws IOException, InterruptedException { - if (!readInternal(extractorInput, null, 0, length)) { - throw new EOFException(); - } - } - - /** - * Reads {@code length} bytes from the reading position, reading from {@code extractorInput} to - * populate the buffer if required. - * - * @param extractorInput {@link ExtractorInput} from which additional data should be read. - * @param length Number of bytes to read. - * @throws IOException Thrown if there was an error reading from the stream. - * @throws InterruptedException Thrown if reading from the stream was interrupted. - * @throws EOFException Thrown if the end of the file was reached. - */ - public void read(ExtractorInput extractorInput, byte[] target, int offset, int length) - throws IOException, InterruptedException { - if (!readInternal(extractorInput, target, offset, length)) { - throw new EOFException(); - } - } - - /** - * Reads {@code length} bytes from the reading position, reading from {@code extractorInput} to - * populate the buffer if required. - * - *
Returns {@code false} if the end of the stream has been reached. Throws {@link EOFException} - * if the read request could only be partially satisfied. Returns {@code true} otherwise. - * - * @param extractorInput {@link ExtractorInput} from which additional data should be read. - * @param length Number of bytes to read. - * @return Whether the extractor input is at the end of the stream. - * @throws IOException Thrown if there was an error reading from the stream. - * @throws InterruptedException Thrown if reading from the stream was interrupted. - * @throws EOFException Thrown if the end of the file was reached. - */ - public boolean readAllowingEndOfInput(ExtractorInput extractorInput, byte[] target, int offset, - int length) throws IOException, InterruptedException { - return readInternal(extractorInput, target, offset, length); - } - - private boolean readInternal(ExtractorInput extractorInput, byte[] target, int offset, int length) - throws InterruptedException, IOException { - if (!ensureLoaded(extractorInput, length)) { - return false; - } - if (target != null) { - System.arraycopy(buffer.data, readPosition, target, offset, length); - } - readPosition += length; - return true; - } - - /** Ensures the buffer contains enough data to read {@code length} bytes. */ - private boolean ensureLoaded(ExtractorInput extractorInput, int length) - throws InterruptedException, IOException { - if (length + readPosition - markPosition > capacity) { - throw new BufferOverflowException(); - } - - int bytesToLoad = length - (writePosition - readPosition); - if (bytesToLoad > 0) { - if (!extractorInput.readFully(buffer.data, writePosition, bytesToLoad, true)) { - return false; - } - writePosition += bytesToLoad; - } - return true; - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index 7a07c76bb1..f39fa403dd 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -49,7 +49,6 @@ public final class Mp3Extractor implements Extractor { private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); private final long forcedFirstSampleTimestampUs; - private final BufferingInput inputBuffer; private final ParsableByteArray scratch; private final MpegAudioHeader synchronizedHeader; @@ -79,7 +78,6 @@ public final class Mp3Extractor implements Extractor { */ public Mp3Extractor(long forcedFirstSampleTimestampUs) { this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; - inputBuffer = new BufferingInput(MpegAudioHeader.MAX_FRAME_SIZE_BYTES * 3); scratch = new ParsableByteArray(4); synchronizedHeader = new MpegAudioHeader(); basisTimeUs = -1; @@ -157,7 +155,6 @@ public final class Mp3Extractor implements Extractor { samplesRead = 0; basisTimeUs = -1; sampleBytesRemaining = 0; - inputBuffer.reset(); } @Override @@ -178,7 +175,7 @@ public final class Mp3Extractor implements Extractor { return RESULT_END_OF_INPUT; } if (basisTimeUs == -1) { - basisTimeUs = seeker.getTimeUs(getPosition(extractorInput, inputBuffer)); + basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); if (forcedFirstSampleTimestampUs != -1) { long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0); basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs; @@ -186,24 +183,15 @@ public final class Mp3Extractor implements Extractor { } sampleBytesRemaining = synchronizedHeader.frameSize; } - - long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate); - - // Start by draining any buffered bytes, then read directly from the extractor input. - sampleBytesRemaining -= inputBuffer.drainToOutput(trackOutput, sampleBytesRemaining); - if (sampleBytesRemaining > 0) { - inputBuffer.mark(); - int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true); - if (bytesAppended == C.RESULT_END_OF_INPUT) { - return RESULT_END_OF_INPUT; - } - sampleBytesRemaining -= bytesAppended; - // Return if we still need more data. - if (sampleBytesRemaining > 0) { - return RESULT_CONTINUE; - } + int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; } - + sampleBytesRemaining -= bytesAppended; + if (sampleBytesRemaining > 0) { + return RESULT_CONTINUE; + } + long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate); trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, synchronizedHeader.frameSize, 0, null); samplesRead += synchronizedHeader.samplesPerFrame; sampleBytesRemaining = 0; @@ -215,11 +203,10 @@ public final class Mp3Extractor implements Extractor { */ private long maybeResynchronize(ExtractorInput extractorInput) throws IOException, InterruptedException { - inputBuffer.mark(); - if (!inputBuffer.readAllowingEndOfInput(extractorInput, scratch.data, 0, 4)) { + extractorInput.resetPeekPosition(); + if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { return RESULT_END_OF_INPUT; } - inputBuffer.returnToMark(); scratch.setPosition(0); int sampleHeaderData = scratch.readInt(); @@ -232,14 +219,14 @@ public final class Mp3Extractor implements Extractor { } synchronizedHeaderData = 0; - inputBuffer.skip(extractorInput, 1); + extractorInput.skipFully(1); return synchronizeCatchingEndOfInput(extractorInput); } private long synchronizeCatchingEndOfInput(ExtractorInput extractorInput) throws IOException, InterruptedException { - // An EOFException will be raised if any read operation was partially satisfied. If a seek - // operation resulted in reading from within the last frame, we may try to read past the end of + // An EOFException will be raised if any peek operation was partially satisfied. If a seek + // operation resulted in reading from within the last frame, we may try to peek past the end of // the file in a partially-satisfied read operation, so we need to catch the exception. try { return synchronize(extractorInput); @@ -249,39 +236,28 @@ public final class Mp3Extractor implements Extractor { } private long synchronize(ExtractorInput extractorInput) throws IOException, InterruptedException { - // TODO: Use peekFully instead of a buffering input, and deduplicate with sniff(). - if (extractorInput.getPosition() == 0) { - // Before preparation completes, retrying loads from the start, so clear any buffered data. - inputBuffer.reset(); - } else { - // After preparation completes, retrying resumes loading from the old position, so return to - // the start of buffered data to parse it again. - inputBuffer.returnToMark(); - } - - long startPosition = getPosition(extractorInput, inputBuffer); - - // Skip any ID3 header at the start of the file. + // TODO: Deduplicate with sniff(). + extractorInput.resetPeekPosition(); + long startPosition = extractorInput.getPosition(); if (startPosition == 0) { + // Skip any ID3 headers at the start of the file. while (true) { - inputBuffer.read(extractorInput, scratch.data, 0, 3); + extractorInput.peekFully(scratch.data, 0, 3); scratch.setPosition(0); if (scratch.readUnsignedInt24() != ID3_TAG) { break; } - extractorInput.skipFully(3); + extractorInput.skipFully(3 + 2 + 1); // "ID3", version, flags extractorInput.readFully(scratch.data, 0, 4); int headerLength = ((scratch.data[0] & 0x7F) << 21) | ((scratch.data[1] & 0x7F) << 14) | ((scratch.data[2] & 0x7F) << 7) | (scratch.data[3] & 0x7F); extractorInput.skipFully(headerLength); - inputBuffer.reset(); - startPosition = getPosition(extractorInput, inputBuffer); + startPosition = extractorInput.getPosition(); } - inputBuffer.returnToMark(); + extractorInput.resetPeekPosition(); } // Try to find four consecutive valid MPEG audio frames. - inputBuffer.mark(); long headerPosition = startPosition; int validFrameCount = 0; int candidateSynchronizedHeaderData = 0; @@ -290,7 +266,7 @@ public final class Mp3Extractor implements Extractor { throw new ParserException("Searched too many bytes while resynchronizing."); } - if (!inputBuffer.readAllowingEndOfInput(extractorInput, scratch.data, 0, 4)) { + if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { return RESULT_END_OF_INPUT; } @@ -304,9 +280,7 @@ public final class Mp3Extractor implements Extractor { candidateSynchronizedHeaderData = 0; // Try reading a header starting at the next byte. - inputBuffer.returnToMark(); - inputBuffer.skip(extractorInput, 1); - inputBuffer.mark(); + extractorInput.skipFully(1); headerPosition++; continue; } @@ -323,11 +297,11 @@ public final class Mp3Extractor implements Extractor { } // Look for more headers. - inputBuffer.skip(extractorInput, frameSize - 4); + extractorInput.advancePeekPosition(frameSize - 4); } - // The input buffer read position is now synchronized. - inputBuffer.returnToMark(); + // The read position is now synchronized. + extractorInput.resetPeekPosition(); synchronizedHeaderData = candidateSynchronizedHeaderData; if (seeker == null) { setupSeeker(extractorInput, headerPosition); @@ -341,55 +315,48 @@ public final class Mp3Extractor implements Extractor { } /** - * Sets {@link #seeker} to seek using metadata from {@link #inputBuffer}, which should have its - * position set to the start of the first frame in the stream. On returning, - * {@link #inputBuffer}'s position and mark will be set to the start of the first frame of audio. + * Sets {@link #seeker} to seek using metadata read from {@code extractorInput}, which should + * provide data from the start of the first frame in the stream. On returning, the input's + * position will be set to the start of the first frame of audio. * - * @param extractorInput Source of data for {@link #inputBuffer}. - * @param headerPosition Position (byte offset) of the synchronized header in the stream. + * @param extractorInput The {@link ExtractorInput} from which to read. + * @param headerPosition The position (byte offset) of the synchronized header in the stream. * @throws IOException Thrown if there was an error reading from the stream. Not expected if the - * next two frames were already read during synchronization. + * next two frames were already peeked during synchronization. * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if - * the next two frames were already read during synchronization. + * the next two frames were already peeked during synchronization. */ private void setupSeeker(ExtractorInput extractorInput, long headerPosition) throws IOException, InterruptedException { // Try to set up seeking based on a XING or VBRI header. - if (parseSeekerFrame(extractorInput, headerPosition, extractorInput.getLength())) { - // Discard the parsed header so we start reading from the first audio frame. - inputBuffer.mark(); + ParsableByteArray frame = new ParsableByteArray(synchronizedHeader.frameSize); + extractorInput.peekFully(frame.data, 0, synchronizedHeader.frameSize); + if (parseSeekerFrame(frame, headerPosition, extractorInput.getLength())) { + extractorInput.skipFully(synchronizedHeader.frameSize); if (seeker != null) { return; } - // If there was a header but it was not usable, synchronize to the next frame so we don't - // use an invalid bitrate for CBR seeking. This read is guaranteed to succeed if the frame was + // If there was a header but it was not usable, synchronize to the next frame so we don't use + // an invalid bitrate for CBR seeking. Peeking is guaranteed to succeed if the frame was // already read during synchronization. - inputBuffer.read(extractorInput, scratch.data, 0, 4); - scratch.setPosition(0); headerPosition += synchronizedHeader.frameSize; + extractorInput.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); } - - inputBuffer.returnToMark(); seeker = new ConstantBitrateSeeker(headerPosition, synchronizedHeader.bitrate * 1000, extractorInput.getLength()); } /** - * Consumes the frame at {@link #inputBuffer}'s current position, advancing it to the next frame. - * The mark is not modified. {@link #seeker} will be assigned based on seeking metadata in the - * frame. If there is no seeking metadata, returns {@code false} and sets {@link #seeker} to null. - * If seeking metadata is present and unusable, returns {@code true} and sets {@link #seeker} to - * null. Otherwise, returns {@code true} and assigns {@link #seeker}. + * Tries to read seeking metadata from the given {@code frame}. If there is no seeking metadata, + * returns {@code false} and sets {@link #seeker} to null. If seeking metadata is present and + * unusable, returns {@code true} and sets {@link #seeker} to null. Otherwise, returns + * {@code true} and assigns {@link #seeker}. */ - private boolean parseSeekerFrame(ExtractorInput extractorInput, long headerPosition, - long inputLength) throws IOException, InterruptedException { - // Read the first frame so it can be parsed for seeking metadata. - inputBuffer.mark(); + private boolean parseSeekerFrame(ParsableByteArray frame, long headerPosition, long inputLength) { seeker = null; - ParsableByteArray frame = - inputBuffer.getParsableByteArray(extractorInput, synchronizedHeader.frameSize); // Check if there is a XING header. int xingBase; @@ -427,11 +394,6 @@ public final class Mp3Extractor implements Extractor { return false; } - /** Returns the reading position of {@code bufferingInput} relative to the extractor's stream. */ - private static long getPosition(ExtractorInput extractorInput, BufferingInput bufferingInput) { - return extractorInput.getPosition() - bufferingInput.getAvailableByteCount(); - } - /** * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be * used to work out the new sample basis timestamp after seeking and resynchronization.