Commit 7cddd1b1 by olly Committed by Oliver Woodman

Move OGG extractor to use FakeExtractorInput + Simplify.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117220360
parent bccffb01
...@@ -15,38 +15,41 @@ ...@@ -15,38 +15,41 @@
*/ */
package com.google.android.exoplayer.extractor.ogg; package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.extractor.ogg.OggVorbisExtractor.VorbisSetup;
import com.google.android.exoplayer.testutil.FakeExtractorInput;
import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException;
import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import android.util.Log;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.io.IOException;
/** /**
* Unit test for {@link OggVorbisExtractor}. * Unit test for {@link OggVorbisExtractor}.
*/ */
public final class OggVorbisExtractorTest extends TestCase { public final class OggVorbisExtractorTest extends TestCase {
private static final String TAG = "OggVorbisExtractorTest";
private OggVorbisExtractor extractor; private OggVorbisExtractor extractor;
private RecordableOggExtractorInput extractorInput; private ParsableByteArray scratch;
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
extractorInput = new RecordableOggExtractorInput(1024 * 64);
extractor = new OggVorbisExtractor(); extractor = new OggVorbisExtractor();
scratch = new ParsableByteArray(new byte[255 * 255], 0);
} }
public void testSniff() throws Exception { public void testSniff() throws Exception {
extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x02); byte[] data = TestUtil.joinByteArrays(
extractorInput.recordOggLaces(new byte[]{120, 120}); TestData.buildOggHeader(0x02, 0, 1000, 0x02),
assertTrue(extractor.sniff(extractorInput)); TestUtil.createByteArray(120, 120)); // Laces
assertTrue(sniff(createInput(data)));
} }
public void testSniffFails() throws Exception { public void testSniffFails() throws Exception {
extractorInput.recordOggHeader((byte) 0x00, 0, (byte) 0); byte[] data = TestData.buildOggHeader(0x00, 0, 1000, 0x00);
assertFalse(extractor.sniff(extractorInput)); assertFalse(sniff(createInput(data)));
} }
public void testAppendNumberOfSamples() throws Exception { public void testAppendNumberOfSamples() throws Exception {
...@@ -60,57 +63,62 @@ public final class OggVorbisExtractorTest extends TestCase { ...@@ -60,57 +63,62 @@ public final class OggVorbisExtractorTest extends TestCase {
assertEquals(0x01, buffer.data[3]); assertEquals(0x01, buffer.data[3]);
} }
public void testReadSetupHeadersWithIOExceptions() { public void testReadSetupHeadersWithIOExceptions() throws IOException, InterruptedException {
extractorInput.doThrowExceptionsAtRead(true);
extractorInput.doThrowExceptionsAtPeek(true);
byte[] data = TestData.getVorbisHeaderPages(); byte[] data = TestData.getVorbisHeaderPages();
extractorInput.record(data); OggVorbisExtractor.VorbisSetup vorbisSetup = readSetupHeaders(createInput(data));
assertNotNull(vorbisSetup.idHeader);
assertNotNull(vorbisSetup.commentHeader);
assertNotNull(vorbisSetup.setupHeaderData);
assertNotNull(vorbisSetup.modes);
assertEquals(45, vorbisSetup.commentHeader.length);
assertEquals(30, vorbisSetup.idHeader.data.length);
assertEquals(3597, vorbisSetup.setupHeaderData.length);
assertEquals(-1, vorbisSetup.idHeader.bitrateMax);
assertEquals(-1, vorbisSetup.idHeader.bitrateMin);
assertEquals(66666, vorbisSetup.idHeader.bitrateNominal);
assertEquals(512, vorbisSetup.idHeader.blockSize0);
assertEquals(1024, vorbisSetup.idHeader.blockSize1);
assertEquals(2, vorbisSetup.idHeader.channels);
assertTrue(vorbisSetup.idHeader.framingFlag);
assertEquals(22050, vorbisSetup.idHeader.sampleRate);
assertEquals(0, vorbisSetup.idHeader.version);
assertEquals("Xiph.Org libVorbis I 20030909", vorbisSetup.commentHeader.vendor);
assertEquals(1, vorbisSetup.iLogModes);
assertEquals(data[data.length - 1],
vorbisSetup.setupHeaderData[vorbisSetup.setupHeaderData.length - 1]);
assertFalse(vorbisSetup.modes[0].blockFlag);
assertTrue(vorbisSetup.modes[1].blockFlag);
}
int exceptionCount = 0; private static FakeExtractorInput createInput(byte[] data) {
int maxExceptions = 20; return new FakeExtractorInput.Builder().setData(data).setSimulateIOErrors(true)
OggVorbisExtractor.VorbisSetup vorbisSetup; .setSimulateUnknownLength(true).setSimulatePartialReads(true).build();
while (exceptionCount < maxExceptions) { }
private boolean sniff(FakeExtractorInput input) throws InterruptedException, IOException {
while (true) {
try { try {
vorbisSetup = extractor.readSetupHeaders(extractorInput, return extractor.sniff(input);
new ParsableByteArray(new byte[255 * 255], 0)); } catch (SimulatedIOException e) {
// Ignore.
assertNotNull(vorbisSetup.idHeader);
assertNotNull(vorbisSetup.commentHeader);
assertNotNull(vorbisSetup.setupHeaderData);
assertNotNull(vorbisSetup.modes);
assertEquals(45, vorbisSetup.commentHeader.length);
assertEquals(30, vorbisSetup.idHeader.data.length);
assertEquals(3597, vorbisSetup.setupHeaderData.length);
assertEquals(-1, vorbisSetup.idHeader.bitrateMax);
assertEquals(-1, vorbisSetup.idHeader.bitrateMin);
assertEquals(66666, vorbisSetup.idHeader.bitrateNominal);
assertEquals(512, vorbisSetup.idHeader.blockSize0);
assertEquals(1024, vorbisSetup.idHeader.blockSize1);
assertEquals(2, vorbisSetup.idHeader.channels);
assertTrue(vorbisSetup.idHeader.framingFlag);
assertEquals(22050, vorbisSetup.idHeader.sampleRate);
assertEquals(0, vorbisSetup.idHeader.version);
assertEquals("Xiph.Org libVorbis I 20030909", vorbisSetup.commentHeader.vendor);
assertEquals(1, vorbisSetup.iLogModes);
assertEquals(data[data.length - 1],
vorbisSetup.setupHeaderData[vorbisSetup.setupHeaderData.length - 1]);
assertFalse(vorbisSetup.modes[0].blockFlag);
assertTrue(vorbisSetup.modes[1].blockFlag);
break;
} catch (Throwable e) {
Log.e(TAG, e.getMessage(), e);
extractorInput.resetPeekPosition();
exceptionCount++;
} }
} }
if (exceptionCount >= maxExceptions) { }
fail("more than " + maxExceptions + " exceptions thrown");
private VorbisSetup readSetupHeaders(FakeExtractorInput input)
throws IOException, InterruptedException {
while (true) {
try {
return extractor.readSetupHeaders(input, scratch);
} catch (SimulatedIOException e) {
// Ignore.
}
} }
} }
......
/*
* Copyright (C) 2015 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.C;
import com.google.android.exoplayer.extractor.ExtractorInput;
import java.io.EOFException;
import java.io.IOException;
/**
* Implementation of {@link ExtractorInput} for testing purpose.
*/
/* package */ class RecordableExtractorInput implements ExtractorInput {
private byte[] data;
private int readPosition;
private int writePosition;
private int peekPosition;
private boolean throwExceptionsAtRead = false;
private boolean throwExceptionsAtPeek = false;
private int numberOfReadsUntilException = 1;
private int numberOfPeeksUntilException = 1;
private int readCounter;
private int peekCounter;
private int maxReadExceptions = Integer.MAX_VALUE;
private int maxPeekExceptions = Integer.MAX_VALUE;
private int readExceptionCounter;
private int peekExceptionCounter;
/**
* Constructs an instance with a initial array of bytes.
*
* @param data The initial data.
* @param writePosition The {@code writePosition} from where to start recording.
*/
public RecordableExtractorInput(byte[] data, int writePosition) {
this.data = data;
this.writePosition = writePosition;
}
/**
* Constructs an instance with an empty data array with length {@code maxBytes}.
*
* @param maxBytes the maximal number of bytes this {@code ExtractorInput} can store.
*/
public RecordableExtractorInput(int maxBytes) {
this(new byte[maxBytes], 0);
}
@Override
public int read(byte[] target, int offset, int length) throws IOException, InterruptedException {
readFully(target, offset, length);
return isEOF() ? C.RESULT_END_OF_INPUT : length;
}
@Override
public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
readCounter++;
if (throwExceptionsAtRead
&& readExceptionCounter < maxReadExceptions
&& readCounter % numberOfReadsUntilException == 0) {
readCounter = 0;
numberOfReadsUntilException++;
readExceptionCounter++;
throw new IOException("deliberately thrown an exception for testing");
}
if (readPosition + length > writePosition) {
if (!allowEndOfInput) {
throw new EOFException();
}
return false;
}
System.arraycopy(data, readPosition, target, offset, length);
readPosition += length;
peekPosition = readPosition;
return true;
}
@Override
public void readFully(byte[] target, int offset, int length)
throws IOException, InterruptedException {
readFully(target, offset, length, false);
}
@Override
public int skip(int length) throws IOException, InterruptedException {
skipFully(length);
return isEOF() ? C.RESULT_END_OF_INPUT : length;
}
private boolean isEOF() {
return readPosition == writePosition;
}
@Override
public boolean skipFully(int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
if (readPosition + length >= writePosition) {
if (!allowEndOfInput) {
throw new EOFException();
}
return false;
}
readPosition += length;
peekPosition = readPosition;
return true;
}
@Override
public void skipFully(int length) throws IOException, InterruptedException {
skipFully(length, false);
}
@Override
public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
peekCounter++;
if (throwExceptionsAtPeek
&& peekExceptionCounter < maxPeekExceptions
&& peekCounter % numberOfPeeksUntilException == 0) {
peekCounter = 0;
numberOfPeeksUntilException++;
peekExceptionCounter++;
throw new IOException("deliberately thrown an exception for testing");
}
if (peekPosition + length > writePosition) {
if (!allowEndOfInput) {
throw new EOFException();
}
return false;
}
System.arraycopy(data, peekPosition, target, offset, length);
peekPosition += length;
return true;
}
@Override
public void peekFully(byte[] target, int offset, int length)
throws IOException, InterruptedException {
peekFully(target, offset, length, false);
}
@Override
public boolean advancePeekPosition(int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
if (peekPosition + length >= writePosition) {
if (!allowEndOfInput) {
throw new EOFException();
}
return false;
}
peekPosition += length;
return true;
}
@Override
public void advancePeekPosition(int length) throws IOException, InterruptedException {
advancePeekPosition(length, false);
}
@Override
public void resetPeekPosition() {
peekPosition = readPosition;
}
@Override
public long getPeekPosition() {
return peekPosition;
}
@Override
public long getPosition() {
return readPosition;
}
@Override
public long getLength() {
return writePosition;
}
/**
* Records the {@code bytes}.
*
* @param bytes the bytes to record.
*/
public void record(final byte[] bytes) {
System.arraycopy(bytes, 0, data, writePosition, bytes.length);
writePosition += bytes.length;
}
/** Records a single byte. **/
public void record(byte b) {
record(new byte[] {b});
}
/**
* Gets a byte array with length {@code length} with ascending values starting from 0 (zero).
*
* @param length the length of the array.
* @return an array of bytes with ascending values.
*/
public static byte[] getBytesGrowingValues(int length) {
return fillBytesGrowingValues(new byte[length], length, (byte) 0);
}
/**
* Gets a byte array with length {@code length} with ascending values starting
* from {@code startValue}.
*
* @param length the length of the array.
* @param startValue the value from which to start.
* @return an array of bytes with ascending values starting from {@code startValue}.
*/
public static byte[] getBytesGrowingValues(int length, byte startValue) {
return fillBytesGrowingValues(new byte[length], length, startValue);
}
/**
* Fills the byte array passed as argument with ascending values.
*
* @param bytes the byte array to fill with values.
* @param limit the number of bytes to set in the array.
* @param startValue the startValue from which the values in the array have to start.
*/
public static byte[] fillBytesGrowingValues(byte[] bytes, int limit, byte startValue) {
for (int i = 0; i < bytes.length; i++) {
if (i < limit) {
bytes[i] = (byte) ((i + startValue) % 255);
} else {
bytes[i] = 0;
}
}
return bytes;
}
public void setMaxReadExceptions(int maxReadExceptions) {
this.maxReadExceptions = maxReadExceptions;
}
public void setMaxPeekExceptions(int maxPeekExceptions) {
this.maxPeekExceptions = maxPeekExceptions;
}
public void setNumberOfReadsUntilException(int numberOfReadsUntilException) {
this.numberOfReadsUntilException = numberOfReadsUntilException;
}
public void setNumberOfPeeksUntilException(int numberOfPeeksUntilException) {
this.numberOfPeeksUntilException = numberOfPeeksUntilException;
}
public void doThrowExceptionsAtRead(boolean throwExceptionsAtRead) {
this.throwExceptionsAtRead = throwExceptionsAtRead;
}
public void doThrowExceptionsAtPeek(boolean throwExceptionsAtPeek) {
this.throwExceptionsAtPeek = throwExceptionsAtPeek;
}
}
/*
* Copyright (C) 2015 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;
/**
* A {@link RecordableOggExtractorInput} with convenient methods to record an OGG byte stream.
*/
/* package */ final class RecordableOggExtractorInput extends RecordableExtractorInput {
public static final byte STREAM_REVISION = 0x00;
private long pageSequenceCounter;
public RecordableOggExtractorInput(byte[] data, int writeOffset) {
super(data, writeOffset);
pageSequenceCounter = 1000;
}
public RecordableOggExtractorInput(int maxBytes) {
this(new byte[maxBytes], 0);
}
/**
* Syntax sugar to make tests more readable.
*
* @param laces the laces to record to the data.
*/
protected void recordOggLaces(final byte[] laces) {
record(laces);
}
/**
* Syntax sugar to make tests more readable.
*
* @param packet the packet bytes to record to the data.
*/
protected void recordOggPacket(final byte[] packet) {
record(packet);
}
protected void recordOggHeader(final byte headerType, final long granule,
final byte pageSegmentCount) {
record((byte) 0x4F); // O
record((byte) 0x67); // g
record((byte) 0x67); // g
record((byte) 0x53); // S
record(STREAM_REVISION);
record(headerType);
recordGranulePosition(granule);
record((byte) 0x00); // LSB of data serial number
record((byte) 0x10);
record((byte) 0x00);
record((byte) 0x00); // MSB of data serial number
recordPageSequenceCounter();
record((byte) 0x00); // LSB of page checksum
record((byte) 0x00);
record((byte) 0x00);
record((byte) 0x00); // MSB of page checksum
record(pageSegmentCount); // 0 - 255
}
protected void recordGranulePosition(long granule) {
record((byte) (granule & 0xFF));
record((byte) ((granule >> 8) & 0xFF));
record((byte) ((granule >> 16) & 0xFF));
record((byte) ((granule >> 24) & 0xFF));
record((byte) ((granule >> 32) & 0xFF));
record((byte) ((granule >> 40) & 0xFF));
record((byte) ((granule >> 48) & 0xFF));
record((byte) ((granule >> 56) & 0xFF));
}
protected void recordPageSequenceCounter() {
record((byte) (pageSequenceCounter & 0xFF));
record((byte) ((pageSequenceCounter >> 8) & 0xFF));
record((byte) ((pageSequenceCounter >> 16) & 0xFF));
record((byte) ((pageSequenceCounter++ >> 24) & 0xFF));
}
}
...@@ -15,11 +15,42 @@ ...@@ -15,11 +15,42 @@
*/ */
package com.google.android.exoplayer.extractor.ogg; package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.testutil.TestUtil;
/** /**
* Provides ogg/vorbis test data in bytes for unit tests. * Provides ogg/vorbis test data in bytes for unit tests.
*/ */
/* package */ final class TestData { /* package */ final class TestData {
public static byte[] buildOggHeader(int headerType, long granule, int pageSequenceCounter,
int pageSegmentCount) {
return TestUtil.createByteArray(
0x4F, 0x67, 0x67, 0x53, // Oggs.
0x00, // Stream revision.
headerType,
(int) (granule >> 0) & 0xFF,
(int) (granule >> 8) & 0xFF,
(int) (granule >> 16) & 0xFF,
(int) (granule >> 24) & 0xFF,
(int) (granule >> 32) & 0xFF,
(int) (granule >> 40) & 0xFF,
(int) (granule >> 48) & 0xFF,
(int) (granule >> 56) & 0xFF,
0x00, // LSB of data serial number.
0x10,
0x00,
0x00, // MSB of data serial number.
(pageSequenceCounter >> 0) & 0xFF,
(pageSequenceCounter >> 8) & 0xFF,
(pageSequenceCounter >> 16) & 0xFF,
(pageSequenceCounter >> 24) & 0xFF,
0x00, // LSB of page checksum.
0x00,
0x00,
0x00, // MSB of page checksum.
pageSegmentCount);
}
/** /**
* Returns the initial two pages of bytes which by spec contain the three vorbis header packets: * Returns the initial two pages of bytes which by spec contain the three vorbis header packets:
* identification, comment and setup header. * identification, comment and setup header.
......
...@@ -28,7 +28,7 @@ import java.io.IOException; ...@@ -28,7 +28,7 @@ import java.io.IOException;
/** /**
* A fake {@link ExtractorInput} capable of simulating various scenarios. * A fake {@link ExtractorInput} capable of simulating various scenarios.
* <p> * <p>
* Read, skip and peek errors can be simulated using {@link Builder#setSimulateIOErrors)}. When * Read, skip and peek errors can be simulated using {@link Builder#setSimulateIOErrors}. When
* enabled each read and skip will throw a {@link SimulatedIOException} unless one has already been * enabled each read and skip will throw a {@link SimulatedIOException} unless one has already been
* thrown from the current position. Each peek will throw {@link SimulatedIOException} unless one * thrown from the current position. Each peek will throw {@link SimulatedIOException} unless one
* has already been thrown from the current peek position. When a {@link SimulatedIOException} is * has already been thrown from the current peek position. When a {@link SimulatedIOException} is
...@@ -43,7 +43,7 @@ import java.io.IOException; ...@@ -43,7 +43,7 @@ import java.io.IOException;
* bytes then it will be fully satisfied, since it has the same target position of 10. * bytes then it will be fully satisfied, since it has the same target position of 10.
* <p> * <p>
* Unknown data length can be simulated using {@link Builder#setSimulateUnknownLength}. When enabled * Unknown data length can be simulated using {@link Builder#setSimulateUnknownLength}. When enabled
* {@link getLength()} will return {@link C#LENGTH_UNBOUNDED} rather than the length of the data. * {@link #getLength()} will return {@link C#LENGTH_UNBOUNDED} rather than the length of the data.
*/ */
public final class FakeExtractorInput implements ExtractorInput { public final class FakeExtractorInput implements ExtractorInput {
......
...@@ -56,7 +56,10 @@ public class TestUtil { ...@@ -56,7 +56,10 @@ public class TestUtil {
} }
public static byte[] buildTestData(int length, int seed) { public static byte[] buildTestData(int length, int seed) {
Random random = new Random(seed); return buildTestData(length, new Random(seed));
}
public static byte[] buildTestData(int length, Random random) {
byte[] source = new byte[length]; byte[] source = new byte[length];
random.nextBytes(source); random.nextBytes(source);
return source; return source;
......
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