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;
}
}
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