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
This commit is contained in:
andrewlewis 2016-12-08 06:33:53 -08:00 committed by Oliver Woodman
parent 2fce364936
commit f45751872d
18 changed files with 70 additions and 53 deletions

View File

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

View File

@ -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.

View File

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

View File

@ -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();

View File

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

View File

@ -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();

View File

@ -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;
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 {
}; };
// 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 {
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 {
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 {
} }
@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 {
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 {
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 {
// 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;
@ -478,6 +482,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return earliestSampleTrackIndex; return earliestSampleTrackIndex;
} }
/**
* 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}.
*/ */

View File

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

View File

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

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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();

View File

@ -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();

View File

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

View File

@ -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;
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;
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;
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;
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) {

View File

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

View File

@ -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();
} }

View File

@ -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 {
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 {
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 {
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);
} }
} }