Fix DefaultOggSeeker seeking

- When in STATE_SEEK with targetGranule==0, seeking would exit
  without checking that the input was positioned at the correct
  place.
- Seeking could fail due to trying to read beyond the end of the
  stream.
- Seeking was not robust against IO errors during the skip phase
  that occurs after the binary search has sufficiently converged.

PiperOrigin-RevId: 261317035
This commit is contained in:
olly 2019-08-02 15:20:35 +01:00 committed by Oliver Woodman
parent 90ab05c574
commit 91c62ea26f
4 changed files with 129 additions and 252 deletions

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
@ -35,11 +36,12 @@ import java.io.IOException;
private static final int STATE_SEEK_TO_END = 0; private static final int STATE_SEEK_TO_END = 0;
private static final int STATE_READ_LAST_PAGE = 1; private static final int STATE_READ_LAST_PAGE = 1;
private static final int STATE_SEEK = 2; private static final int STATE_SEEK = 2;
private static final int STATE_IDLE = 3; private static final int STATE_SKIP = 3;
private static final int STATE_IDLE = 4;
private final OggPageHeader pageHeader = new OggPageHeader(); private final OggPageHeader pageHeader = new OggPageHeader();
private final long startPosition; private final long payloadStartPosition;
private final long endPosition; private final long payloadEndPosition;
private final StreamReader streamReader; private final StreamReader streamReader;
private int state; private int state;
@ -55,26 +57,27 @@ import java.io.IOException;
/** /**
* Constructs an OggSeeker. * Constructs an OggSeeker.
* *
* @param startPosition Start position of the payload (inclusive).
* @param endPosition End position of the payload (exclusive).
* @param streamReader The {@link StreamReader} that owns this seeker. * @param streamReader The {@link StreamReader} that owns this seeker.
* @param payloadStartPosition Start position of the payload (inclusive).
* @param payloadEndPosition End position of the payload (exclusive).
* @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 * @param firstPayloadPageIsLastPage Whether the first payload page is also the last page.
* ogg stream.
*/ */
public DefaultOggSeeker( public DefaultOggSeeker(
long startPosition,
long endPosition,
StreamReader streamReader, StreamReader streamReader,
long payloadStartPosition,
long payloadEndPosition,
long firstPayloadPageSize, long firstPayloadPageSize,
long firstPayloadPageGranulePosition, long firstPayloadPageGranulePosition,
boolean firstPayloadPageIsLastPage) { boolean firstPayloadPageIsLastPage) {
Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition); Assertions.checkArgument(
payloadStartPosition >= 0 && payloadEndPosition > payloadStartPosition);
this.streamReader = streamReader; this.streamReader = streamReader;
this.startPosition = startPosition; this.payloadStartPosition = payloadStartPosition;
this.endPosition = endPosition; this.payloadEndPosition = payloadEndPosition;
if (firstPayloadPageSize == endPosition - startPosition || firstPayloadPageIsLastPage) { if (firstPayloadPageSize == payloadEndPosition - payloadStartPosition
|| firstPayloadPageIsLastPage) {
totalGranules = firstPayloadPageGranulePosition; totalGranules = firstPayloadPageGranulePosition;
state = STATE_IDLE; state = STATE_IDLE;
} else { } else {
@ -91,7 +94,7 @@ import java.io.IOException;
positionBeforeSeekToEnd = input.getPosition(); positionBeforeSeekToEnd = input.getPosition();
state = STATE_READ_LAST_PAGE; state = STATE_READ_LAST_PAGE;
// Seek to the end just before the last page of stream to get the duration. // Seek to the end just before the last page of stream to get the duration.
long lastPageSearchPosition = endPosition - OggPageHeader.MAX_PAGE_SIZE; long lastPageSearchPosition = payloadEndPosition - OggPageHeader.MAX_PAGE_SIZE;
if (lastPageSearchPosition > positionBeforeSeekToEnd) { if (lastPageSearchPosition > positionBeforeSeekToEnd) {
return lastPageSearchPosition; return lastPageSearchPosition;
} }
@ -101,137 +104,110 @@ import java.io.IOException;
state = STATE_IDLE; state = STATE_IDLE;
return positionBeforeSeekToEnd; return positionBeforeSeekToEnd;
case STATE_SEEK: case STATE_SEEK:
long currentGranule; long position = getNextSeekPosition(input);
if (targetGranule == 0) { if (position != C.POSITION_UNSET) {
currentGranule = 0; return position;
} else {
long position = getNextSeekPosition(targetGranule, input);
if (position >= 0) {
return position;
}
currentGranule = skipToPageOfGranule(input, targetGranule, -(position + 2));
} }
state = STATE_SKIP;
// Fall through.
case STATE_SKIP:
skipToPageOfTargetGranule(input);
state = STATE_IDLE; state = STATE_IDLE;
return -(currentGranule + 2); return -(startGranule + 2);
default: default:
// Never happens. // Never happens.
throw new IllegalStateException(); throw new IllegalStateException();
} }
} }
@Override
public void startSeek(long targetGranule) {
Assertions.checkArgument(state == STATE_IDLE || state == STATE_SEEK);
this.targetGranule = targetGranule;
state = STATE_SEEK;
resetSeeking();
}
@Override @Override
public OggSeekMap createSeekMap() { public OggSeekMap createSeekMap() {
return totalGranules != 0 ? new OggSeekMap() : null; return totalGranules != 0 ? new OggSeekMap() : null;
} }
@VisibleForTesting @Override
public void resetSeeking() { public void startSeek(long targetGranule) {
start = startPosition; this.targetGranule = targetGranule;
end = endPosition; state = STATE_SEEK;
start = payloadStartPosition;
end = payloadEndPosition;
startGranule = 0; startGranule = 0;
endGranule = totalGranules; endGranule = totalGranules;
} }
/** /**
* Returns a position converging to the {@code targetGranule} to which the {@link ExtractorInput} * Performs a single step of a seeking binary search, returning the byte position from which data
* has to seek and then be passed for another call until a negative number is returned. If a * should be provided for the next step, or {@link C#POSITION_UNSET} if the search has converged.
* negative number is returned the input is at a position which is before the target page and at * If the search has converged then {@link #skipToPageOfTargetGranule(ExtractorInput)} should be
* which it is sensible to just skip pages to the target granule and pre-roll instead of doing * called to skip to the target page.
* another seek request.
* *
* @param targetGranule The target granule position to seek to.
* @param input The {@link ExtractorInput} to read from. * @param input The {@link ExtractorInput} to read from.
* @return The position to seek the {@link ExtractorInput} to for a next call or -(currentGranule * @return The byte position from which data should be provided for the next step, or {@link
* + 2) if it's close enough to skip to the target page. * C#POSITION_UNSET} if the search has converged.
* @throws IOException If reading from the input fails. * @throws IOException If reading from the input fails.
* @throws InterruptedException If interrupted while reading from the input. * @throws InterruptedException If interrupted while reading from the input.
*/ */
@VisibleForTesting private long getNextSeekPosition(ExtractorInput input) throws IOException, InterruptedException {
public long getNextSeekPosition(long targetGranule, ExtractorInput input)
throws IOException, InterruptedException {
if (start == end) { if (start == end) {
return -(startGranule + 2); return C.POSITION_UNSET;
} }
long initialPosition = input.getPosition(); long currentPosition = input.getPosition();
if (!skipToNextPage(input, end)) { if (!skipToNextPage(input, end)) {
if (start == initialPosition) { if (start == currentPosition) {
throw new IOException("No ogg page can be found."); throw new IOException("No ogg page can be found.");
} }
return start; return start;
} }
pageHeader.populate(input, false); pageHeader.populate(input, /* quiet= */ false);
input.resetPeekPosition(); input.resetPeekPosition();
long granuleDistance = targetGranule - pageHeader.granulePosition; long granuleDistance = targetGranule - pageHeader.granulePosition;
int pageSize = pageHeader.headerSize + pageHeader.bodySize; int pageSize = pageHeader.headerSize + pageHeader.bodySize;
if (granuleDistance < 0 || granuleDistance > MATCH_RANGE) { if (0 <= granuleDistance && granuleDistance < MATCH_RANGE) {
if (granuleDistance < 0) { return C.POSITION_UNSET;
end = initialPosition;
endGranule = pageHeader.granulePosition;
} else {
start = input.getPosition() + pageSize;
startGranule = pageHeader.granulePosition;
if (end - start + pageSize < MATCH_BYTE_RANGE) {
input.skipFully(pageSize);
return -(startGranule + 2);
}
}
if (end - start < MATCH_BYTE_RANGE) {
end = start;
return start;
}
long offset = pageSize * (granuleDistance <= 0 ? 2L : 1L);
long nextPosition = input.getPosition() - offset
+ (granuleDistance * (end - start) / (endGranule - startGranule));
nextPosition = Math.max(nextPosition, start);
nextPosition = Math.min(nextPosition, end - 1);
return nextPosition;
} }
// position accepted (before target granule and within MATCH_RANGE) if (granuleDistance < 0) {
input.skipFully(pageSize); end = currentPosition;
return -(pageHeader.granulePosition + 2); endGranule = pageHeader.granulePosition;
} else {
start = input.getPosition() + pageSize;
startGranule = pageHeader.granulePosition;
}
if (end - start < MATCH_BYTE_RANGE) {
end = start;
return start;
}
long offset = pageSize * (granuleDistance <= 0 ? 2L : 1L);
long nextPosition =
input.getPosition()
- offset
+ (granuleDistance * (end - start) / (endGranule - startGranule));
return Util.constrainValue(nextPosition, start, end - 1);
} }
/** /**
* Skips to the position of the start of the page containing the {@code targetGranule} and returns * Skips forward to the start of the page containing the {@code targetGranule}.
* the granule of the page previous to the target page.
* *
* @param input The {@link ExtractorInput} to read from. * @param input The {@link ExtractorInput} to read from.
* @param targetGranule The target granule.
* @param currentGranule The current granule or -1 if it's unknown.
* @return The granule of the prior page or the {@code currentGranule} if there isn't a prior
* page.
* @throws ParserException If populating the page header fails. * @throws ParserException If populating the page header fails.
* @throws IOException If reading from the input fails. * @throws IOException If reading from the input fails.
* @throws InterruptedException If interrupted while reading from the input. * @throws InterruptedException If interrupted while reading from the input.
*/ */
@VisibleForTesting private void skipToPageOfTargetGranule(ExtractorInput input)
long skipToPageOfGranule(ExtractorInput input, long targetGranule, long currentGranule)
throws IOException, InterruptedException { throws IOException, InterruptedException {
pageHeader.populate(input, false); pageHeader.populate(input, /* quiet= */ false);
while (pageHeader.granulePosition < targetGranule) { while (pageHeader.granulePosition < targetGranule) {
input.skipFully(pageHeader.headerSize + pageHeader.bodySize); input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
// Store in a member field to be able to resume after IOExceptions. start = input.getPosition();
currentGranule = pageHeader.granulePosition; startGranule = pageHeader.granulePosition;
// Peek next header. pageHeader.populate(input, /* quiet= */ false);
pageHeader.populate(input, false);
} }
input.resetPeekPosition(); input.resetPeekPosition();
return currentGranule;
} }
/** /**
@ -244,7 +220,7 @@ import java.io.IOException;
*/ */
@VisibleForTesting @VisibleForTesting
void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException { void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException {
if (!skipToNextPage(input, endPosition)) { if (!skipToNextPage(input, payloadEndPosition)) {
// Not found until eof. // Not found until eof.
throw new EOFException(); throw new EOFException();
} }
@ -261,7 +237,7 @@ import java.io.IOException;
*/ */
private boolean skipToNextPage(ExtractorInput input, long limit) private boolean skipToNextPage(ExtractorInput input, long limit)
throws IOException, InterruptedException { throws IOException, InterruptedException {
limit = Math.min(limit + 3, endPosition); limit = Math.min(limit + 3, payloadEndPosition);
byte[] buffer = new byte[2048]; byte[] buffer = new byte[2048];
int peekLength = buffer.length; int peekLength = buffer.length;
while (true) { while (true) {
@ -302,8 +278,8 @@ import java.io.IOException;
long readGranuleOfLastPage(ExtractorInput input) throws IOException, InterruptedException { long readGranuleOfLastPage(ExtractorInput input) 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() < payloadEndPosition) {
pageHeader.populate(input, false); pageHeader.populate(input, /* quiet= */ false);
input.skipFully(pageHeader.headerSize + pageHeader.bodySize); input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
} }
return pageHeader.granulePosition; return pageHeader.granulePosition;
@ -320,10 +296,11 @@ import java.io.IOException;
public SeekPoints getSeekPoints(long timeUs) { public SeekPoints getSeekPoints(long timeUs) {
long targetGranule = streamReader.convertTimeToGranule(timeUs); long targetGranule = streamReader.convertTimeToGranule(timeUs);
long estimatedPosition = long estimatedPosition =
startPosition payloadStartPosition
+ (targetGranule * (endPosition - startPosition) / totalGranules) + (targetGranule * (payloadEndPosition - payloadStartPosition) / totalGranules)
- DEFAULT_OFFSET; - DEFAULT_OFFSET;
estimatedPosition = Util.constrainValue(estimatedPosition, startPosition, endPosition - 1); estimatedPosition =
Util.constrainValue(estimatedPosition, payloadStartPosition, payloadEndPosition - 1);
return new SeekPoints(new SeekPoint(timeUs, estimatedPosition)); return new SeekPoints(new SeekPoint(timeUs, estimatedPosition));
} }

View File

@ -148,9 +148,9 @@ import java.io.IOException;
boolean isLastPage = (firstPayloadPageHeader.type & 0x04) != 0; // Type 4 is end of stream. boolean isLastPage = (firstPayloadPageHeader.type & 0x04) != 0; // Type 4 is end of stream.
oggSeeker = oggSeeker =
new DefaultOggSeeker( new DefaultOggSeeker(
this,
payloadStartPosition, payloadStartPosition,
input.getLength(), input.getLength(),
this,
firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize, firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize,
firstPayloadPageHeader.granulePosition, firstPayloadPageHeader.granulePosition,
isLastPage); isLastPage);

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -36,9 +35,9 @@ public final class DefaultOggSeekerTest {
public void testSetupWithUnsetEndPositionFails() { public void testSetupWithUnsetEndPositionFails() {
try { try {
new DefaultOggSeeker( new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ C.LENGTH_UNSET,
/* streamReader= */ new TestStreamReader(), /* streamReader= */ new TestStreamReader(),
/* payloadStartPosition= */ 0,
/* payloadEndPosition= */ C.LENGTH_UNSET,
/* firstPayloadPageSize= */ 1, /* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 1, /* firstPayloadPageGranulePosition= */ 1,
/* firstPayloadPageIsLastPage= */ false); /* firstPayloadPageIsLastPage= */ false);
@ -62,9 +61,9 @@ public final class DefaultOggSeekerTest {
TestStreamReader streamReader = new TestStreamReader(); TestStreamReader streamReader = new TestStreamReader();
DefaultOggSeeker oggSeeker = DefaultOggSeeker oggSeeker =
new DefaultOggSeeker( new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ testFile.data.length,
/* streamReader= */ streamReader, /* streamReader= */ streamReader,
/* payloadStartPosition= */ 0,
/* payloadEndPosition= */ testFile.data.length,
/* firstPayloadPageSize= */ testFile.firstPayloadPageSize, /* firstPayloadPageSize= */ testFile.firstPayloadPageSize,
/* firstPayloadPageGranulePosition= */ testFile.firstPayloadPageGranulePosition, /* firstPayloadPageGranulePosition= */ testFile.firstPayloadPageGranulePosition,
/* firstPayloadPageIsLastPage= */ false); /* firstPayloadPageIsLastPage= */ false);
@ -78,70 +77,56 @@ public final class DefaultOggSeekerTest {
input.setPosition((int) nextSeekPosition); input.setPosition((int) nextSeekPosition);
} }
// Test granule 0 from file start // Test granule 0 from file start.
assertThat(seekTo(input, oggSeeker, 0, 0)).isEqualTo(0); long granule = seekTo(input, oggSeeker, 0, 0);
assertThat(granule).isEqualTo(0);
assertThat(input.getPosition()).isEqualTo(0); assertThat(input.getPosition()).isEqualTo(0);
// Test granule 0 from file end // Test granule 0 from file end.
assertThat(seekTo(input, oggSeeker, 0, testFile.data.length - 1)).isEqualTo(0); granule = seekTo(input, oggSeeker, 0, testFile.data.length - 1);
assertThat(granule).isEqualTo(0);
assertThat(input.getPosition()).isEqualTo(0); assertThat(input.getPosition()).isEqualTo(0);
{ // Test last granule // Test last granule.
long currentGranule = seekTo(input, oggSeeker, testFile.lastGranule, 0); granule = seekTo(input, oggSeeker, testFile.lastGranule, 0);
long position = testFile.data.length; long position = testFile.data.length;
assertThat( // TODO: Simplify this.
(testFile.lastGranule > currentGranule && position > input.getPosition()) assertThat(
|| (testFile.lastGranule == currentGranule && position == input.getPosition())) (testFile.lastGranule > granule && position > input.getPosition())
.isTrue(); || (testFile.lastGranule == granule && position == input.getPosition()))
} .isTrue();
{ // Test exact granule // Test exact granule.
input.setPosition(testFile.data.length / 2); input.setPosition(testFile.data.length / 2);
oggSeeker.skipToNextPage(input); oggSeeker.skipToNextPage(input);
assertThat(pageHeader.populate(input, true)).isTrue(); assertThat(pageHeader.populate(input, true)).isTrue();
long position = input.getPosition() + pageHeader.headerSize + pageHeader.bodySize; position = input.getPosition() + pageHeader.headerSize + pageHeader.bodySize;
long currentGranule = seekTo(input, oggSeeker, pageHeader.granulePosition, 0); granule = seekTo(input, oggSeeker, pageHeader.granulePosition, 0);
assertThat( // TODO: Simplify this.
(pageHeader.granulePosition > currentGranule && position > input.getPosition()) assertThat(
|| (pageHeader.granulePosition == currentGranule (pageHeader.granulePosition > granule && position > input.getPosition())
&& position == input.getPosition())) || (pageHeader.granulePosition == granule && position == input.getPosition()))
.isTrue(); .isTrue();
}
for (int i = 0; i < 100; i += 1) { for (int i = 0; i < 100; i += 1) {
long targetGranule = (long) (random.nextDouble() * testFile.lastGranule); long targetGranule = (long) (random.nextDouble() * testFile.lastGranule);
int initialPosition = random.nextInt(testFile.data.length); int initialPosition = random.nextInt(testFile.data.length);
granule = seekTo(input, oggSeeker, targetGranule, initialPosition);
long currentGranule = seekTo(input, oggSeeker, targetGranule, initialPosition);
long currentPosition = input.getPosition(); long currentPosition = input.getPosition();
if (granule == 0) {
assertWithMessage("getNextSeekPosition() didn't leave input on a page start.")
.that(pageHeader.populate(input, true))
.isTrue();
if (currentGranule == 0) {
assertThat(currentPosition).isEqualTo(0); assertThat(currentPosition).isEqualTo(0);
} else { } else {
int previousPageStart = testFile.findPreviousPageStart(currentPosition); int previousPageStart = testFile.findPreviousPageStart(currentPosition);
input.setPosition(previousPageStart); input.setPosition(previousPageStart);
assertThat(pageHeader.populate(input, true)).isTrue(); pageHeader.populate(input, false);
assertThat(currentGranule).isEqualTo(pageHeader.granulePosition); assertThat(granule).isEqualTo(pageHeader.granulePosition);
} }
input.setPosition((int) currentPosition); input.setPosition((int) currentPosition);
oggSeeker.skipToPageOfGranule(input, targetGranule, -1); pageHeader.populate(input, false);
long positionDiff = Math.abs(input.getPosition() - currentPosition); // The target granule should be within the current page.
assertThat(granule).isAtMost(targetGranule);
long granuleDiff = currentGranule - targetGranule; assertThat(targetGranule).isLessThan(pageHeader.granulePosition);
if ((granuleDiff > DefaultOggSeeker.MATCH_RANGE || granuleDiff < 0)
&& positionDiff > DefaultOggSeeker.MATCH_BYTE_RANGE) {
fail(
"granuleDiff ("
+ granuleDiff
+ ") or positionDiff ("
+ positionDiff
+ ") is more than allowed.");
}
} }
} }
@ -149,18 +134,15 @@ public final class DefaultOggSeekerTest {
FakeExtractorInput input, DefaultOggSeeker oggSeeker, long targetGranule, int initialPosition) FakeExtractorInput input, DefaultOggSeeker oggSeeker, long targetGranule, int initialPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
long nextSeekPosition = initialPosition; long nextSeekPosition = initialPosition;
oggSeeker.startSeek(targetGranule);
int count = 0; int count = 0;
oggSeeker.resetSeeking(); while (nextSeekPosition >= 0) {
do {
input.setPosition((int) nextSeekPosition);
nextSeekPosition = oggSeeker.getNextSeekPosition(targetGranule, input);
if (count++ > 100) { if (count++ > 100) {
fail("infinite loop?"); fail("Seek failed to converge in 100 iterations");
} }
} while (nextSeekPosition >= 0); input.setPosition((int) nextSeekPosition);
nextSeekPosition = oggSeeker.read(input);
}
return -(nextSeekPosition + 2); return -(nextSeekPosition + 2);
} }
@ -171,8 +153,7 @@ public final class DefaultOggSeekerTest {
} }
@Override @Override
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) {
throws IOException, InterruptedException {
return false; return false;
} }
} }

View File

@ -85,9 +85,9 @@ public final class DefaultOggSeekerUtilMethodsTest {
throws IOException, InterruptedException { throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = DefaultOggSeeker oggSeeker =
new DefaultOggSeeker( new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ extractorInput.getLength(),
/* streamReader= */ new FlacReader(), /* streamReader= */ new FlacReader(),
/* payloadStartPosition= */ 0,
/* payloadEndPosition= */ extractorInput.getLength(),
/* firstPayloadPageSize= */ 1, /* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2, /* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false); /* firstPayloadPageIsLastPage= */ false);
@ -99,87 +99,6 @@ public final class DefaultOggSeekerUtilMethodsTest {
} }
} }
@Test
public void testSkipToPageOfGranule() throws IOException, InterruptedException {
byte[] packet = TestUtil.buildTestData(3 * 254, random);
byte[] data = TestUtil.joinByteArrays(
OggTestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
OggTestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
OggTestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
// expect to be granule of the previous page returned as elapsedSamples
skipToPageOfGranule(input, 54000, 40000);
// expect to be at the start of the third page
assertThat(input.getPosition()).isEqualTo(2 * (30 + (3 * 254)));
}
@Test
public void testSkipToPageOfGranulePreciseMatch() throws IOException, InterruptedException {
byte[] packet = TestUtil.buildTestData(3 * 254, random);
byte[] data = TestUtil.joinByteArrays(
OggTestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
OggTestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
OggTestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
skipToPageOfGranule(input, 40000, 20000);
// expect to be at the start of the second page
assertThat(input.getPosition()).isEqualTo(30 + (3 * 254));
}
@Test
public void testSkipToPageOfGranuleAfterTargetPage() throws IOException, InterruptedException {
byte[] packet = TestUtil.buildTestData(3 * 254, random);
byte[] data = TestUtil.joinByteArrays(
OggTestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
OggTestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
OggTestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
skipToPageOfGranule(input, 10000, -1);
assertThat(input.getPosition()).isEqualTo(0);
}
private void skipToPageOfGranule(ExtractorInput input, long granule,
long elapsedSamplesExpected) throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker =
new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ input.getLength(),
/* streamReader= */ new FlacReader(),
/* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false);
while (true) {
try {
assertThat(oggSeeker.skipToPageOfGranule(input, granule, -1))
.isEqualTo(elapsedSamplesExpected);
return;
} catch (FakeExtractorInput.SimulatedIOException e) {
input.resetPeekPosition();
}
}
}
@Test @Test
public void testReadGranuleOfLastPage() throws IOException, InterruptedException { public void testReadGranuleOfLastPage() throws IOException, InterruptedException {
FakeExtractorInput input = OggTestData.createInput(TestUtil.joinByteArrays( FakeExtractorInput input = OggTestData.createInput(TestUtil.joinByteArrays(
@ -204,7 +123,7 @@ public final class DefaultOggSeekerUtilMethodsTest {
assertReadGranuleOfLastPage(input, 60000); assertReadGranuleOfLastPage(input, 60000);
fail(); fail();
} catch (EOFException e) { } catch (EOFException e) {
// ignored // Ignored.
} }
} }
@ -216,7 +135,7 @@ public final class DefaultOggSeekerUtilMethodsTest {
assertReadGranuleOfLastPage(input, 60000); assertReadGranuleOfLastPage(input, 60000);
fail(); fail();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// ignored // Ignored.
} }
} }
@ -224,9 +143,9 @@ public final class DefaultOggSeekerUtilMethodsTest {
throws IOException, InterruptedException { throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = DefaultOggSeeker oggSeeker =
new DefaultOggSeeker( new DefaultOggSeeker(
/* startPosition= */ 0,
/* endPosition= */ input.getLength(),
/* streamReader= */ new FlacReader(), /* streamReader= */ new FlacReader(),
/* payloadStartPosition= */ 0,
/* payloadEndPosition= */ input.getLength(),
/* firstPayloadPageSize= */ 1, /* firstPayloadPageSize= */ 1,
/* firstPayloadPageGranulePosition= */ 2, /* firstPayloadPageGranulePosition= */ 2,
/* firstPayloadPageIsLastPage= */ false); /* firstPayloadPageIsLastPage= */ false);
@ -235,7 +154,7 @@ public final class DefaultOggSeekerUtilMethodsTest {
assertThat(oggSeeker.readGranuleOfLastPage(input)).isEqualTo(expected); assertThat(oggSeeker.readGranuleOfLastPage(input)).isEqualTo(expected);
break; break;
} catch (FakeExtractorInput.SimulatedIOException e) { } catch (FakeExtractorInput.SimulatedIOException e) {
// ignored // Ignored.
} }
} }
} }