Commit 17f8ac8a by eguven Committed by Oliver Woodman

Validate Extractor behavior when load position is reset to 0 following an error.

Added a new method TestUtil.consumeTestData() to emulate
the exact behaviour and modified OggExtractorFileTests to
use it. Rest of the test will be fixed in follow up CLs.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123320538
parent c2b89d62
Showing with 174 additions and 91 deletions
...@@ -127,7 +127,7 @@ public final class FlacExtractor implements Extractor { ...@@ -127,7 +127,7 @@ public final class FlacExtractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
decoder.flush(); decoder.flush();
} }
......
...@@ -25,9 +25,9 @@ import com.google.android.exoplayer.util.MimeTypes; ...@@ -25,9 +25,9 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -100,25 +100,8 @@ public final class Mp4ExtractorTest extends TestCase { ...@@ -100,25 +100,8 @@ public final class Mp4ExtractorTest extends TestCase {
/** Empty byte array. */ /** Empty byte array. */
private static final byte[] EMPTY = new byte[0]; private static final byte[] EMPTY = new byte[0];
private Mp4Extractor extractor;
private FakeExtractorOutput extractorOutput;
@Override
public void setUp() {
extractor = new Mp4Extractor();
extractorOutput = new FakeExtractorOutput();
extractor.init(extractorOutput);
}
@Override
public void tearDown() {
extractor = null;
extractorOutput = null;
}
public void testParsesValidMp4File() throws Exception { public void testParsesValidMp4File() throws Exception {
TestUtil.consumeTestData(extractor, FakeExtractorOutput extractorOutput = consumeTestData(true, false);
getTestInputData(true /* includeStss */, false /* mp4vFormat */));
// The seek map is correct. // The seek map is correct.
assertSeekMap(extractorOutput.seekMap, true); assertSeekMap(extractorOutput.seekMap, true);
...@@ -144,8 +127,7 @@ public final class Mp4ExtractorTest extends TestCase { ...@@ -144,8 +127,7 @@ public final class Mp4ExtractorTest extends TestCase {
} }
public void testParsesValidMp4FileWithoutStss() throws Exception { public void testParsesValidMp4FileWithoutStss() throws Exception {
TestUtil.consumeTestData(extractor, FakeExtractorOutput extractorOutput = consumeTestData(false, false);
getTestInputData(false /* includeStss */, false /* mp4vFormat */));
// The seek map is correct. // The seek map is correct.
assertSeekMap(extractorOutput.seekMap, false); assertSeekMap(extractorOutput.seekMap, false);
...@@ -162,8 +144,7 @@ public final class Mp4ExtractorTest extends TestCase { ...@@ -162,8 +144,7 @@ public final class Mp4ExtractorTest extends TestCase {
} }
public void testParsesValidMp4vFile() throws Exception { public void testParsesValidMp4vFile() throws Exception {
TestUtil.consumeTestData(extractor, FakeExtractorOutput extractorOutput = consumeTestData(true, true);
getTestInputData(true /* includeStss */, true /* mp4vFormat */));
// The seek map is correct. // The seek map is correct.
assertSeekMap(extractorOutput.seekMap, true); assertSeekMap(extractorOutput.seekMap, true);
...@@ -364,9 +345,11 @@ public final class Mp4ExtractorTest extends TestCase { ...@@ -364,9 +345,11 @@ public final class Mp4ExtractorTest extends TestCase {
return CHUNK_OFFSETS[chunkIndex] + offsetInChunk; return CHUNK_OFFSETS[chunkIndex] + offsetInChunk;
} }
private static byte[] getTestInputData(boolean includeStss, boolean mp4vFormat) { private static FakeExtractorOutput consumeTestData(boolean includeStss, boolean mp4vFormat)
return includeStss ? getTestMp4File(mp4vFormat) throws IOException, InterruptedException {
byte[] testInputData = includeStss ? getTestMp4File(mp4vFormat)
: getTestMp4FileWithoutSynchronizationData(mp4vFormat); : getTestMp4FileWithoutSynchronizationData(mp4vFormat);
return TestUtil.consumeTestData(new Mp4Extractor(), testInputData);
} }
/** Gets a valid MP4 file with audio/video tracks and synchronization data. */ /** Gets a valid MP4 file with audio/video tracks and synchronization data. */
......
...@@ -34,6 +34,7 @@ public final class OggExtractorFileTests extends InstrumentationTestCase { ...@@ -34,6 +34,7 @@ public final class OggExtractorFileTests extends InstrumentationTestCase {
public static final String OPUS_TEST_FILE = "ogg/bear.opus"; public static final String OPUS_TEST_FILE = "ogg/bear.opus";
public static final String FLAC_TEST_FILE = "ogg/bear_flac.ogg"; public static final String FLAC_TEST_FILE = "ogg/bear_flac.ogg";
public static final String FLAC_NS_TEST_FILE = "ogg/bear_flac_noseektable.ogg"; public static final String FLAC_NS_TEST_FILE = "ogg/bear_flac_noseektable.ogg";
public static final String VORBIS_TEST_FILE = "ogg/bear_vorbis.ogg";
public void testOpus() throws Exception { public void testOpus() throws Exception {
parseFile(OPUS_TEST_FILE, false, false, false, MimeTypes.AUDIO_OPUS, 2747500, 275); parseFile(OPUS_TEST_FILE, false, false, false, MimeTypes.AUDIO_OPUS, 2747500, 275);
...@@ -66,6 +67,13 @@ public final class OggExtractorFileTests extends InstrumentationTestCase { ...@@ -66,6 +67,13 @@ public final class OggExtractorFileTests extends InstrumentationTestCase {
parseFile(FLAC_NS_TEST_FILE, true, true, true, MimeTypes.AUDIO_FLAC, C.UNSET_TIME_US, 33); parseFile(FLAC_NS_TEST_FILE, true, true, true, MimeTypes.AUDIO_FLAC, C.UNSET_TIME_US, 33);
} }
public void testVorbis() throws Exception {
parseFile(VORBIS_TEST_FILE, false, false, false, MimeTypes.AUDIO_VORBIS, 2741000, 180);
parseFile(VORBIS_TEST_FILE, false, true, false, MimeTypes.AUDIO_VORBIS, C.UNSET_TIME_US, 180);
parseFile(VORBIS_TEST_FILE, true, false, true, MimeTypes.AUDIO_VORBIS, 2741000, 180);
parseFile(VORBIS_TEST_FILE, true, true, true, MimeTypes.AUDIO_VORBIS, C.UNSET_TIME_US, 180);
}
private FakeTrackOutput parseFile(String testFile, boolean simulateIOErrors, private FakeTrackOutput parseFile(String testFile, boolean simulateIOErrors,
boolean simulateUnknownLength, boolean simulatePartialReads, String expectedMimeType, boolean simulateUnknownLength, boolean simulatePartialReads, String expectedMimeType,
long expectedDuration, int expectedSampleCount) throws Exception { long expectedDuration, int expectedSampleCount) throws Exception {
...@@ -78,9 +86,7 @@ public final class OggExtractorFileTests extends InstrumentationTestCase { ...@@ -78,9 +86,7 @@ public final class OggExtractorFileTests extends InstrumentationTestCase {
OggExtractor extractor = new OggExtractor(); OggExtractor extractor = new OggExtractor();
assertTrue(TestUtil.sniffTestData(extractor, input)); assertTrue(TestUtil.sniffTestData(extractor, input));
input.resetPeekPosition(); input.resetPeekPosition();
FakeExtractorOutput extractorOutput = new FakeExtractorOutput(); FakeExtractorOutput extractorOutput = TestUtil.consumeTestData(extractor, input, true);
extractor.init(extractorOutput);
TestUtil.consumeTestData(extractor, input);
assertEquals(1, extractorOutput.trackOutputs.size()); assertEquals(1, extractorOutput.trackOutputs.size());
FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0); FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0);
......
...@@ -50,6 +50,15 @@ public final class FakeTrackOutput implements TrackOutput { ...@@ -50,6 +50,15 @@ public final class FakeTrackOutput implements TrackOutput {
sampleEncryptionKeys = new ArrayList<>(); sampleEncryptionKeys = new ArrayList<>();
} }
public void clear() {
sampleData = new byte[0];
sampleTimesUs.clear();
sampleFlags.clear();
sampleStartOffsets.clear();
sampleEndOffsets.clear();
sampleEncryptionKeys.clear();
}
@Override @Override
public void format(Format format) { public void format(Format format) {
this.format = format; this.format = format;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.testutil; package com.google.android.exoplayer.testutil;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.PositionHolder; import com.google.android.exoplayer.extractor.PositionHolder;
import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException; import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException;
...@@ -52,13 +53,21 @@ public class TestUtil { ...@@ -52,13 +53,21 @@ public class TestUtil {
} }
} }
public static void consumeTestData(Extractor extractor, byte[] data) public static FakeExtractorOutput consumeTestData(Extractor extractor, byte[] data)
throws IOException, InterruptedException { throws IOException, InterruptedException {
consumeTestData(extractor, newExtractorInput(data)); return consumeTestData(extractor, newExtractorInput(data));
} }
public static void consumeTestData(Extractor extractor, FakeExtractorInput input) public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input)
throws IOException, InterruptedException { throws IOException, InterruptedException {
return consumeTestData(extractor, input, false);
}
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
boolean retryFromStartIfLive) throws IOException, InterruptedException {
FakeExtractorOutput output = new FakeExtractorOutput();
extractor.init(output);
PositionHolder seekPositionHolder = new PositionHolder(); PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE; int readResult = Extractor.RESULT_CONTINUE;
while (readResult != Extractor.RESULT_END_OF_INPUT) { while (readResult != Extractor.RESULT_END_OF_INPUT) {
...@@ -70,9 +79,22 @@ public class TestUtil { ...@@ -70,9 +79,22 @@ public class TestUtil {
input.setPosition((int) seekPosition); input.setPosition((int) seekPosition);
} }
} catch (SimulatedIOException e) { } catch (SimulatedIOException e) {
// Ignore. if (!retryFromStartIfLive) {
continue;
}
boolean isOnDemand = input.getLength() != C.LENGTH_UNBOUNDED
|| (output.seekMap != null && output.seekMap.getDurationUs() != C.UNSET_TIME_US);
if (isOnDemand) {
continue;
}
input.setPosition(0);
for (int i = 0; i < output.numberOfTracks; i++) {
output.trackOutputs.valueAt(i).clear();
}
extractor.seek(0);
} }
} }
return output;
} }
public static byte[] buildTestData(int length) { public static byte[] buildTestData(int length) {
......
...@@ -81,7 +81,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput ...@@ -81,7 +81,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
extractor.init(this); extractor.init(this);
extractorInitialized = true; extractorInitialized = true;
} else { } else {
extractor.seek(); extractor.seek(0);
} }
} }
......
...@@ -90,12 +90,13 @@ public interface Extractor { ...@@ -90,12 +90,13 @@ public interface Extractor {
* Notifies the extractor that a seek has occurred. * Notifies the extractor that a seek has occurred.
* <p> * <p>
* Following a call to this method, the {@link ExtractorInput} passed to the next invocation of * Following a call to this method, the {@link ExtractorInput} passed to the next invocation of
* {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from a * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from {@code
* random access position in the stream. Valid random access positions are the start of the * position} in the stream. Valid random access positions are the start of the stream and
* stream and positions that can be obtained from any {@link SeekMap} passed to the * positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}.
* {@link ExtractorOutput}. *
* @param position The seek position.
*/ */
void seek(); void seek(long position);
/** /**
* Releases all kept resources. * Releases all kept resources.
......
...@@ -134,6 +134,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -134,6 +134,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private TrackGroupArray tracks; private TrackGroupArray tracks;
private long durationUs; private long durationUs;
private boolean[] trackEnabledStates; private boolean[] trackEnabledStates;
private long length;
private long downstreamPositionUs; private long downstreamPositionUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
...@@ -237,6 +238,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -237,6 +238,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
extractorHolder = new ExtractorHolder(extractors, this); extractorHolder = new ExtractorHolder(extractors, this);
pendingResetPositionUs = C.UNSET_TIME_US; pendingResetPositionUs = C.UNSET_TIME_US;
sampleQueues = new DefaultTrackOutput[0]; sampleQueues = new DefaultTrackOutput[0];
length = C.LENGTH_UNBOUNDED;
} }
/** /**
...@@ -491,11 +493,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -491,11 +493,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
@Override @Override
public void onLoadCompleted(Loadable loadable, long elapsedMs) { public void onLoadCompleted(Loadable loadable, long elapsedMs) {
copyLengthFromLoader();
loadingFinished = true; loadingFinished = true;
} }
@Override @Override
public void onLoadCanceled(Loadable loadable, long elapsedMs) { public void onLoadCanceled(Loadable loadable, long elapsedMs) {
copyLengthFromLoader();
if (enabledTrackCount > 0) { if (enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs); restartFrom(pendingResetPositionUs);
} else { } else {
...@@ -506,6 +510,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -506,6 +510,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
@Override @Override
public int onLoadError(Loadable loadable, long elapsedMs, IOException e) { public int onLoadError(Loadable loadable, long elapsedMs, IOException e) {
copyLengthFromLoader();
notifyLoadError(e); notifyLoadError(e);
if (isLoadableExceptionFatal(e)) { if (isLoadableExceptionFatal(e)) {
return Loader.DONT_RETRY_FATAL; return Loader.DONT_RETRY_FATAL;
...@@ -539,6 +544,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -539,6 +544,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Internal methods. // Internal methods.
private void copyLengthFromLoader() {
if (length == C.LENGTH_UNBOUNDED) {
length = loadable.length;
}
}
private void seekToInternal(long positionUs) { private void seekToInternal(long positionUs) {
// Treat all seeks into non-seekable media as being to t=0. // Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0; positionUs = seekMap.isSeekable() ? positionUs : 0;
...@@ -588,23 +599,19 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -588,23 +599,19 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private void configureRetry() { private void configureRetry() {
Assertions.checkState(loadable != null); Assertions.checkState(loadable != null);
if (!prepared) { if (length != C.LENGTH_UNBOUNDED
// We don't know whether we're playing an on-demand or a live stream. For a live stream we || (seekMap != null && seekMap.getDurationUs() != C.UNSET_TIME_US)) {
// need to load from the start, as outlined below. Since we might be playing a live stream, // We're playing an on-demand stream. Resume the current loadable, which will
// play it safe and load from the start. // request data starting from the point it left off.
clearSampleQueues(); } else {
loadable.setLoadPosition(0); // We're playing a stream of unknown length and duration. Assume it's live, and
} else if (!seekMap.isSeekable() && durationUs == C.UNSET_TIME_US) {
// We're playing a non-seekable stream with unknown duration. Assume it's live, and
// therefore that the data at the uri is a continuously shifting window of the latest // therefore that the data at the uri is a continuously shifting window of the latest
// available media. For this case there's no way to continue loading from where a previous // available media. For this case there's no way to continue loading from where a
// load finished, so it's necessary to load from the start whenever commencing a new load. // previous load finished, so it's necessary to load from the start whenever commencing
notifyReset = true; // a new load.
notifyReset = prepared;
clearSampleQueues(); clearSampleQueues();
loadable.setLoadPosition(0); loadable.setLoadPosition(0);
} else {
// We're playing a seekable on-demand stream. Resume the current loadable, which will
// request data starting from the point it left off.
} }
} }
...@@ -703,6 +710,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -703,6 +710,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
private boolean pendingExtractorSeek; private boolean pendingExtractorSeek;
private long length;
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
Allocator allocator, int requestedBufferSize) { Allocator allocator, int requestedBufferSize) {
...@@ -711,8 +719,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -711,8 +719,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
this.extractorHolder = Assertions.checkNotNull(extractorHolder); this.extractorHolder = Assertions.checkNotNull(extractorHolder);
this.allocator = Assertions.checkNotNull(allocator); this.allocator = Assertions.checkNotNull(allocator);
this.requestedBufferSize = requestedBufferSize; this.requestedBufferSize = requestedBufferSize;
positionHolder = new PositionHolder(); this.positionHolder = new PositionHolder();
pendingExtractorSeek = true; this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNBOUNDED;
} }
public void setLoadPosition(long position) { public void setLoadPosition(long position) {
...@@ -737,14 +746,14 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -737,14 +746,14 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
ExtractorInput input = null; ExtractorInput input = null;
try { try {
long position = positionHolder.position; long position = positionHolder.position;
long length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null)); length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null));
if (length != C.LENGTH_UNBOUNDED) { if (length != C.LENGTH_UNBOUNDED) {
length += position; length += position;
} }
input = new DefaultExtractorInput(dataSource, position, length); input = new DefaultExtractorInput(dataSource, position, length);
Extractor extractor = extractorHolder.selectExtractor(input); Extractor extractor = extractorHolder.selectExtractor(input);
if (pendingExtractorSeek) { if (pendingExtractorSeek) {
extractor.seek(); extractor.seek(position);
pendingExtractorSeek = false; pendingExtractorSeek = false;
} }
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
......
...@@ -114,7 +114,7 @@ public final class FlvExtractor implements Extractor, SeekMap { ...@@ -114,7 +114,7 @@ public final class FlvExtractor implements Extractor, SeekMap {
} }
@Override @Override
public void seek() { public void seek(long position) {
parserState = STATE_READING_FLV_HEADER; parserState = STATE_READING_FLV_HEADER;
bytesToNextTagHeader = 0; bytesToNextTagHeader = 0;
} }
......
...@@ -289,7 +289,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -289,7 +289,7 @@ public final class MatroskaExtractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
clusterTimecodeUs = UNKNOWN; clusterTimecodeUs = UNKNOWN;
blockState = BLOCK_STATE_START; blockState = BLOCK_STATE_START;
reader.reset(); reader.reset();
......
...@@ -104,7 +104,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -104,7 +104,7 @@ public final class Mp3Extractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
synchronizedHeaderData = 0; synchronizedHeaderData = 0;
samplesRead = 0; samplesRead = 0;
basisTimeUs = -1; basisTimeUs = -1;
......
...@@ -165,7 +165,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -165,7 +165,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
int trackCount = trackBundles.size(); int trackCount = trackBundles.size();
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
trackBundles.valueAt(i).reset(); trackBundles.valueAt(i).reset();
......
...@@ -97,7 +97,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -97,7 +97,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
@Override @Override
public void seek() { public void seek(long position) {
containerAtoms.clear(); containerAtoms.clear();
atomHeaderBytesRead = 0; atomHeaderBytesRead = 0;
sampleBytesWritten = 0; sampleBytesWritten = 0;
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.extractor.ogg; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.FlacStreamInfo; import com.google.android.exoplayer.util.FlacStreamInfo;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -47,6 +46,15 @@ import java.util.List; ...@@ -47,6 +46,15 @@ import java.util.List;
data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC" data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC"
} }
@Override
protected void reset(boolean headerData) {
super.reset(headerData);
if (headerData) {
streamInfo = null;
flacOggSeeker = null;
}
}
//@VisibleForTesting //@VisibleForTesting
public static boolean isAudioPacket(byte[] data) { public static boolean isAudioPacket(byte[] data) {
return data[0] == AUDIO_PACKET_TYPE; return data[0] == AUDIO_PACKET_TYPE;
...@@ -73,7 +81,6 @@ import java.util.List; ...@@ -73,7 +81,6 @@ import java.util.List;
streamInfo.bitRate(), streamInfo.channels, streamInfo.sampleRate, initializationData, streamInfo.bitRate(), streamInfo.channels, streamInfo.sampleRate, initializationData,
null, 0, null); null, 0, null);
} else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) { } else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) {
Assertions.checkArgument(flacOggSeeker == null);
flacOggSeeker = new FlacOggSeeker(); flacOggSeeker = new FlacOggSeeker();
flacOggSeeker.parseSeekTable(packet); flacOggSeeker.parseSeekTable(packet);
} else if (isAudioPacket(data)) { } else if (isAudioPacket(data)) {
......
...@@ -70,8 +70,8 @@ public class OggExtractor implements Extractor { ...@@ -70,8 +70,8 @@ public class OggExtractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
streamReader.seek(); streamReader.seek(position);
} }
@Override @Override
......
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import com.google.android.exoplayer.util.Util;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
...@@ -39,9 +40,10 @@ import java.util.List; ...@@ -39,9 +40,10 @@ import java.util.List;
*/ */
private static final int SAMPLE_RATE = 48000; private static final int SAMPLE_RATE = 48000;
private static final int OPUS_CODE = Util.getIntegerCodeForString("Opus");
private static final byte[] OPUS_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}; private static final byte[] OPUS_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'};
private boolean headerRead; private boolean headerRead;
private boolean tagsSkipped;
public static boolean verifyBitstreamType(ParsableByteArray data) { public static boolean verifyBitstreamType(ParsableByteArray data) {
if (data.bytesLeft() < OPUS_SIGNATURE.length) { if (data.bytesLeft() < OPUS_SIGNATURE.length) {
...@@ -53,6 +55,14 @@ import java.util.List; ...@@ -53,6 +55,14 @@ import java.util.List;
} }
@Override @Override
protected void reset(boolean headerData) {
super.reset(headerData);
if (headerData) {
headerRead = false;
}
}
@Override
protected long preparePayload(ParsableByteArray packet) { protected long preparePayload(ParsableByteArray packet) {
return convertTimeToGranule(getPacketDurationUs(packet.data)); return convertTimeToGranule(getPacketDurationUs(packet.data));
} }
...@@ -73,11 +83,10 @@ import java.util.List; ...@@ -73,11 +83,10 @@ import java.util.List;
setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, Format.NO_VALUE, setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, Format.NO_VALUE,
Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, "und"); Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, "und");
headerRead = true; headerRead = true;
} else if (!tagsSkipped) {
// Skip tags packet
tagsSkipped = true;
} else { } else {
return false; boolean headerPacket = packet.readInt() == OPUS_CODE;
packet.setPosition(0);
return headerPacket;
} }
return true; return true;
} }
......
...@@ -18,8 +18,9 @@ import java.io.IOException; ...@@ -18,8 +18,9 @@ import java.io.IOException;
/* package */ abstract class StreamReader { /* package */ abstract class StreamReader {
private static final int STATE_READ_HEADERS = 0; private static final int STATE_READ_HEADERS = 0;
private static final int STATE_READ_PAYLOAD = 1; private static final int STATE_SKIP_HEADERS = 1;
private static final int STATE_END_OF_INPUT = 2; private static final int STATE_READ_PAYLOAD = 2;
private static final int STATE_END_OF_INPUT = 3;
static class SetupData { static class SetupData {
Format format; Format format;
...@@ -38,27 +39,44 @@ import java.io.IOException; ...@@ -38,27 +39,44 @@ import java.io.IOException;
private SetupData setupData; private SetupData setupData;
private long lengthOfReadPacket; private long lengthOfReadPacket;
private boolean seekMapSet; private boolean seekMapSet;
private boolean formatSet;
void init(ExtractorOutput output, TrackOutput trackOutput) { void init(ExtractorOutput output, TrackOutput trackOutput) {
this.extractorOutput = output; this.extractorOutput = output;
this.trackOutput = trackOutput; this.trackOutput = trackOutput;
this.oggPacket = new OggPacket(); this.oggPacket = new OggPacket();
this.setupData = new SetupData();
this.state = STATE_READ_HEADERS; reset(true);
this.targetGranule = -1;
this.payloadStartPosition = 0;
} }
/** /**
* @see Extractor#seek() * Resets the state of the {@link StreamReader}.
* @param headerData Resets parsed header data too.
*/ */
final void seek() { protected void reset(boolean headerData) {
oggPacket.reset(); if (headerData) {
setupData = new SetupData();
payloadStartPosition = 0;
state = STATE_READ_HEADERS;
} else {
state = STATE_SKIP_HEADERS;
}
targetGranule = -1;
currentGranule = 0;
}
if (state != STATE_READ_HEADERS) { /**
targetGranule = oggSeeker.startSeek(); * @see Extractor#seek(long)
state = STATE_READ_PAYLOAD; */
final void seek(long position) {
oggPacket.reset();
if (position == 0) {
reset(!seekMapSet);
} else {
if (state != STATE_READ_HEADERS) {
targetGranule = oggSeeker.startSeek();
state = STATE_READ_PAYLOAD;
}
} }
} }
...@@ -71,6 +89,11 @@ import java.io.IOException; ...@@ -71,6 +89,11 @@ import java.io.IOException;
case STATE_READ_HEADERS: case STATE_READ_HEADERS:
return readHeaders(input); return readHeaders(input);
case STATE_SKIP_HEADERS:
input.skipFully((int) payloadStartPosition);
state = STATE_READ_PAYLOAD;
return Extractor.RESULT_CONTINUE;
case STATE_READ_PAYLOAD: case STATE_READ_PAYLOAD:
return readPayload(input, seekPosition); return readPayload(input, seekPosition);
...@@ -80,8 +103,7 @@ import java.io.IOException; ...@@ -80,8 +103,7 @@ import java.io.IOException;
} }
} }
private int readHeaders(ExtractorInput input) private int readHeaders(ExtractorInput input) throws IOException, InterruptedException {
throws IOException, InterruptedException {
boolean readingHeaders = true; boolean readingHeaders = true;
while (readingHeaders) { while (readingHeaders) {
if (!oggPacket.populate(input)) { if (!oggPacket.populate(input)) {
...@@ -97,7 +119,10 @@ import java.io.IOException; ...@@ -97,7 +119,10 @@ import java.io.IOException;
} }
sampleRate = setupData.format.sampleRate; sampleRate = setupData.format.sampleRate;
trackOutput.format(setupData.format); if (!formatSet) {
trackOutput.format(setupData.format);
formatSet = true;
}
if (setupData.oggSeeker != null) { if (setupData.oggSeeker != null) {
oggSeeker = setupData.oggSeeker; oggSeeker = setupData.oggSeeker;
......
...@@ -45,6 +45,18 @@ import java.util.ArrayList; ...@@ -45,6 +45,18 @@ import java.util.ArrayList;
} }
@Override @Override
protected void reset(boolean headerData) {
super.reset(headerData);
if (headerData) {
vorbisSetup = null;
vorbisIdHeader = null;
commentHeader = null;
}
previousPacketBlockSize = 0;
seenFirstAudioPacket = false;
}
@Override
protected void onSeekEnd(long currentGranule) { protected void onSeekEnd(long currentGranule) {
super.onSeekEnd(currentGranule); super.onSeekEnd(currentGranule);
seenFirstAudioPacket = currentGranule != 0; seenFirstAudioPacket = currentGranule != 0;
......
...@@ -120,7 +120,7 @@ public final class AdtsExtractor implements Extractor { ...@@ -120,7 +120,7 @@ public final class AdtsExtractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
startedPacket = false; startedPacket = false;
adtsReader.seek(); adtsReader.seek();
} }
......
...@@ -113,7 +113,7 @@ public final class PsExtractor implements Extractor { ...@@ -113,7 +113,7 @@ public final class PsExtractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
ptsTimestampAdjuster.reset(); ptsTimestampAdjuster.reset();
for (int i = 0; i < psPayloadReaders.size(); i++) { for (int i = 0; i < psPayloadReaders.size(); i++) {
psPayloadReaders.valueAt(i).seek(); psPayloadReaders.valueAt(i).seek();
......
...@@ -116,7 +116,7 @@ public final class TsExtractor implements Extractor { ...@@ -116,7 +116,7 @@ public final class TsExtractor implements Extractor {
} }
@Override @Override
public void seek() { public void seek(long position) {
ptsTimestampAdjuster.reset(); ptsTimestampAdjuster.reset();
for (int i = 0; i < tsPayloadReaders.size(); i++) { for (int i = 0; i < tsPayloadReaders.size(); i++) {
tsPayloadReaders.valueAt(i).seek(); tsPayloadReaders.valueAt(i).seek();
......
...@@ -54,7 +54,7 @@ public final class WavExtractor implements Extractor, SeekMap { ...@@ -54,7 +54,7 @@ public final class WavExtractor implements Extractor, SeekMap {
} }
@Override @Override
public void seek() { public void seek(long position) {
pendingBytes = 0; pendingBytes = 0;
} }
......
...@@ -80,7 +80,7 @@ import java.util.regex.Pattern; ...@@ -80,7 +80,7 @@ import java.util.regex.Pattern;
} }
@Override @Override
public void seek() { public void seek(long position) {
// This extractor is only used for the HLS use case, which should not call this method. // This extractor is only used for the HLS use case, which should not call this method.
throw new IllegalStateException(); throw new IllegalStateException();
} }
......
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