Commit f4575187 by andrewlewis Committed by Oliver Woodman

Move track sample index updates out of getPosition(long).

This requires knowing the seek time in Extractor.seek, so that it's possible to
pick the latest synchronization sample at/before the seek time for each track
(rather than the earliest synchronization sample after the seek position).

Also remove the STATE_AFTER_SEEK state which should no longer be needed.

Issue: #2167

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=141432598
parent 2fce3649
Showing with 70 additions and 53 deletions
...@@ -155,7 +155,7 @@ public final class FlacExtractor implements Extractor { ...@@ -155,7 +155,7 @@ public final class FlacExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
if (position == 0) { if (position == 0) {
metadataParsed = false; metadataParsed = false;
} }
......
...@@ -93,9 +93,10 @@ public interface Extractor { ...@@ -93,9 +93,10 @@ public interface Extractor {
* position} in the stream. Valid random access positions are the start of the stream and * position} in the stream. Valid random access positions are the start of the stream and
* positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}. * positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}.
* *
* @param position The seek position. * @param position The byte offset in the stream from which data will be provided.
* @param timeUs The seek time in microseconds.
*/ */
void seek(long position); void seek(long position, long timeUs);
/** /**
* Releases all kept resources. * Releases all kept resources.
......
...@@ -126,7 +126,7 @@ public final class FlvExtractor implements Extractor, SeekMap { ...@@ -126,7 +126,7 @@ public final class FlvExtractor implements Extractor, SeekMap {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
parserState = STATE_READING_FLV_HEADER; parserState = STATE_READING_FLV_HEADER;
bytesToNextTagHeader = 0; bytesToNextTagHeader = 0;
} }
......
...@@ -318,7 +318,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -318,7 +318,7 @@ public final class MatroskaExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
clusterTimecodeUs = C.TIME_UNSET; clusterTimecodeUs = C.TIME_UNSET;
blockState = BLOCK_STATE_START; blockState = BLOCK_STATE_START;
reader.reset(); reader.reset();
......
...@@ -123,7 +123,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -123,7 +123,7 @@ public final class Mp3Extractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
synchronizedHeaderData = 0; synchronizedHeaderData = 0;
basisTimeUs = C.TIME_UNSET; basisTimeUs = C.TIME_UNSET;
samplesRead = 0; samplesRead = 0;
......
...@@ -194,7 +194,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -194,7 +194,7 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
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();
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
...@@ -33,6 +34,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil; ...@@ -33,6 +34,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Stack; import java.util.Stack;
...@@ -54,11 +57,15 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -54,11 +57,15 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}; };
// Parser states. /**
private static final int STATE_AFTER_SEEK = 0; * Parser states.
private static final int STATE_READING_ATOM_HEADER = 1; */
private static final int STATE_READING_ATOM_PAYLOAD = 2; @Retention(RetentionPolicy.SOURCE)
private static final int STATE_READING_SAMPLE = 3; @IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE})
private @interface State {}
private static final int STATE_READING_ATOM_HEADER = 0;
private static final int STATE_READING_ATOM_PAYLOAD = 1;
private static final int STATE_READING_SAMPLE = 2;
// Brand stored in the ftyp atom for QuickTime media. // Brand stored in the ftyp atom for QuickTime media.
private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt "); private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt ");
...@@ -76,6 +83,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -76,6 +83,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private final ParsableByteArray atomHeader; private final ParsableByteArray atomHeader;
private final Stack<ContainerAtom> containerAtoms; private final Stack<ContainerAtom> containerAtoms;
@State
private int parserState; private int parserState;
private int atomType; private int atomType;
private long atomSize; private long atomSize;
...@@ -96,7 +104,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -96,7 +104,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
containerAtoms = new Stack<>(); containerAtoms = new Stack<>();
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4); nalLength = new ParsableByteArray(4);
enterReadingAtomHeaderState();
} }
@Override @Override
...@@ -110,12 +117,16 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -110,12 +117,16 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
containerAtoms.clear(); containerAtoms.clear();
atomHeaderBytesRead = 0; atomHeaderBytesRead = 0;
sampleBytesWritten = 0; sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0; sampleCurrentNalBytesRemaining = 0;
parserState = STATE_AFTER_SEEK; if (position == 0) {
enterReadingAtomHeaderState();
} else if (tracks != null) {
updateSampleIndices(timeUs);
}
} }
@Override @Override
...@@ -128,13 +139,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -128,13 +139,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
throws IOException, InterruptedException { throws IOException, InterruptedException {
while (true) { while (true) {
switch (parserState) { switch (parserState) {
case STATE_AFTER_SEEK:
if (input.getPosition() == 0) {
enterReadingAtomHeaderState();
} else {
parserState = STATE_READING_SAMPLE;
}
break;
case STATE_READING_ATOM_HEADER: case STATE_READING_ATOM_HEADER:
if (!readAtomHeader(input)) { if (!readAtomHeader(input)) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
...@@ -145,8 +149,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -145,8 +149,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return RESULT_SEEK; return RESULT_SEEK;
} }
break; break;
default: case STATE_READING_SAMPLE:
return readSample(input, seekPosition); return readSample(input, seekPosition);
default:
throw new IllegalStateException();
} }
} }
} }
...@@ -173,8 +179,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -173,8 +179,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Handle the case where the requested time is before the first synchronization sample. // Handle the case where the requested time is before the first synchronization sample.
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
} }
track.sampleIndex = sampleIndex;
long offset = sampleTable.offsets[sampleIndex]; long offset = sampleTable.offsets[sampleIndex];
if (offset < earliestSamplePosition) { if (offset < earliestSamplePosition) {
earliestSamplePosition = offset; earliestSamplePosition = offset;
...@@ -479,6 +483,21 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -479,6 +483,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
/** /**
* Updates every track's sample index to point its latest sync sample before/at {@code timeUs}.
*/
private void updateSampleIndices(long timeUs) {
for (Mp4Track track : tracks) {
TrackSampleTable sampleTable = track.sampleTable;
int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs);
if (sampleIndex == C.INDEX_UNSET) {
// Handle the case where the requested time is before the first synchronization sample.
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
}
track.sampleIndex = sampleIndex;
}
}
/**
* Returns whether the extractor should decode a leaf atom with type {@code atom}. * Returns whether the extractor should decode a leaf atom with type {@code atom}.
*/ */
private static boolean shouldParseLeafAtom(int atom) { private static boolean shouldParseLeafAtom(int atom) {
......
...@@ -82,7 +82,7 @@ public class OggExtractor implements Extractor { ...@@ -82,7 +82,7 @@ public class OggExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
streamReader.seek(position); streamReader.seek(position);
} }
......
...@@ -105,7 +105,7 @@ public final class RawCcExtractor implements Extractor { ...@@ -105,7 +105,7 @@ public final class RawCcExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
parserState = STATE_READING_HEADER; parserState = STATE_READING_HEADER;
} }
......
...@@ -125,7 +125,7 @@ public final class Ac3Extractor implements Extractor { ...@@ -125,7 +125,7 @@ public final class Ac3Extractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
startedPacket = false; startedPacket = false;
reader.seek(); reader.seek();
} }
......
...@@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor { ...@@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
startedPacket = false; startedPacket = false;
reader.seek(); reader.seek();
} }
......
...@@ -127,7 +127,7 @@ public final class PsExtractor implements Extractor { ...@@ -127,7 +127,7 @@ public final class PsExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
timestampAdjuster.reset(); timestampAdjuster.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();
......
...@@ -149,7 +149,7 @@ public final class TsExtractor implements Extractor { ...@@ -149,7 +149,7 @@ public final class TsExtractor implements Extractor {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
timestampAdjuster.reset(); timestampAdjuster.reset();
tsPacketBuffer.reset(); tsPacketBuffer.reset();
continuityCounters.clear(); continuityCounters.clear();
......
...@@ -66,7 +66,7 @@ public final class WavExtractor implements Extractor, SeekMap { ...@@ -66,7 +66,7 @@ public final class WavExtractor implements Extractor, SeekMap {
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
pendingBytes = 0; pendingBytes = 0;
} }
......
...@@ -453,7 +453,7 @@ import java.io.IOException; ...@@ -453,7 +453,7 @@ import java.io.IOException;
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
return; return;
} }
loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs)); loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs), pendingResetPositionUs);
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
} }
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
...@@ -486,7 +486,7 @@ import java.io.IOException; ...@@ -486,7 +486,7 @@ import java.io.IOException;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]); sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]);
} }
loadable.setLoadPosition(0); loadable.setLoadPosition(0, 0);
} }
} }
...@@ -578,6 +578,7 @@ import java.io.IOException; ...@@ -578,6 +578,7 @@ import java.io.IOException;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
private boolean pendingExtractorSeek; private boolean pendingExtractorSeek;
private long seekTimeUs;
private long length; private long length;
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
...@@ -591,8 +592,9 @@ import java.io.IOException; ...@@ -591,8 +592,9 @@ import java.io.IOException;
this.length = C.LENGTH_UNSET; this.length = C.LENGTH_UNSET;
} }
public void setLoadPosition(long position) { public void setLoadPosition(long position, long timeUs) {
positionHolder.position = position; positionHolder.position = position;
seekTimeUs = timeUs;
pendingExtractorSeek = true; pendingExtractorSeek = true;
} }
...@@ -620,7 +622,7 @@ import java.io.IOException; ...@@ -620,7 +622,7 @@ import java.io.IOException;
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(position); extractor.seek(position, seekTimeUs);
pendingExtractorSeek = false; pendingExtractorSeek = false;
} }
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
......
...@@ -92,7 +92,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput ...@@ -92,7 +92,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
extractor.init(this); extractor.init(this);
extractorInitialized = true; extractorInitialized = true;
} else { } else {
extractor.seek(0); extractor.seek(0, 0);
if (resendFormatOnInit && sentFormat != null) { if (resendFormatOnInit && sentFormat != null) {
trackOutput.format(sentFormat); trackOutput.format(sentFormat);
} }
......
...@@ -79,7 +79,7 @@ import java.util.regex.Pattern; ...@@ -79,7 +79,7 @@ import java.util.regex.Pattern;
} }
@Override @Override
public void seek(long position) { public void seek(long position, long timeUs) {
// 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();
} }
......
...@@ -66,28 +66,23 @@ public class TestUtil { ...@@ -66,28 +66,23 @@ public class TestUtil {
} }
} }
public static FakeExtractorOutput consumeTestData(Extractor extractor, byte[] data) public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
throws IOException, InterruptedException { long timeUs) throws IOException, InterruptedException {
return consumeTestData(extractor, newExtractorInput(data)); return consumeTestData(extractor, input, timeUs, false);
}
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input)
throws IOException, InterruptedException {
return consumeTestData(extractor, input, false);
} }
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
boolean retryFromStartIfLive) throws IOException, InterruptedException { long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException {
FakeExtractorOutput output = new FakeExtractorOutput(); FakeExtractorOutput output = new FakeExtractorOutput();
extractor.init(output); extractor.init(output);
consumeTestData(extractor, input, output, retryFromStartIfLive); consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive);
return output; return output;
} }
private static void consumeTestData(Extractor extractor, FakeExtractorInput input, private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs,
FakeExtractorOutput output, boolean retryFromStartIfLive) FakeExtractorOutput output, boolean retryFromStartIfLive)
throws IOException, InterruptedException { throws IOException, InterruptedException {
extractor.seek(input.getPosition()); extractor.seek(input.getPosition(), timeUs);
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) {
...@@ -114,7 +109,7 @@ public class TestUtil { ...@@ -114,7 +109,7 @@ public class TestUtil {
for (int i = 0; i < output.numberOfTracks; i++) { for (int i = 0; i < output.numberOfTracks; i++) {
output.trackOutputs.valueAt(i).clear(); output.trackOutputs.valueAt(i).clear();
} }
extractor.seek(0); extractor.seek(0, 0);
} }
} }
} }
...@@ -277,7 +272,7 @@ public class TestUtil { ...@@ -277,7 +272,7 @@ public class TestUtil {
Assert.assertTrue(sniffTestData(extractor, input)); Assert.assertTrue(sniffTestData(extractor, input));
input.resetPeekPosition(); input.resetPeekPosition();
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, true); FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true);
if (simulateUnknownLength if (simulateUnknownLength
&& assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) { && assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) {
...@@ -297,7 +292,7 @@ public class TestUtil { ...@@ -297,7 +292,7 @@ public class TestUtil {
extractorOutput.trackOutputs.valueAt(i).clear(); extractorOutput.trackOutputs.valueAt(i).clear();
} }
consumeTestData(extractor, input, extractorOutput, false); consumeTestData(extractor, input, timeUs, extractorOutput, false);
extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION); extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION);
} }
} }
......
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