diff --git a/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java b/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java index f673c06f00..5bdeccc4d9 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java @@ -57,8 +57,8 @@ public final class FakeTrackOutput implements TrackOutput { } @Override - public int sampleData(ExtractorInput input, int length) throws IOException, - InterruptedException { + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { byte[] newData = new byte[length]; input.readFully(newData, 0, length); sampleData = TestUtil.joinByteArrays(sampleData, newData); diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java index 7cc0c72eeb..0c585419e7 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java @@ -127,8 +127,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput } @Override - public int sampleData(ExtractorInput input, int length) throws IOException, InterruptedException { - return output.sampleData(input, length); + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + return output.sampleData(input, length, allowEndOfInput); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java index 8b602cc22d..72c317f535 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java @@ -106,8 +106,9 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu } @Override - public int sampleData(ExtractorInput input, int length) throws IOException, InterruptedException { - return getOutput().sampleData(input, length); + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + return getOutput().sampleData(input, length, allowEndOfInput); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java index a83092ad3f..dd6c68c749 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java @@ -138,7 +138,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu } @Override - public int sampleData(ExtractorInput input, int length) throws IOException, InterruptedException { + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { throw new IllegalStateException("Unexpected sample data in initialization chunk"); } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java index 9e7a2a84df..de2c7067cc 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java @@ -109,10 +109,8 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk { // Load the sample data. int result = 0; while (result != C.RESULT_END_OF_INPUT) { - result = getOutput().sampleData(dataSource, Integer.MAX_VALUE); - if (result != C.RESULT_END_OF_INPUT) { - bytesLoaded += result; - } + bytesLoaded += result; + result = getOutput().sampleData(dataSource, Integer.MAX_VALUE, true); } int sampleSize = bytesLoaded; if (headerData != null) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java index 330f320d22..00689ae243 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java @@ -15,12 +15,14 @@ */ package com.google.android.exoplayer.extractor; +import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.upstream.Allocator; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.util.ParsableByteArray; +import java.io.EOFException; import java.io.IOException; /** @@ -225,8 +227,20 @@ public class DefaultTrackOutput implements TrackOutput { // Called by the loading thread. - public int sampleData(DataSource dataSource, int length) throws IOException { - return rollingBuffer.appendData(dataSource, length); + /** + * Invoked to write sample data to the output. + * + * @param dataSource A {@link DataSource} from which to read the sample data. + * @param length The maximum length to 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 number of bytes appended. + * @throws IOException If an error occurred reading from the input. + */ + public int sampleData(DataSource dataSource, int length, boolean allowEndOfInput) + throws IOException { + return rollingBuffer.appendData(dataSource, length, allowEndOfInput); } // TrackOutput implementation. Called by the loading thread. @@ -237,8 +251,9 @@ public class DefaultTrackOutput implements TrackOutput { } @Override - public int sampleData(ExtractorInput input, int length) throws IOException, InterruptedException { - return rollingBuffer.appendData(input, length); + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + return rollingBuffer.appendData(input, length, allowEndOfInput); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java index 4cc229d847..c6cdb8b1c4 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java @@ -23,6 +23,7 @@ import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.ParsableByteArray; +import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.LinkedBlockingDeque; @@ -350,26 +351,27 @@ import java.util.concurrent.LinkedBlockingDeque; * Appends data to the rolling buffer. * * @param dataSource The source from which to read. - * @param length The maximum length of the read, or {@link C#LENGTH_UNBOUNDED} if the caller does - * not wish to impose a limit. - * @return The number of bytes appended. + * @param length The maximum length of the read. + * @param allowEndOfInput True if encountering the end of the input having appended 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 number of bytes appended, or {@link C#RESULT_END_OF_INPUT} if the input has ended. * @throws IOException If an error occurs reading from the source. */ - public int appendData(DataSource dataSource, int length) throws IOException { - ensureSpaceForWrite(); - int remainingAllocationCapacity = allocationLength - lastAllocationOffset; - length = length != C.LENGTH_UNBOUNDED ? Math.min(length, remainingAllocationCapacity) - : remainingAllocationCapacity; - - int bytesRead = dataSource.read(lastAllocation.data, + public int appendData(DataSource dataSource, int length, boolean allowEndOfInput) + throws IOException { + length = prepareForAppend(length); + int bytesAppended = dataSource.read(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), length); - if (bytesRead == C.RESULT_END_OF_INPUT) { - return C.RESULT_END_OF_INPUT; + if (bytesAppended == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); } - - lastAllocationOffset += bytesRead; - totalBytesWritten += bytesRead; - return bytesRead; + lastAllocationOffset += bytesAppended; + totalBytesWritten += bytesAppended; + return bytesAppended; } /** @@ -377,17 +379,27 @@ import java.util.concurrent.LinkedBlockingDeque; * * @param input The source from which to read. * @param length The maximum length of the read. - * @return The number of bytes appended. + * @param allowEndOfInput True if encountering the end of the input having appended 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 number of bytes appended, or {@link C#RESULT_END_OF_INPUT} if the input has ended. * @throws IOException If an error occurs reading from the source. + * @throws InterruptedException If the thread has been interrupted. */ - public int appendData(ExtractorInput input, int length) throws IOException, InterruptedException { - ensureSpaceForWrite(); - int thisWriteLength = Math.min(length, allocationLength - lastAllocationOffset); - input.readFully(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), - thisWriteLength); - lastAllocationOffset += thisWriteLength; - totalBytesWritten += thisWriteLength; - return thisWriteLength; + public int appendData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + length = prepareForAppend(length); + int bytesAppended = input.read(lastAllocation.data, + lastAllocation.translateOffset(lastAllocationOffset), length); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + lastAllocationOffset += bytesAppended; + totalBytesWritten += bytesAppended; + return bytesAppended; } /** @@ -397,16 +409,14 @@ import java.util.concurrent.LinkedBlockingDeque; * @param length The length of the data to append. */ public void appendData(ParsableByteArray buffer, int length) { - int remainingWriteLength = length; - while (remainingWriteLength > 0) { - ensureSpaceForWrite(); - int thisWriteLength = Math.min(remainingWriteLength, allocationLength - lastAllocationOffset); + while (length > 0) { + int thisAppendLength = prepareForAppend(length); buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), - thisWriteLength); - lastAllocationOffset += thisWriteLength; - remainingWriteLength -= thisWriteLength; + thisAppendLength); + lastAllocationOffset += thisAppendLength; + totalBytesWritten += thisAppendLength; + length -= thisAppendLength; } - totalBytesWritten += length; } /** @@ -424,14 +434,16 @@ import java.util.concurrent.LinkedBlockingDeque; } /** - * Ensures at least one byte can be written, obtaining an additional allocation if necessary. + * Prepares the rolling sample buffer for an append of up to {@code length} bytes, returning the + * number of bytes that can actually be appended. */ - private void ensureSpaceForWrite() { + private int prepareForAppend(int length) { if (lastAllocationOffset == allocationLength) { lastAllocationOffset = 0; lastAllocation = allocator.allocate(); dataQueue.add(lastAllocation); } + return Math.min(length, allocationLength - lastAllocationOffset); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java index fa639d75ba..699fddb256 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java @@ -15,10 +15,12 @@ */ package com.google.android.exoplayer.extractor; +import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.util.ParsableByteArray; +import java.io.EOFException; import java.io.IOException; /** @@ -38,11 +40,15 @@ public interface TrackOutput { * * @param input An {@link ExtractorInput} from which to read the sample data. * @param length The maximum length to 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 number of bytes appended. * @throws IOException If an error occurred reading from the input. * @throws InterruptedException If the thread was interrupted. */ - int sampleData(ExtractorInput input, int length) throws IOException, InterruptedException; + int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; /** * Invoked to write sample data to the output. @@ -56,14 +62,16 @@ public interface TrackOutput { * Invoked when metadata associated with a sample has been extracted from the stream. *

* The corresponding sample data will have already been passed to the output via calls to - * {@link #sampleData(ExtractorInput, int)} or {@link #sampleData(ParsableByteArray, int)}. + * {@link #sampleData(ExtractorInput, int, boolean)} or + * {@link #sampleData(ParsableByteArray, int)}. * * @param timeUs The media timestamp associated with the sample, in microseconds. * @param flags Flags associated with the sample. See {@link SampleHolder#flags}. * @param size The size of the sample data, in bytes. * @param offset The number of bytes that have been passed to - * {@link #sampleData(ExtractorInput, int)} or {@link #sampleData(ParsableByteArray, int)} - * since the last byte belonging to the sample whose metadata is being passed. + * {@link #sampleData(ExtractorInput, int, boolean)} or + * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample + * whose metadata is being passed. * @param encryptionKey The encryption key associated with the sample. May be null. */ void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey); 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 deee4d3a33..269030ca04 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 @@ -170,11 +170,11 @@ public final class Mp3Extractor implements Extractor { sampleBytesRemaining -= inputBuffer.drainToOutput(trackOutput, sampleBytesRemaining); if (sampleBytesRemaining > 0) { inputBuffer.mark(); - try { - sampleBytesRemaining -= trackOutput.sampleData(extractorInput, sampleBytesRemaining); - } catch (EOFException e) { + 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; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index ab028754c0..9307f8446d 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -663,14 +663,14 @@ public final class FragmentedMp4Extractor implements Extractor { sampleSize += nalUnitLengthFieldLengthDiff; } else { // Write the payload of the NAL unit. - int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining); + int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } } } else { while (sampleBytesWritten < sampleSize) { - int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten); + int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index d5fbc3e70f..b5db6e4cc5 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -298,6 +298,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { return RESULT_END_OF_INPUT; } Mp4Track track = tracks[trackIndex]; + TrackOutput trackOutput = track.trackOutput; int sampleIndex = track.sampleIndex; long position = track.sampleTable.offsets[sampleIndex]; long skipAmount = position - input.getPosition() + sampleBytesWritten; @@ -327,24 +328,24 @@ public final class Mp4Extractor implements Extractor, SeekMap { sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); // Write a start code for the current NAL unit. nalStartCode.setPosition(0); - track.trackOutput.sampleData(nalStartCode, 4); + trackOutput.sampleData(nalStartCode, 4); sampleBytesWritten += 4; sampleSize += nalUnitLengthFieldLengthDiff; } else { // Write the payload of the NAL unit. - int writtenBytes = track.trackOutput.sampleData(input, sampleCurrentNalBytesRemaining); + int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } } } else { while (sampleBytesWritten < sampleSize) { - int writtenBytes = track.trackOutput.sampleData(input, sampleSize - sampleBytesWritten); + int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); sampleBytesWritten += writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes; } } - track.trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], + trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex], track.sampleTable.flags[sampleIndex], sampleSize, 0, null); track.sampleIndex++; sampleBytesWritten = 0; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java index e540067606..29687aefe3 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java @@ -885,7 +885,7 @@ public final class WebmExtractor implements Extractor { bytesRead = Math.min(length, strippedBytesLeft); output.sampleData(sampleStrippedBytes, bytesRead); } else { - bytesRead = output.sampleData(input, length); + bytesRead = output.sampleData(input, length, false); } sampleBytesRead += bytesRead; sampleBytesWritten += bytesRead;