Commit 514edb69 by aquilescanta Committed by Oliver Woodman

Add a type check for OGG files with a single payload page

Also make some javadocs more consistent with the rest of the library.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=211071559
parent 595b6b8f
...@@ -23,9 +23,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -23,9 +23,7 @@ import com.google.android.exoplayer2.util.Assertions;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
/** /** Seeks in an Ogg stream. */
* Used to seek in an Ogg stream.
*/
/* package */ final class DefaultOggSeeker implements OggSeeker { /* package */ final class DefaultOggSeeker implements OggSeeker {
//@VisibleForTesting //@VisibleForTesting
...@@ -56,19 +54,27 @@ import java.io.IOException; ...@@ -56,19 +54,27 @@ import java.io.IOException;
/** /**
* Constructs an OggSeeker. * Constructs an OggSeeker.
*
* @param startPosition Start position of the payload (inclusive). * @param startPosition Start position of the payload (inclusive).
* @param endPosition End position of the payload (exclusive). * @param endPosition End position of the payload (exclusive).
* @param streamReader StreamReader instance which owns this OggSeeker * @param streamReader The {@link StreamReader} that owns this seeker.
* @param firstPayloadPageSize The total size of the first payload page, in bytes. * @param firstPayloadPageSize The total size of the first payload page, in bytes.
* @param firstPayloadPageGranulePosition The granule position of the first payload page. * @param firstPayloadPageGranulePosition The granule position of the first payload page.
* @param firstPayloadPageIsLastPage Whether the first payload page is also the last page in the
* ogg stream.
*/ */
public DefaultOggSeeker(long startPosition, long endPosition, StreamReader streamReader, public DefaultOggSeeker(
int firstPayloadPageSize, long firstPayloadPageGranulePosition) { long startPosition,
long endPosition,
StreamReader streamReader,
long firstPayloadPageSize,
long firstPayloadPageGranulePosition,
boolean firstPayloadPageIsLastPage) {
Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition); Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition);
this.streamReader = streamReader; this.streamReader = streamReader;
this.startPosition = startPosition; this.startPosition = startPosition;
this.endPosition = endPosition; this.endPosition = endPosition;
if (firstPayloadPageSize == endPosition - startPosition) { if (firstPayloadPageSize == endPosition - startPosition || firstPayloadPageIsLastPage) {
totalGranules = firstPayloadPageGranulePosition; totalGranules = firstPayloadPageGranulePosition;
state = STATE_IDLE; state = STATE_IDLE;
} else { } else {
...@@ -240,11 +246,11 @@ import java.io.IOException; ...@@ -240,11 +246,11 @@ import java.io.IOException;
* Skips to the next page. * Skips to the next page.
* *
* @param input The {@code ExtractorInput} to skip to the next page. * @param input The {@code ExtractorInput} to skip to the next page.
* @throws IOException thrown if peeking/reading from the input fails. * @throws IOException If peeking/reading from the input fails.
* @throws InterruptedException thrown if interrupted while peeking/reading from the input. * @throws InterruptedException If the thread is interrupted.
* @throws EOFException if the next page can't be found before the end of the input. * @throws EOFException If the next page can't be found before the end of the input.
*/ */
//@VisibleForTesting // @VisibleForTesting
void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException { void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException {
if (!skipToNextPage(input, endPosition)) { if (!skipToNextPage(input, endPosition)) {
// Not found until eof. // Not found until eof.
...@@ -256,21 +262,21 @@ import java.io.IOException; ...@@ -256,21 +262,21 @@ import java.io.IOException;
* Skips to the next page. Searches for the next page header. * Skips to the next page. Searches for the next page header.
* *
* @param input The {@code ExtractorInput} to skip to the next page. * @param input The {@code ExtractorInput} to skip to the next page.
* @param until Searches until this position. * @param limit The limit up to which the search should take place.
* @return true if the next page is found. * @return Whether the next page was found.
* @throws IOException thrown if peeking/reading from the input fails. * @throws IOException thrown if peeking/reading from the input fails.
* @throws InterruptedException thrown if interrupted while peeking/reading from the input. * @throws InterruptedException thrown if interrupted while peeking/reading from the input.
*/ */
//@VisibleForTesting // @VisibleForTesting
boolean skipToNextPage(ExtractorInput input, long until) boolean skipToNextPage(ExtractorInput input, long limit)
throws IOException, InterruptedException { throws IOException, InterruptedException {
until = Math.min(until + 3, endPosition); limit = Math.min(limit + 3, endPosition);
byte[] buffer = new byte[2048]; byte[] buffer = new byte[2048];
int peekLength = buffer.length; int peekLength = buffer.length;
while (true) { while (true) {
if (input.getPosition() + peekLength > until) { if (input.getPosition() + peekLength > limit) {
// Make sure to not peek beyond the end of the input. // Make sure to not peek beyond the end of the input.
peekLength = (int) (until - input.getPosition()); peekLength = (int) (limit - input.getPosition());
if (peekLength < 4) { if (peekLength < 4) {
// Not found until end. // Not found until end.
return false; return false;
...@@ -278,7 +284,9 @@ import java.io.IOException; ...@@ -278,7 +284,9 @@ import java.io.IOException;
} }
input.peekFully(buffer, 0, peekLength, false); input.peekFully(buffer, 0, peekLength, false);
for (int i = 0; i < peekLength - 3; i++) { for (int i = 0; i < peekLength - 3; i++) {
if (buffer[i] == 'O' && buffer[i + 1] == 'g' && buffer[i + 2] == 'g' if (buffer[i] == 'O'
&& buffer[i + 1] == 'g'
&& buffer[i + 2] == 'g'
&& buffer[i + 3] == 'S') { && buffer[i + 3] == 'S') {
// Match! Skip to the start of the pattern. // Match! Skip to the start of the pattern.
input.skipFully(i); input.skipFully(i);
...@@ -295,13 +303,12 @@ import java.io.IOException; ...@@ -295,13 +303,12 @@ import java.io.IOException;
* total number of samples per channel. * total number of samples per channel.
* *
* @param input The {@link ExtractorInput} to read from. * @param input The {@link ExtractorInput} to read from.
* @return the total number of samples of this input. * @return The total number of samples of this input.
* @throws IOException thrown if reading from the input fails. * @throws IOException If reading from the input fails.
* @throws InterruptedException thrown if interrupted while reading from the input. * @throws InterruptedException If the thread is interrupted.
*/ */
//@VisibleForTesting // @VisibleForTesting
long readGranuleOfLastPage(ExtractorInput input) long readGranuleOfLastPage(ExtractorInput input) throws IOException, InterruptedException {
throws IOException, InterruptedException {
skipToNextPage(input); skipToNextPage(input);
pageHeader.reset(); pageHeader.reset();
while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < endPosition) { while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < endPosition) {
......
...@@ -51,11 +51,11 @@ import java.util.Arrays; ...@@ -51,11 +51,11 @@ import java.util.Arrays;
* can resume properly from an error while reading a continued packet spanned across multiple * can resume properly from an error while reading a continued packet spanned across multiple
* pages. * pages.
* *
* @param input the {@link ExtractorInput} to read data from. * @param input The {@link ExtractorInput} to read data from.
* @return {@code true} if the read was successful. {@code false} if the end of the input was * @return {@code true} if the read was successful. The read fails if the end of the input is
* encountered having read no data. * encountered without reading data.
* @throws IOException thrown if reading from the input fails. * @throws IOException If reading from the input fails.
* @throws InterruptedException thrown if interrupted while reading from input. * @throws InterruptedException If the thread is interrupted.
*/ */
public boolean populate(ExtractorInput input) throws IOException, InterruptedException { public boolean populate(ExtractorInput input) throws IOException, InterruptedException {
Assertions.checkState(input != null); Assertions.checkState(input != null);
......
...@@ -71,13 +71,13 @@ import java.io.IOException; ...@@ -71,13 +71,13 @@ import java.io.IOException;
/** /**
* Peeks an Ogg page header and updates this {@link OggPageHeader}. * Peeks an Ogg page header and updates this {@link OggPageHeader}.
* *
* @param input the {@link ExtractorInput} to read from. * @param input The {@link ExtractorInput} to read from.
* @param quiet if {@code true} no Exceptions are thrown but {@code false} is return if something * @param quiet If {@code true}, no exceptions are thrown but {@code false} is returned if
* goes wrong. * something goes wrong.
* @return {@code true} if the read was successful. {@code false} if the end of the input was * @return {@code true} if the read was successful. The read fails if the end of the input is
* encountered having read no data. * encountered without reading data.
* @throws IOException thrown if reading data fails or the stream is invalid. * @throws IOException If reading data fails or the stream is invalid.
* @throws InterruptedException thrown if thread is interrupted when reading/peeking. * @throws InterruptedException If the thread is interrupted.
*/ */
public boolean populate(ExtractorInput input, boolean quiet) public boolean populate(ExtractorInput input, boolean quiet)
throws IOException, InterruptedException { throws IOException, InterruptedException {
......
...@@ -20,7 +20,6 @@ import com.google.android.exoplayer2.Format; ...@@ -20,7 +20,6 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -67,8 +66,7 @@ import java.util.List; ...@@ -67,8 +66,7 @@ import java.util.List;
} }
@Override @Override
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) {
throws IOException, InterruptedException {
if (!headerRead) { if (!headerRead) {
byte[] metadata = Arrays.copyOf(packet.data, packet.limit()); byte[] metadata = Arrays.copyOf(packet.data, packet.limit());
int channelCount = metadata[9] & 0xFF; int channelCount = metadata[9] & 0xFF;
......
...@@ -144,9 +144,15 @@ import java.io.IOException; ...@@ -144,9 +144,15 @@ import java.io.IOException;
oggSeeker = new UnseekableOggSeeker(); oggSeeker = new UnseekableOggSeeker();
} else { } else {
OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader(); OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader();
oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this, boolean isLastPage = (firstPayloadPageHeader.type & 0x04) != 0; // Type 4 is end of stream.
oggSeeker =
new DefaultOggSeeker(
payloadStartPosition,
input.getLength(),
this,
firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize, firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize,
firstPayloadPageHeader.granulePosition); firstPayloadPageHeader.granulePosition,
isLastPage);
} }
setupData = null; setupData = null;
......
...@@ -35,7 +35,13 @@ public final class DefaultOggSeekerTest { ...@@ -35,7 +35,13 @@ public final class DefaultOggSeekerTest {
@Test @Test
public void testSetupWithUnsetEndPositionFails() { public void testSetupWithUnsetEndPositionFails() {
try { try {
new DefaultOggSeeker(0, C.LENGTH_UNSET, new TestStreamReader(), 1, 1); new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ C.LENGTH_UNSET,
/* streamReader= */ new TestStreamReader(),
/* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 1,
/* firstPayloadPageIsLastPage= */ false);
fail(); fail();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// ignored // ignored
...@@ -56,11 +62,12 @@ public final class DefaultOggSeekerTest { ...@@ -56,11 +62,12 @@ public final class DefaultOggSeekerTest {
TestStreamReader streamReader = new TestStreamReader(); TestStreamReader streamReader = new TestStreamReader();
DefaultOggSeeker oggSeeker = DefaultOggSeeker oggSeeker =
new DefaultOggSeeker( new DefaultOggSeeker(
0, /* startPosition= */ 0,
testFile.data.length, /* endPosition= */ testFile.data.length,
streamReader, /* streamReader= */ streamReader,
testFile.firstPayloadPageSize, /* firstPayloadPageSize= */ testFile.firstPayloadPageSize,
testFile.firstPayloadPageGranulePosition); /* firstPayloadPageGranulePosition= */ testFile.firstPayloadPageGranulePosition,
/* firstPayloadPageIsLastPage= */ false);
OggPageHeader pageHeader = new OggPageHeader(); OggPageHeader pageHeader = new OggPageHeader();
while (true) { while (true) {
......
...@@ -85,8 +85,14 @@ public final class DefaultOggSeekerUtilMethodsTest { ...@@ -85,8 +85,14 @@ public final class DefaultOggSeekerUtilMethodsTest {
private static void skipToNextPage(ExtractorInput extractorInput) private static void skipToNextPage(ExtractorInput extractorInput)
throws IOException, InterruptedException { throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, extractorInput.getLength(), DefaultOggSeeker oggSeeker =
new FlacReader(), 1, 2); new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ extractorInput.getLength(),
/* streamReader= */ new FlacReader(),
/* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false);
while (true) { while (true) {
try { try {
oggSeeker.skipToNextPage(extractorInput); oggSeeker.skipToNextPage(extractorInput);
...@@ -157,7 +163,14 @@ public final class DefaultOggSeekerUtilMethodsTest { ...@@ -157,7 +163,14 @@ public final class DefaultOggSeekerUtilMethodsTest {
private void skipToPageOfGranule(ExtractorInput input, long granule, private void skipToPageOfGranule(ExtractorInput input, long granule,
long elapsedSamplesExpected) throws IOException, InterruptedException { long elapsedSamplesExpected) throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader(), 1, 2); DefaultOggSeeker oggSeeker =
new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ input.getLength(),
/* streamReader= */ new FlacReader(),
/* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false);
while (true) { while (true) {
try { try {
assertThat(oggSeeker.skipToPageOfGranule(input, granule, -1)) assertThat(oggSeeker.skipToPageOfGranule(input, granule, -1))
...@@ -211,7 +224,14 @@ public final class DefaultOggSeekerUtilMethodsTest { ...@@ -211,7 +224,14 @@ public final class DefaultOggSeekerUtilMethodsTest {
private void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected) private void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected)
throws IOException, InterruptedException { throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader(), 1, 2); DefaultOggSeeker oggSeeker =
new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ input.getLength(),
/* streamReader= */ new FlacReader(),
/* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false);
while (true) { while (true) {
try { try {
assertThat(oggSeeker.readGranuleOfLastPage(input)).isEqualTo(expected); assertThat(oggSeeker.readGranuleOfLastPage(input)).isEqualTo(expected);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment