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
public void seek(long position) {
public void seek(long position, long timeUs) {
if (position == 0) {
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
* 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.

View File

@ -126,7 +126,7 @@ public final class FlvExtractor implements Extractor, SeekMap {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
parserState = STATE_READING_FLV_HEADER;
bytesToNextTagHeader = 0;
}

View File

@ -318,7 +318,7 @@ public final class MatroskaExtractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
clusterTimecodeUs = C.TIME_UNSET;
blockState = BLOCK_STATE_START;
reader.reset();

View File

@ -123,7 +123,7 @@ public final class Mp3Extractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
synchronizedHeaderData = 0;
basisTimeUs = C.TIME_UNSET;
samplesRead = 0;

View File

@ -194,7 +194,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
int trackCount = trackBundles.size();
for (int i = 0; i < trackCount; i++) {
trackBundles.valueAt(i).reset();

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.mp4;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
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.Util;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
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;
private static final int STATE_READING_ATOM_HEADER = 1;
private static final int STATE_READING_ATOM_PAYLOAD = 2;
private static final int STATE_READING_SAMPLE = 3;
/**
* Parser states.
*/
@Retention(RetentionPolicy.SOURCE)
@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.
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 Stack<ContainerAtom> containerAtoms;
@State
private int parserState;
private int atomType;
private long atomSize;
@ -96,7 +104,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
containerAtoms = new Stack<>();
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4);
enterReadingAtomHeaderState();
}
@Override
@ -110,12 +117,16 @@ public final class Mp4Extractor implements Extractor, SeekMap {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
containerAtoms.clear();
atomHeaderBytesRead = 0;
sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0;
parserState = STATE_AFTER_SEEK;
if (position == 0) {
enterReadingAtomHeaderState();
} else if (tracks != null) {
updateSampleIndices(timeUs);
}
}
@Override
@ -128,13 +139,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
throws IOException, InterruptedException {
while (true) {
switch (parserState) {
case STATE_AFTER_SEEK:
if (input.getPosition() == 0) {
enterReadingAtomHeaderState();
} else {
parserState = STATE_READING_SAMPLE;
}
break;
case STATE_READING_ATOM_HEADER:
if (!readAtomHeader(input)) {
return RESULT_END_OF_INPUT;
@ -145,8 +149,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return RESULT_SEEK;
}
break;
default:
case STATE_READING_SAMPLE:
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.
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
}
track.sampleIndex = sampleIndex;
long offset = sampleTable.offsets[sampleIndex];
if (offset < earliestSamplePosition) {
earliestSamplePosition = offset;
@ -478,6 +482,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
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}.
*/

View File

@ -82,7 +82,7 @@ public class OggExtractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
streamReader.seek(position);
}

View File

@ -105,7 +105,7 @@ public final class RawCcExtractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
parserState = STATE_READING_HEADER;
}

View File

@ -125,7 +125,7 @@ public final class Ac3Extractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
startedPacket = false;
reader.seek();
}

View File

@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
startedPacket = false;
reader.seek();
}

View File

@ -127,7 +127,7 @@ public final class PsExtractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
timestampAdjuster.reset();
for (int i = 0; i < psPayloadReaders.size(); i++) {
psPayloadReaders.valueAt(i).seek();

View File

@ -149,7 +149,7 @@ public final class TsExtractor implements Extractor {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
timestampAdjuster.reset();
tsPacketBuffer.reset();
continuityCounters.clear();

View File

@ -66,7 +66,7 @@ public final class WavExtractor implements Extractor, SeekMap {
}
@Override
public void seek(long position) {
public void seek(long position, long timeUs) {
pendingBytes = 0;
}

View File

@ -453,7 +453,7 @@ import java.io.IOException;
pendingResetPositionUs = C.TIME_UNSET;
return;
}
loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs));
loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs), pendingResetPositionUs);
pendingResetPositionUs = C.TIME_UNSET;
}
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
@ -486,7 +486,7 @@ import java.io.IOException;
for (int i = 0; i < trackCount; 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 boolean pendingExtractorSeek;
private long seekTimeUs;
private long length;
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
@ -591,8 +592,9 @@ import java.io.IOException;
this.length = C.LENGTH_UNSET;
}
public void setLoadPosition(long position) {
public void setLoadPosition(long position, long timeUs) {
positionHolder.position = position;
seekTimeUs = timeUs;
pendingExtractorSeek = true;
}
@ -620,7 +622,7 @@ import java.io.IOException;
input = new DefaultExtractorInput(dataSource, position, length);
Extractor extractor = extractorHolder.selectExtractor(input);
if (pendingExtractorSeek) {
extractor.seek(position);
extractor.seek(position, seekTimeUs);
pendingExtractorSeek = false;
}
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {

View File

@ -92,7 +92,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
extractor.init(this);
extractorInitialized = true;
} else {
extractor.seek(0);
extractor.seek(0, 0);
if (resendFormatOnInit && sentFormat != null) {
trackOutput.format(sentFormat);
}

View File

@ -79,7 +79,7 @@ import java.util.regex.Pattern;
}
@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.
throw new IllegalStateException();
}

View File

@ -66,28 +66,23 @@ public class TestUtil {
}
}
public static FakeExtractorOutput consumeTestData(Extractor extractor, byte[] data)
throws IOException, InterruptedException {
return consumeTestData(extractor, newExtractorInput(data));
}
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input)
throws IOException, InterruptedException {
return consumeTestData(extractor, input, false);
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
long timeUs) throws IOException, InterruptedException {
return consumeTestData(extractor, input, timeUs, false);
}
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
boolean retryFromStartIfLive) throws IOException, InterruptedException {
long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException {
FakeExtractorOutput output = new FakeExtractorOutput();
extractor.init(output);
consumeTestData(extractor, input, output, retryFromStartIfLive);
consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive);
return output;
}
private static void consumeTestData(Extractor extractor, FakeExtractorInput input,
private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs,
FakeExtractorOutput output, boolean retryFromStartIfLive)
throws IOException, InterruptedException {
extractor.seek(input.getPosition());
extractor.seek(input.getPosition(), timeUs);
PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
while (readResult != Extractor.RESULT_END_OF_INPUT) {
@ -114,7 +109,7 @@ public class TestUtil {
for (int i = 0; i < output.numberOfTracks; i++) {
output.trackOutputs.valueAt(i).clear();
}
extractor.seek(0);
extractor.seek(0, 0);
}
}
}
@ -277,7 +272,7 @@ public class TestUtil {
Assert.assertTrue(sniffTestData(extractor, input));
input.resetPeekPosition();
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, true);
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true);
if (simulateUnknownLength
&& assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) {
@ -297,7 +292,7 @@ public class TestUtil {
extractorOutput.trackOutputs.valueAt(i).clear();
}
consumeTestData(extractor, input, extractorOutput, false);
consumeTestData(extractor, input, timeUs, extractorOutput, false);
extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION);
}
}