Commit b6755c14 by eguven Committed by Oliver Woodman

DefaultOggSeeker loop fix.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123191416
parent 9609302b
......@@ -17,92 +17,134 @@ package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.testutil.FakeExtractorInput;
import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.util.ParsableByteArray;
import junit.framework.TestCase;
import java.io.IOException;
import java.util.Random;
/**
* Unit test for {@link DefaultOggSeeker}.
*/
public final class DefaultOggSeekerTest extends TestCase {
private static final long HEADER_GRANULE = 200000;
private static final int START_POSITION = 0;
private static final int END_POSITION = 1000000;
private static final int TOTAL_SAMPLES = END_POSITION - START_POSITION;
private DefaultOggSeeker oggSeeker;
@Override
public void setUp() throws Exception {
super.setUp();
oggSeeker = DefaultOggSeeker.createOggSeekerForTesting(START_POSITION, END_POSITION,
TOTAL_SAMPLES);
}
public void testSetupUnboundAudioLength() {
try {
new DefaultOggSeeker(0, C.LENGTH_UNBOUNDED, new FlacReader());
new DefaultOggSeeker(0, C.LENGTH_UNBOUNDED, new TestStreamReader());
fail();
} catch (IllegalArgumentException e) {
// ignored
}
}
public void testGetNextSeekPositionMatch() throws IOException, InterruptedException {
assertGetNextSeekPosition(HEADER_GRANULE + DefaultOggSeeker.MATCH_RANGE);
assertGetNextSeekPosition(HEADER_GRANULE + 1);
public void testSeeking() throws IOException, InterruptedException {
Random random = new Random(0);
for (int i = 0; i < 100; i++) {
testSeeking(random);
}
}
public void testGetNextSeekPositionTooHigh() throws IOException, InterruptedException {
assertGetNextSeekPosition(HEADER_GRANULE - 100000);
assertGetNextSeekPosition(HEADER_GRANULE);
}
public void testSeeking(Random random) throws IOException, InterruptedException {
OggTestFile testFile = OggTestFile.generate(random, 1000);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(testFile.data).build();
TestStreamReader streamReader = new TestStreamReader();
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, testFile.data.length, streamReader);
OggPageHeader pageHeader = new OggPageHeader();
public void testGetNextSeekPositionTooLow() throws IOException, InterruptedException {
assertGetNextSeekPosition(HEADER_GRANULE + DefaultOggSeeker.MATCH_RANGE + 1);
assertGetNextSeekPosition(HEADER_GRANULE + 100000);
}
while (true) {
long nextSeekPosition = oggSeeker.read(input);
if (nextSeekPosition == -1) {
break;
}
input.setPosition((int) nextSeekPosition);
}
public void testGetNextSeekPositionBounds() throws IOException, InterruptedException {
assertGetNextSeekPosition(HEADER_GRANULE + TOTAL_SAMPLES);
assertGetNextSeekPosition(HEADER_GRANULE - TOTAL_SAMPLES);
}
// Test granule 0 from file start
assertEquals(0, seekTo(input, oggSeeker, 0, 0));
assertEquals(0, input.getPosition());
// Test granule 0 from file end
assertEquals(0, seekTo(input, oggSeeker, 0, testFile.data.length - 1));
assertEquals(0, input.getPosition());
private void assertGetNextSeekPosition(long targetGranule)
throws IOException, InterruptedException {
int pagePosition = 500000;
FakeExtractorInput input = TestData.createInput(TestUtil.joinByteArrays(
new byte[pagePosition],
TestData.buildOggHeader(0x00, HEADER_GRANULE, 22, 2),
TestUtil.createByteArray(54, 55) // laces
), false);
input.setPosition(pagePosition);
long granuleDiff = targetGranule - HEADER_GRANULE;
long expectedPosition;
if (granuleDiff > 0 && granuleDiff <= DefaultOggSeeker.MATCH_RANGE) {
expectedPosition = -1;
} else {
long doublePageSize = (27 + 2 + 54 + 55) * (granuleDiff <= 0 ? 2 : 1);
expectedPosition = pagePosition - doublePageSize + granuleDiff;
expectedPosition = Math.max(expectedPosition, START_POSITION);
expectedPosition = Math.min(expectedPosition, END_POSITION - 1);
{ // Test last granule
long currentGranule = seekTo(input, oggSeeker, testFile.lastGranule, 0);
long position = testFile.data.length;
assertTrue((testFile.lastGranule > currentGranule && position > input.getPosition())
|| (testFile.lastGranule == currentGranule && position == input.getPosition()));
}
assertGetNextSeekPosition(expectedPosition, targetGranule, input);
}
private void assertGetNextSeekPosition(long expectedPosition, long targetGranule,
FakeExtractorInput input) throws IOException, InterruptedException {
while (true) {
try {
assertEquals(expectedPosition, oggSeeker.getNextSeekPosition(targetGranule, input));
break;
} catch (FakeExtractorInput.SimulatedIOException e) {
// ignored
{ // Test exact granule
input.setPosition(testFile.data.length / 2);
oggSeeker.skipToNextPage(input);
assertTrue(pageHeader.populate(input, true));
long position = input.getPosition() + pageHeader.headerSize + pageHeader.bodySize;
long currentGranule = seekTo(input, oggSeeker, pageHeader.granulePosition, 0);
assertTrue((pageHeader.granulePosition > currentGranule && position > input.getPosition())
|| (pageHeader.granulePosition == currentGranule && position == input.getPosition()));
}
for (int i = 0; i < 100; i += 1) {
long targetGranule = (long) (random.nextDouble() * testFile.lastGranule);
int initialPosition = random.nextInt(testFile.data.length);
long currentGranule = seekTo(input, oggSeeker, targetGranule, initialPosition);
long currentPosition = input.getPosition();
assertTrue("getNextSeekPosition() didn't leave input on a page start.",
pageHeader.populate(input, true));
if (currentGranule == 0) {
assertEquals(0, currentPosition);
} else {
int previousPageStart = testFile.findPreviousPageStart(currentPosition);
input.setPosition(previousPageStart);
assertTrue(pageHeader.populate(input, true));
assertEquals(pageHeader.granulePosition, currentGranule);
}
input.setPosition((int) currentPosition);
oggSeeker.skipToPageOfGranule(input, targetGranule, -1);
long positionDiff = Math.abs(input.getPosition() - currentPosition);
long granuleDiff = currentGranule - targetGranule;
if ((granuleDiff > DefaultOggSeeker.MATCH_RANGE || granuleDiff < 0)
&& positionDiff > DefaultOggSeeker.MATCH_BYTE_RANGE) {
fail(String.format("granuleDiff (%d) or positionDiff (%d) is more than allowed.",
granuleDiff, positionDiff));
}
}
}
private long seekTo(FakeExtractorInput input, DefaultOggSeeker oggSeeker, long targetGranule,
int initialPosition) throws IOException, InterruptedException {
long nextSeekPosition = initialPosition;
int count = 0;
oggSeeker.resetSeeking();
do {
input.setPosition((int) nextSeekPosition);
nextSeekPosition = oggSeeker.getNextSeekPosition(targetGranule, input);
if (count++ > 100) {
fail("infinite loop?");
}
} while (nextSeekPosition >= 0);
return -(nextSeekPosition + 2);
}
private static class TestStreamReader extends StreamReader {
@Override
protected long preparePayload(ParsableByteArray packet) {
return 0;
}
@Override
protected boolean readHeaders(ParsableByteArray packet, long position,
SetupData setupData) throws IOException, InterruptedException {
return false;
}
}
}
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.testutil.FakeExtractorInput;
import com.google.android.exoplayer.testutil.TestUtil;
......@@ -32,7 +31,6 @@ import java.util.Random;
public class DefaultOggSeekerUtilMethodsTest extends TestCase {
private Random random = new Random(0);
private DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, 100, new FlacReader());
public void testSkipToNextPage() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
......@@ -45,17 +43,6 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
assertEquals(4000, extractorInput.getPosition());
}
public void testSkipToNextPageUnbounded() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
TestUtil.buildTestData(4000, random),
new byte[]{'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)
), true);
skipToNextPage(extractorInput);
assertEquals(4000, extractorInput.getPosition());
}
public void testSkipToNextPageOverlap() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
......@@ -67,17 +54,6 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
assertEquals(2046, extractorInput.getPosition());
}
public void testSkipToNextPageOverlapUnbounded() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
TestUtil.buildTestData(2046, random),
new byte[]{'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)
), true);
skipToNextPage(extractorInput);
assertEquals(2046, extractorInput.getPosition());
}
public void testSkipToNextPageInputShorterThanPeekLength() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
......@@ -100,9 +76,11 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
private static void skipToNextPage(ExtractorInput extractorInput)
throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, extractorInput.getLength(),
new FlacReader());
while (true) {
try {
DefaultOggSeeker.skipToNextPage(extractorInput);
oggSeeker.skipToNextPage(extractorInput);
break;
} catch (FakeExtractorInput.SimulatedIOException e) { /* ignored */ }
}
......@@ -110,17 +88,17 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToPageOfGranule() throws IOException, InterruptedException {
byte[] packet = TestUtil.buildTestData(3 * 254, random);
FakeExtractorInput input = TestData.createInput(
TestUtil.joinByteArrays(
TestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet), false);
byte[] data = TestUtil.joinByteArrays(
TestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.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);
......@@ -130,17 +108,17 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToPageOfGranulePreciseMatch() throws IOException, InterruptedException {
byte[] packet = TestUtil.buildTestData(3 * 254, random);
FakeExtractorInput input = TestData.createInput(
TestUtil.joinByteArrays(
TestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet), false);
byte[] data = TestUtil.joinByteArrays(
TestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.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
......@@ -149,32 +127,28 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToPageOfGranuleAfterTargetPage() throws IOException, InterruptedException {
byte[] packet = TestUtil.buildTestData(3 * 254, random);
FakeExtractorInput input = TestData.createInput(
TestUtil.joinByteArrays(
TestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet), false);
try {
skipToPageOfGranule(input, 10000, 20000);
fail();
} catch (ParserException e) {
// ignored
}
byte[] data = TestUtil.joinByteArrays(
TestData.buildOggHeader(0x01, 20000, 1000, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 40000, 1001, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet,
TestData.buildOggHeader(0x04, 60000, 1002, 0x03),
TestUtil.createByteArray(254, 254, 254), // Laces.
packet);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
skipToPageOfGranule(input, 10000, -1);
assertEquals(0, input.getPosition());
}
private void skipToPageOfGranule(ExtractorInput input, long granule,
long elapsedSamplesExpected) throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader());
while (true) {
try {
assertEquals(elapsedSamplesExpected, oggSeeker.skipToPageOfGranule(input, granule));
assertEquals(elapsedSamplesExpected, oggSeeker.skipToPageOfGranule(input, granule, -1));
return;
} catch (FakeExtractorInput.SimulatedIOException e) {
input.resetPeekPosition();
......@@ -221,6 +195,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
private void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected)
throws IOException, InterruptedException {
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader());
while (true) {
try {
assertEquals(expected, oggSeeker.readGranuleOfLastPage(input));
......
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.testutil.TestUtil;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Random;
/**
* Generates test data.
*/
class OggTestFile {
public static final int MAX_PACKET_LENGTH = 2048;
public static final int MAX_SEGMENT_COUNT = 10;
public static final int MAX_GRANULES_IN_PAGE = 100000;
byte[] data;
long lastGranule;
int packetCount;
int pageCount;
private OggTestFile(byte[] data, long lastGranule, int packetCount, int pageCount) {
this.data = data;
this.lastGranule = lastGranule;
this.packetCount = packetCount;
this.pageCount = pageCount;
}
static OggTestFile generate(Random random, int pageCount) {
ArrayList<byte[]> fileData = new ArrayList<>();
int fileSize = 0;
long granule = 0;
int packetLength = -1;
int packetCount = 0;
for (int i = 0; i < pageCount; i++) {
int headerType = 0x00;
if (packetLength >= 0) {
headerType |= 1;
}
if (i == 0) {
headerType |= 2;
}
if (i == pageCount - 1) {
headerType |= 4;
}
granule += random.nextInt(MAX_GRANULES_IN_PAGE - 1) + 1;
int pageSegmentCount = random.nextInt(MAX_SEGMENT_COUNT);
byte[] header = TestData.buildOggHeader(headerType, granule, 0, pageSegmentCount);
fileData.add(header);
fileSize += header.length;
byte[] laces = new byte[pageSegmentCount];
int bodySize = 0;
for (int j = 0; j < pageSegmentCount; j++) {
if (packetLength < 0) {
packetCount++;
if (i < pageCount - 1) {
packetLength = random.nextInt(MAX_PACKET_LENGTH);
} else {
int maxPacketLength = 255 * (pageSegmentCount - j) - 1;
packetLength = random.nextInt(maxPacketLength);
}
} else if (i == pageCount - 1 && j == pageSegmentCount - 1) {
packetLength = Math.min(packetLength, 254);
}
laces[j] = (byte) Math.min(packetLength, 255);
bodySize += laces[j] & 0xFF;
packetLength -= 255;
}
fileData.add(laces);
fileSize += laces.length;
byte[] payload = TestUtil.buildTestData(bodySize, random);
fileData.add(payload);
fileSize += payload.length;
}
byte[] file = new byte[fileSize];
int position = 0;
for (byte[] data : fileData) {
System.arraycopy(data, 0, file, position, data.length);
position += data.length;
}
return new OggTestFile(file, granule, packetCount, pageCount);
}
int findPreviousPageStart(long position) {
for (int i = (int) (position - 4); i >= 0; i--) {
if (data[i] == 'O' && data[i + 1] == 'g' && data[i + 2] == 'g' && data[i + 3] == 'S') {
return i;
}
}
Assert.fail();
return -1;
}
}
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.SeekMap;
......@@ -29,15 +28,17 @@ import java.io.IOException;
*/
/* package */ final class DefaultOggSeeker implements OggSeeker {
//@VisibleForTesting
public static final int MATCH_RANGE = 72000;
//@VisibleForTesting
public static final int MATCH_BYTE_RANGE = 100000;
private static final int DEFAULT_OFFSET = 30000;
private static final int STATE_SEEK_TO_END = 0;
private static final int STATE_READ_LAST_PAGE = 1;
private static final int STATE_SEEK = 2;
private static final int STATE_IDLE = 3;
//@VisibleForTesting
public static final int MATCH_RANGE = 72000;
private static final int DEFAULT_OFFSET = 30000;
private final OggPageHeader pageHeader = new OggPageHeader();
private final long startPosition;
private final long endPosition;
......@@ -48,28 +49,23 @@ import java.io.IOException;
private volatile long queriedGranule;
private long positionBeforeSeekToEnd;
private long targetGranule;
private long elapsedSamples;
public static DefaultOggSeeker createOggSeekerForTesting(long startPosition, long endPosition,
long totalGranules) {
Assertions.checkArgument(totalGranules > 0);
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(startPosition, endPosition, null);
oggSeeker.totalGranules = totalGranules;
return oggSeeker;
}
private long start;
private long end;
private long startGranule;
private long endGranule;
/**
* Constructs an OggSeeker.
* @param startPosition Start position of the payload.
* @param endPosition End position of the payload.
* @param startPosition Start position of the payload (inclusive).
* @param endPosition End position of the payload (exclusive).
* @param streamReader StreamReader instance which owns this OggSeeker
*/
public DefaultOggSeeker(long startPosition, long endPosition, StreamReader streamReader) {
this.streamReader = streamReader;
Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition);
this.streamReader = streamReader;
this.startPosition = startPosition;
this.endPosition = endPosition;
this.queriedGranule = 0;
this.state = STATE_SEEK_TO_END;
}
......@@ -83,9 +79,9 @@ import java.io.IOException;
positionBeforeSeekToEnd = input.getPosition();
state = STATE_READ_LAST_PAGE;
// seek to the end just before the last page of stream to get the duration
long lastPagePosition = input.getLength() - OggPageHeader.MAX_PAGE_SIZE;
if (lastPagePosition > 0) {
return Math.max(lastPagePosition, 0);
long lastPagePosition = endPosition - OggPageHeader.MAX_PAGE_SIZE;
if (lastPagePosition > positionBeforeSeekToEnd) {
return lastPagePosition;
}
// fall through
......@@ -100,14 +96,13 @@ import java.io.IOException;
currentGranule = 0;
} else {
long position = getNextSeekPosition(targetGranule, input);
if (position != -1) {
if (position >= 0) {
return position;
} else {
currentGranule = skipToPageOfGranule(input, targetGranule);
}
currentGranule = skipToPageOfGranule(input, targetGranule, -(position + 2));
}
state = STATE_IDLE;
return -currentGranule - 2;
return -(currentGranule + 2);
default:
// Never happens.
......@@ -120,6 +115,7 @@ import java.io.IOException;
Assertions.checkArgument(state == STATE_IDLE || state == STATE_SEEK);
targetGranule = queriedGranule;
state = STATE_SEEK;
resetSeeking();
return targetGranule;
}
......@@ -128,39 +124,78 @@ import java.io.IOException;
return totalGranules != 0 ? new OggSeekMap() : null;
}
//@VisibleForTesting
public void resetSeeking() {
start = startPosition;
end = endPosition;
startGranule = 0;
endGranule = totalGranules;
}
/**
* Returns a position converging to the {@code targetGranule} to which the {@link ExtractorInput}
* has to seek and then be passed for another call until -1 is return. If -1 is returned the
* input is at a position which is before the start of the page before the target page and at
* has to seek and then be passed for another call until a negative number is returned. If a
* negative number is returned the input is at a position which is before the target page and at
* which it is sensible to just skip pages to the target granule and pre-roll instead of doing
* another seek request.
*
* @param targetGranule the target granule position to seek to.
* @param input the {@link ExtractorInput} to read from.
* @return the position to seek the {@link ExtractorInput} to for a next call or -1 if it's close
* enough to skip to the target page.
* @return the position to seek the {@link ExtractorInput} to for a next call or
* -(currentGranule + 2) if it's close enough to skip to the target page.
* @throws IOException thrown if reading from the input fails.
* @throws InterruptedException thrown if interrupted while reading from the input.
*/
//@VisibleForTesting
public long getNextSeekPosition(long targetGranule, ExtractorInput input)
throws IOException, InterruptedException {
long previousPosition = input.getPosition();
skipToNextPage(input);
if (start == end) {
return -(startGranule + 2);
}
long initialPosition = input.getPosition();
if (!skipToNextPage(input, end)) {
if (start == initialPosition) {
throw new IOException("No ogg page can be found.");
}
return start;
}
pageHeader.populate(input, false);
input.resetPeekPosition();
long granuleDistance = targetGranule - pageHeader.granulePosition;
if (granuleDistance <= 0 || granuleDistance > MATCH_RANGE) {
// estimated position too high or too low
long offset = (pageHeader.bodySize + pageHeader.headerSize)
* (granuleDistance <= 0 ? 2 : 1);
long estimatedPosition = getEstimatedPosition(input.getPosition(), granuleDistance, offset);
if (estimatedPosition != previousPosition) { // Temporary prevention for simple loops
return estimatedPosition;
int pageSize = pageHeader.headerSize + pageHeader.bodySize;
if (granuleDistance < 0 || granuleDistance > MATCH_RANGE) {
if (granuleDistance < 0) {
end = initialPosition;
endGranule = pageHeader.granulePosition;
} else {
start = input.getPosition() + pageSize;
startGranule = pageHeader.granulePosition;
if (end - start + pageSize < MATCH_BYTE_RANGE) {
input.skip(pageSize);
return -(startGranule + 2);
}
}
if (end - start < MATCH_BYTE_RANGE) {
end = start;
return start;
}
long offset = pageSize * (granuleDistance <= 0 ? 2 : 1);
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 (below target granule and within MATCH_RANGE)
input.resetPeekPosition();
return -1;
// position accepted (before target granule and within MATCH_RANGE)
input.skip(pageSize);
return -(pageHeader.granulePosition + 2);
}
private long getEstimatedPosition(long position, long granuleDistance, long offset) {
......@@ -204,21 +239,38 @@ import java.io.IOException;
* @param input The {@code ExtractorInput} to skip to the next page.
* @throws IOException thrown if peeking/reading from the input fails.
* @throws InterruptedException thrown if interrupted while peeking/reading from the input.
* @throws EOFException if the next page can't be found before the end of the input.
*/
//@VisibleForTesting
static void skipToNextPage(ExtractorInput input)
throws IOException, InterruptedException {
void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException {
if (!skipToNextPage(input, endPosition)) {
// Not found until eof.
throw new EOFException();
}
}
/**
* Skips to the next page. Searches for the next page header.
*
* @param input The {@code ExtractorInput} to skip to the next page.
* @param until Searches until this position.
* @return true if the next page is found.
* @throws IOException thrown if peeking/reading from the input fails.
* @throws InterruptedException thrown if interrupted while peeking/reading from the input.
*/
//@VisibleForTesting
boolean skipToNextPage(ExtractorInput input, long until)
throws IOException, InterruptedException {
until = Math.min(until + 3, endPosition);
byte[] buffer = new byte[2048];
int peekLength = buffer.length;
long length = input.getLength();
while (true) {
if (length != C.LENGTH_UNBOUNDED && input.getPosition() + peekLength > length) {
if (input.getPosition() + peekLength > until) {
// Make sure to not peek beyond the end of the input.
peekLength = (int) (length - input.getPosition());
peekLength = (int) (until - input.getPosition());
if (peekLength < 4) {
// Not found until eof.
throw new EOFException();
// Not found until end.
return false;
}
}
input.peekFully(buffer, 0, peekLength, false);
......@@ -227,7 +279,7 @@ import java.io.IOException;
&& buffer[i + 3] == 'S') {
// Match! Skip to the start of the pattern.
input.skipFully(i);
return;
return true;
}
}
// Overlap by not skipping the entire peekLength.
......@@ -247,10 +299,9 @@ import java.io.IOException;
//@VisibleForTesting
long readGranuleOfLastPage(ExtractorInput input)
throws IOException, InterruptedException {
Assertions.checkArgument(input.getLength() != C.LENGTH_UNBOUNDED); // never read forever!
skipToNextPage(input);
pageHeader.reset();
while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < input.getLength()) {
while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < endPosition) {
pageHeader.populate(input, false);
input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
}
......@@ -259,39 +310,31 @@ import java.io.IOException;
/**
* Skips to the position of the start of the page containing the {@code targetGranule} and
* returns the elapsed samples which is the granule of the page previous to the target page.
* <p>
* Note that the position of the {@code input} must be before the start of the page previous to
* the page containing the targetGranule to get the correct number of elapsed samples.
* Which is in short like: {@code pos(input) <= pos(targetPage.pageSequence - 1)}.
* returns the granule of the page previous to the target page.
*
* @param input the {@link ExtractorInput} to read from.
* @param targetGranule the target granule (number of frames per channel).
* @return the number of elapsed samples at the start of the target page.
* @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 thrown if populating the page header fails.
* @throws IOException thrown if reading from the input fails.
* @throws InterruptedException thrown if interrupted while reading from the input.
*/
//@VisibleForTesting
long skipToPageOfGranule(ExtractorInput input, long targetGranule)
long skipToPageOfGranule(ExtractorInput input, long targetGranule, long currentGranule)
throws IOException, InterruptedException {
skipToNextPage(input);
pageHeader.populate(input, false);
while (pageHeader.granulePosition < targetGranule) {
input.skipFully(pageHeader.headerSize + pageHeader.bodySize);
// Store in a member field to be able to resume after IOExceptions.
elapsedSamples = pageHeader.granulePosition;
currentGranule = pageHeader.granulePosition;
// Peek next header.
pageHeader.populate(input, false);
}
if (elapsedSamples == 0) {
throw new ParserException();
}
input.resetPeekPosition();
long returnValue = elapsedSamples;
// Reset member state.
elapsedSamples = 0;
return returnValue;
return currentGranule;
}
}
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